Skip to content

Commit ae5b638

Browse files
committed
e2e: Move underlay setup to providers
Signed-off-by: Enrique Llorente <[email protected]>
1 parent 1870116 commit ae5b638

File tree

7 files changed

+263
-306
lines changed

7 files changed

+263
-306
lines changed

test/e2e/infraprovider/api/api.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"errors"
55
"fmt"
66
"strings"
7+
8+
"k8s.io/kubernetes/test/e2e/framework"
79
)
810

911
// Provider represents the infrastructure provider
@@ -37,6 +39,21 @@ type Provider interface {
3739
GetK8HostPort() uint16 // supported K8 host ports
3840
}
3941

42+
// Underlay represents the configuration for an underlay network.
43+
// Note: The physical network referenced by PhysicalNetworkName must be pre-created and available.
44+
type Underlay struct {
45+
// PhysicalNetworkName is the name of the pre-created physical network to use.
46+
PhysicalNetworkName string
47+
// LogicalNetworkName is the logical network name to be used.
48+
LogicalNetworkName string
49+
// BridgeName is the name of the bridge associated with the underlay.
50+
BridgeName string
51+
// PortName is the name of the port on the bridge.
52+
PortName string
53+
// VlanID is the VLAN identifier for the underlay network.
54+
VlanID int
55+
}
56+
4057
type Context interface {
4158
CreateExternalContainer(container ExternalContainer) (ExternalContainer, error)
4259
DeleteExternalContainer(container ExternalContainer) error
@@ -46,6 +63,7 @@ type Context interface {
4663
AttachNetwork(network Network, instance string) (NetworkInterface, error)
4764
DetachNetwork(network Network, instance string) error
4865
GetAttachedNetworks() (Networks, error)
66+
SetupUnderlay(f *framework.Framework, underlay Underlay) error
4967

5068
AddCleanUpFn(func() error)
5169
}

test/e2e/infraprovider/providers/kind/kind.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ import (
1313

1414
"github.com/onsi/ginkgo/v2"
1515
"github.com/ovn-org/ovn-kubernetes/test/e2e/containerengine"
16+
"github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig"
1617
"github.com/ovn-org/ovn-kubernetes/test/e2e/images"
1718
"github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider/api"
1819
"github.com/ovn-org/ovn-kubernetes/test/e2e/infraprovider/portalloc"
1920

21+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2022
"k8s.io/apimachinery/pkg/util/wait"
2123
"k8s.io/kubernetes/test/e2e/framework"
2224
utilnet "k8s.io/utils/net"
@@ -359,6 +361,83 @@ func (c *contextKind) getAttachedNetworks() (api.Networks, error) {
359361
return attachedNetworks, nil
360362
}
361363

364+
func (c *contextKind) SetupUnderlay(f *framework.Framework, underlay api.Underlay) error {
365+
if underlay.LogicalNetworkName == "" {
366+
return fmt.Errorf("underlay logical network name must be set")
367+
}
368+
369+
if underlay.PhysicalNetworkName == "" {
370+
underlay.PhysicalNetworkName = "underlay"
371+
}
372+
373+
if underlay.BridgeName == "" {
374+
underlay.BridgeName = secondaryBridge
375+
}
376+
377+
const (
378+
ovsKubeNodeLabel = "app=ovnkube-node"
379+
)
380+
381+
ovsPodList, err := f.ClientSet.CoreV1().Pods(deploymentconfig.Get().OVNKubernetesNamespace()).List(
382+
context.Background(),
383+
metav1.ListOptions{LabelSelector: ovsKubeNodeLabel},
384+
)
385+
if err != nil {
386+
return fmt.Errorf("failed to list OVS pods with label %q at namespace %q: %w", ovsKubeNodeLabel, deploymentconfig.Get().OVNKubernetesNamespace(), err)
387+
}
388+
389+
if len(ovsPodList.Items) == 0 {
390+
return fmt.Errorf("no pods with label %q in namespace %q", ovsKubeNodeLabel, deploymentconfig.Get().OVNKubernetesNamespace())
391+
}
392+
for _, ovsPod := range ovsPodList.Items {
393+
if underlay.BridgeName != deploymentconfig.Get().ExternalBridgeName() {
394+
underlayInterface, err := getNetworkInterface(ovsPod.Spec.NodeName, underlay.PhysicalNetworkName)
395+
if err != nil {
396+
return fmt.Errorf("failed to get underlay interface for network %s on node %s: %w", underlay.PhysicalNetworkName, ovsPod.Spec.NodeName, err)
397+
}
398+
c.AddCleanUpFn(func() error {
399+
if err := removeOVSBridge(ovsPod.Namespace, ovsPod.Name, underlay.BridgeName); err != nil {
400+
return fmt.Errorf("failed to remove OVS bridge %s for pod %s/%s during cleanup: %w", underlay.BridgeName, ovsPod.Namespace, ovsPod.Name, err)
401+
}
402+
return nil
403+
})
404+
if err := ensureOVSBridge(ovsPod.Namespace, ovsPod.Name, underlay.BridgeName); err != nil {
405+
return fmt.Errorf("failed to add OVS bridge %s for pod %s/%s: %w", underlay.BridgeName, ovsPod.Namespace, ovsPod.Name, err)
406+
}
407+
408+
if err := ovsAttachPortToBridge(ovsPod.Namespace, ovsPod.Name, underlay.BridgeName, underlayInterface.InfName); err != nil {
409+
return fmt.Errorf("failed to attach port %s to bridge %s for pod %s/%s: %w", underlayInterface.InfName, underlay.BridgeName, ovsPod.Namespace, ovsPod.Name, err)
410+
}
411+
if underlay.VlanID > 0 {
412+
if err := ovsEnableVLANAccessPort(ovsPod.Namespace, ovsPod.Name, underlay.BridgeName, underlayInterface.InfName, underlay.VlanID); err != nil {
413+
return fmt.Errorf("failed to enable VLAN %d on port %s for bridge %s for pod %s/%s: %w", underlay.VlanID, underlayInterface.InfName, underlay.BridgeName, ovsPod.Namespace, ovsPod.Name, err)
414+
}
415+
}
416+
}
417+
c.AddCleanUpFn(func() error {
418+
if err := configureBridgeMappings(
419+
ovsPod.Namespace,
420+
ovsPod.Name,
421+
defaultNetworkBridgeMapping(),
422+
); err != nil {
423+
return fmt.Errorf("failed to restore default bridge mappings for pod %s/%s during cleanup: %w", ovsPod.Namespace, ovsPod.Name, err)
424+
}
425+
return nil
426+
})
427+
428+
if err := configureBridgeMappings(
429+
ovsPod.Namespace,
430+
ovsPod.Name,
431+
defaultNetworkBridgeMapping(),
432+
bridgeMapping(underlay.LogicalNetworkName, underlay.BridgeName),
433+
); err != nil {
434+
return fmt.Errorf("failed to configure bridge mappings for pod %s/%s for logical network %s to bridge %s: %w", ovsPod.Namespace, ovsPod.Name, underlay.LogicalNetworkName, underlay.BridgeName, err)
435+
}
436+
}
437+
return nil
438+
439+
}
440+
362441
func (c *contextKind) AddCleanUpFn(cleanUpFn func() error) {
363442
c.Lock()
364443
defer c.Unlock()
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package kind
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"time"
7+
8+
"github.com/ovn-org/ovn-kubernetes/test/e2e/deploymentconfig"
9+
10+
e2epodoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
11+
)
12+
13+
const (
14+
secondaryBridge = "ovsbr1"
15+
)
16+
17+
func ensureOVSBridge(podNamespace, podName string, bridgeName string) error {
18+
cmd := fmt.Sprintf("ovs-vsctl br-exists %[1]s || ovs-vsctl add-br %[1]s", bridgeName)
19+
if _, err := e2epodoutput.RunHostCmdWithRetries(podNamespace, podName, cmd, time.Second, time.Second*5); err != nil {
20+
return fmt.Errorf("failed to add ovs bridge %q: %v", bridgeName, err)
21+
}
22+
return nil
23+
}
24+
25+
func removeOVSBridge(podNamespace, podName string, bridgeName string) error {
26+
cmd := fmt.Sprintf("if ovs-vsctl br-exists %[1]s; then ovs-vsctl del-br %[1]s; fi", bridgeName)
27+
if _, err := e2epodoutput.RunHostCmdWithRetries(podNamespace, podName, cmd, time.Second, time.Second*5); err != nil {
28+
return fmt.Errorf("failed to remove ovs bridge %q: %v", bridgeName, err)
29+
}
30+
return nil
31+
}
32+
33+
func ovsAttachPortToBridge(podNamespace, podName string, bridgeName string, portName string) error {
34+
cmd := fmt.Sprintf("ovs-vsctl list port %[2]s || ovs-vsctl add-port %[1]s %[2]s", bridgeName, portName)
35+
if _, err := e2epodoutput.RunHostCmdWithRetries(podNamespace, podName, cmd, time.Second, time.Second*5); err != nil {
36+
return fmt.Errorf("failed to addadd port %s from OVS bridge %s: %v", portName, bridgeName, err)
37+
}
38+
return nil
39+
}
40+
41+
func ovsEnableVLANAccessPort(podNamespace, podName string, bridgeName string, portName string, vlanID int) error {
42+
cmd := fmt.Sprintf("ovs-vsctl set port %[1]s tag=%[2]d vlan_mode=access", portName, vlanID)
43+
if _, err := e2epodoutput.RunHostCmdWithRetries(podNamespace, podName, cmd, time.Second, time.Second*5); err != nil {
44+
return fmt.Errorf("failed to enable vlan access port %s from OVS bridge %s: %v", portName, bridgeName, err)
45+
}
46+
return nil
47+
}
48+
49+
type BridgeMapping struct {
50+
physnet string
51+
ovsBridge string
52+
}
53+
54+
func (bm BridgeMapping) String() string {
55+
return fmt.Sprintf("%s:%s", bm.physnet, bm.ovsBridge)
56+
}
57+
58+
type BridgeMappings []BridgeMapping
59+
60+
func (bms BridgeMappings) String() string {
61+
return strings.Join(Map(bms, func(bm BridgeMapping) string { return bm.String() }), ",")
62+
}
63+
64+
func Map[T, V any](items []T, fn func(T) V) []V {
65+
result := make([]V, len(items))
66+
for i, t := range items {
67+
result[i] = fn(t)
68+
}
69+
return result
70+
}
71+
72+
func configureBridgeMappings(podNamespace, podName string, mappings ...BridgeMapping) error {
73+
mappingsString := fmt.Sprintf("external_ids:ovn-bridge-mappings=%s", BridgeMappings(mappings).String())
74+
cmd := strings.Join([]string{"ovs-vsctl", "set", "open", ".", mappingsString}, " ")
75+
if _, err := e2epodoutput.RunHostCmdWithRetries(podNamespace, podName, cmd, time.Second, time.Second*5); err != nil {
76+
return fmt.Errorf("failed to configure bridge mappings %q: %v", mappingsString, err)
77+
}
78+
return nil
79+
}
80+
81+
func defaultNetworkBridgeMapping() BridgeMapping {
82+
return BridgeMapping{
83+
physnet: "physnet",
84+
ovsBridge: deploymentconfig.Get().ExternalBridgeName(),
85+
}
86+
}
87+
88+
func bridgeMapping(physnet, ovsBridge string) BridgeMapping {
89+
return BridgeMapping{
90+
physnet: physnet,
91+
ovsBridge: ovsBridge,
92+
}
93+
}

test/e2e/kubevirt.go

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1721,17 +1721,7 @@ write_files:
17211721

17221722
if td.topology == udnv1.NetworkTopologyLocalnet {
17231723
By("setting up the localnet underlay")
1724-
nodes := ovsPods(clientSet)
1725-
Expect(nodes).NotTo(BeEmpty())
1726-
DeferCleanup(func() {
1727-
if e2eframework.TestContext.DeleteNamespace && (e2eframework.TestContext.DeleteNamespaceOnFailure || !CurrentSpecReport().Failed()) {
1728-
By("tearing down the localnet underlay")
1729-
Expect(teardownUnderlay(nodes, secondaryBridge)).To(Succeed())
1730-
}
1731-
})
1732-
1733-
const secondaryInterfaceName = "eth1"
1734-
Expect(setupUnderlay(nodes, secondaryBridge, secondaryInterfaceName, networkName, 0 /*vlanID*/)).To(Succeed())
1724+
Expect(providerCtx.SetupUnderlay(fr, infraapi.Underlay{LogicalNetworkName: networkName})).To(Succeed())
17351725
}
17361726
createCUDN(cudn)
17371727

@@ -2209,20 +2199,10 @@ chpasswd: { expire: False }
22092199
)
22102200
DescribeTable("should maintain tcp connection with minimal downtime", func(td func(vmi *kubevirtv1.VirtualMachineInstance)) {
22112201
By("setting up the localnet underlay")
2212-
nodes := ovsPods(clientSet)
2213-
Expect(nodes).NotTo(BeEmpty())
2214-
DeferCleanup(func() {
2215-
if e2eframework.TestContext.DeleteNamespace && (e2eframework.TestContext.DeleteNamespaceOnFailure || !CurrentSpecReport().Failed()) {
2216-
By("tearing down the localnet underlay")
2217-
Expect(teardownUnderlay(nodes, secondaryBridge)).To(Succeed())
2218-
}
2219-
})
2220-
22212202
cudn, networkName := kubevirt.GenerateCUDN(namespace, "net1", udnv1.NetworkTopologyLocalnet, udnv1.NetworkRoleSecondary, udnv1.DualStackCIDRs{})
22222203
createCUDN(cudn)
22232204

2224-
const secondaryInterfaceName = "eth1"
2225-
Expect(setupUnderlay(nodes, secondaryBridge, secondaryInterfaceName, networkName, 0 /*vlanID*/)).To(Succeed())
2205+
Expect(providerCtx.SetupUnderlay(fr, infraapi.Underlay{LogicalNetworkName: networkName})).To(Succeed())
22262206

22272207
workerNodeList, err := fr.ClientSet.CoreV1().Nodes().List(context.Background(), metav1.ListOptions{LabelSelector: labels.FormatLabels(map[string]string{"node-role.kubernetes.io/worker": ""})})
22282208
Expect(err).NotTo(HaveOccurred())

0 commit comments

Comments
 (0)