Commit 07d8b542 by Torkel Ödegaard

working on rethinkdb stuff

parent d8dca203
Subproject commit 34ab1e529b499af836631f8076c2c4df02be5860
Subproject commit 06de80f2a764b76df6047fc4700c74aaf5734521
go get code.google.com/p/goprotobuf/{proto,protoc-gen-go}
package api
import (
log "github.com/alecthomas/log4go"
"github.com/gin-gonic/gin"
"github.com/torkelo/grafana-pro/pkg/models"
)
......@@ -16,7 +17,7 @@ func init() {
func (self *HttpServer) getDashboard(c *gin.Context) {
id := c.Params.ByName("id")
dash, err := self.store.GetById(id)
dash, err := self.store.GetDashboardByTitle(id, "test")
if err != nil {
c.JSON(404, newErrorResponse("Dashboard not found"))
return
......@@ -30,6 +31,7 @@ func (self *HttpServer) search(c *gin.Context) {
results, err := self.store.Query(query)
if err != nil {
log.Error("Store query error: %v", err)
c.JSON(500, newErrorResponse("Failed"))
return
}
......@@ -41,9 +43,17 @@ func (self *HttpServer) postDashboard(c *gin.Context) {
var command saveDashboardCommand
if c.EnsureBody(&command) {
err := self.store.Save(&models.Dashboard{Data: command.Dashboard})
dashboard := models.NewDashboard("test")
dashboard.Data = command.Dashboard
dashboard.Title = dashboard.Data["title"].(string)
if dashboard.Data["id"] != nil {
dashboard.Id = dashboard.Data["id"].(string)
}
err := self.store.SaveDashboard(dashboard)
if err == nil {
c.JSON(200, gin.H{"status": "saved"})
c.JSON(200, gin.H{"status": "success", "id": dashboard.Id})
return
}
}
......
......@@ -3,20 +3,39 @@ package models
import (
"encoding/json"
"io"
"time"
)
type Dashboard struct {
Id string `gorethink:"id,omitempty"`
AccountId string
LastModifiedByUserId string
LastModifiedByDate time.Time
CreatedDate time.Time
Title string
Tags []string
Data map[string]interface{}
}
type UserContext struct {
UserId string
AccountId string
}
type SearchResult struct {
Type string `json:"title"`
Id string `json:"id"`
Title string `json:"title"`
}
func NewDashboard(title string) *Dashboard {
dash := &Dashboard{}
dash.Id = ""
dash.AccountId = "test"
dash.LastModifiedByDate = time.Now()
dash.CreatedDate = time.Now()
dash.LastModifiedByUserId = "123"
dash.Title = title
dash.Data = make(map[string]interface{})
dash.Data["title"] = title
......@@ -31,23 +50,11 @@ func NewFromJson(reader io.Reader) (*Dashboard, error) {
return nil, err
}
return dash, nil
}
/*type DashboardServices struct {
}
dash.Title = dash.Data["title"].(string)
type DashboardServicesFilter struct {
return dash, nil
}
type DashboardServicesFilterTime struct {
From string To string
}*/
func (dash *Dashboard) GetString(prop string) string {
return dash.Data[prop].(string)
}
func (dash *Dashboard) Title() string {
return dash.GetString("title")
}
package stores
import (
"encoding/json"
"io"
"os"
"path/filepath"
"strings"
log "github.com/alecthomas/log4go"
"github.com/torkelo/grafana-pro/pkg/models"
)
type fileStore struct {
dataDir string
dashDir string
cache map[string]*models.Dashboard
}
func NewFileStore(dataDir string) *fileStore {
if dirDoesNotExist(dataDir) {
log.Crashf("FileStore failed to initialize, dataDir does not exist %v", dataDir)
}
dashDir := filepath.Join(dataDir, "dashboards")
if dirDoesNotExist(dashDir) {
log.Debug("Did not find dashboard dir, creating...")
err := os.Mkdir(dashDir, 0777)
if err != nil {
log.Crashf("FileStore failed to initialize, could not create directory %v, error: %v", dashDir, err)
}
}
store := &fileStore{}
store.dataDir = dataDir
store.dashDir = dashDir
store.cache = make(map[string]*models.Dashboard)
go store.scanFiles()
return store
}
func (store *fileStore) scanFiles() {
visitor := func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if f.IsDir() {
return nil
}
if strings.HasSuffix(f.Name(), ".json") {
err = store.loadDashboardIntoCache(path)
if err != nil {
return err
}
}
return nil
}
err := filepath.Walk(store.dashDir, visitor)
if err != nil {
log.Error("FileStore::updateCache failed %v", err)
}
}
func (store fileStore) loadDashboardIntoCache(filename string) error {
log.Info("Loading dashboard file %v into cache", filename)
dash, err := loadDashboardFromFile(filename)
if err != nil {
return err
}
store.cache[dash.Title()] = dash
return nil
}
func (store *fileStore) Close() {
}
func (store *fileStore) GetById(id string) (*models.Dashboard, error) {
log.Debug("FileStore::GetById id = %v", id)
filename := store.getFilePathForDashboard(id)
return loadDashboardFromFile(filename)
}
func (store *fileStore) Save(dash *models.Dashboard) error {
filename := store.getFilePathForDashboard(dash.Title())
log.Debug("Saving dashboard %v to %v", dash.Title(), filename)
var err error
var data []byte
if data, err = json.Marshal(dash.Data); err != nil {
return err
}
return writeFile(filename, data)
}
func (store *fileStore) Query(query string) ([]*models.SearchResult, error) {
results := make([]*models.SearchResult, 0, 50)
for _, dash := range store.cache {
item := &models.SearchResult{
Id: dash.Title(),
Type: "dashboard",
}
results = append(results, item)
}
return results, nil
}
func loadDashboardFromFile(filename string) (*models.Dashboard, error) {
log.Debug("FileStore::loading dashboard from file %v", filename)
configFile, err := os.Open(filename)
if err != nil {
return nil, err
}
return models.NewFromJson(configFile)
}
func (store *fileStore) getFilePathForDashboard(id string) string {
id = strings.ToLower(id)
id = strings.Replace(id, " ", "-", -1)
return filepath.Join(store.dashDir, id) + ".json"
}
func dirDoesNotExist(dir string) bool {
_, err := os.Stat(dir)
return os.IsNotExist(err)
}
func writeFile(filename string, data []byte) error {
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
n, err := f.Write(data)
if err == nil && n < len(data) {
err = io.ErrShortWrite
}
if err1 := f.Close(); err == nil {
err = err1
}
return err
}
//
// import (
// "encoding/json"
// "io"
// "os"
// "path/filepath"
// "strings"
//
// log "github.com/alecthomas/log4go"
// "github.com/torkelo/grafana-pro/pkg/models"
// )
//
// type fileStore struct {
// dataDir string
// dashDir string
// cache map[string]*models.Dashboard
// }
//
// func NewFileStore(dataDir string) *fileStore {
//
// if dirDoesNotExist(dataDir) {
// log.Crashf("FileStore failed to initialize, dataDir does not exist %v", dataDir)
// }
//
// dashDir := filepath.Join(dataDir, "dashboards")
//
// if dirDoesNotExist(dashDir) {
// log.Debug("Did not find dashboard dir, creating...")
// err := os.Mkdir(dashDir, 0777)
// if err != nil {
// log.Crashf("FileStore failed to initialize, could not create directory %v, error: %v", dashDir, err)
// }
// }
//
// store := &fileStore{}
// store.dataDir = dataDir
// store.dashDir = dashDir
// store.cache = make(map[string]*models.Dashboard)
// store.scanFiles()
//
// return store
// }
//
// func (store *fileStore) scanFiles() {
// visitor := func(path string, f os.FileInfo, err error) error {
// if err != nil {
// return err
// }
// if f.IsDir() {
// return nil
// }
// if strings.HasSuffix(f.Name(), ".json") {
// err = store.loadDashboardIntoCache(path)
// if err != nil {
// return err
// }
// }
// return nil
// }
//
// err := filepath.Walk(store.dashDir, visitor)
// if err != nil {
// log.Error("FileStore::updateCache failed %v", err)
// }
// }
//
// func (store fileStore) loadDashboardIntoCache(filename string) error {
// log.Info("Loading dashboard file %v into cache", filename)
// dash, err := loadDashboardFromFile(filename)
// if err != nil {
// return err
// }
//
// store.cache[dash.Title] = dash
//
// return nil
// }
//
// func (store *fileStore) Close() {
//
// }
//
// func (store *fileStore) GetById(id string) (*models.Dashboard, error) {
// log.Debug("FileStore::GetById id = %v", id)
// filename := store.getFilePathForDashboard(id)
//
// return loadDashboardFromFile(filename)
// }
//
// func (store *fileStore) Save(dash *models.Dashboard) error {
// filename := store.getFilePathForDashboard(dash.Title)
//
// log.Debug("Saving dashboard %v to %v", dash.Title, filename)
//
// var err error
// var data []byte
// if data, err = json.Marshal(dash.Data); err != nil {
// return err
// }
//
// return writeFile(filename, data)
// }
//
// func (store *fileStore) Query(query string) ([]*models.SearchResult, error) {
// results := make([]*models.SearchResult, 0, 50)
//
// for _, dash := range store.cache {
// item := &models.SearchResult{
// Id: dash.Title,
// Type: "dashboard",
// }
// results = append(results, item)
// }
//
// return results, nil
// }
//
// func loadDashboardFromFile(filename string) (*models.Dashboard, error) {
// log.Debug("FileStore::loading dashboard from file %v", filename)
//
// configFile, err := os.Open(filename)
// if err != nil {
// return nil, err
// }
//
// return models.NewFromJson(configFile)
// }
//
// func (store *fileStore) getFilePathForDashboard(id string) string {
// id = strings.ToLower(id)
// id = strings.Replace(id, " ", "-", -1)
// return filepath.Join(store.dashDir, id) + ".json"
// }
//
// func dirDoesNotExist(dir string) bool {
// _, err := os.Stat(dir)
// return os.IsNotExist(err)
// }
//
// func writeFile(filename string, data []byte) error {
// f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
// if err != nil {
// return err
// }
// n, err := f.Write(data)
// if err == nil && n < len(data) {
// err = io.ErrShortWrite
// }
// if err1 := f.Close(); err == nil {
// err = err1
// }
//
// return err
// }
package stores
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"testing"
. "github.com/smartystreets/goconvey/convey"
"github.com/torkelo/grafana-pro/pkg/models"
)
func TestFileStore(t *testing.T) {
GivenFileStore("When saving a dashboard", t, func(store *fileStore) {
dashboard := models.NewDashboard("hello")
err := store.Save(dashboard)
Convey("should be saved to disk", func() {
So(err, ShouldBeNil)
_, err = os.Stat(store.getFilePathForDashboard("hello"))
So(err, ShouldBeNil)
})
})
GivenFileStore("When getting a saved dashboard", t, func(store *fileStore) {
copyDashboardToTempData("default.json", "", store.dashDir)
dash, err := store.GetById("default")
Convey("should be read from disk", func() {
So(err, ShouldBeNil)
So(dash, ShouldNotBeNil)
So(dash.Title(), ShouldEqual, "Grafana Play Home")
})
})
GivenFileStore("when getting dashboard with capital letters", t, func(store *fileStore) {
copyDashboardToTempData("annotations.json", "", store.dashDir)
dash, err := store.GetById("AnnoTations")
Convey("should be read from disk", func() {
So(err, ShouldBeNil)
So(dash, ShouldNotBeNil)
So(dash.Title(), ShouldEqual, "Annotations")
})
})
GivenFileStore("When copying dashboards into data dir", t, func(store *fileStore) {
copyDashboardToTempData("annotations.json", "", store.dashDir)
copyDashboardToTempData("default.json", "", store.dashDir)
copyDashboardToTempData("graph-styles.json", "", store.dashDir)
store.scanFiles()
Convey("scan should generate index of all dashboards", func() {
result, err := store.Query("*")
So(err, ShouldBeNil)
So(len(result), ShouldEqual, 3)
})
})
}
func copyDashboardToTempData(name string, destName string, dir string) {
if destName == "" {
destName = name
}
source, _ := filepath.Abs("../../data/dashboards/" + name)
dest := filepath.Join(dir, destName)
err := copyFile(dest, source)
if err != nil {
panic(fmt.Sprintf("failed to copy file %v", name))
}
}
func GivenFileStore(desc string, t *testing.T, f func(store *fileStore)) {
Convey(desc, t, func() {
tempDir, _ := ioutil.TempDir("", "store")
store := NewFileStore(tempDir)
f(store)
Reset(func() {
os.RemoveAll(tempDir)
})
})
}
func copyFile(dst, src string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
cerr := out.Close()
if err != nil {
return err
}
return cerr
}
//
// import (
// "fmt"
// "io"
// "io/ioutil"
// "os"
// "path/filepath"
// "testing"
//
// . "github.com/smartystreets/goconvey/convey"
// "github.com/torkelo/grafana-pro/pkg/models"
// )
//
// func TestFileStore(t *testing.T) {
//
// GivenFileStore("When saving a dashboard", t, func(store *fileStore) {
// dashboard := models.NewDashboard("hello")
//
// err := store.Save(dashboard)
//
// Convey("should be saved to disk", func() {
// So(err, ShouldBeNil)
//
// _, err = os.Stat(store.getFilePathForDashboard("hello"))
// So(err, ShouldBeNil)
// })
// })
//
// GivenFileStore("When getting a saved dashboard", t, func(store *fileStore) {
// copyDashboardToTempData("default.json", "", store.dashDir)
// dash, err := store.GetById("default")
//
// Convey("should be read from disk", func() {
// So(err, ShouldBeNil)
// So(dash, ShouldNotBeNil)
//
// So(dash.Title, ShouldEqual, "Grafana Play Home")
// })
// })
//
// GivenFileStore("when getting dashboard with capital letters", t, func(store *fileStore) {
// copyDashboardToTempData("annotations.json", "", store.dashDir)
// dash, err := store.GetById("AnnoTations")
//
// Convey("should be read from disk", func() {
// So(err, ShouldBeNil)
// So(dash, ShouldNotBeNil)
//
// So(dash.Title, ShouldEqual, "Annotations")
// })
// })
//
// GivenFileStore("When copying dashboards into data dir", t, func(store *fileStore) {
// copyDashboardToTempData("annotations.json", "", store.dashDir)
// copyDashboardToTempData("default.json", "", store.dashDir)
// copyDashboardToTempData("graph-styles.json", "", store.dashDir)
// store.scanFiles()
//
// Convey("scan should generate index of all dashboards", func() {
//
// result, err := store.Query("*")
// So(err, ShouldBeNil)
// So(len(result), ShouldEqual, 3)
// })
// })
// }
//
// func copyDashboardToTempData(name string, destName string, dir string) {
// if destName == "" {
// destName = name
// }
// source, _ := filepath.Abs("../../data/dashboards/" + name)
// dest := filepath.Join(dir, destName)
// err := copyFile(dest, source)
// if err != nil {
// panic(fmt.Sprintf("failed to copy file %v", name))
// }
// }
//
// func GivenFileStore(desc string, t *testing.T, f func(store *fileStore)) {
// Convey(desc, t, func() {
// tempDir, _ := ioutil.TempDir("", "store")
//
// store := NewFileStore(tempDir)
//
// f(store)
//
// Reset(func() {
// os.RemoveAll(tempDir)
// })
// })
// }
//
// func copyFile(dst, src string) error {
// in, err := os.Open(src)
// if err != nil {
// return err
// }
// defer in.Close()
// out, err := os.Create(dst)
// if err != nil {
// return err
// }
// defer out.Close()
// _, err = io.Copy(out, in)
// cerr := out.Close()
// if err != nil {
// return err
// }
// return cerr
// }
package stores
import (
"time"
log "github.com/alecthomas/log4go"
r "github.com/dancannon/gorethink"
"github.com/torkelo/grafana-pro/pkg/models"
)
type rethinkStore struct {
session *r.Session
}
type RethinkCfg struct {
DatabaseName string
}
func NewRethinkStore(config *RethinkCfg) *rethinkStore {
log.Info("Initializing rethink storage")
session, err := r.Connect(r.ConnectOpts{
Address: "localhost:28015",
Database: config.DatabaseName,
MaxIdle: 10,
IdleTimeout: time.Second * 10,
})
if err != nil {
log.Crash("Failed to connect to rethink database %v", err)
}
r.DbCreate(config.DatabaseName).Exec(session)
r.Db(config.DatabaseName).TableCreate("dashboards").Exec(session)
r.Db(config.DatabaseName).Table("dashboards").IndexCreateFunc("AccountIdTitle", func(row r.Term) interface{} {
return []interface{}{row.Field("AccountId"), row.Field("Title")}
}).Exec(session)
return &rethinkStore{
session: session,
}
}
func (self *rethinkStore) SaveDashboard(dash *models.Dashboard) error {
resp, err := r.Table("dashboards").Insert(dash).RunWrite(self.session)
if err != nil {
return err
}
log.Info("Inserted: %v, Errors: %v, Updated: %v", resp.Inserted, resp.Errors, resp.Updated)
log.Info("First error:", resp.FirstError)
if len(resp.GeneratedKeys) > 0 {
dash.Id = resp.GeneratedKeys[0]
}
return nil
}
func (self *rethinkStore) GetDashboardByTitle(title string, accountId string) (*models.Dashboard, error) {
resp, err := r.Table("dashboards").GetAllByIndex("AccountIdTitle", []interface{}{accountId, title}).Run(self.session)
if err != nil {
return nil, err
}
var dashboard models.Dashboard
err = resp.One(&dashboard)
if err != nil {
return nil, err
}
return &dashboard, nil
}
func (self *rethinkStore) Query(query string) ([]*models.SearchResult, error) {
docs, err := r.Table("dashboards").Filter(r.Row.Field("Title").Match(".*")).Run(self.session)
if err != nil {
return nil, err
}
results := make([]*models.SearchResult, 0, 50)
var dashboard models.Dashboard
for docs.Next(&dashboard) {
log.Info("title: ", dashboard.Title)
results = append(results, &models.SearchResult{
Title: dashboard.Title,
Id: dashboard.Id,
})
}
return results, nil
}
func (self *rethinkStore) Close() {}
package stores
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
"github.com/torkelo/grafana-pro/pkg/models"
)
func TestRethinkStore(t *testing.T) {
Convey("Insert dashboard", t, func() {
store := NewRethinkStore(&RethinkCfg{DatabaseName: "tests"})
//defer r.DbDrop("tests").Exec(store.session)
dashboard := models.NewDashboard("test")
dashboard.AccountId = "123"
err := store.SaveDashboard(dashboard)
So(err, ShouldBeNil)
So(dashboard.Id, ShouldNotBeEmpty)
read, err := store.GetDashboardByTitle("test", "123")
So(err, ShouldBeNil)
So(read, ShouldNotBeNil)
})
}
......@@ -5,12 +5,12 @@ import (
)
type Store interface {
GetById(id string) (*models.Dashboard, error)
Save(dash *models.Dashboard) error
GetDashboardByTitle(id string, accountId string) (*models.Dashboard, error)
SaveDashboard(dash *models.Dashboard) error
Query(query string) ([]*models.SearchResult, error)
Close()
}
func New() Store {
return NewFileStore("data")
return NewRethinkStore(&RethinkCfg{DatabaseName: "grafana"})
}
docker kill gfdev
docker rm gfdev
docker run -d -p 8180:8080 -p 28015:28015 -p 29015:29015 \
--name rethinkdb \
-v /var/docker/grafana-pro-rethinkdb:/data \
dockerfile/rethinkdb
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment