diff --git a/sonic_data_client/client_test.go b/sonic_data_client/client_test.go index 320938b3..6f864faf 100644 --- a/sonic_data_client/client_test.go +++ b/sonic_data_client/client_test.go @@ -1,445 +1,451 @@ -package client - -import ( - "sync" - "errors" - "testing" - "os" - "time" - "reflect" - "io/ioutil" - "encoding/json" - "fmt" - - "github.com/jipanyang/gnxi/utils/xpath" - "github.com/sonic-net/sonic-gnmi/swsscommon" - gnmipb "github.com/openconfig/gnmi/proto/gnmi" -) - -var testFile string = "/etc/sonic/ut.cp.json" - -func JsonEqual(a, b []byte) (bool, error) { - var j1, j2 interface{} - var err error - if err = json.Unmarshal(a, &j1); err != nil { - return false, err - } - if err = json.Unmarshal(b, &j2); err != nil { - return false, err - } - return reflect.DeepEqual(j1, j2), nil -} - -func TestJsonClientNegative(t *testing.T) { - os.Remove(testFile) - _, err := NewJsonClient(testFile) - if err == nil { - t.Errorf("Should fail without checkpoint") - } - - text := "{" - err = ioutil.WriteFile(testFile, []byte(text), 0644) - if err != nil { - t.Errorf("Fail to create test file") - } - _, err = NewJsonClient(testFile) - if err == nil { - t.Errorf("Should fail with invalid checkpoint") - } -} - -func TestJsonAdd(t *testing.T) { - text := "{}" - err := ioutil.WriteFile(testFile, []byte(text), 0644) - if err != nil { - t.Errorf("Fail to create test file") - } - client, err := NewJsonClient(testFile) - if err != nil { - t.Errorf("Create client fail: %v", err) - } - path_list := [][]string { - []string { - "DASH_QOS", - }, - []string { - "DASH_QOS", - "qos_02", - }, - []string { - "DASH_QOS", - "qos_03", - "bw", - }, - []string { - "DASH_VNET", - "vnet001", - "address_spaces", - }, - []string { - "DASH_VNET", - "vnet002", - "address_spaces", - "0", - }, - } - value_list := []string { - `{"qos_01": {"bw": "54321", "cps": "1000", "flows": "300"}}`, - `{"bw": "10001", "cps": "1001", "flows": "101"}`, - `"20001"`, - `["10.250.0.0", "192.168.3.0", "139.66.72.9"]`, - `"6.6.6.6"`, - } - for i := 0; i < len(path_list); i++ { - path := path_list[i] - value := value_list[i] - err = client.Add(path, value) - if err != nil { - t.Errorf("Add %v fail: %v", path, err) - } - res, err := client.Get(path) - if err != nil { - t.Errorf("Get %v fail: %v", path, err) - } - ok, err := JsonEqual([]byte(value), res) - if err != nil { - t.Errorf("Compare json fail: %v", err) - return - } - if ok != true { - t.Errorf("%v and %v do not match", value, string(res)) - } - } -} - -func TestJsonAddNegative(t *testing.T) { - text := "{}" - err := ioutil.WriteFile(testFile, []byte(text), 0644) - if err != nil { - t.Errorf("Fail to create test file") - } - client, err := NewJsonClient(testFile) - if err != nil { - t.Errorf("Create client fail: %v", err) - } - path_list := [][]string { - []string { - "DASH_QOS", - }, - []string { - "DASH_QOS", - "qos_02", - }, - []string { - "DASH_QOS", - "qos_03", - "bw", - }, - []string { - "DASH_VNET", - "vnet001", - "address_spaces", - }, - []string { - "DASH_VNET", - "vnet002", - "address_spaces", - "0", - }, - []string { - "DASH_VNET", - "vnet002", - "address_spaces", - "abc", - }, - []string { - "DASH_VNET", - "vnet002", - "address_spaces", - "100", - }, - } - value_list := []string { - `{"qos_01": {"bw": "54321", "cps": "1000", "flows": "300"}`, - `{"bw": "10001", "cps": "1001", "flows": "101"`, - `20001`, - `["10.250.0.0", "192.168.3.0", "139.66.72.9"`, - `"6.6.6.6`, - `"6.6.6.6"`, - `"6.6.6.6"`, - } - for i := 0; i < len(path_list); i++ { - path := path_list[i] - value := value_list[i] - err = client.Add(path, value) - if err == nil { - t.Errorf("Add %v should fail: %v", path, err) - } - } -} - -func TestJsonRemove(t *testing.T) { - text := "{}" - err := ioutil.WriteFile(testFile, []byte(text), 0644) - if err != nil { - t.Errorf("Fail to create test file") - } - client, err := NewJsonClient(testFile) - if err != nil { - t.Errorf("Create client fail: %v", err) - } - path_list := [][]string { - []string { - "DASH_QOS", - }, - []string { - "DASH_QOS", - "qos_02", - }, - []string { - "DASH_QOS", - "qos_03", - "bw", - }, - []string { - "DASH_VNET", - "vnet001", - "address_spaces", - }, - []string { - "DASH_VNET", - "vnet002", - "address_spaces", - "0", - }, - } - value_list := []string { - `{"qos_01": {"bw": "54321", "cps": "1000", "flows": "300"}}`, - `{"bw": "10001", "cps": "1001", "flows": "101"}`, - `"20001"`, - `["10.250.0.0", "192.168.3.0", "139.66.72.9"]`, - `"6.6.6.6"`, - } - for i := 0; i < len(path_list); i++ { - path := path_list[i] - value := value_list[i] - err = client.Add(path, value) - if err != nil { - t.Errorf("Add %v fail: %v", path, err) - } - err = client.Remove(path) - if err != nil { - t.Errorf("Remove %v fail: %v", path, err) - } - _, err := client.Get(path) - if err == nil { - t.Errorf("Get %v should fail: %v", path, err) - } - } -} - -func TestJsonRemoveNegative(t *testing.T) { - text := "{}" - err := ioutil.WriteFile(testFile, []byte(text), 0644) - if err != nil { - t.Errorf("Fail to create test file") - } - client, err := NewJsonClient(testFile) - if err != nil { - t.Errorf("Create client fail: %v", err) - } - path_list := [][]string { - []string { - "DASH_QOS", - }, - []string { - "DASH_VNET", - "vnet001", - "address_spaces", - }, - } - value_list := []string { - `{"qos_01": {"bw": "54321", "cps": "1000", "flows": "300"}}`, - `["10.250.0.0", "192.168.3.0", "139.66.72.9"]`, - } - for i := 0; i < len(path_list); i++ { - path := path_list[i] - value := value_list[i] - err = client.Add(path, value) - if err != nil { - t.Errorf("Add %v fail: %v", path, err) - } - } - - remove_list := [][]string { - []string { - "DASH_QOS", - "qos_02", - }, - []string { - "DASH_QOS", - "qos_03", - "bw", - }, - []string { - "DASH_VNET", - "vnet001", - "address_spaces", - "abc", - }, - []string { - "DASH_VNET", - "vnet001", - "address_spaces", - "100", - }, - } - for i := 0; i < len(remove_list); i++ { - path := remove_list[i] - err = client.Remove(path) - if err == nil { - t.Errorf("Remove %v should fail: %v", path, err) - } - } -} - -func TestParseTarget(t *testing.T) { - var test_paths []*gnmipb.Path - var err error - - _, err = ParseTarget("test", test_paths) - if err != nil { - t.Errorf("ParseTarget failed for empty path: %v", err) - } - - test_target := "TEST_DB" - path, err := xpath.ToGNMIPath("sonic-db:" + test_target + "/VLAN") - test_paths = append(test_paths, path) - target, err := ParseTarget("", test_paths) - if err != nil { - t.Errorf("ParseTarget failed to get target: %v", err) - } - if target != test_target { - t.Errorf("ParseTarget return wrong target: %v", target) - } - target, err = ParseTarget("INVALID_DB", test_paths) - if err == nil { - t.Errorf("ParseTarget should fail for conflict") - } -} - -func mockGetFunc() ([]byte, error) { - return nil, errors.New("mock error") -} - -func TestNonDbClientGetError(t *testing.T) { - var gnmipbPath *gnmipb.Path = &gnmipb.Path{ - Element: []string{"mockPath"}, - } - - path2Getter := map[*gnmipb.Path]dataGetFunc{ - gnmipbPath: mockGetFunc, - } - - // Create a NonDbClient with the mocked dataGetFunc - client := &NonDbClient{ - path2Getter: path2Getter, - } - - var w *sync.WaitGroup - _, err := client.Get(w) - if errors.Is(err, errors.New("mock error")) { - t.Errorf("Expected error from NonDbClient.Get, got nil") - } -} - -/* - Helper method for receive data from ZmqConsumerStateTable - consumer: Receive data from consumer - return: - true: data received - false: not receive any data after retry -*/ -func ReceiveFromZmq(consumer swsscommon.ZmqConsumerStateTable) (bool) { - receivedData := swsscommon.NewKeyOpFieldsValuesQueue() - retry := 0; - for { - // sender's ZMQ may disconnect, wait and retry for reconnect - time.Sleep(time.Duration(1000) * time.Millisecond) - consumer.Pops(receivedData) - if receivedData.Size() == 0 { - retry++ - if retry >= 10 { - return false - } - } else { - return true - } - } -} - -func TestZmqReconnect(t *testing.T) { - // create ZMQ server - db := swsscommon.NewDBConnector(APPL_DB_NAME, SWSS_TIMEOUT, false) - zmqServer := swsscommon.NewZmqServer("tcp://*:1234") - var TEST_TABLE string = "DASH_ROUTE" - consumer := swsscommon.NewZmqConsumerStateTable(db, TEST_TABLE, zmqServer) - - // create ZMQ client side - zmqAddress := "tcp://127.0.0.1:1234" - client := MixedDbClient { - applDB : swsscommon.NewDBConnector(APPL_DB_NAME, SWSS_TIMEOUT, false), - tableMap : map[string]swsscommon.ProducerStateTable{}, - zmqClient : swsscommon.NewZmqClient(zmqAddress), - } - - data := map[string]string{} - var TEST_KEY string = "TestKey" - client.DbSetTable(TEST_TABLE, TEST_KEY, data) - if !ReceiveFromZmq(consumer) { - t.Errorf("Receive data from ZMQ failed") - } - - // recreate ZMQ server to trigger re-connect - swsscommon.DeleteZmqConsumerStateTable(consumer) - swsscommon.DeleteZmqServer(zmqServer) - zmqServer = swsscommon.NewZmqServer("tcp://*:1234") - consumer = swsscommon.NewZmqConsumerStateTable(db, TEST_TABLE, zmqServer) - - // send data again, client will reconnect - client.DbSetTable(TEST_TABLE, TEST_KEY, data) - if !ReceiveFromZmq(consumer) { - t.Errorf("Receive data from ZMQ failed") - } -} - -func TestRetryHelper(t *testing.T) { - // create ZMQ server - zmqServer := swsscommon.NewZmqServer("tcp://*:2234") - - // create ZMQ client side - zmqAddress := "tcp://127.0.0.1:2234" - zmqClient := swsscommon.NewZmqClient(zmqAddress) - returnError := true - exeCount := 0 - RetryHelper( - zmqClient, - func () (err error) { - exeCount++ - if returnError { - returnError = false - return fmt.Errorf("connection_reset") - } - return nil - }) - - if exeCount == 1 { - t.Errorf("RetryHelper does not retry") - } - - if exeCount > 2 { - t.Errorf("RetryHelper retry too much") - } - - swsscommon.DeleteZmqServer(zmqServer) -} +package client + +import ( + "sync" + "errors" + "testing" + "os" + "time" + "reflect" + "io/ioutil" + "encoding/json" + "fmt" + + "github.com/jipanyang/gnxi/utils/xpath" + "github.com/sonic-net/sonic-gnmi/swsscommon" + gnmipb "github.com/openconfig/gnmi/proto/gnmi" +) + +var testFile string = "/etc/sonic/ut.cp.json" + +func JsonEqual(a, b []byte) (bool, error) { + var j1, j2 interface{} + var err error + if err = json.Unmarshal(a, &j1); err != nil { + return false, err + } + if err = json.Unmarshal(b, &j2); err != nil { + return false, err + } + return reflect.DeepEqual(j1, j2), nil +} + +func TestJsonClientNegative(t *testing.T) { + os.Remove(testFile) + _, err := NewJsonClient(testFile) + if err == nil { + t.Errorf("Should fail without checkpoint") + } + + text := "{" + err = ioutil.WriteFile(testFile, []byte(text), 0644) + if err != nil { + t.Errorf("Fail to create test file") + } + _, err = NewJsonClient(testFile) + if err == nil { + t.Errorf("Should fail with invalid checkpoint") + } +} + +func TestJsonAdd(t *testing.T) { + text := "{}" + err := ioutil.WriteFile(testFile, []byte(text), 0644) + if err != nil { + t.Errorf("Fail to create test file") + } + client, err := NewJsonClient(testFile) + if err != nil { + t.Errorf("Create client fail: %v", err) + } + path_list := [][]string { + []string { + "DASH_QOS", + }, + []string { + "DASH_QOS", + "qos_02", + }, + []string { + "DASH_QOS", + "qos_03", + "bw", + }, + []string { + "DASH_VNET", + "vnet001", + "address_spaces", + }, + []string { + "DASH_VNET", + "vnet002", + "address_spaces", + "0", + }, + } + value_list := []string { + `{"qos_01": {"bw": "54321", "cps": "1000", "flows": "300"}}`, + `{"bw": "10001", "cps": "1001", "flows": "101"}`, + `"20001"`, + `["10.250.0.0", "192.168.3.0", "139.66.72.9"]`, + `"6.6.6.6"`, + } + for i := 0; i < len(path_list); i++ { + path := path_list[i] + value := value_list[i] + err = client.Add(path, value) + if err != nil { + t.Errorf("Add %v fail: %v", path, err) + } + res, err := client.Get(path) + if err != nil { + t.Errorf("Get %v fail: %v", path, err) + } + ok, err := JsonEqual([]byte(value), res) + if err != nil { + t.Errorf("Compare json fail: %v", err) + return + } + if ok != true { + t.Errorf("%v and %v do not match", value, string(res)) + } + } + path := []string{} + res, err := client.Get(path) + if err != nil { + t.Errorf("Get %v fail: %v", path, err) + } + t.Logf("Result %s", string(res)) +} + +func TestJsonAddNegative(t *testing.T) { + text := "{}" + err := ioutil.WriteFile(testFile, []byte(text), 0644) + if err != nil { + t.Errorf("Fail to create test file") + } + client, err := NewJsonClient(testFile) + if err != nil { + t.Errorf("Create client fail: %v", err) + } + path_list := [][]string { + []string { + "DASH_QOS", + }, + []string { + "DASH_QOS", + "qos_02", + }, + []string { + "DASH_QOS", + "qos_03", + "bw", + }, + []string { + "DASH_VNET", + "vnet001", + "address_spaces", + }, + []string { + "DASH_VNET", + "vnet002", + "address_spaces", + "0", + }, + []string { + "DASH_VNET", + "vnet002", + "address_spaces", + "abc", + }, + []string { + "DASH_VNET", + "vnet002", + "address_spaces", + "100", + }, + } + value_list := []string { + `{"qos_01": {"bw": "54321", "cps": "1000", "flows": "300"}`, + `{"bw": "10001", "cps": "1001", "flows": "101"`, + `20001`, + `["10.250.0.0", "192.168.3.0", "139.66.72.9"`, + `"6.6.6.6`, + `"6.6.6.6"`, + `"6.6.6.6"`, + } + for i := 0; i < len(path_list); i++ { + path := path_list[i] + value := value_list[i] + err = client.Add(path, value) + if err == nil { + t.Errorf("Add %v should fail: %v", path, err) + } + } +} + +func TestJsonRemove(t *testing.T) { + text := "{}" + err := ioutil.WriteFile(testFile, []byte(text), 0644) + if err != nil { + t.Errorf("Fail to create test file") + } + client, err := NewJsonClient(testFile) + if err != nil { + t.Errorf("Create client fail: %v", err) + } + path_list := [][]string { + []string { + "DASH_QOS", + }, + []string { + "DASH_QOS", + "qos_02", + }, + []string { + "DASH_QOS", + "qos_03", + "bw", + }, + []string { + "DASH_VNET", + "vnet001", + "address_spaces", + }, + []string { + "DASH_VNET", + "vnet002", + "address_spaces", + "0", + }, + } + value_list := []string { + `{"qos_01": {"bw": "54321", "cps": "1000", "flows": "300"}}`, + `{"bw": "10001", "cps": "1001", "flows": "101"}`, + `"20001"`, + `["10.250.0.0", "192.168.3.0", "139.66.72.9"]`, + `"6.6.6.6"`, + } + for i := 0; i < len(path_list); i++ { + path := path_list[i] + value := value_list[i] + err = client.Add(path, value) + if err != nil { + t.Errorf("Add %v fail: %v", path, err) + } + err = client.Remove(path) + if err != nil { + t.Errorf("Remove %v fail: %v", path, err) + } + _, err := client.Get(path) + if err == nil { + t.Errorf("Get %v should fail: %v", path, err) + } + } +} + +func TestJsonRemoveNegative(t *testing.T) { + text := "{}" + err := ioutil.WriteFile(testFile, []byte(text), 0644) + if err != nil { + t.Errorf("Fail to create test file") + } + client, err := NewJsonClient(testFile) + if err != nil { + t.Errorf("Create client fail: %v", err) + } + path_list := [][]string { + []string { + "DASH_QOS", + }, + []string { + "DASH_VNET", + "vnet001", + "address_spaces", + }, + } + value_list := []string { + `{"qos_01": {"bw": "54321", "cps": "1000", "flows": "300"}}`, + `["10.250.0.0", "192.168.3.0", "139.66.72.9"]`, + } + for i := 0; i < len(path_list); i++ { + path := path_list[i] + value := value_list[i] + err = client.Add(path, value) + if err != nil { + t.Errorf("Add %v fail: %v", path, err) + } + } + + remove_list := [][]string { + []string { + "DASH_QOS", + "qos_02", + }, + []string { + "DASH_QOS", + "qos_03", + "bw", + }, + []string { + "DASH_VNET", + "vnet001", + "address_spaces", + "abc", + }, + []string { + "DASH_VNET", + "vnet001", + "address_spaces", + "100", + }, + } + for i := 0; i < len(remove_list); i++ { + path := remove_list[i] + err = client.Remove(path) + if err == nil { + t.Errorf("Remove %v should fail: %v", path, err) + } + } +} + +func TestParseTarget(t *testing.T) { + var test_paths []*gnmipb.Path + var err error + + _, err = ParseTarget("test", test_paths) + if err != nil { + t.Errorf("ParseTarget failed for empty path: %v", err) + } + + test_target := "TEST_DB" + path, err := xpath.ToGNMIPath("sonic-db:" + test_target + "/VLAN") + test_paths = append(test_paths, path) + target, err := ParseTarget("", test_paths) + if err != nil { + t.Errorf("ParseTarget failed to get target: %v", err) + } + if target != test_target { + t.Errorf("ParseTarget return wrong target: %v", target) + } + target, err = ParseTarget("INVALID_DB", test_paths) + if err == nil { + t.Errorf("ParseTarget should fail for conflict") + } +} + +func mockGetFunc() ([]byte, error) { + return nil, errors.New("mock error") +} + +func TestNonDbClientGetError(t *testing.T) { + var gnmipbPath *gnmipb.Path = &gnmipb.Path{ + Element: []string{"mockPath"}, + } + + path2Getter := map[*gnmipb.Path]dataGetFunc{ + gnmipbPath: mockGetFunc, + } + + // Create a NonDbClient with the mocked dataGetFunc + client := &NonDbClient{ + path2Getter: path2Getter, + } + + var w *sync.WaitGroup + _, err := client.Get(w) + if errors.Is(err, errors.New("mock error")) { + t.Errorf("Expected error from NonDbClient.Get, got nil") + } +} + +/* + Helper method for receive data from ZmqConsumerStateTable + consumer: Receive data from consumer + return: + true: data received + false: not receive any data after retry +*/ +func ReceiveFromZmq(consumer swsscommon.ZmqConsumerStateTable) (bool) { + receivedData := swsscommon.NewKeyOpFieldsValuesQueue() + retry := 0; + for { + // sender's ZMQ may disconnect, wait and retry for reconnect + time.Sleep(time.Duration(1000) * time.Millisecond) + consumer.Pops(receivedData) + if receivedData.Size() == 0 { + retry++ + if retry >= 10 { + return false + } + } else { + return true + } + } +} + +func TestZmqReconnect(t *testing.T) { + // create ZMQ server + db := swsscommon.NewDBConnector(APPL_DB_NAME, SWSS_TIMEOUT, false) + zmqServer := swsscommon.NewZmqServer("tcp://*:1234") + var TEST_TABLE string = "DASH_ROUTE" + consumer := swsscommon.NewZmqConsumerStateTable(db, TEST_TABLE, zmqServer) + + // create ZMQ client side + zmqAddress := "tcp://127.0.0.1:1234" + client := MixedDbClient { + applDB : swsscommon.NewDBConnector(APPL_DB_NAME, SWSS_TIMEOUT, false), + tableMap : map[string]swsscommon.ProducerStateTable{}, + zmqClient : swsscommon.NewZmqClient(zmqAddress), + } + + data := map[string]string{} + var TEST_KEY string = "TestKey" + client.DbSetTable(TEST_TABLE, TEST_KEY, data) + if !ReceiveFromZmq(consumer) { + t.Errorf("Receive data from ZMQ failed") + } + + // recreate ZMQ server to trigger re-connect + swsscommon.DeleteZmqConsumerStateTable(consumer) + swsscommon.DeleteZmqServer(zmqServer) + zmqServer = swsscommon.NewZmqServer("tcp://*:1234") + consumer = swsscommon.NewZmqConsumerStateTable(db, TEST_TABLE, zmqServer) + + // send data again, client will reconnect + client.DbSetTable(TEST_TABLE, TEST_KEY, data) + if !ReceiveFromZmq(consumer) { + t.Errorf("Receive data from ZMQ failed") + } +} + +func TestRetryHelper(t *testing.T) { + // create ZMQ server + zmqServer := swsscommon.NewZmqServer("tcp://*:2234") + + // create ZMQ client side + zmqAddress := "tcp://127.0.0.1:2234" + zmqClient := swsscommon.NewZmqClient(zmqAddress) + returnError := true + exeCount := 0 + RetryHelper( + zmqClient, + func () (err error) { + exeCount++ + if returnError { + returnError = false + return fmt.Errorf("connection_reset") + } + return nil + }) + + if exeCount == 1 { + t.Errorf("RetryHelper does not retry") + } + + if exeCount > 2 { + t.Errorf("RetryHelper retry too much") + } + + swsscommon.DeleteZmqServer(zmqServer) +} diff --git a/sonic_data_client/json_client.go b/sonic_data_client/json_client.go index ba6e3666..d54f5dee 100644 --- a/sonic_data_client/json_client.go +++ b/sonic_data_client/json_client.go @@ -1,379 +1,385 @@ -package client - -import ( - "os" - "fmt" - "strconv" - "io/ioutil" - "encoding/json" - - log "github.com/golang/glog" -) - -type JsonClient struct { - jsonData map[string]interface{} -} - -func DecodeJsonTable(database map[string]interface{}, tableName string) (map[string]interface{}, error) { - vtable, ok := database[tableName] - if !ok { - log.V(2).Infof("Invalid database %v -> %v", tableName, database) - return nil, fmt.Errorf("Invalid database %v -> %v", tableName, database) - } - v, ok := vtable.(map[string]interface{}) - if !ok { - log.V(2).Infof("Invalid table %v", vtable) - return nil, fmt.Errorf("Invalid table %v", vtable) - } - return v, nil -} - -func DecodeJsonEntry(table map[string]interface{}, entryName string) (map[string]interface{}, error) { - ventry, ok := table[entryName] - if !ok { - log.V(2).Infof("Invalid entry %v", table) - return nil, fmt.Errorf("Invalid entry %v", table) - } - v, ok := ventry.(map[string]interface{}) - if !ok { - log.V(2).Infof("Invalid entry %v", ventry) - return nil, fmt.Errorf("Invalid entry %v", ventry) - } - return v, nil -} - -func DecodeJsonField(entry map[string]interface{}, fieldName string) (*string, []interface{}, error) { - vfield, ok := entry[fieldName] - if !ok { - log.V(2).Infof("Invalid entry %v", entry) - return nil, nil, fmt.Errorf("Invalid entry %v", entry) - } - str, ok := vfield.(string) - if ok { - return &str, nil, nil - } - list, ok := vfield.([]interface{}) - if ok { - return nil, list, nil - } - return nil, nil, fmt.Errorf("Invalid field %v", vfield) -} - -func DecodeJsonListItem(list []interface{}, index string) (*string, error) { - id, err := strconv.Atoi(index) - if err != nil { - log.V(2).Infof("Invalid index %v", index) - return nil, fmt.Errorf("Invalid index %v", index) - } - if id < 0 || id >= len(list) { - log.V(2).Infof("Invalid index %v", index) - return nil, fmt.Errorf("Invalid index %v", index) - } - vitem := list[id] - str, ok := vitem.(string) - if ok { - return &str, nil - } - return nil, fmt.Errorf("Invalid item %v", vitem) -} - -func NewJsonClient(fileName string) (*JsonClient, error) { - var client JsonClient - - jsonFile, err := os.Open(fileName) - if err != nil { - return nil, err - } - defer jsonFile.Close() - - jsonData, err := ioutil.ReadAll(jsonFile) - if err!= nil { - return nil, err - } - res, err := parseJson([]byte(jsonData)) - if err != nil { - return nil, err - } - var ok bool - client.jsonData, ok = res.(map[string]interface{}) - if !ok { - log.V(2).Infof("Invalid checkpoint %v", fileName) - return nil, fmt.Errorf("Invalid checkpoint %v", fileName) - } - - return &client, nil -} - -func (c *JsonClient) Get(path []string) ([]byte, error) { - // The expect real db path could be in one of the formats: - // <1> DB Table - // <2> DB Table Key - // <3> DB Table Key Field - // <4> DB Table Key Field Index - jv := []byte{} - switch len(path) { - case 1: // only table name provided - vtable, err := DecodeJsonTable(c.jsonData, path[0]) - if err != nil { - return nil, err - } - jv, err = emitJSON(&vtable) - if err != nil { - return nil, err - } - case 2: // Second element must be table key - vtable, err := DecodeJsonTable(c.jsonData, path[0]) - if err != nil { - return nil, err - } - ventry, err := DecodeJsonEntry(vtable, path[1]) - if err != nil { - return nil, err - } - jv, err = emitJSON(&ventry) - if err != nil { - return nil, err - } - case 3: // Third element must be field name - vtable, err := DecodeJsonTable(c.jsonData, path[0]) - if err != nil { - return nil, err - } - ventry, err := DecodeJsonEntry(vtable, path[1]) - if err != nil { - return nil, err - } - vstr, vlist, err := DecodeJsonField(ventry, path[2]) - if err != nil { - return nil, err - } - if vstr != nil { - jv = []byte(`"` + *vstr + `"`) - } else if vlist != nil { - jv, err = json.Marshal(vlist) - if err != nil { - return nil, err - } - } - case 4: // Fourth element must be list index - vtable, err := DecodeJsonTable(c.jsonData, path[0]) - if err != nil { - return nil, err - } - ventry, err := DecodeJsonEntry(vtable, path[1]) - if err != nil { - return nil, err - } - _, vlist, err := DecodeJsonField(ventry, path[2]) - if err != nil { - return nil, err - } - vstr, err := DecodeJsonListItem(vlist, path[3]) - if err != nil { - return nil, err - } - if vstr != nil { - jv = []byte(`"` + *vstr + `"`) - } else { - return nil, fmt.Errorf("Invalid db table Path %v", path) - } - default: - log.V(2).Infof("Invalid db table Path %v", path) - return nil, fmt.Errorf("Invalid db table Path %v", path) - } - return jv, nil -} - -func (c *JsonClient) Add(path []string, value string) error { - // The expect real db path could be in one of the formats: - // <1> DB Table - // <2> DB Table Key - // <3> DB Table Key Field - // <4> DB Table Key Field Index - switch len(path) { - case 1: // only table name provided - vtable, err := parseJson([]byte(value)) - if err != nil { - return fmt.Errorf("Fail to parse %v", value) - } - v, ok := vtable.(map[string]interface{}) - if !ok { - log.V(2).Infof("Invalid table %v", vtable) - return fmt.Errorf("Invalid table %v", vtable) - } - c.jsonData[path[0]] = v - case 2: // Second element must be table key - vtable, err := DecodeJsonTable(c.jsonData, path[0]) - if err != nil { - vtable = make(map[string]interface{}) - c.jsonData[path[0]] = vtable - } - ventry, err := parseJson([]byte(value)) - if err != nil { - return fmt.Errorf("Fail to parse %v", value) - } - v, ok := ventry.(map[string]interface{}) - if !ok { - log.V(2).Infof("Invalid entry %v", ventry) - return fmt.Errorf("Invalid entry %v", ventry) - } - vtable[path[1]] = v - case 3: // Third element must be field name - vtable, err := DecodeJsonTable(c.jsonData, path[0]) - if err != nil { - vtable = make(map[string]interface{}) - c.jsonData[path[0]] = vtable - } - ventry, err := DecodeJsonEntry(vtable, path[1]) - if err != nil { - ventry = make(map[string]interface{}) - vtable[path[1]] = ventry - } - vfield, err := parseJson([]byte(value)) - if err != nil { - return fmt.Errorf("Fail to parse %v", value) - } - vstr, ok := vfield.(string) - if ok { - ventry[path[2]] = vstr - return nil - } - vlist, ok := vfield.([]interface{}) - if ok { - ventry[path[2]] = vlist - return nil - } - log.V(2).Infof("Invalid field %v", vfield) - return fmt.Errorf("Invalid field %v", vfield) - case 4: // Fourth element must be list index - id, err := strconv.Atoi(path[3]) - if err != nil { - log.V(2).Infof("Invalid index %v", path[3]) - return fmt.Errorf("Invalid index %v", path[3]) - } - vtable, err := DecodeJsonTable(c.jsonData, path[0]) - if err != nil { - vtable = make(map[string]interface{}) - c.jsonData[path[0]] = vtable - } - ventry, err := DecodeJsonEntry(vtable, path[1]) - if err != nil { - ventry = make(map[string]interface{}) - vtable[path[1]] = ventry - } - vstr, vlist, err := DecodeJsonField(ventry, path[2]) - if err != nil { - vlist = make([]interface{}, 0) - ventry[path[2]] = vlist - } - if vstr != nil { - log.V(2).Infof("Invalid target field %v", ventry) - return fmt.Errorf("Invalid target field %v", ventry) - } - if id < 0 || id > len(vlist) { - log.V(2).Infof("Invalid index %v", id) - return fmt.Errorf("Invalid index %v", id) - } - if id == len(vlist) { - vlist = append(vlist, "") - ventry[path[2]] = vlist - } - v, err := parseJson([]byte(value)) - if err != nil { - return fmt.Errorf("Fail to parse %v", value) - } - vlist[id] = v - default: - log.V(2).Infof("Invalid db table Path %v", path) - return fmt.Errorf("Invalid db table Path %v", path) - } - - return nil -} - -func (c *JsonClient) Remove(path []string) error { - // The expect real db path could be in one of the formats: - // <1> DB Table - // <2> DB Table Key - // <3> DB Table Key Field - // <4> DB Table Key Field Index - switch len(path) { - case 1: // only table name provided - _, err := DecodeJsonTable(c.jsonData, path[0]) - if err != nil { - return err - } - delete(c.jsonData, path[0]) - case 2: // Second element must be table key - vtable, err := DecodeJsonTable(c.jsonData, path[0]) - if err != nil { - return err - } - _, err = DecodeJsonEntry(vtable, path[1]) - if err != nil { - return err - } - delete(vtable, path[1]) - if len(vtable) == 0 { - delete(c.jsonData, path[0]) - } - case 3: // Third element must be field name - vtable, err := DecodeJsonTable(c.jsonData, path[0]) - if err != nil { - return err - } - ventry, err := DecodeJsonEntry(vtable, path[1]) - if err != nil { - return err - } - _, _, err = DecodeJsonField(ventry, path[2]) - if err != nil { - return err - } - delete(ventry, path[2]) - if len(ventry) == 0 { - delete(vtable, path[1]) - } - if len(vtable) == 0 { - delete(c.jsonData, path[0]) - } - case 4: // Fourth element must be list index - id, err := strconv.Atoi(path[3]) - if err != nil { - log.V(2).Infof("Invalid index %v", path[3]) - return fmt.Errorf("Invalid index %v", path[3]) - } - vtable, err := DecodeJsonTable(c.jsonData, path[0]) - if err != nil { - return err - } - ventry, err := DecodeJsonEntry(vtable, path[1]) - if err != nil { - return err - } - _, vlist, err := DecodeJsonField(ventry, path[2]) - if err != nil { - return err - } - _, err = DecodeJsonListItem(vlist, path[3]) - if err != nil { - return err - } - vlist = append(vlist[:id], vlist[id+1:]...) - ventry[path[2]] = vlist - if len(vlist) == 0 { - delete(ventry, path[2]) - } - if len(ventry) == 0 { - delete(vtable, path[1]) - } - if len(vtable) == 0 { - delete(c.jsonData, path[0]) - } - default: - log.V(2).Infof("Invalid db table Path %v", path) - return fmt.Errorf("Invalid db table Path %v", path) - } - - return nil +package client + +import ( + "os" + "fmt" + "strconv" + "io/ioutil" + "encoding/json" + + log "github.com/golang/glog" +) + +type JsonClient struct { + jsonData map[string]interface{} +} + +func DecodeJsonTable(database map[string]interface{}, tableName string) (map[string]interface{}, error) { + vtable, ok := database[tableName] + if !ok { + log.V(2).Infof("Invalid database %v -> %v", tableName, database) + return nil, fmt.Errorf("Invalid database %v -> %v", tableName, database) + } + v, ok := vtable.(map[string]interface{}) + if !ok { + log.V(2).Infof("Invalid table %v", vtable) + return nil, fmt.Errorf("Invalid table %v", vtable) + } + return v, nil +} + +func DecodeJsonEntry(table map[string]interface{}, entryName string) (map[string]interface{}, error) { + ventry, ok := table[entryName] + if !ok { + log.V(2).Infof("Invalid entry %v", table) + return nil, fmt.Errorf("Invalid entry %v", table) + } + v, ok := ventry.(map[string]interface{}) + if !ok { + log.V(2).Infof("Invalid entry %v", ventry) + return nil, fmt.Errorf("Invalid entry %v", ventry) + } + return v, nil +} + +func DecodeJsonField(entry map[string]interface{}, fieldName string) (*string, []interface{}, error) { + vfield, ok := entry[fieldName] + if !ok { + log.V(2).Infof("Invalid entry %v", entry) + return nil, nil, fmt.Errorf("Invalid entry %v", entry) + } + str, ok := vfield.(string) + if ok { + return &str, nil, nil + } + list, ok := vfield.([]interface{}) + if ok { + return nil, list, nil + } + return nil, nil, fmt.Errorf("Invalid field %v", vfield) +} + +func DecodeJsonListItem(list []interface{}, index string) (*string, error) { + id, err := strconv.Atoi(index) + if err != nil { + log.V(2).Infof("Invalid index %v", index) + return nil, fmt.Errorf("Invalid index %v", index) + } + if id < 0 || id >= len(list) { + log.V(2).Infof("Invalid index %v", index) + return nil, fmt.Errorf("Invalid index %v", index) + } + vitem := list[id] + str, ok := vitem.(string) + if ok { + return &str, nil + } + return nil, fmt.Errorf("Invalid item %v", vitem) +} + +func NewJsonClient(fileName string) (*JsonClient, error) { + var client JsonClient + + jsonFile, err := os.Open(fileName) + if err != nil { + return nil, err + } + defer jsonFile.Close() + + jsonData, err := ioutil.ReadAll(jsonFile) + if err!= nil { + return nil, err + } + res, err := parseJson([]byte(jsonData)) + if err != nil { + return nil, err + } + var ok bool + client.jsonData, ok = res.(map[string]interface{}) + if !ok { + log.V(2).Infof("Invalid checkpoint %v", fileName) + return nil, fmt.Errorf("Invalid checkpoint %v", fileName) + } + + return &client, nil +} + +func (c *JsonClient) Get(path []string) ([]byte, error) { + // The expect real db path could be in one of the formats: + // <1> DB Table + // <2> DB Table Key + // <3> DB Table Key Field + // <4> DB Table Key Field Index + jv := []byte{} + switch len(path) { + case 0: // Empty path + var err error + jv, err = emitJSON(&c.jsonData) + if err != nil { + return nil, err + } + case 1: // only table name provided + vtable, err := DecodeJsonTable(c.jsonData, path[0]) + if err != nil { + return nil, err + } + jv, err = emitJSON(&vtable) + if err != nil { + return nil, err + } + case 2: // Second element must be table key + vtable, err := DecodeJsonTable(c.jsonData, path[0]) + if err != nil { + return nil, err + } + ventry, err := DecodeJsonEntry(vtable, path[1]) + if err != nil { + return nil, err + } + jv, err = emitJSON(&ventry) + if err != nil { + return nil, err + } + case 3: // Third element must be field name + vtable, err := DecodeJsonTable(c.jsonData, path[0]) + if err != nil { + return nil, err + } + ventry, err := DecodeJsonEntry(vtable, path[1]) + if err != nil { + return nil, err + } + vstr, vlist, err := DecodeJsonField(ventry, path[2]) + if err != nil { + return nil, err + } + if vstr != nil { + jv = []byte(`"` + *vstr + `"`) + } else if vlist != nil { + jv, err = json.Marshal(vlist) + if err != nil { + return nil, err + } + } + case 4: // Fourth element must be list index + vtable, err := DecodeJsonTable(c.jsonData, path[0]) + if err != nil { + return nil, err + } + ventry, err := DecodeJsonEntry(vtable, path[1]) + if err != nil { + return nil, err + } + _, vlist, err := DecodeJsonField(ventry, path[2]) + if err != nil { + return nil, err + } + vstr, err := DecodeJsonListItem(vlist, path[3]) + if err != nil { + return nil, err + } + if vstr != nil { + jv = []byte(`"` + *vstr + `"`) + } else { + return nil, fmt.Errorf("Invalid db table Path %v", path) + } + default: + log.V(2).Infof("Invalid db table Path %v", path) + return nil, fmt.Errorf("Invalid db table Path %v", path) + } + return jv, nil +} + +func (c *JsonClient) Add(path []string, value string) error { + // The expect real db path could be in one of the formats: + // <1> DB Table + // <2> DB Table Key + // <3> DB Table Key Field + // <4> DB Table Key Field Index + switch len(path) { + case 1: // only table name provided + vtable, err := parseJson([]byte(value)) + if err != nil { + return fmt.Errorf("Fail to parse %v", value) + } + v, ok := vtable.(map[string]interface{}) + if !ok { + log.V(2).Infof("Invalid table %v", vtable) + return fmt.Errorf("Invalid table %v", vtable) + } + c.jsonData[path[0]] = v + case 2: // Second element must be table key + vtable, err := DecodeJsonTable(c.jsonData, path[0]) + if err != nil { + vtable = make(map[string]interface{}) + c.jsonData[path[0]] = vtable + } + ventry, err := parseJson([]byte(value)) + if err != nil { + return fmt.Errorf("Fail to parse %v", value) + } + v, ok := ventry.(map[string]interface{}) + if !ok { + log.V(2).Infof("Invalid entry %v", ventry) + return fmt.Errorf("Invalid entry %v", ventry) + } + vtable[path[1]] = v + case 3: // Third element must be field name + vtable, err := DecodeJsonTable(c.jsonData, path[0]) + if err != nil { + vtable = make(map[string]interface{}) + c.jsonData[path[0]] = vtable + } + ventry, err := DecodeJsonEntry(vtable, path[1]) + if err != nil { + ventry = make(map[string]interface{}) + vtable[path[1]] = ventry + } + vfield, err := parseJson([]byte(value)) + if err != nil { + return fmt.Errorf("Fail to parse %v", value) + } + vstr, ok := vfield.(string) + if ok { + ventry[path[2]] = vstr + return nil + } + vlist, ok := vfield.([]interface{}) + if ok { + ventry[path[2]] = vlist + return nil + } + log.V(2).Infof("Invalid field %v", vfield) + return fmt.Errorf("Invalid field %v", vfield) + case 4: // Fourth element must be list index + id, err := strconv.Atoi(path[3]) + if err != nil { + log.V(2).Infof("Invalid index %v", path[3]) + return fmt.Errorf("Invalid index %v", path[3]) + } + vtable, err := DecodeJsonTable(c.jsonData, path[0]) + if err != nil { + vtable = make(map[string]interface{}) + c.jsonData[path[0]] = vtable + } + ventry, err := DecodeJsonEntry(vtable, path[1]) + if err != nil { + ventry = make(map[string]interface{}) + vtable[path[1]] = ventry + } + vstr, vlist, err := DecodeJsonField(ventry, path[2]) + if err != nil { + vlist = make([]interface{}, 0) + ventry[path[2]] = vlist + } + if vstr != nil { + log.V(2).Infof("Invalid target field %v", ventry) + return fmt.Errorf("Invalid target field %v", ventry) + } + if id < 0 || id > len(vlist) { + log.V(2).Infof("Invalid index %v", id) + return fmt.Errorf("Invalid index %v", id) + } + if id == len(vlist) { + vlist = append(vlist, "") + ventry[path[2]] = vlist + } + v, err := parseJson([]byte(value)) + if err != nil { + return fmt.Errorf("Fail to parse %v", value) + } + vlist[id] = v + default: + log.V(2).Infof("Invalid db table Path %v", path) + return fmt.Errorf("Invalid db table Path %v", path) + } + + return nil +} + +func (c *JsonClient) Remove(path []string) error { + // The expect real db path could be in one of the formats: + // <1> DB Table + // <2> DB Table Key + // <3> DB Table Key Field + // <4> DB Table Key Field Index + switch len(path) { + case 1: // only table name provided + _, err := DecodeJsonTable(c.jsonData, path[0]) + if err != nil { + return err + } + delete(c.jsonData, path[0]) + case 2: // Second element must be table key + vtable, err := DecodeJsonTable(c.jsonData, path[0]) + if err != nil { + return err + } + _, err = DecodeJsonEntry(vtable, path[1]) + if err != nil { + return err + } + delete(vtable, path[1]) + if len(vtable) == 0 { + delete(c.jsonData, path[0]) + } + case 3: // Third element must be field name + vtable, err := DecodeJsonTable(c.jsonData, path[0]) + if err != nil { + return err + } + ventry, err := DecodeJsonEntry(vtable, path[1]) + if err != nil { + return err + } + _, _, err = DecodeJsonField(ventry, path[2]) + if err != nil { + return err + } + delete(ventry, path[2]) + if len(ventry) == 0 { + delete(vtable, path[1]) + } + if len(vtable) == 0 { + delete(c.jsonData, path[0]) + } + case 4: // Fourth element must be list index + id, err := strconv.Atoi(path[3]) + if err != nil { + log.V(2).Infof("Invalid index %v", path[3]) + return fmt.Errorf("Invalid index %v", path[3]) + } + vtable, err := DecodeJsonTable(c.jsonData, path[0]) + if err != nil { + return err + } + ventry, err := DecodeJsonEntry(vtable, path[1]) + if err != nil { + return err + } + _, vlist, err := DecodeJsonField(ventry, path[2]) + if err != nil { + return err + } + _, err = DecodeJsonListItem(vlist, path[3]) + if err != nil { + return err + } + vlist = append(vlist[:id], vlist[id+1:]...) + ventry[path[2]] = vlist + if len(vlist) == 0 { + delete(ventry, path[2]) + } + if len(ventry) == 0 { + delete(vtable, path[1]) + } + if len(vtable) == 0 { + delete(c.jsonData, path[0]) + } + default: + log.V(2).Infof("Invalid db table Path %v", path) + return fmt.Errorf("Invalid db table Path %v", path) + } + + return nil } \ No newline at end of file