diff --git a/integration-tests/lab_tests/lab00/lab00_test.go b/integration-tests/lab_tests/lab00/lab00_test.go index 02479ef27fb37fe1890c94cd0b55b7ef451cc3e1..8463e6d007f578b6e697d585608ab097bfb65633 100644 --- a/integration-tests/lab_tests/lab00/lab00_test.go +++ b/integration-tests/lab_tests/lab00/lab00_test.go @@ -114,7 +114,6 @@ func TestLab00(t *testing.T) { } // Change the hostname of both managed network elements. - for _, mne := range managedNetworkElements { hostnamePathListRequest, err := lab_utils.SetHostnamePathListRequestSingleMne(mne.id, mne.newHostname, mnepb.ApiOperation_API_OPERATION_UPDATE) if err != nil { diff --git a/integration-tests/lab_tests/lab01/lab01_test.go b/integration-tests/lab_tests/lab01/lab01_test.go index a19f2ea3708fe578fe363e0aae74a6d59f328f42..326af206d7357797806df3314b2171874978ea50 100644 --- a/integration-tests/lab_tests/lab01/lab01_test.go +++ b/integration-tests/lab_tests/lab01/lab01_test.go @@ -14,25 +14,44 @@ import ( ) const ( - pndID = "5f20f34b-cbd0-4511-9ddc-c50cf6a3b49d" - targetAName = "gnmi-targetA" - targetAUUID = "e3cc56ad-2a6c-4b76-8315-5f17a6dc25bf" - targetBName = "gnmi-targetB" - targetBUUID = "87c8bcc1-d95c-401e-b447-f02c56abd8b1" - targetInterfaceEth1 = "eth1" - targetInterfaceEth2 = "eth2" - targetRoute = "route" - targetAInterfacePathEth1 = "switch0/s0-eth1.json" - targetAInterfacePathEth2 = "switch0/s0-eth2.json" - targetARoutePath = "switch0/s0-route.json" - targetBInterfacePathEth1 = "switch1/s1-eth1.json" - targetBInterfacePathEth2 = "switch1/s1-eth2.json" - targetBRoutePath = "switch1/s1-route.json" - relativePathToJSONSources = "../utils/json_sources/Lab01/" - pathToRouteConfig = "network-instances/network-instance" - pathToInterfaceConfig = "interfaces/interface" + pndID = "5f20f34b-cbd0-4511-9ddc-c50cf6a3b49d" + targetAName = "gnmi-targetA" + targetAUUID = "e3cc56ad-2a6c-4b76-8315-5f17a6dc25bf" + targetBName = "gnmi-targetB" + targetBUUID = "87c8bcc1-d95c-401e-b447-f02c56abd8b1" + targetLeafInterfaceEth1 = "eth1" + targetLeafInterfaceEth2 = "eth2" + targetLeafRoute = "route" + targetAInterfaceFilePathEth1 = "switch0/s0-eth1.json" + targetAInterfaceFilePathEth2 = "switch0/s0-eth2.json" + targetARouteFilePath = "switch0/s0-route.json" + targetBInterfaceFilePathEth1 = "switch1/s1-eth1.json" + targetBInterfaceFilePathEth2 = "switch1/s1-eth2.json" + targetBRouteFilePath = "switch1/s1-route.json" + relativeFilePathToJSONSources = "../utils/json_sources/Lab01/" ) +var centosOCmds = [][]string{ + {"docker", "exec", "clab-gosdn_csbi_arista_base-centos0", "ip", "link", "set", "dev", "eth1", "mtu", "1500"}, + {"docker", "exec", "clab-gosdn_csbi_arista_base-centos0", "ip", "address", "add", "10.0.0.100/24", "dev", "eth1"}, + {"docker", "exec", "clab-gosdn_csbi_arista_base-centos0", "ip", "route", "del", "default"}, + {"docker", "exec", "clab-gosdn_csbi_arista_base-centos0", "ip", "route", "add", "default", "via", "10.0.0.1", "dev", "eth1"}, + {"docker", "exec", "clab-gosdn_csbi_arista_base-centos0", "ip", "route", "add", "10.50.0.2/32", "via", "10.0.0.1", "dev", "eth1"}, +} + +var centos1Cmds = [][]string{ + {"docker", "exec", "clab-gosdn_csbi_arista_base-centos1", "ip", "link", "set", "dev", "eth1", "mtu", "1500"}, + {"docker", "exec", "clab-gosdn_csbi_arista_base-centos1", "ip", "address", "add", "192.168.0.100/24", "dev", "eth1"}, + {"docker", "exec", "clab-gosdn_csbi_arista_base-centos1", "ip", "route", "del", "default"}, + {"docker", "exec", "clab-gosdn_csbi_arista_base-centos1", "ip", "route", "add", "default", "via", "192.168.0.1", "dev", "eth1"}, + {"docker", "exec", "clab-gosdn_csbi_arista_base-centos1", "ip", "route", "add", "10.50.0.1/32", "via", "192.168.0.1", "dev", "eth1"}, +} + +var centosPingCmds = [][]string{ + {"docker", "exec", "clab-gosdn_csbi_arista_base-centos0", "ping", "192.168.0.100", "-c", "3"}, + {"docker", "exec", "clab-gosdn_csbi_arista_base-centos1", "ping", "10.0.0.100", "-c", "3"}, +} + // Represents the yang models stored in JSON files. // Contains the target leaf in the managed network element, the path to the JSON file and the JSON value, parsed as string. type configEntry struct { @@ -56,16 +75,16 @@ var managedNetworkElements = []managedNetworkElement{ addr: integration_test_utils.DefaultTargetAAdress, configEntries: []configEntry{ { - leaf: targetInterfaceEth1, - path: targetAInterfacePathEth1, + leaf: targetLeafInterfaceEth1, + path: targetAInterfaceFilePathEth1, }, { - leaf: targetInterfaceEth2, - path: targetAInterfacePathEth2, + leaf: targetLeafInterfaceEth2, + path: targetAInterfaceFilePathEth2, }, { - leaf: targetRoute, - path: targetARoutePath, + leaf: targetLeafRoute, + path: targetARouteFilePath, }, }, }, @@ -75,16 +94,16 @@ var managedNetworkElements = []managedNetworkElement{ addr: integration_test_utils.DefaultTargetBAdress, configEntries: []configEntry{ { - leaf: targetInterfaceEth1, - path: targetBInterfacePathEth1, + leaf: targetLeafInterfaceEth1, + path: targetBInterfaceFilePathEth1, }, { - leaf: targetInterfaceEth2, - path: targetBInterfacePathEth2, + leaf: targetLeafInterfaceEth2, + path: targetBInterfaceFilePathEth2, }, { - leaf: targetRoute, - path: targetBRoutePath, + leaf: targetLeafRoute, + path: targetBRouteFilePath, }, }, }, @@ -121,7 +140,7 @@ func TestMain(m *testing.M) { // The following test describes a chain of commands (see // https://code.fbi.h-da.de/danet/gosdn/-/wikis/Labs/Lab01). -func TestLab01(t *testing.T) { +func TestLab01ChangeAndCheckNetworkConfigPaths(t *testing.T) { defer integration_test_utils.ApplySDNConfig(conn, ctx, defaultSDNConfig) mneService := mnepb.NewNetworkElementServiceClient(conn) @@ -150,7 +169,7 @@ func TestLab01(t *testing.T) { // Cast JSON config files into strings for _, mne := range managedNetworkElements { for i := 0; i < len(mne.configEntries); i++ { - jsonValue, err := lab_utils.ParseFileContent(relativePathToJSONSources + mne.configEntries[i].path) + jsonValue, err := lab_utils.ParseFileContent(relativeFilePathToJSONSources + mne.configEntries[i].path) if err != nil { t.Error(err) } @@ -158,25 +177,13 @@ func TestLab01(t *testing.T) { } } - // Modify the interface of both managed network elements. + // Modify the configuration of the network elements. for _, mne := range managedNetworkElements { for i := 0; i < len(mne.configEntries); i++ { - var pathListRequest *mnepb.SetPathListRequest - var err error - - // Interface change request - if strings.Contains(mne.configEntries[i].leaf, "eth") { - pathListRequest, err = lab_utils.SetPathListRequestFromJsonImport(pathToInterfaceConfig, mne.configEntries[i].leaf, mne.id, mnepb.ApiOperation_API_OPERATION_UPDATE, mne.configEntries[i].value, pndID) - if err != nil { - t.Error(err) - } - - // Route change request - } else if strings.Contains(mne.configEntries[i].leaf, "route") { - pathListRequest, err = lab_utils.SetPathListRequestFromJsonImport(pathToRouteConfig, mne.configEntries[i].leaf, mne.id, mnepb.ApiOperation_API_OPERATION_UPDATE, mne.configEntries[i].value, pndID) - if err != nil { - t.Error(err) - } + // Create change request + pathListRequest, err := lab_utils.SetPathListRequestFromJsonImport(mne.configEntries[i].leaf, mne.id, pndID, mnepb.ApiOperation_API_OPERATION_UPDATE, mne.configEntries[i].value) + if err != nil { + t.Error(err) } // Set the requested path on managed network element @@ -193,9 +200,43 @@ func TestLab01(t *testing.T) { } } - // Check if the network interface has been changed + // TODO Find a nicer and more accurate way + // Check if the paths have been changed + // for _, mne := range managedNetworkElements { + // for i := 0; i < len(mne.configEntries); i++ { + // // Create get request + // pathRequest := lab_utils.GetPathRequest(mne.configEntries[i].leaf, mne.id, pndID) + // resp, err := mneService.GetPath(ctx, pathRequest) + // if err != nil { + // t.Error(err) + // } + + // // Check if leaf has expected value + // configEntryFormatted := lab_utils.RemoveStringFormatting(string(mne.configEntries[i].value)) + // isJsonSubsetOfMneValue, err := lab_utils.IsASubstring(string(resp.MneNotification[0].GetUpdate()[0].GetVal().GetJsonIetfVal()), configEntryFormatted) + // if err != nil { + // t.Error(err) + // } + + // assert.Equal(t, 1, len(resp.GetMneNotification()), "Exactly one response is expected.") + // assert.True(t, isJsonSubsetOfMneValue, "Exptected the new changed routing or interface config.") + // } + // } +} - // Check if the routing configuration has been changed +func TestLab01CheckConnectionOfNetworkElements(t *testing.T) { + // Configure Interfaces and create routes on clients centos0 and centos1 + for i := 0; i < len(centosOCmds); i++ { + lab_utils.ExecBashCmd(centosOCmds[i]) + } + for i := 0; i < len(centos1Cmds); i++ { + lab_utils.ExecBashCmd(centos1Cmds[i]) + } - // Ping to check connection between both managed network elements. + // Ping to check connection between centos0 > gnmi-targetA > gnmi-targetB > centos1 + // and centos1 > gnmi-targetB > gnmi-targetA > centos0 + for i := 0; i < len(centosPingCmds); i++ { + output := lab_utils.ExecBashCmd(centosPingCmds[i]) + assert.True(t, strings.Contains(output, "3 packets transmitted, 3 received, 0% packet loss")) + } } diff --git a/integration-tests/lab_tests/utils/labUtils.go b/integration-tests/lab_tests/utils/labUtils.go index ceb960871e759231e219a5522b89ac022d47cfc7..2c08bc62d79626b787b90a307da5b14bed1d2bfa 100644 --- a/integration-tests/lab_tests/utils/labUtils.go +++ b/integration-tests/lab_tests/utils/labUtils.go @@ -2,7 +2,13 @@ package lab_utils import ( "context" + "encoding/json" + "fmt" "os" + "os/exec" + "reflect" + "sort" + "strings" mnepb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/networkelement" tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport" @@ -11,6 +17,11 @@ import ( "github.com/openconfig/ygot/ygot" ) +const ( + routePath = "network-instances/network-instance[name=default]/protocols" + interfacePath = "interfaces/interface" +) + func GetHostnameSingleMne(mneId string, pndID string) *mnepb.GetPathRequest { gpr := &mnepb.GetPathRequest{ Timestamp: integration_test_utils.GetTimestamp(), @@ -104,15 +115,22 @@ func CreateAddListRequestSingleMne(addr, name, id, username, password string, pn return alr } -func SetPathListRequestFromJsonImport(pathToLeaf string, leaf string, mneId string, operation mnepb.ApiOperation, newInterfaceConfig []byte, pndID string) (*mnepb.SetPathListRequest, error) { - // Extract network interface - completePathToLeaf := pathToLeaf + "[name=" + leaf + "]" - interfacePath, err := ygot.StringToStructuredPath(completePathToLeaf) +func SetPathListRequestFromJsonImport(leaf, mneId, pndID string, operation mnepb.ApiOperation, newInterfaceConfig []byte) (*mnepb.SetPathListRequest, error) { + // Build path to config + var completePathToLeaf string + if strings.Contains(leaf, "eth") { + completePathToLeaf = interfacePath + "[name=" + leaf + "]" + } else if strings.Contains(leaf, "route") { + completePathToLeaf = routePath + } + + // Create the yang path + structuredPath, err := ygot.StringToStructuredPath(completePathToLeaf) if err != nil { return nil, err } - // Create the yang path + // Create typed value typedValue := &gnmi.TypedValue{ Value: &gnmi.TypedValue_JsonIetfVal{ JsonIetfVal: newInterfaceConfig, @@ -125,7 +143,7 @@ func SetPathListRequestFromJsonImport(pathToLeaf string, leaf string, mneId stri ChangeRequest: []*mnepb.ChangeRequest{ { Mneid: mneId, - Path: interfacePath, + Path: structuredPath, Value: typedValue, ApiOp: operation, }, @@ -141,3 +159,81 @@ func ParseFileContent(path string) ([]byte, error) { } return fileContent, nil } + +func GetPathRequest(leaf, mneId, pndID string) *mnepb.GetPathRequest { + var completePathToLeaf string + if strings.Contains(leaf, "eth") { + completePathToLeaf = interfacePath + "[name=" + leaf + "]" + } else if strings.Contains(leaf, "route") { + completePathToLeaf = routePath + } + gpr := &mnepb.GetPathRequest{ + Timestamp: integration_test_utils.GetTimestamp(), + Mneid: mneId, + Path: completePathToLeaf, + Pid: pndID, + } + + return gpr +} + +func RemoveStringFormatting(formattedString string) string { + removedFormatting := strings.Replace(formattedString, " ", "", -1) + removedFormatting = strings.Replace(removedFormatting, "\n", "", -1) + return removedFormatting +} + +func IsASubstring(x, y string) (bool, error) { + // Unmarshal JSON strings into map[string]interface{} + var mapX, mapY map[string]interface{} + if err := json.Unmarshal([]byte(x), &mapX); err != nil { + return false, err + } + if err := json.Unmarshal([]byte(y), &mapY); err != nil { + return false, err + } + + // Sort keys for comparison + sortMap(&mapX) + sortMap(&mapY) + + fmt.Println(mapX) + fmt.Println(mapY) + + // Check if all key-value pairs in mapX are present in mapY + for key, valX := range mapX { + valY, ok := mapY[key] + if !ok || !reflect.DeepEqual(valX, valY) { + return false, nil + } + } + return true, nil +} + +// Sorts keys of a map[string]interface{}. +func sortMap(m *map[string]interface{}) { + keys := make([]string, 0, len(*m)) + for k := range *m { + keys = append(keys, k) + } + sort.Strings(keys) + + sorted := make(map[string]interface{}) + for _, k := range keys { + sorted[k] = (*m)[k] + } + *m = sorted +} + +func ExecBashCmd(args []string) string { + cmd := exec.Command(args[0], args[1:]...) + + output, err := cmd.CombinedOutput() + if err != nil { + fmt.Println(err) + } + + // Convert bytes.Buffer to string + outputString := string(output) + return outputString +} diff --git a/makefiles/clab/Makefile b/makefiles/clab/Makefile index ced8dc85084a0899962f19a4d327c6ff9429642b..db823d2f0b5228a2f1484a863217ff3992757181 100644 --- a/makefiles/clab/Makefile +++ b/makefiles/clab/Makefile @@ -40,3 +40,10 @@ containerlab-integration-tests-start: create-clab-dir containerize-all generate- containerlab-integration-tests-stop: create-clab-dir cd $(CLAB_DIR) &&\ sudo containerlab destroy --topo $(MAKEFILE_DIR)dev_env_data/clab/integration-tests.yaml + +containerlab-integration-tests-rush: create-clab-dir containerize-all generate-all-certs + cd $(CLAB_DIR) &&\ + sudo containerlab deploy --topo $(MAKEFILE_DIR)dev_env_data/clab/integration-tests.yaml --log-level debug + go clean -testcache + go test -p 1 ./integration-tests/... -v + sudo containerlab destroy --topo $(MAKEFILE_DIR)dev_env_data/clab/integration-tests.yaml