From cf5b99e2c11415ac1d7362e02aacc56acc526716 Mon Sep 17 00:00:00 2001 From: Ram Lavi Date: Tue, 1 Oct 2024 14:54:44 +0300 Subject: [PATCH] tests/e2e, persistentip: Add primary UDN test case This commit adds a new test subtree, checking persistentIPs on workloads with primary UDN. Signed-off-by: Ram Lavi --- test/e2e/persistentips_test.go | 8 +++ test/env/generate.go | 74 +++++++++++++++++++++++++++ test/env/getter.go | 92 +++++++++++++++++++++++++++++++++- 3 files changed, 173 insertions(+), 1 deletion(-) diff --git a/test/e2e/persistentips_test.go b/test/e2e/persistentips_test.go index 5def557c..81d50f8e 100644 --- a/test/e2e/persistentips_test.go +++ b/test/e2e/persistentips_test.go @@ -43,6 +43,7 @@ import ( const networkInterfaceName = "multus" const ( + rolePrimary = "primary" roleSecondary = "secondary" ) @@ -84,6 +85,8 @@ var _ = DescribeTableSubtree("Persistent IPs", func(params testParams) { nad = testenv.GenerateLayer2WithSubnetNAD(td.Namespace, params.role) if params.role == roleSecondary { vmi = testenv.GenerateAlpineWithMultusVMI(td.Namespace, networkInterfaceName, nad.Name) + } else if params.role == rolePrimary { + vmi = testenv.GenerateAlpineWithPrimaryUDNVMI(td.Namespace) } vm = testenv.NewVirtualMachine(vmi, testenv.WithRunning()) @@ -314,6 +317,11 @@ var _ = DescribeTableSubtree("Persistent IPs", func(params testParams) { role: roleSecondary, getIPsFunc: getIPsFromVMIStatus, }), + Entry("primary UDN", + testParams{ + role: rolePrimary, + getIPsFunc: testenv.GetIPsFromNetworkStatusAnnotation, + }), ) func foregroundDeleteOptions() *client.DeleteOptions { diff --git a/test/env/generate.go b/test/env/generate.go index 596e72e7..9373472f 100644 --- a/test/env/generate.go +++ b/test/env/generate.go @@ -97,6 +97,80 @@ func GenerateAlpineWithMultusVMI(namespace, interfaceName, networkName string) * } } +func GenerateAlpineWithPrimaryUDNVMI(namespace string) *kubevirtv1.VirtualMachineInstance { + const interfaceName = "passtnet" + return &kubevirtv1.VirtualMachineInstance{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: RandomName("alpine", 16), + }, + Spec: kubevirtv1.VirtualMachineInstanceSpec{ + Domain: kubevirtv1.DomainSpec{ + Resources: kubevirtv1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("2048Mi"), + }, + }, + Devices: kubevirtv1.Devices{ + Disks: []kubevirtv1.Disk{ + { + DiskDevice: kubevirtv1.DiskDevice{ + Disk: &kubevirtv1.DiskTarget{ + Bus: kubevirtv1.DiskBusVirtio, + }, + }, + Name: "containerdisk", + }, + }, + Interfaces: []kubevirtv1.Interface{ + { + Name: interfaceName, + Binding: &kubevirtv1.PluginBinding{ + Name: "passt", + }, + }, + }, + }, + }, + Networks: []kubevirtv1.Network{ + { + Name: interfaceName, + NetworkSource: kubevirtv1.NetworkSource{ + Pod: &kubevirtv1.PodNetwork{}, + }, + }, + }, + TerminationGracePeriodSeconds: pointer.Int64(5), + Volumes: []kubevirtv1.Volume{ + { + Name: "containerdisk", + VolumeSource: kubevirtv1.VolumeSource{ + ContainerDisk: &kubevirtv1.ContainerDiskSource{ + Image: "quay.io/kubevirtci/alpine-container-disk-demo:devel_alt", + }, + }, + }, + { + Name: "cloudinitdisk", + VolumeSource: kubevirtv1.VolumeSource{ + CloudInitNoCloud: &kubevirtv1.CloudInitNoCloudSource{ + NetworkData: cloudInitNetworkData(), + }, + }, + }, + }, + }, + } +} + +func cloudInitNetworkData() string { + return ` +version: 2 +ethernets: + eth0: + dhcp4: true` +} + type VMOption func(vm *kubevirtv1.VirtualMachine) func NewVirtualMachine(vmi *kubevirtv1.VirtualMachineInstance, opts ...VMOption) *kubevirtv1.VirtualMachine { diff --git a/test/env/getter.go b/test/env/getter.go index 70db908a..8e922586 100644 --- a/test/env/getter.go +++ b/test/env/getter.go @@ -2,9 +2,16 @@ package env import ( "context" + "encoding/json" + "fmt" + + nadv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + + corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - kubevirtv1 "kubevirt.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" + + kubevirtv1 "kubevirt.io/api/core/v1" ) // ThisVMI fetches the latest state of the VirtualMachineInstance. If the object does not exist, nil is returned. @@ -44,3 +51,86 @@ func GetIPsFromVMIStatus(vmi *kubevirtv1.VirtualMachineInstance, networkInterfac } return ifaceStatus.IPs } + +func getPodByVirtualMachineInstance(vmi *kubevirtv1.VirtualMachineInstance) (*corev1.Pod, error) { + pod, err := lookupPodBySelector(vmi.Namespace, vmiLabelSelector(vmi), vmiFieldSelector(vmi)) + if err != nil { + return nil, fmt.Errorf("failed to find pod for VMI %s (%s)", vmi.Name, string(vmi.GetUID())) + } + return pod, nil +} + +func lookupPodBySelector(namespace string, labelSelector, fieldSelector map[string]string) (*corev1.Pod, error) { + pods := &corev1.PodList{} + err := Client.List(context.Background(), pods, + client.InNamespace(namespace), + client.MatchingLabels(labelSelector), + client.MatchingFields(fieldSelector)) + if err != nil { + return nil, err + } + + if len(pods.Items) == 0 { + return nil, fmt.Errorf("failed to lookup pod") + } + + return &pods.Items[0], nil +} + +func vmiLabelSelector(vmi *kubevirtv1.VirtualMachineInstance) map[string]string { + return map[string]string{kubevirtv1.CreatedByLabel: string(vmi.GetUID())} +} + +func vmiFieldSelector(vmi *kubevirtv1.VirtualMachineInstance) map[string]string { + fieldSelectors := map[string]string{} + if vmi.Status.Phase == kubevirtv1.Running { + const podPhase = "status.phase" + fieldSelectors[podPhase] = string(corev1.PodRunning) + } + if node := vmi.Status.NodeName; node != "" { + const nodeName = "spec.nodeName" + fieldSelectors[nodeName] = node + } + return fieldSelectors +} + +func parsePodNetworkStatusAnnotation(podNetStatus string) ([]nadv1.NetworkStatus, error) { + if len(podNetStatus) == 0 { + return nil, fmt.Errorf("network status annotation not found") + } + + var netStatus []nadv1.NetworkStatus + if err := json.Unmarshal([]byte(podNetStatus), &netStatus); err != nil { + return nil, err + } + + return netStatus, nil +} + +func getDefaultNetworkStatus(vmi *kubevirtv1.VirtualMachineInstance) (*nadv1.NetworkStatus, error) { + virtLauncherPod, err := getPodByVirtualMachineInstance(vmi) + if err != nil { + return nil, err + } + + netStatuses, err := parsePodNetworkStatusAnnotation(virtLauncherPod.Annotations[nadv1.NetworkStatusAnnot]) + if err != nil { + return nil, err + } + + for _, netStatus := range netStatuses { + if netStatus.Default { + return &netStatus, nil + } + } + return nil, fmt.Errorf("primary IPs not found") +} + +func GetIPsFromNetworkStatusAnnotation(vmi *kubevirtv1.VirtualMachineInstance) ([]string, error) { + defaultNetworkStatus, err := getDefaultNetworkStatus(vmi) + if err != nil { + return nil, err + } + + return defaultNetworkStatus.IPs, nil +}