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, &currentStateYaml).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, &currentStateYaml).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, &currentStateYaml).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  }