datasources: change to optimisic concurrency

prerequisite for #9504
This commit is contained in:
bergquist
2017-10-23 07:55:16 +02:00
parent 48f384dcf1
commit 89923bf77a
8 changed files with 196 additions and 72 deletions
+20 -6
View File
@@ -119,7 +119,13 @@ func AddDataSource(c *middleware.Context, cmd m.AddDataSourceCommand) {
return return
} }
c.JSON(200, util.DynMap{"message": "Datasource added", "id": cmd.Result.Id, "name": cmd.Result.Name}) ds := convertModelToDtos(cmd.Result)
c.JSON(200, util.DynMap{
"message": "Datasource added",
"id": cmd.Result.Id,
"name": cmd.Result.Name,
"datasource": ds,
})
} }
func UpdateDataSource(c *middleware.Context, cmd m.UpdateDataSourceCommand) Response { func UpdateDataSource(c *middleware.Context, cmd m.UpdateDataSourceCommand) Response {
@@ -133,10 +139,19 @@ func UpdateDataSource(c *middleware.Context, cmd m.UpdateDataSourceCommand) Resp
err = bus.Dispatch(&cmd) err = bus.Dispatch(&cmd)
if err != nil { if err != nil {
return ApiError(500, "Failed to update datasource", err) if err == m.ErrDataSouceUpdatingOldVersion {
return ApiError(500, "Failed to update datasource. Reload new version and try again", err)
} else {
return ApiError(500, "Failed to update datasource", err)
}
} }
ds := convertModelToDtos(cmd.Result)
return Json(200, util.DynMap{"message": "Datasource updated", "id": cmd.Id, "name": cmd.Name}) return Json(200, util.DynMap{
"message": "Datasource updated",
"id": cmd.Id,
"name": cmd.Name,
"datasource": ds,
})
} }
func fillWithSecureJsonData(cmd *m.UpdateDataSourceCommand) error { func fillWithSecureJsonData(cmd *m.UpdateDataSourceCommand) error {
@@ -158,8 +173,6 @@ func fillWithSecureJsonData(cmd *m.UpdateDataSourceCommand) error {
} }
} }
// set version from db
cmd.Version = ds.Version
return nil return nil
} }
@@ -228,6 +241,7 @@ func convertModelToDtos(ds *m.DataSource) dtos.DataSource {
IsDefault: ds.IsDefault, IsDefault: ds.IsDefault,
JsonData: ds.JsonData, JsonData: ds.JsonData,
SecureJsonFields: map[string]bool{}, SecureJsonFields: map[string]bool{},
Version: ds.Version,
} }
for k, v := range ds.SecureJsonData { for k, v := range ds.SecureJsonData {
+59
View File
@@ -0,0 +1,59 @@
package dtos
import (
"strings"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
)
type DataSource struct {
Id int64 `json:"id"`
OrgId int64 `json:"orgId"`
Name string `json:"name"`
Type string `json:"type"`
TypeLogoUrl string `json:"typeLogoUrl"`
Access m.DsAccess `json:"access"`
Url string `json:"url"`
Password string `json:"password"`
User string `json:"user"`
Database string `json:"database"`
BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"`
WithCredentials bool `json:"withCredentials"`
IsDefault bool `json:"isDefault"`
JsonData *simplejson.Json `json:"jsonData,omitempty"`
SecureJsonFields map[string]bool `json:"secureJsonFields"`
Version int `json:"version"`
}
type DataSourceListItemDTO struct {
Id int64 `json:"id"`
OrgId int64 `json:"orgId"`
Name string `json:"name"`
Type string `json:"type"`
TypeLogoUrl string `json:"typeLogoUrl"`
Access m.DsAccess `json:"access"`
Url string `json:"url"`
Password string `json:"password"`
User string `json:"user"`
Database string `json:"database"`
BasicAuth bool `json:"basicAuth"`
IsDefault bool `json:"isDefault"`
JsonData *simplejson.Json `json:"jsonData,omitempty"`
}
type DataSourceList []DataSourceListItemDTO
func (slice DataSourceList) Len() int {
return len(slice)
}
func (slice DataSourceList) Less(i, j int) bool {
return strings.ToLower(slice[i].Name) < strings.ToLower(slice[j].Name)
}
func (slice DataSourceList) Swap(i, j int) {
slice[i], slice[j] = slice[j], slice[i]
}
-50
View File
@@ -37,56 +37,6 @@ type CurrentUser struct {
HelpFlags1 m.HelpFlags1 `json:"helpFlags1"` HelpFlags1 m.HelpFlags1 `json:"helpFlags1"`
} }
type DataSource struct {
Id int64 `json:"id"`
OrgId int64 `json:"orgId"`
Name string `json:"name"`
Type string `json:"type"`
TypeLogoUrl string `json:"typeLogoUrl"`
Access m.DsAccess `json:"access"`
Url string `json:"url"`
Password string `json:"password"`
User string `json:"user"`
Database string `json:"database"`
BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"`
WithCredentials bool `json:"withCredentials"`
IsDefault bool `json:"isDefault"`
JsonData *simplejson.Json `json:"jsonData,omitempty"`
SecureJsonFields map[string]bool `json:"secureJsonFields"`
}
type DataSourceListItemDTO struct {
Id int64 `json:"id"`
OrgId int64 `json:"orgId"`
Name string `json:"name"`
Type string `json:"type"`
TypeLogoUrl string `json:"typeLogoUrl"`
Access m.DsAccess `json:"access"`
Url string `json:"url"`
Password string `json:"password"`
User string `json:"user"`
Database string `json:"database"`
BasicAuth bool `json:"basicAuth"`
IsDefault bool `json:"isDefault"`
JsonData *simplejson.Json `json:"jsonData,omitempty"`
}
type DataSourceList []DataSourceListItemDTO
func (slice DataSourceList) Len() int {
return len(slice)
}
func (slice DataSourceList) Less(i, j int) bool {
return strings.ToLower(slice[i].Name) < strings.ToLower(slice[j].Name)
}
func (slice DataSourceList) Swap(i, j int) {
slice[i], slice[j] = slice[j], slice[i]
}
type MetricRequest struct { type MetricRequest struct {
From string `json:"from"` From string `json:"from"`
To string `json:"to"` To string `json:"to"`
+8 -5
View File
@@ -25,8 +25,9 @@ const (
// Typed errors // Typed errors
var ( var (
ErrDataSourceNotFound = errors.New("Data source not found") ErrDataSourceNotFound = errors.New("Data source not found")
ErrDataSourceNameExists = errors.New("Data source with same name already exists") ErrDataSourceNameExists = errors.New("Data source with same name already exists")
ErrDataSouceUpdatingOldVersion = errors.New("Trying to update old version of datasouce")
) )
type DsAccess string type DsAccess string
@@ -131,10 +132,12 @@ type UpdateDataSourceCommand struct {
IsDefault bool `json:"isDefault"` IsDefault bool `json:"isDefault"`
JsonData *simplejson.Json `json:"jsonData"` JsonData *simplejson.Json `json:"jsonData"`
SecureJsonData map[string]string `json:"secureJsonData"` SecureJsonData map[string]string `json:"secureJsonData"`
Version int `json:"version"`
OrgId int64 `json:"-"` OrgId int64 `json:"-"`
Id int64 `json:"-"` Id int64 `json:"-"`
Version int `json:"-"`
Result *DataSource
} }
type DeleteDataSourceByIdCommand struct { type DeleteDataSourceByIdCommand struct {
+17 -3
View File
@@ -3,6 +3,8 @@ package sqlstore
import ( import (
"time" "time"
"github.com/go-xorm/xorm"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/securejsondata" "github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/metrics"
@@ -69,7 +71,6 @@ func DeleteDataSourceByName(cmd *m.DeleteDataSourceByNameCommand) error {
} }
func AddDataSource(cmd *m.AddDataSourceCommand) error { func AddDataSource(cmd *m.AddDataSourceCommand) error {
return inTransaction(func(sess *DBSession) error { return inTransaction(func(sess *DBSession) error {
existing := m.DataSource{OrgId: cmd.OrgId, Name: cmd.Name} existing := m.DataSource{OrgId: cmd.OrgId, Name: cmd.Name}
has, _ := sess.Get(&existing) has, _ := sess.Get(&existing)
@@ -96,6 +97,7 @@ func AddDataSource(cmd *m.AddDataSourceCommand) error {
SecureJsonData: securejsondata.GetEncryptedJsonData(cmd.SecureJsonData), SecureJsonData: securejsondata.GetEncryptedJsonData(cmd.SecureJsonData),
Created: time.Now(), Created: time.Now(),
Updated: time.Now(), Updated: time.Now(),
Version: 1,
} }
if _, err := sess.Insert(ds); err != nil { if _, err := sess.Insert(ds); err != nil {
@@ -122,7 +124,6 @@ func updateIsDefaultFlag(ds *m.DataSource, sess *DBSession) error {
} }
func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error { func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error {
return inTransaction(func(sess *DBSession) error { return inTransaction(func(sess *DBSession) error {
ds := &m.DataSource{ ds := &m.DataSource{
Id: cmd.Id, Id: cmd.Id,
@@ -149,12 +150,25 @@ func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error {
sess.UseBool("basic_auth") sess.UseBool("basic_auth")
sess.UseBool("with_credentials") sess.UseBool("with_credentials")
_, err := sess.Where("id=? and org_id=?", ds.Id, ds.OrgId).Update(ds) var updateSession *xorm.Session
if cmd.Version != 0 {
updateSession = sess.Where("id=? and org_id=? and (version = ? or version < ?)", ds.Id, ds.OrgId, cmd.Version, cmd.Version)
} else {
updateSession = sess.Where("id=? and org_id=?", ds.Id, ds.OrgId)
}
affected, err := updateSession.Update(ds)
if err != nil { if err != nil {
return err return err
} }
if affected == 0 {
return m.ErrDataSouceUpdatingOldVersion
}
err = updateIsDefaultFlag(ds, sess) err = updateIsDefaultFlag(ds, sess)
cmd.Result = ds
return err return err
}) })
} }
+83 -7
View File
@@ -35,12 +35,9 @@ type Test struct {
} }
func TestDataAccess(t *testing.T) { func TestDataAccess(t *testing.T) {
Convey("Testing DB", t, func() { Convey("Testing DB", t, func() {
InitTestDB(t) InitTestDB(t)
Convey("Can add datasource", func() { Convey("Can add datasource", func() {
err := AddDataSource(&m.AddDataSourceCommand{ err := AddDataSource(&m.AddDataSourceCommand{
OrgId: 10, OrgId: 10,
Name: "laban", Name: "laban",
@@ -65,7 +62,6 @@ func TestDataAccess(t *testing.T) {
}) })
Convey("Given a datasource", func() { Convey("Given a datasource", func() {
err := AddDataSource(&m.AddDataSourceCommand{ err := AddDataSource(&m.AddDataSourceCommand{
OrgId: 10, OrgId: 10,
Name: "nisse", Name: "nisse",
@@ -81,6 +77,89 @@ func TestDataAccess(t *testing.T) {
ds := query.Result[0] ds := query.Result[0]
Convey(" updated ", func() {
cmd := &m.UpdateDataSourceCommand{
Id: ds.Id,
OrgId: 10,
Name: "nisse",
Type: m.DS_GRAPHITE,
Access: m.DS_ACCESS_PROXY,
Url: "http://test",
Version: ds.Version,
}
Convey("with same version as source", func() {
err := UpdateDataSource(cmd)
So(err, ShouldBeNil)
})
Convey("when someone else updated between read and update", func() {
query := m.GetDataSourcesQuery{OrgId: 10}
err = GetDataSources(&query)
So(err, ShouldBeNil)
ds := query.Result[0]
intendedUpdate := &m.UpdateDataSourceCommand{
Id: ds.Id,
OrgId: 10,
Name: "nisse",
Type: m.DS_GRAPHITE,
Access: m.DS_ACCESS_PROXY,
Url: "http://test",
Version: ds.Version,
}
updateFromOtherUser := &m.UpdateDataSourceCommand{
Id: ds.Id,
OrgId: 10,
Name: "nisse",
Type: m.DS_GRAPHITE,
Access: m.DS_ACCESS_PROXY,
Url: "http://test",
Version: ds.Version,
}
err := UpdateDataSource(updateFromOtherUser)
So(err, ShouldBeNil)
err = UpdateDataSource(intendedUpdate)
So(err, ShouldNotBeNil)
})
Convey("updating datasource without version", func() {
cmd := &m.UpdateDataSourceCommand{
Id: ds.Id,
OrgId: 10,
Name: "nisse",
Type: m.DS_GRAPHITE,
Access: m.DS_ACCESS_PROXY,
Url: "http://test",
}
Convey("should not raise errors", func() {
err := UpdateDataSource(cmd)
So(err, ShouldBeNil)
})
})
Convey("updating datasource without higher version", func() {
cmd := &m.UpdateDataSourceCommand{
Id: ds.Id,
OrgId: 10,
Name: "nisse",
Type: m.DS_GRAPHITE,
Access: m.DS_ACCESS_PROXY,
Url: "http://test",
Version: 90000,
}
Convey("should not raise errors", func() {
err := UpdateDataSource(cmd)
So(err, ShouldBeNil)
})
})
})
Convey("Can delete datasource by id", func() { Convey("Can delete datasource by id", func() {
err := DeleteDataSourceById(&m.DeleteDataSourceByIdCommand{Id: ds.Id, OrgId: ds.OrgId}) err := DeleteDataSourceById(&m.DeleteDataSourceByIdCommand{Id: ds.Id, OrgId: ds.OrgId})
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -104,9 +183,6 @@ func TestDataAccess(t *testing.T) {
GetDataSources(&query) GetDataSources(&query)
So(len(query.Result), ShouldEqual, 1) So(len(query.Result), ShouldEqual, 1)
}) })
}) })
}) })
} }
@@ -120,4 +120,10 @@ func addDataSourceMigration(mg *Migrator) {
{Name: "json_data", Type: DB_Text, Nullable: true}, {Name: "json_data", Type: DB_Text, Nullable: true},
{Name: "secure_json_data", Type: DB_Text, Nullable: true}, {Name: "secure_json_data", Type: DB_Text, Nullable: true},
})) }))
const setVersionToOneWhereZero = `UPDATE data_source SET version = 1 WHERE version = 0`
mg.AddMigration("Update initial version to 1", new(RawSqlMigration).
Sqlite(setVersionToOneWhereZero).
Postgres(setVersionToOneWhereZero).
Mysql(setVersionToOneWhereZero))
} }
+3 -1
View File
@@ -150,13 +150,15 @@ export class DataSourceEditCtrl {
} }
if (this.current.id) { if (this.current.id) {
return this.backendSrv.put('/api/datasources/' + this.current.id, this.current).then(() => { return this.backendSrv.put('/api/datasources/' + this.current.id, this.current).then((result) => {
this.current = result.datasource;
this.updateFrontendSettings().then(() => { this.updateFrontendSettings().then(() => {
this.testDatasource(); this.testDatasource();
}); });
}); });
} else { } else {
return this.backendSrv.post('/api/datasources', this.current).then(result => { return this.backendSrv.post('/api/datasources', this.current).then(result => {
this.current = result.datasource;
this.updateFrontendSettings(); this.updateFrontendSettings();
datasourceCreated = true; datasourceCreated = true;