Skip to content

Commit

Permalink
kubevirt, e2e: Add DHCP/ND test case for UDPN
Browse files Browse the repository at this point in the history
This change create a virt-launcher pod backed up by a pod running
NetworkManager with a working DHCP client to test that ovn-k is
answering back the ipv4/ipv6 DHCP/NP messages to configure networking.

Signed-off-by: Enrique Llorente <ellorent@redhat.com>
  • Loading branch information
qinqon authored and oshoval committed Oct 13, 2024
1 parent 3827ae6 commit f24ec1d
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 3 deletions.
136 changes: 134 additions & 2 deletions test/e2e/kubevirt.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"net"
"net/netip"
"os"
"os/exec"
"strings"
Expand All @@ -14,6 +15,7 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util"
"github.com/ovn-org/ovn-kubernetes/test/e2e/diagnostics"
"github.com/ovn-org/ovn-kubernetes/test/e2e/kubevirt"
Expand Down Expand Up @@ -71,6 +73,10 @@ func newControllerRuntimeClient() (crclient.Client, error) {
if err != nil {
return nil, err
}
err = corev1.AddToScheme(scheme)
if err != nil {
return nil, err
}
return crclient.New(config, crclient.Options{
Scheme: scheme,
})
Expand Down Expand Up @@ -303,8 +309,8 @@ var _ = Describe("Kubevirt Virtual Machines", func() {
checkPodRunningReady = func() func(Gomega, *corev1.Pod) {
return func(g Gomega, pod *corev1.Pod) {
ok, err := testutils.PodRunningReady(pod)
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeTrue())
g.Expect(err).ToNot(HaveOccurred())
g.Expect(ok).To(BeTrue())
}
}

Expand Down Expand Up @@ -1420,4 +1426,130 @@ passwd:
}),
)
})
Context("with kubevirt VM using layer2 UDPN", func() {
var (
podName = "virt-launcher-vm1"
cidrIPv4 = "10.128.0.0/24"
cidrIPv6 = "2010:100:200::/60"
primaryUDNNetworkStatus nadapi.NetworkStatus
virtLauncherCommand = func(command string) (string, error) {
stdout, stderr, err := ExecShellInPodWithFullOutput(fr, namespace, podName, command)
if err != nil {
return "", fmt.Errorf("%s: %s: %w", stdout, stderr, err)
}
return stdout, nil
}
primaryUDNValueFor = func(ty, field string) ([]string, error) {
output, err := virtLauncherCommand(fmt.Sprintf(`nmcli -e no -g %s %s show ovn-udn1`, field, ty))
if err != nil {
return nil, err
}
return strings.Split(output, " | "), nil
}
primaryUDNValueForConnection = func(field string) ([]string, error) {
return primaryUDNValueFor("connection", field)
}
primaryUDNValueForDevice = func(field string) ([]string, error) {
return primaryUDNValueFor("device", field)
}
)
BeforeEach(func() {
netConfig := newNetworkAttachmentConfig(
networkAttachmentConfigParams{
namespace: namespace,
name: "net1",
topology: "layer2",
cidr: correctCIDRFamily(cidrIPv4, cidrIPv6),
role: "primary",
mtu: 1300,
})
By("Creating NetworkAttachmentDefinition")
Expect(crClient.Create(context.Background(), generateNAD(netConfig))).To(Succeed())

By("Create virt-launcher pod")
kubevirtPod := kubevirt.GenerateFakeVirtLauncherPod(namespace, "vm1")
Expect(crClient.Create(context.Background(), kubevirtPod)).To(Succeed())

By("Wait for virt-launcher pod to be ready and primary UDN network status to pop up")
waitForPodsCondition([]*corev1.Pod{kubevirtPod}, func(g Gomega, pod *corev1.Pod) {
ok, err := testutils.PodRunningReady(pod)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(ok).To(BeTrue())

primaryUDNNetworkStatuses, err := podNetworkStatus(pod, func(networkStatus nadapi.NetworkStatus) bool {
return networkStatus.Default
})
g.Expect(err).ToNot(HaveOccurred())
g.Expect(primaryUDNNetworkStatuses).To(HaveLen(1))
primaryUDNNetworkStatus = primaryUDNNetworkStatuses[0]
})

By("Wait NetworkManager readiness")
Eventually(func() error {
_, err := virtLauncherCommand("systemctl is-active NetworkManager")
return err
}).
WithTimeout(5 * time.Second).
WithPolling(time.Second).
Should(Succeed())

By("Reconfigure primary UDN interface to use dhcp/nd for ipv4 and ipv6")
_, err := virtLauncherCommand(kubevirt.GenerateAddressDiscoveryConfigurationCommand("ovn-udn1"))
Expect(err).ToNot(HaveOccurred())

})
It("should configure IPv4 and IPv6 using DHCP and NDP", func() {
dnsService, err := fr.ClientSet.CoreV1().Services(config.Kubernetes.DNSServiceNamespace).
Get(context.Background(), config.Kubernetes.DNSServiceName, metav1.GetOptions{})
Expect(err).ToNot(HaveOccurred())

if isIPv4Supported() {
expectedIP, err := matchIPv4StringFamily(primaryUDNNetworkStatus.IPs)
Expect(err).ToNot(HaveOccurred())

expectedDNS, err := matchIPv4StringFamily(dnsService.Spec.ClusterIPs)
Expect(err).ToNot(HaveOccurred())

_, cidr, err := net.ParseCIDR(cidrIPv4)
Expect(err).ToNot(HaveOccurred())
expectedGateway := util.GetNodeGatewayIfAddr(cidr).IP.String()

Eventually(primaryUDNValueForConnection).
WithArguments("DHCP4.OPTION").
WithTimeout(10 * time.Second).
WithPolling(time.Second).
Should(ContainElements(
"host_name = vm1",
fmt.Sprintf("ip_address = %s", expectedIP),
fmt.Sprintf("domain_name_servers = %s", expectedDNS),
fmt.Sprintf("routers = %s", expectedGateway),
fmt.Sprintf("interface_mtu = 1300"),
))
Expect(primaryUDNValueForConnection("IP4.ADDRESS")).To(ConsistOf(expectedIP + "/24"))
Expect(primaryUDNValueForConnection("IP4.GATEWAY")).To(ConsistOf(expectedGateway))
Expect(primaryUDNValueForConnection("IP4.DNS")).To(ConsistOf(expectedDNS))
Expect(primaryUDNValueForDevice("GENERAL.MTU")).To(ConsistOf("1300"))
}

if isIPv6Supported() {
expectedIP, err := matchIPv6StringFamily(primaryUDNNetworkStatus.IPs)
Expect(err).ToNot(HaveOccurred())
Eventually(primaryUDNValueFor).
WithArguments("connection", "DHCP6.OPTION").
WithTimeout(10 * time.Second).
WithPolling(time.Second).
Should(ContainElements(
"fqdn_fqdn = vm1",
fmt.Sprintf("ip6_address = %s", expectedIP),
))
Expect(primaryUDNValueForConnection("IP6.ADDRESS")).To(SatisfyAll(HaveLen(2), ContainElements(expectedIP+"/128")))
Expect(primaryUDNValueForConnection("IP6.GATEWAY")).To(ConsistOf(WithTransform(func(ipv6 string) bool {
return netip.MustParseAddr(ipv6).IsLinkLocalUnicast()
}, BeTrue())))
Expect(primaryUDNValueForConnection("IP6.ROUTE")).To(ContainElement(ContainSubstring(fmt.Sprintf("dst = %s", cidrIPv6))))
Expect(primaryUDNValueForDevice("GENERAL.MTU")).To(ConsistOf("1300"))
}

})
})
})
11 changes: 11 additions & 0 deletions test/e2e/kubevirt/net.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package kubevirt

