MM-14246 - Plugin framework: support transactional semantics with KV Store (#10634)

* MM-14246 - Plugin framework: support transactional semantics with KV Store

Rename old, new variable names

Moving New function to the bottom

* Made CompareAndUpdate sync, updated tests

* Removed going through channel in CompareAndSetPluginKey

* Inserting new key when oldValue is nil to KVCompareAndSet

* Updated error text to include CompareAndSet
Этот коммит содержится в:
Ali F
2019-04-23 13:35:17 -04:00
коммит произвёл Christopher Speller
родитель 446c6d452e
Коммит 6f8577b4c1
10 изменённых файлов: 311 добавлений и 0 удалений

Просмотреть файл

@@ -662,6 +662,10 @@ func (api *PluginAPI) KVSet(key string, value []byte) *model.AppError {
return api.app.SetPluginKey(api.id, key, value)
}
func (api *PluginAPI) KVCompareAndSet(key string, oldValue, newValue []byte) (bool, *model.AppError) {
return api.app.CompareAndSetPluginKey(api.id, key, oldValue, newValue)
}
func (api *PluginAPI) KVSetWithExpiry(key string, value []byte, expireInSeconds int64) *model.AppError {
return api.app.SetPluginKeyWithExpiry(api.id, key, value, expireInSeconds)
}

Просмотреть файл

@@ -659,3 +659,99 @@ func TestBasicAPIPlugins(t *testing.T) {
}
}
}
func TestPluginAPIKVCompareAndSet(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
api := th.SetupPluginAPI()
testCases := []struct {
Description string
ExpectedValue []byte
}{
{
Description: "Testing non-nil, non-empty value",
ExpectedValue: []byte("value1"),
},
{
Description: "Testing empty value",
ExpectedValue: []byte(""),
},
}
for i, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {
expectedKey := fmt.Sprintf("Key%d", i)
expectedValueEmpty := []byte("")
expectedValue1 := testCase.ExpectedValue
expectedValue2 := []byte("value2")
expectedValue3 := []byte("value3")
// Attempt update using an incorrect old value
updated, err := api.KVCompareAndSet(expectedKey, expectedValue2, expectedValue1)
require.Nil(t, err)
require.False(t, updated)
// Make sure no key is already created
value, err := api.KVGet(expectedKey)
require.Nil(t, err)
require.Nil(t, value)
// Insert using nil old value
updated, err = api.KVCompareAndSet(expectedKey, nil, expectedValue1)
require.Nil(t, err)
require.True(t, updated)
// Get inserted value
value, err = api.KVGet(expectedKey)
require.Nil(t, err)
require.Equal(t, expectedValue1, value)
// Attempt to insert again using nil old value
updated, err = api.KVCompareAndSet(expectedKey, nil, expectedValue2)
require.Nil(t, err)
require.False(t, updated)
// Get old value to assert nothing has changed
value, err = api.KVGet(expectedKey)
require.Nil(t, err)
require.Equal(t, expectedValue1, value)
// Update using correct old value
updated, err = api.KVCompareAndSet(expectedKey, expectedValue1, expectedValue2)
require.Nil(t, err)
require.True(t, updated)
value, err = api.KVGet(expectedKey)
require.Nil(t, err)
require.Equal(t, expectedValue2, value)
// Update using incorrect old value
updated, err = api.KVCompareAndSet(expectedKey, []byte("incorrect"), expectedValue3)
require.Nil(t, err)
require.False(t, updated)
value, err = api.KVGet(expectedKey)
require.Nil(t, err)
require.Equal(t, expectedValue2, value)
// Update using nil old value
updated, err = api.KVCompareAndSet(expectedKey, nil, expectedValue3)
require.Nil(t, err)
require.False(t, updated)
value, err = api.KVGet(expectedKey)
require.Nil(t, err)
require.Equal(t, expectedValue2, value)
// Update using empty old value
updated, err = api.KVCompareAndSet(expectedKey, expectedValueEmpty, expectedValue3)
require.Nil(t, err)
require.False(t, updated)
value, err = api.KVGet(expectedKey)
require.Nil(t, err)
require.Equal(t, expectedValue2, value)
})
}
}

