github.com/nmstate/kubernetes-nmstate@v0.82.0/test/e2e/handler/utils.go (about) 1 /* 2 Copyright The Kubernetes NMState Authors. 3 4 5 Licensed under the Apache License, Version 2.0 (the "License"); 6 you may not use this file except in compliance with the License. 7 You may obtain a copy of the License at 8 9 http://www.apache.org/licenses/LICENSE-2.0 10 11 Unless required by applicable law or agreed to in writing, software 12 distributed under the License is distributed on an "AS IS" BASIS, 13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 See the License for the specific language governing permissions and 15 limitations under the License. 16 */ 17 18 package handler 19 20 import ( 21 "context" 22 "encoding/json" 23 "fmt" 24 "strconv" 25 "strings" 26 "time" 27 28 . "github.com/onsi/ginkgo/v2" 29 . "github.com/onsi/gomega" 30 31 "github.com/tidwall/gjson" 32 33 apierrors "k8s.io/apimachinery/pkg/api/errors" 34 "k8s.io/apimachinery/pkg/types" 35 "k8s.io/apimachinery/pkg/util/intstr" 36 "sigs.k8s.io/yaml" 37 38 dynclient "sigs.k8s.io/controller-runtime/pkg/client" 39 40 nmstate "github.com/nmstate/kubernetes-nmstate/api/shared" 41 nmstatev1 "github.com/nmstate/kubernetes-nmstate/api/v1" 42 nmstatev1beta1 "github.com/nmstate/kubernetes-nmstate/api/v1beta1" 43 nmstatenode "github.com/nmstate/kubernetes-nmstate/pkg/node" 44 "github.com/nmstate/kubernetes-nmstate/test/cmd" 45 "github.com/nmstate/kubernetes-nmstate/test/e2e/handler/linuxbridge" 46 "github.com/nmstate/kubernetes-nmstate/test/e2e/policy" 47 testenv "github.com/nmstate/kubernetes-nmstate/test/env" 48 "github.com/nmstate/kubernetes-nmstate/test/environment" 49 "github.com/nmstate/kubernetes-nmstate/test/runner" 50 ) 51 52 const ReadTimeout = 180 * time.Second 53 const ReadInterval = 1 * time.Second 54 const TestPolicy = "test-policy" 55 56 var ( 57 bridgeCounter = 0 58 bondCounter = 0 59 maxUnavailable = environment.GetVarWithDefault("NMSTATE_MAX_UNAVAILABLE", nmstatenode.DefaultMaxunavailable) 60 ) 61 62 func Byf(message string, arguments ...interface{}) { 63 By(fmt.Sprintf(message, arguments...)) 64 } 65 66 func interfaceName(iface interface{}) string { 67 name, hasName := iface.(map[string]interface{})["name"] 68 Expect(hasName). 69 To( 70 BeTrue(), 71 "should have name field in the interfaces, "+ 72 "https://github.com/nmstate/nmstate/blob/base/libnmstate/schemas/operational-state.yaml", 73 ) 74 return name.(string) 75 } 76 77 func interfacesName(interfaces []interface{}) []string { 78 var names []string 79 for _, iface := range interfaces { 80 names = append(names, interfaceName(iface)) 81 } 82 return names 83 } 84 85 func interfaceByName(interfaces []interface{}, searchedName string) map[string]interface{} { 86 var dummy map[string]interface{} 87 for _, iface := range interfaces { 88 if interfaceName(iface) == searchedName { 89 return iface.(map[string]interface{}) 90 } 91 } 92 Fail(fmt.Sprintf("interface %s not found at %+v", searchedName, interfaces)) 93 return dummy 94 } 95 96 func setDesiredStateWithPolicyAndCaptureAndNodeSelector( 97 name string, 98 desiredState nmstate.State, 99 capture map[string]string, 100 nodeSelector map[string]string, 101 ) error { 102 policy := nmstatev1.NodeNetworkConfigurationPolicy{} 103 policy.Name = name 104 key := types.NamespacedName{Name: name} 105 err := testenv.Client.Get(context.TODO(), key, &policy) 106 policy.Spec.DesiredState = desiredState 107 policy.Spec.Capture = capture 108 policy.Spec.NodeSelector = nodeSelector 109 maxUnavailableIntOrString := intstr.FromString(maxUnavailable) 110 policy.Spec.MaxUnavailable = &maxUnavailableIntOrString 111 if err != nil { 112 if apierrors.IsNotFound(err) { 113 return testenv.Client.Create(context.TODO(), &policy) 114 } 115 return err 116 } 117 err = testenv.Client.Update(context.TODO(), &policy) 118 if err != nil { 119 fmt.Println("Update error: " + err.Error()) 120 } 121 return err 122 } 123 124 func setDesiredStateWithPolicyAndNodeSelector(name string, desiredState nmstate.State, nodeSelector map[string]string) error { 125 return setDesiredStateWithPolicyAndCaptureAndNodeSelector(name, desiredState, nil, nodeSelector) 126 } 127 128 func setDesiredStateWithPolicyAndNodeSelectorEventually(name string, desiredState nmstate.State, nodeSelector map[string]string) { 129 setDesiredStateWithPolicyAndCaptureAndNodeSelectorEventually(name, desiredState, nil, nodeSelector) 130 } 131 132 func setDesiredStateWithPolicyAndCaptureAndNodeSelectorEventually( 133 name string, 134 desiredState nmstate.State, 135 capture map[string]string, 136 nodeSelector map[string]string, 137 ) { 138 Eventually(func() error { 139 return setDesiredStateWithPolicyAndCaptureAndNodeSelector(name, desiredState, capture, nodeSelector) 140 }, ReadTimeout, ReadInterval).ShouldNot(HaveOccurred(), fmt.Sprintf("Failed updating desired state : %s", desiredState)) 141 //FIXME: until we don't have webhook we have to wait for reconcile 142 // to start so we are sure that conditions are reset and we can 143 // check them correctly 144 time.Sleep(1 * time.Second) 145 } 146 147 func setDesiredStateWithPolicyWithoutNodeSelector(name string, desiredState nmstate.State) { 148 setDesiredStateWithPolicyAndNodeSelectorEventually(name, desiredState, map[string]string{}) 149 } 150 151 func setDesiredStateWithPolicy(name string, desiredState nmstate.State) { 152 runAtWorkers := map[string]string{"node-role.kubernetes.io/worker": ""} 153 setDesiredStateWithPolicyAndNodeSelectorEventually(name, desiredState, runAtWorkers) 154 } 155 156 func setDesiredStateWithPolicyAndCapture(name string, desiredState nmstate.State, capture map[string]string) { 157 runAtWorkers := map[string]string{"node-role.kubernetes.io/worker": ""} 158 setDesiredStateWithPolicyAndCaptureAndNodeSelectorEventually(name, desiredState, capture, runAtWorkers) 159 } 160 161 func updateDesiredState(desiredState nmstate.State) { 162 updateDesiredStateWithCapture(desiredState, nil) 163 } 164 165 func updateDesiredStateWithCapture(desiredState nmstate.State, capture map[string]string) { 166 setDesiredStateWithPolicyAndCapture(TestPolicy, desiredState, capture) 167 } 168 169 func updateDesiredStateAndWait(desiredState nmstate.State) { 170 updateDesiredStateWithCaptureAndWait(desiredState, nil) 171 } 172 173 func updateDesiredStateWithCaptureAndWait(desiredState nmstate.State, capture map[string]string) { 174 updateDesiredStateWithCapture(desiredState, capture) 175 policy.WaitForAvailableTestPolicy() 176 } 177 178 func updateDesiredStateAtNode(node string, desiredState nmstate.State) { 179 updateDesiredStateWithCaptureAtNode(node, desiredState, nil) 180 } 181 182 func updateDesiredStateWithCaptureAtNode(node string, desiredState nmstate.State, capture map[string]string) { 183 nodeSelector := map[string]string{"kubernetes.io/hostname": node} 184 setDesiredStateWithPolicyAndCaptureAndNodeSelectorEventually(TestPolicy, desiredState, capture, nodeSelector) 185 } 186 187 func updateDesiredStateAtNodeAndWait(node string, desiredState nmstate.State) { 188 updateDesiredStateWithCaptureAtNodeAndWait(node, desiredState, nil) 189 } 190 191 func updateDesiredStateWithCaptureAtNodeAndWait(node string, desiredState nmstate.State, capture map[string]string) { 192 updateDesiredStateWithCaptureAtNode(node, desiredState, capture) 193 policy.WaitForAvailableTestPolicy() 194 } 195 196 // TODO: After we implement policy delete (it will cleanUp desiredState) we have to remove this. 197 func resetDesiredStateForNodes() { 198 By("Resetting nics state primary up and secondaries disable ipv4 and ipv6") 199 updateDesiredState(resetPrimaryAndSecondaryNICs()) 200 defer deletePolicy(TestPolicy) 201 policy.WaitForAvailableTestPolicy() 202 } 203 204 func nodeNetworkState(key types.NamespacedName) nmstatev1beta1.NodeNetworkState { 205 state := nmstatev1beta1.NodeNetworkState{} 206 Eventually(func() error { 207 return testenv.Client.Get(context.TODO(), key, &state) 208 }, ReadTimeout, ReadInterval).ShouldNot(HaveOccurred()) 209 return state 210 } 211 212 func nodeNetworkConfigurationPolicy(policyName string) nmstatev1.NodeNetworkConfigurationPolicy { 213 key := types.NamespacedName{Name: policyName} 214 policy := nmstatev1.NodeNetworkConfigurationPolicy{} 215 EventuallyWithOffset(1, func() error { 216 return testenv.Client.Get(context.TODO(), key, &policy) 217 }, ReadTimeout, ReadInterval).ShouldNot(HaveOccurred()) 218 return policy 219 } 220 221 func deleteNodeNeworkStates() { 222 nodeNetworkStateList := &nmstatev1beta1.NodeNetworkStateList{} 223 err := testenv.Client.List(context.TODO(), nodeNetworkStateList, &dynclient.ListOptions{}) 224 Expect(err).ToNot(HaveOccurred()) 225 var deleteErrors []error 226 for i := range nodeNetworkStateList.Items { 227 deleteErrors = append(deleteErrors, testenv.Client.Delete(context.TODO(), &nodeNetworkStateList.Items[i])) 228 } 229 Expect(deleteErrors).ToNot(ContainElement(HaveOccurred())) 230 } 231 232 func deletePolicy(name string) { 233 Byf("Deleting policy %s", name) 234 policy := &nmstatev1.NodeNetworkConfigurationPolicy{} 235 policy.Name = name 236 err := testenv.Client.Delete(context.TODO(), policy) 237 if apierrors.IsNotFound(err) { 238 return 239 } 240 ExpectWithOffset(1, err).ToNot(HaveOccurred()) 241 242 // Wait for policy to be removed 243 EventuallyWithOffset(1, func() bool { 244 err := testenv.Client.Get(context.TODO(), types.NamespacedName{Name: name}, &nmstatev1.NodeNetworkConfigurationPolicy{}) 245 return apierrors.IsNotFound(err) 246 }, 60*time.Second, 1*time.Second).Should(BeTrue(), fmt.Sprintf("Policy %s not deleted", name)) 247 248 // Wait for enactments to be removed calculate timeout taking into account 249 // the number of nodes, looks like it affect the time it takes to 250 // delete enactments 251 enactmentsDeleteTimeout := time.Duration(60+20*len(nodes)) * time.Second 252 for _, node := range nodes { 253 enactmentKey := nmstate.EnactmentKey(node, name) 254 Eventually(func() bool { 255 err := testenv.Client.Get(context.TODO(), enactmentKey, &nmstatev1beta1.NodeNetworkConfigurationEnactment{}) 256 // if we face an unexpected error do a failure since 257 // we don't know if enactment was deleted 258 if err != nil && !apierrors.IsNotFound(err) { 259 Fail(fmt.Sprintf("Unexpected error waitting for enactment deletion: %v", err)) 260 } 261 return apierrors.IsNotFound(err) 262 }, enactmentsDeleteTimeout, 1*time.Second).Should(BeTrue(), fmt.Sprintf("Enactment %s not deleted", enactmentKey.Name)) 263 } 264 } 265 266 func restartNodeWithoutWaiting(node string) { 267 Byf("Restarting node %s", node) 268 // Use halt so reboot command does not get stuck also 269 // this command always fail since connection is closed 270 // so let's not check err 271 runner.RunAtNode(node, "sudo", "halt", "--reboot") 272 } 273 274 func waitForNodeToStart(node string) { 275 Byf("Waiting till node %s is rebooted", node) 276 // It will wait till uptime -p will return up that means that node was currently rebooted and is 0 min up 277 Eventually(func() string { 278 output, err := runner.RunAtNode(node, "uptime", "-p") 279 if err != nil { 280 return "not yet" 281 } 282 return output 283 }, 300*time.Second, 5*time.Second).ShouldNot(Equal("up"), fmt.Sprintf("Node %s failed to start after reboot", node)) 284 } 285 286 func createDummyConnection(nodesToModify []string, dummyName string) []error { 287 Byf("Creating dummy %s", dummyName) 288 _, errs := runner.RunAtNodes( 289 nodesToModify, 290 "sudo", 291 "nmcli", 292 "con", 293 "add", 294 "type", 295 "dummy", 296 "con-name", 297 dummyName, 298 "ifname", 299 dummyName, 300 "ip4", 301 "192.169.1.50/24", 302 ) 303 _, upErrs := runner.RunAtNodes(nodesToModify, "sudo", "nmcli", "con", "up", dummyName) 304 errs = append(errs, upErrs...) 305 return errs 306 } 307 308 func createDummyConnectionAtNodes(dummyName string) []error { 309 return createDummyConnection(nodes, dummyName) 310 } 311 312 func createDummyConnectionAtAllNodes(dummyName string) []error { 313 return createDummyConnection(allNodes, dummyName) 314 } 315 316 func deleteConnection(nodesToModify []string, name string) []error { 317 Byf("Delete connection %s", name) 318 _, errs := runner.RunAtNodes(nodesToModify, "sudo", "nmcli", "con", "delete", name) 319 return errs 320 } 321 322 func deleteDevice(nodesToModify []string, name string) []error { 323 Byf("Delete device %s at nodes %v", name, nodesToModify) 324 _, errs := runner.RunAtNodes(nodesToModify, "sudo", "nmcli", "device", "delete", name) 325 return errs 326 } 327 328 func waitForInterfaceDeletion(nodesToCheck []string, interfaceName string) { 329 for _, nodeName := range nodesToCheck { 330 Eventually(func() []string { 331 return interfacesNameForNode(nodeName) 332 }, 2*nmstatenode.NetworkStateRefresh, time.Second).ShouldNot(ContainElement(interfaceName)) 333 } 334 } 335 336 func deleteConnectionAndWait(nodesToModify []string, interfaceName string) { 337 deleteConnection(nodesToModify, interfaceName) 338 deleteDevice(nodesToModify, interfaceName) 339 waitForInterfaceDeletion(nodesToModify, interfaceName) 340 } 341 342 func interfaces(state nmstate.State) []interface{} { 343 var stateUnstructured map[string]interface{} 344 err := yaml.Unmarshal(state.Raw, &stateUnstructured) 345 Expect(err).ToNot(HaveOccurred(), "Should parse correctly yaml: %s", state) 346 interfaces := stateUnstructured["interfaces"].([]interface{}) 347 return interfaces 348 } 349 350 func currentState(node string, currentStateYaml *nmstate.State) AsyncAssertion { 351 key := types.NamespacedName{Name: node} 352 return Eventually(func() nmstate.RawState { 353 *currentStateYaml = nodeNetworkState(key).Status.CurrentState 354 return currentStateYaml.Raw 355 }, ReadTimeout, ReadInterval) 356 } 357 358 func interfacesNameForNode(node string) []string { 359 var currentStateYaml nmstate.State 360 currentState(node, ¤tStateYaml).ShouldNot(BeEmpty()) 361 362 interfaces := interfaces(currentStateYaml) 363 Expect(interfaces).ToNot(BeEmpty(), "Node %s should have network interfaces", node) 364 365 return interfacesName(interfaces) 366 } 367 368 func interfacesNameForNodeEventually(node string) AsyncAssertion { 369 return Eventually(func() []string { 370 return interfacesNameForNode(node) 371 }, ReadTimeout, ReadInterval) 372 } 373 374 func ipAddressForNodeInterfaceEventually(node, iface string) AsyncAssertion { 375 return Eventually(func() string { 376 return ipv4Address(node, iface) 377 }, ReadTimeout, ReadInterval) 378 } 379 380 func ipV6AddressForNodeInterfaceEventually(node, iface string) AsyncAssertion { 381 return Eventually(func() string { 382 return ipv6Address(node, iface) 383 }, ReadTimeout, ReadInterval) 384 } 385 386 func routeDestForNodeInterfaceEventually(node, destIP string) AsyncAssertion { 387 return Eventually(func() string { 388 return routeDest(node, destIP) 389 }, ReadTimeout, ReadInterval) 390 } 391 392 func vlanForNodeInterfaceEventually(node, iface string) AsyncAssertion { 393 return Eventually(func() string { 394 return vlan(node, iface) 395 }, ReadTimeout, ReadInterval) 396 } 397 398 // vrfForNodeInterfaceEventually asserts that VRF with vrfID is eventually created. 399 func vrfForNodeInterfaceEventually(node, vrfID string) AsyncAssertion { 400 return Eventually(func() string { 401 return vrf(node, vrfID) 402 }, ReadTimeout, ReadInterval) 403 } 404 405 func interfacesForNode(node string) AsyncAssertion { 406 return Eventually(func() []interface{} { 407 var currentStateYaml nmstate.State 408 currentState(node, ¤tStateYaml).ShouldNot(BeEmpty()) 409 410 interfaces := interfaces(currentStateYaml) 411 Expect(interfaces).ToNot(BeEmpty(), "Node %s should have network interfaces", node) 412 413 return interfaces 414 }, ReadTimeout, ReadInterval) 415 } 416 417 func bridgeVlansAtNode(node string) (string, error) { 418 return runner.RunAtNode(node, "sudo", "bridge", "-j", "vlan", "show") 419 } 420 421 func getVLANFlagsEventually(node, connection string, vlan int) AsyncAssertion { //nolint:unparam 422 Byf("Getting vlan filtering flags for node %s connection %s and vlan %d", node, connection, vlan) 423 return Eventually(func() []string { 424 bridgeVlans, err := bridgeVlansAtNode(node) 425 if err != nil { 426 return []string{} 427 } 428 429 if !gjson.Valid(bridgeVlans) { 430 By("Getting vlan filtering from non-json output") 431 // There is a bug [1] at centos8 and output is and invalid json 432 // so it parses the non json output 433 // [1] https://bugs.centos.org/view.php?id=16533 434 output, err := cmd.Run("test/e2e/get-bridge-vlans-flags-el8.sh", false, node, connection, strconv.Itoa(vlan)) 435 Expect(err).ToNot(HaveOccurred()) 436 return strings.Split(output, " ") 437 } else { 438 By("Getting vlan filtering from json output") 439 parsedBridgeVlans := gjson.Parse(bridgeVlans) 440 441 gjsonExpression := linuxbridge.BuildGJsonExpression(bridgeVlans) 442 vlanFlagsFilter := fmt.Sprintf(gjsonExpression+".flags", connection, vlan) 443 444 vlanFlags := parsedBridgeVlans.Get(vlanFlagsFilter) 445 if !vlanFlags.Exists() { 446 return []string{} 447 } 448 449 matchingVLANFlags := []string{} 450 for _, flag := range vlanFlags.Array() { 451 matchingVLANFlags = append(matchingVLANFlags, flag.String()) 452 } 453 return matchingVLANFlags 454 } 455 }, ReadTimeout, ReadInterval) 456 } 457 458 func hasVlans(node, connection string, minVlan, maxVlan int) AsyncAssertion { //nolint:unparam 459 ExpectWithOffset(1, minVlan).To(BeNumerically(">", 0)) 460 ExpectWithOffset(1, maxVlan).To(BeNumerically(">", 0)) 461 ExpectWithOffset(1, maxVlan).To(BeNumerically(">=", minVlan)) 462 463 Byf("Check %s has %s with vlan filtering vids %d-%d", node, connection, minVlan, maxVlan) 464 return Eventually(func() error { 465 bridgeVlans, err := bridgeVlansAtNode(node) 466 if err != nil { 467 return err 468 } 469 if !gjson.Valid(bridgeVlans) { 470 // There is a bug [1] at centos8 and output is and invalid json 471 // so it parses the non json output 472 // [1] https://bugs.centos.org/view.php?id=16533 473 _, err := cmd.Run( 474 "test/e2e/check-bridge-has-vlans-el8.sh", 475 false, 476 node, 477 connection, 478 strconv.Itoa(minVlan), 479 strconv.Itoa(maxVlan), 480 ) 481 if err != nil { 482 return err 483 } 484 } else { 485 parsedBridgeVlans := gjson.Parse(bridgeVlans) 486 gjsonExpression := linuxbridge.BuildGJsonExpression(bridgeVlans) 487 for expectedVlan := minVlan; expectedVlan <= maxVlan; expectedVlan++ { 488 vlanByIDAndConection := fmt.Sprintf(gjsonExpression, connection, expectedVlan) 489 if !parsedBridgeVlans.Get(vlanByIDAndConection).Exists() { 490 return fmt.Errorf("bridge connection %s has no vlan %d, obtainedVlans: \n %s", connection, expectedVlan, bridgeVlans) 491 } 492 } 493 } 494 return nil 495 }, ReadTimeout, ReadInterval) 496 } 497 498 func vlansCardinality(node, connection string) AsyncAssertion { 499 Byf("Getting vlan cardinality for node %s connection %s", node, connection) 500 return Eventually(func() (int, error) { 501 bridgeVlans, err := bridgeVlansAtNode(node) 502 if err != nil { 503 return 0, err 504 } 505 506 return len(gjson.Parse(bridgeVlans).Get(connection).Array()), nil 507 }, ReadTimeout, ReadInterval) 508 } 509 510 func bridgeDescription(node, bridgeName string) AsyncAssertion { 511 return Eventually(func() (string, error) { 512 return runner.RunAtNode(node, "sudo", "ip", "-d", "link", "show", "type", "bridge", bridgeName) 513 }, ReadTimeout, ReadInterval) 514 } 515 516 func nextBridge() string { 517 bridgeCounter++ 518 return fmt.Sprintf("br%d", bridgeCounter) 519 } 520 521 func nextBond() string { 522 bondCounter++ 523 return fmt.Sprintf("bond%d", bondCounter) 524 } 525 526 func currentStateJSON(node string) []byte { 527 key := types.NamespacedName{Name: node} 528 currentState := nodeNetworkState(key).Status.CurrentState 529 currentStateJSON, err := yaml.YAMLToJSON(currentState.Raw) 530 ExpectWithOffset(1, err).ToNot(HaveOccurred()) 531 return currentStateJSON 532 } 533 534 func dhcpFlag(node, name string) bool { 535 path := fmt.Sprintf("interfaces.#(name==\"%s\").ipv4.dhcp", name) 536 return gjson.ParseBytes(currentStateJSON(node)).Get(path).Bool() 537 } 538 539 func autoDNS(node, name string) bool { 540 path := fmt.Sprintf("interfaces.#(name==\"%s\").ipv4.auto-dns", name) 541 return gjson.ParseBytes(currentStateJSON(node)).Get(path).Bool() 542 } 543 544 func ifaceInSlice(ifaceName string, names []string) bool { 545 for _, name := range names { 546 if ifaceName == name { 547 return true 548 } 549 } 550 return false 551 } 552 553 // return a json with all node interfaces and their state e.g. 554 // {"cni0":"up","docker0":"up","eth0":"up","eth1":"down","eth2":"down","lo":"down"} 555 // use exclude to filter out interfaces you don't care about 556 func nodeInterfacesState(node string, exclude []string) []byte { 557 var currentStateYaml nmstate.State 558 currentState(node, ¤tStateYaml).ShouldNot(BeEmpty()) 559 560 interfaces := interfaces(currentStateYaml) 561 ifacesState := make(map[string]string) 562 for _, iface := range interfaces { 563 name := interfaceName(iface) 564 if ifaceInSlice(name, exclude) { 565 continue 566 } 567 state, hasState := iface.(map[string]interface{})["state"] 568 if !hasState { 569 state = "unknown" 570 } 571 ifacesState[name] = state.(string) 572 } 573 ret, err := json.Marshal(ifacesState) 574 if err != nil { 575 return []byte{} 576 } 577 return ret 578 } 579 580 func lldpEnabled(node, iface string) string { 581 path := fmt.Sprintf("interfaces.#(name==\"%s\").lldp.enabled", iface) 582 return gjson.ParseBytes(currentStateJSON(node)).Get(path).String() 583 } 584 585 func ipv4Address(node, iface string) string { 586 path := fmt.Sprintf("interfaces.#(name==\"%s\").ipv4.address.0.ip", iface) 587 return gjson.ParseBytes(currentStateJSON(node)).Get(path).String() 588 } 589 590 func ipv6Address(node, iface string) string { 591 path := fmt.Sprintf("interfaces.#(name==\"%s\").ipv6.address.0.ip", iface) 592 return gjson.ParseBytes(currentStateJSON(node)).Get(path).String() 593 } 594 595 func macAddress(node, iface string) string { 596 path := fmt.Sprintf("interfaces.#(name==\"%s\").mac-address", iface) 597 return gjson.ParseBytes(currentStateJSON(node)).Get(path).String() 598 } 599 600 func defaultRouteNextHopInterface(node string) AsyncAssertion { 601 return Eventually(func() string { 602 path := "routes.running.#(destination==\"0.0.0.0/0\").next-hop-interface" 603 return gjson.ParseBytes(currentStateJSON(node)).Get(path).String() 604 }, 15*time.Second, 1*time.Second) 605 } 606 607 func routeDest(node, destIP string) string { 608 path := fmt.Sprintf("routes.running.#(destination==%q)", destIP) 609 return gjson.ParseBytes(currentStateJSON(node)).Get(path).String() 610 } 611 612 // routeNextHopInterfaceWithTableID checks if a route with destIP exists in the default routing table. 613 func routeNextHopInterface(node, destIP string) AsyncAssertion { 614 return routeNextHopInterfaceWithTableID(node, destIP, "") 615 } 616 617 // routeNextHopInterfaceWithTableID checks if a route with destIP exists in table tableID. If tableID is the empty 618 // string, use the default table-id (254). 619 func routeNextHopInterfaceWithTableID(node, destIP, tableID string) AsyncAssertion { 620 if tableID == "" { 621 tableID = "254" 622 } 623 return Eventually(func() string { 624 path := fmt.Sprintf("routes.running.#(table-id==%s)#|#(destination==%q).next-hop-interface", tableID, destIP) 625 return gjson.ParseBytes(currentStateJSON(node)).Get(path).String() 626 }, 15*time.Second, 1*time.Second) 627 } 628 629 func vlan(node, iface string) string { 630 vlanFilter := fmt.Sprintf("interfaces.#(name==\"%s\").vlan.id", iface) 631 return gjson.ParseBytes(currentStateJSON(node)).Get(vlanFilter).String() 632 } 633 634 // vrf verifies if the VRF with vrfID was created on node. 635 func vrf(node, vrfID string) string { 636 vrfFilter := fmt.Sprintf("interfaces.#(name==vrf%s).vrf.route-table-id", vrfID) 637 return gjson.ParseBytes(currentStateJSON(node)).Get(vrfFilter).String() 638 } 639 640 func kubectlAndCheck(command ...string) { 641 out, err := cmd.Kubectl(command...) 642 Expect(err).ShouldNot(HaveOccurred(), out) 643 } 644 645 func skipIfNotKubernetes() { 646 provider := environment.GetVarWithDefault("KUBEVIRT_PROVIDER", "k8s") 647 if !strings.Contains(provider, "k8s") { 648 Skip("Tutorials use interface naming that is available only on Kubernetes providers") 649 } 650 } 651 652 func maxUnavailableNodes() int { 653 m, _ := nmstatenode.ScaledMaxUnavailableNodeCount(len(nodes), intstr.FromString(nmstatenode.DefaultMaxunavailable)) 654 return m 655 } 656 657 func dnsResolverServerForNodeEventually(node string) AsyncAssertion { 658 return Eventually(func() []string { 659 return dnsResolverForNode(node, "dns-resolver.running.server") 660 }, ReadTimeout, ReadInterval) 661 } 662 663 func dnsResolverSearchForNodeEventually(node string) AsyncAssertion { 664 return Eventually(func() []string { 665 return dnsResolverForNode(node, "dns-resolver.running.search") 666 }, ReadTimeout, ReadInterval) 667 } 668 669 func dnsResolverForNode(node, path string) []string { 670 var arr []string 671 672 elemList := gjson.ParseBytes(currentStateJSON(node)).Get(path).Array() 673 for _, elem := range elemList { 674 arr = append(arr, elem.String()) 675 } 676 return arr 677 }