import (
"fmt"
)

func GenerateAddressDiscoveryConfigurationCommand(iface string) string {
return fmt.Sprintf(`
nmcli c mod %[1]s ipv4.addresses "" ipv6.addresses "" ipv4.gateway "" ipv6.gateway "" ipv6.method auto ipv4.method auto ipv6.addr-gen-mode eui64
nmcli d reapply %[1]s`, iface)
}
33 changes: 33 additions & 0 deletions test/e2e/kubevirt/pod.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package kubevirt

import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"

kubevirtv1 "kubevirt.io/api/core/v1"
)

func GenerateFakeVirtLauncherPod(namespace, vmName string) *corev1.Pod {
return &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "virt-launcher-" + vmName,
Namespace: namespace,
Labels: map[string]string{
kubevirtv1.VirtualMachineNameLabel: vmName,
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Name: "compute",
Image: "quay.io/nmstate/c10s-nmstate-dev:latest",
SecurityContext: &corev1.SecurityContext{
Privileged: ptr.To(true),
Capabilities: &corev1.Capabilities{
Add: []corev1.Capability{"NET_ADMIN"},
},
},
}},
},
}
}
7 changes: 6 additions & 1 deletion test/e2e/multihoming_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type networkAttachmentConfigParams struct {
vlanID int
allowPersistentIPs bool
role string
mtu int
}

type networkAttachmentConfig struct {
Expand All @@ -83,6 +84,9 @@ func uniqueNadName(originalNetName string) string {
}

func generateNAD(config networkAttachmentConfig) *nadapi.NetworkAttachmentDefinition {
if config.mtu == 0 {
config.mtu = 1300
}
nadSpec := fmt.Sprintf(
`
{
Expand All @@ -92,7 +96,7 @@ func generateNAD(config networkAttachmentConfig) *nadapi.NetworkAttachmentDefini
"topology":%q,
"subnets": %q,
"excludeSubnets": %q,
"mtu": 1300,
"mtu": %d,
"netAttachDefName": %q,
"vlanID": %d,
"allowPersistentIPs": %t,
Expand All @@ -103,6 +107,7 @@ func generateNAD(config networkAttachmentConfig) *nadapi.NetworkAttachmentDefini
config.topology,
config.cidr,
strings.Join(config.excludeCIDRs, ","),
config.mtu,
namespacedName(config.namespace, config.name),
config.vlanID,
config.allowPersistentIPs,
Expand Down
9 changes: 9 additions & 0 deletions test/e2e/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util"

v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -1247,3 +1248,11 @@ func isKernelModuleLoaded(nodeName, kernelModuleName string) bool {
}
return false
}

func matchIPv4StringFamily(ipStrings []string) (string, error) {
return util.MatchIPStringFamily(false /*ipv4*/, ipStrings)
}

func matchIPv6StringFamily(ipStrings []string) (string, error) {
return util.MatchIPStringFamily(true /*ipv6*/, ipStrings)
}

0 comments on commit f24ec1d

Please sign in to comment.