github.com/nmstate/kubernetes-nmstate@v0.82.0/test/e2e/handler/default_bridged_network_test.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  	"fmt"
    23  	"time"
    24  
    25  	. "github.com/onsi/ginkgo/v2"
    26  	. "github.com/onsi/gomega"
    27  
    28  	corev1 "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/types"
    30  
    31  	nmstate "github.com/nmstate/kubernetes-nmstate/api/shared"
    32  	"github.com/nmstate/kubernetes-nmstate/test/e2e/policy"
    33  	testenv "github.com/nmstate/kubernetes-nmstate/test/env"
    34  )
    35  
    36  func createBridgeOnTheDefaultInterface() nmstate.State {
    37  	return nmstate.NewState(fmt.Sprintf(`interfaces:
    38    - name: brext
    39      type: linux-bridge
    40      state: up
    41      ipv4:
    42        dhcp: true
    43        enabled: true
    44      bridge:
    45        options:
    46          stp:
    47            enabled: false
    48        port:
    49        - name: %s
    50  `, primaryNic))
    51  }
    52  
    53  func resetDefaultInterface() nmstate.State {
    54  	return nmstate.NewState(fmt.Sprintf(`interfaces:
    55    - name: %s
    56      type: ethernet
    57      state: up
    58      ipv4:
    59        enabled: true
    60        dhcp: true
    61    - name: brext
    62      type: linux-bridge
    63      state: absent
    64  `, primaryNic))
    65  }
    66  
    67  var _ = Describe("NodeNetworkConfigurationPolicy default bridged network", func() {
    68  	var (
    69  		DefaultNetwork = "default-network"
    70  	)
    71  	Context("when there is a default interface with dynamic address", func() {
    72  		addressByNode := map[string]string{}
    73  
    74  		BeforeEach(func() {
    75  			Byf("Check %s is the default route interface and has dynamic address", primaryNic)
    76  			for _, node := range nodes {
    77  				defaultRouteNextHopInterface(node).Should(Equal(primaryNic))
    78  				Expect(dhcpFlag(node, primaryNic)).Should(BeTrue())
    79  			}
    80  
    81  			By("Fetching current IP address")
    82  			for _, node := range nodes {
    83  				address := ""
    84  				Eventually(func() string {
    85  					address = ipv4Address(node, primaryNic)
    86  					return address
    87  				}, 15*time.Second, 1*time.Second).ShouldNot(BeEmpty(), fmt.Sprintf("Interface %s has no ipv4 address", primaryNic))
    88  				addressByNode[node] = address
    89  			}
    90  		})
    91  
    92  		Context("and linux bridge is configured on top of the default interface", func() {
    93  			BeforeEach(func() {
    94  				By("Creating the policy")
    95  				setDesiredStateWithPolicy(DefaultNetwork, createBridgeOnTheDefaultInterface())
    96  
    97  				By("Waiting until the node becomes ready again")
    98  				waitForNodesReady()
    99  
   100  				By("Waiting for policy to be ready")
   101  				policy.WaitForAvailablePolicy(DefaultNetwork)
   102  			})
   103  
   104  			AfterEach(func() {
   105  				Byf("Removing bridge and configuring %s with dhcp", primaryNic)
   106  				setDesiredStateWithPolicy(DefaultNetwork, resetDefaultInterface())
   107  
   108  				By("Waiting until the node becomes ready again")
   109  				waitForNodesReady()
   110  
   111  				By("Wait for policy to be ready")
   112  				policy.WaitForAvailablePolicy(DefaultNetwork)
   113  
   114  				Byf("Check %s has the default ip address", primaryNic)
   115  				for _, node := range nodes {
   116  					Eventually(
   117  						func() string {
   118  							return ipv4Address(node, primaryNic)
   119  						},
   120  						30*time.Second,
   121  						1*time.Second,
   122  					).Should(Equal(addressByNode[node]), fmt.Sprintf("Interface %s address is not the original one", primaryNic))
   123  				}
   124  
   125  				Byf("Check %s is back as the default route interface", primaryNic)
   126  				for _, node := range nodes {
   127  					defaultRouteNextHopInterface(node).Should(Equal(primaryNic))
   128  				}
   129  
   130  				By("Remove the policy")
   131  				deletePolicy(DefaultNetwork)
   132  
   133  				By("Reset desired state at all nodes")
   134  				resetDesiredStateForNodes()
   135  			})
   136  
   137  			It("should successfully move default IP address on top of the bridge", func() {
   138  				checkThatBridgeTookOverTheDefaultIP(nodes, "brext", addressByNode)
   139  			})
   140  
   141  			It("should keep the default IP address after node reboot", func() {
   142  				nodeToReboot := nodes[0]
   143  
   144  				restartNodeWithoutWaiting(nodeToReboot)
   145  
   146  				By("Wait for policy re-reconciled after node reboot")
   147  				policy.WaitForPolicyTransitionUpdate(DefaultNetwork)
   148  				policy.WaitForAvailablePolicy(DefaultNetwork)
   149  
   150  				Byf("Node %s was rebooted, verifying that bridge took over the default IP", nodeToReboot)
   151  				checkThatBridgeTookOverTheDefaultIP([]string{nodeToReboot}, "brext", addressByNode)
   152  			})
   153  		})
   154  	})
   155  })
   156  
   157  func nodeReadyConditionStatus(nodeName string) (corev1.ConditionStatus, error) {
   158  	key := types.NamespacedName{Name: nodeName}
   159  	node := corev1.Node{}
   160  	// We use a special context here to ensure that Client.Get does not
   161  	// get stuck and honor the Eventually timeout and interval values.
   162  	// It will return a timeout error in case of .Get takes more time than
   163  	// expected so Eventually will retry after expected interval value.
   164  	oneSecondTimeoutCtx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
   165  	defer cancel()
   166  	err := testenv.Client.Get(oneSecondTimeoutCtx, key, &node)
   167  	if err != nil {
   168  		return "", err
   169  	}
   170  	for _, condition := range node.Status.Conditions {
   171  		if condition.Type == corev1.NodeReady {
   172  			return condition.Status, nil
   173  		}
   174  	}
   175  	return corev1.ConditionUnknown, nil
   176  }
   177  
   178  func waitForNodesReady() {
   179  	time.Sleep(5 * time.Second)
   180  	for _, node := range nodes {
   181  		EventuallyWithOffset(1, func() (corev1.ConditionStatus, error) {
   182  			return nodeReadyConditionStatus(node)
   183  		}, 5*time.Minute, 10*time.Second).Should(Equal(corev1.ConditionTrue))
   184  	}
   185  }
   186  
   187  func checkThatBridgeTookOverTheDefaultIP(nodesToCheck []string, bridgeName string, addressByNode map[string]string) {
   188  	By("Verifying that the bridge obtained node's default IP")
   189  	for _, node := range nodesToCheck {
   190  		Eventually(
   191  			func() string {
   192  				return ipv4Address(node, bridgeName)
   193  			},
   194  			15*time.Second,
   195  			1*time.Second,
   196  		).Should(Equal(addressByNode[node]), fmt.Sprintf("Interface %s has not take over the %s address", bridgeName, primaryNic))
   197  	}
   198  
   199  	By("Verify that next-hop-interface for default route is the bridge")
   200  	for _, node := range nodesToCheck {
   201  		defaultRouteNextHopInterface(node).Should(Equal(bridgeName))
   202  
   203  		By("Verify that VLAN configuration is done properly")
   204  		hasVlans(node, primaryNic, 2, 4094).Should(Succeed())
   205  		getVLANFlagsEventually(node, bridgeName, 1).Should(ConsistOf("PVID", Or(Equal("Egress Untagged"), Equal("untagged"))))
   206  	}
   207  }