Просмотреть файл

@@ -47,6 +47,27 @@ func (a *App) SetPluginKeyWithExpiry(pluginId string, key string, value []byte,
return nil
}
func (a *App) CompareAndSetPluginKey(pluginId string, key string, oldValue, newValue []byte) (bool, *model.AppError) {
kv := &model.PluginKeyValue{
PluginId: pluginId,
Key: key,
Value: newValue,
}
updated, err := a.Srv.Store.Plugin().CompareAndSet(kv, oldValue)
if err != nil {
mlog.Error("Failed to compare and set plugin key value", mlog.String("plugin_id", pluginId), mlog.String("key", key), mlog.Err(err))
return updated, err
}
// Clean up a previous entry using the hashed key, if it exists.
if result := <-a.Srv.Store.Plugin().Delete(pluginId, getKeyHash(key)); result.Err != nil {
mlog.Error("Failed to clean up previously hashed plugin key value", mlog.String("plugin_id", pluginId), mlog.String("key", key), mlog.Err(result.Err))
}
return updated, nil
}
func (a *App) GetPluginKey(pluginId string, key string) ([]byte, *model.AppError) {
if result := <-a.Srv.Store.Plugin().Get(pluginId, key); result.Err == nil {
return result.Data.(*model.PluginKeyValue).Value, nil

Просмотреть файл

@@ -125,6 +125,63 @@ func TestPluginKeyValueStore(t *testing.T) {
assert.Equal(t, []string{"key", "key3", "key4", hashedKey2}, list)
}
func TestPluginKeyValueStoreCompareAndSet(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
pluginId := "testpluginid"
defer func() {
assert.Nil(t, th.App.DeletePluginKey(pluginId, "key"))
}()
// Set using Set api for key2
assert.Nil(t, th.App.SetPluginKey(pluginId, "key2", []byte("test")))
ret, err := th.App.GetPluginKey(pluginId, "key2")
assert.Nil(t, err)
assert.Equal(t, []byte("test"), ret)
// Attempt to insert value for key2
updated, err := th.App.CompareAndSetPluginKey(pluginId, "key2", nil, []byte("test2"))
assert.Nil(t, err)
assert.False(t, updated)
ret, err = th.App.GetPluginKey(pluginId, "key2")
assert.Nil(t, err)
assert.Equal(t, []byte("test"), ret)
// Insert new value for key
updated, err = th.App.CompareAndSetPluginKey(pluginId, "key", nil, []byte("test"))
assert.Nil(t, err)
assert.True(t, updated)
ret, err = th.App.GetPluginKey(pluginId, "key")
assert.Nil(t, err)
assert.Equal(t, []byte("test"), ret)
// Should fail to insert again
updated, err = th.App.CompareAndSetPluginKey(pluginId, "key", nil, []byte("test3"))
assert.Nil(t, err)
assert.False(t, updated)
ret, err = th.App.GetPluginKey(pluginId, "key")
assert.Nil(t, err)
assert.Equal(t, []byte("test"), ret)
// Test updating using incorrect old value
updated, err = th.App.CompareAndSetPluginKey(pluginId, "key", []byte("oldvalue"), []byte("test3"))
assert.Nil(t, err)
assert.False(t, updated)
ret, err = th.App.GetPluginKey(pluginId, "key")
assert.Nil(t, err)
assert.Equal(t, []byte("test"), ret)
// Test updating using correct old value
updated, err = th.App.CompareAndSetPluginKey(pluginId, "key", []byte("test"), []byte("test2"))
assert.Nil(t, err)
assert.True(t, updated)
ret, err = th.App.GetPluginKey(pluginId, "key")
assert.Nil(t, err)
assert.Equal(t, []byte("test2"), ret)
}
func TestServePluginRequest(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()