| package datastore |
| |
| import ( |
| "encoding/json" |
| "reflect" |
| "testing" |
| |
| "github.com/docker/docker/libnetwork/options" |
| "gotest.tools/v3/assert" |
| ) |
| |
| var dummyKey = "dummy" |
| |
| // NewCustomDataStore can be used by other Tests in order to use custom datastore |
| func NewTestDataStore() DataStore { |
| return &datastore{scope: LocalScope, store: NewMockStore()} |
| } |
| |
| func TestKey(t *testing.T) { |
| eKey := []string{"hello", "world"} |
| sKey := Key(eKey...) |
| if sKey != "docker/network/v1.0/hello/world/" { |
| t.Fatalf("unexpected key : %s", sKey) |
| } |
| } |
| |
| func TestParseKey(t *testing.T) { |
| keySlice, err := ParseKey("/docker/network/v1.0/hello/world/") |
| if err != nil { |
| t.Fatal(err) |
| } |
| eKey := []string{"hello", "world"} |
| if len(keySlice) < 2 || !reflect.DeepEqual(eKey, keySlice) { |
| t.Fatalf("unexpected unkey : %s", keySlice) |
| } |
| } |
| |
| func TestInvalidDataStore(t *testing.T) { |
| config := ScopeCfg{ |
| Client: ScopeClientCfg{ |
| Provider: "invalid", |
| Address: "localhost:8500", |
| }, |
| } |
| _, err := NewDataStore(config) |
| if err == nil { |
| t.Fatal("Invalid Datastore connection configuration must result in a failure") |
| } |
| } |
| |
| func TestKVObjectFlatKey(t *testing.T) { |
| store := NewTestDataStore() |
| expected := dummyKVObject("1000", true) |
| err := store.PutObject(expected) |
| if err != nil { |
| t.Fatal(err) |
| } |
| keychain := []string{dummyKey, "1000"} |
| data, err := store.KVStore().Get(Key(keychain...)) |
| if err != nil { |
| t.Fatal(err) |
| } |
| var n dummyObject |
| json.Unmarshal(data.Value, &n) |
| if n.Name != expected.Name { |
| t.Fatal("Dummy object doesn't match the expected object") |
| } |
| } |
| |
| func TestAtomicKVObjectFlatKey(t *testing.T) { |
| store := NewTestDataStore() |
| expected := dummyKVObject("1111", true) |
| assert.Check(t, !expected.Exists()) |
| err := store.PutObjectAtomic(expected) |
| if err != nil { |
| t.Fatal(err) |
| } |
| assert.Check(t, expected.Exists()) |
| |
| // PutObjectAtomic automatically sets the Index again. Hence the following must pass. |
| |
| err = store.PutObjectAtomic(expected) |
| if err != nil { |
| t.Fatal("Atomic update should succeed.") |
| } |
| |
| // Get the latest index and try PutObjectAtomic again for the same Key |
| // This must succeed as well |
| data, err := store.KVStore().Get(Key(expected.Key()...)) |
| if err != nil { |
| t.Fatal(err) |
| } |
| n := dummyObject{} |
| json.Unmarshal(data.Value, &n) |
| n.ID = "1111" |
| n.SetIndex(data.LastIndex) |
| n.ReturnValue = true |
| err = store.PutObjectAtomic(&n) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Get the Object using GetObject, then set again. |
| newObj := dummyObject{} |
| err = store.GetObject(Key(expected.Key()...), &newObj) |
| if err != nil { |
| t.Fatal(err) |
| } |
| assert.Check(t, newObj.Exists()) |
| err = store.PutObjectAtomic(&n) |
| if err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| // dummy data used to test the datastore |
| type dummyObject struct { |
| Name string `kv:"leaf"` |
| NetworkType string `kv:"leaf"` |
| EnableIPv6 bool `kv:"leaf"` |
| Rec *recStruct `kv:"recursive"` |
| Dict map[string]*recStruct `kv:"iterative"` |
| Generic options.Generic `kv:"iterative"` |
| ID string |
| DBIndex uint64 |
| DBExists bool |
| SkipSave bool |
| ReturnValue bool |
| } |
| |
| func (n *dummyObject) Key() []string { |
| return []string{dummyKey, n.ID} |
| } |
| |
| func (n *dummyObject) KeyPrefix() []string { |
| return []string{dummyKey} |
| } |
| |
| func (n *dummyObject) Value() []byte { |
| if !n.ReturnValue { |
| return nil |
| } |
| |
| b, err := json.Marshal(n) |
| if err != nil { |
| return nil |
| } |
| return b |
| } |
| |
| func (n *dummyObject) SetValue(value []byte) error { |
| return json.Unmarshal(value, n) |
| } |
| |
| func (n *dummyObject) Index() uint64 { |
| return n.DBIndex |
| } |
| |
| func (n *dummyObject) SetIndex(index uint64) { |
| n.DBIndex = index |
| n.DBExists = true |
| } |
| |
| func (n *dummyObject) Exists() bool { |
| return n.DBExists |
| } |
| |
| func (n *dummyObject) Skip() bool { |
| return n.SkipSave |
| } |
| |
| func (n *dummyObject) DataScope() string { |
| return LocalScope |
| } |
| |
| func (n *dummyObject) MarshalJSON() ([]byte, error) { |
| netMap := make(map[string]interface{}) |
| netMap["name"] = n.Name |
| netMap["networkType"] = n.NetworkType |
| netMap["enableIPv6"] = n.EnableIPv6 |
| netMap["generic"] = n.Generic |
| return json.Marshal(netMap) |
| } |
| |
| func (n *dummyObject) UnmarshalJSON(b []byte) (err error) { |
| var netMap map[string]interface{} |
| if err := json.Unmarshal(b, &netMap); err != nil { |
| return err |
| } |
| n.Name = netMap["name"].(string) |
| n.NetworkType = netMap["networkType"].(string) |
| n.EnableIPv6 = netMap["enableIPv6"].(bool) |
| n.Generic = netMap["generic"].(map[string]interface{}) |
| return nil |
| } |
| |
| // dummy structure to test "recursive" cases |
| type recStruct struct { |
| Name string `kv:"leaf"` |
| Field1 int `kv:"leaf"` |
| Dict map[string]string `kv:"iterative"` |
| DBIndex uint64 |
| DBExists bool |
| SkipSave bool |
| } |
| |
| func (r *recStruct) Key() []string { |
| return []string{"recStruct"} |
| } |
| func (r *recStruct) Value() []byte { |
| b, err := json.Marshal(r) |
| if err != nil { |
| return nil |
| } |
| return b |
| } |
| |
| func (r *recStruct) SetValue(value []byte) error { |
| return json.Unmarshal(value, r) |
| } |
| |
| func (r *recStruct) Index() uint64 { |
| return r.DBIndex |
| } |
| |
| func (r *recStruct) SetIndex(index uint64) { |
| r.DBIndex = index |
| r.DBExists = true |
| } |
| |
| func (r *recStruct) Exists() bool { |
| return r.DBExists |
| } |
| |
| func (r *recStruct) Skip() bool { |
| return r.SkipSave |
| } |
| |
| func dummyKVObject(id string, retValue bool) *dummyObject { |
| cDict := make(map[string]string) |
| cDict["foo"] = "bar" |
| cDict["hello"] = "world" |
| n := dummyObject{ |
| Name: "testNw", |
| NetworkType: "bridge", |
| EnableIPv6: true, |
| Rec: &recStruct{"gen", 5, cDict, 0, false, false}, |
| ID: id, |
| DBIndex: 0, |
| ReturnValue: retValue, |
| DBExists: false, |
| SkipSave: false} |
| generic := make(map[string]interface{}) |
| generic["label1"] = &recStruct{"value1", 1, cDict, 0, false, false} |
| generic["label2"] = "subnet=10.1.1.0/16" |
| n.Generic = generic |
| return &n |
| } |