Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions test/extended/networking/kubevirt/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"strings"
"time"

"sigs.k8s.io/yaml"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"

Expand Down Expand Up @@ -93,6 +95,81 @@ func (c *Client) GetJSONPath(resource, name, jsonPath string) (string, error) {
}
return strings.TrimSuffix(strings.TrimPrefix(output, `"`), `"`), nil
}

func (c *Client) GetPodsByLabel(labelKey, labelValue string) ([]string, error) {
output, err := c.oc.AsAdmin().Run("get").Args("pods", "-n", c.oc.Namespace(), "-l", fmt.Sprintf("%s=%s", labelKey, labelValue), "-o", "name").Output()
if err != nil {
return nil, err
}
if output == "" {
return []string{}, nil
}

lines := strings.Split(strings.TrimSpace(output), "\n")
podNames := make([]string, 0, len(lines))
for _, line := range lines {
if line != "" {
podName := strings.TrimPrefix(line, "pod/")
podNames = append(podNames, podName)
}
}
return podNames, nil
}

func (c *Client) GetEventsForPod(podName string) ([]string, error) {
output, err := c.oc.AsAdmin().Run("get").Args("events", "-n", c.oc.Namespace(), "--field-selector", fmt.Sprintf("involvedObject.name=%s,involvedObject.kind=Pod", podName), "-o", "custom-columns=MESSAGE:.message", "--no-headers").Output()
if err != nil {
return nil, err
}
if output == "" {
return []string{}, nil
}
lines := strings.Split(strings.TrimSpace(output), "\n")
messages := make([]string, 0, len(lines))
for _, line := range lines {
if line != "" {
messages = append(messages, line)
}
}
return messages, nil
}

type Option func(map[string]interface{})

func (c *Client) CreateVMIFromSpec(vmNamespace, vmName string, vmiSpec map[string]interface{}, opts ...Option) error {
newVMI := map[string]interface{}{
"apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachineInstance",
"metadata": map[string]interface{}{
"name": vmName,
"namespace": vmNamespace,
},
"spec": vmiSpec,
}

for _, opt := range opts {
opt(newVMI)
}

newVMIYAML, err := yaml.Marshal(newVMI)
if err != nil {
return err
}

return c.Apply(string(newVMIYAML))
}

func WithAnnotations(annotations map[string]string) Option {
return func(cr map[string]interface{}) {
metadata, hasMetadata := cr["metadata"].(map[string]interface{})
if !hasMetadata {
metadata = make(map[string]interface{})
cr["metadata"] = metadata
}
metadata["annotations"] = annotations
}
}

func ensureVirtctl(oc *exutil.CLI, dir string) (string, error) {
filepath := filepath.Join(dir, "virtctl")
_, err := os.Stat(filepath)
Expand Down
108 changes: 108 additions & 0 deletions test/extended/networking/livemigration.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import (
"github.com/openshift/origin/test/extended/util/image"
)

const kvIPRequestsAnnot = "network.kubevirt.io/addresses"

var _ = Describe("[sig-network][OCPFeatureGate:PersistentIPsForVirtualization][Feature:Layer2LiveMigration] Kubevirt Virtual Machines", func() {
// disable automatic namespace creation, we need to add the required UDN label
oc := exutil.NewCLIWithoutNamespace("network-segmentation-e2e")
Expand Down Expand Up @@ -312,6 +314,34 @@ var _ = Describe("[sig-network][OCPFeatureGate:PersistentIPsForVirtualization][F
preconfiguredMAC: "02:0A:0B:0C:0D:51",
},
),
Entry(
"[OCPFeatureGate:PreconfiguredUDNAddresses] when the VM with preconfigured IP address is created when the address is already taken",
networkAttachmentConfigParams{
name: nadName,
topology: "layer2",
role: "primary",
allowPersistentIPs: true,
},
kubevirt.FedoraVMWithPreconfiguredPrimaryUDNAttachment,
duplicateVM,
workloadNetworkConfig{
preconfiguredIPs: []string{"203.203.0.100", "2014:100:200::100"},
},
),
Entry(
"[OCPFeatureGate:PreconfiguredUDNAddresses] when the VM with preconfigured MAC address is created when the address is already taken",
networkAttachmentConfigParams{
name: nadName,
topology: "layer2",
role: "primary",
allowPersistentIPs: true,
},
kubevirt.FedoraVMWithPreconfiguredPrimaryUDNAttachment,
duplicateVM,
workloadNetworkConfig{
preconfiguredMAC: "02:00:00:22:22:22",
},
),
)
},
Entry("NetworkAttachmentDefinitions", func(c networkAttachmentConfigParams) networkAttachmentConfig {
Expand Down Expand Up @@ -537,6 +567,80 @@ func verifyVMMAC(virtClient *kubevirt.Client, vmName, expectedMAC string) {
Should(Equal(expectedMAC))
}

func duplicateVM(cli *kubevirt.Client, vmNamespace, vmName string) {
GinkgoHelper()
duplicateVMName := vmName + "-duplicate"
By(fmt.Sprintf("Duplicating VM %s/%s to %s/%s", vmNamespace, vmName, vmNamespace, duplicateVMName))

vmiSpecJSON, err := cli.GetJSONPath("vmi", vmName, "{.spec}")
Expect(err).NotTo(HaveOccurred())
var vmiSpec map[string]interface{}
Expect(json.Unmarshal([]byte(vmiSpecJSON), &vmiSpec)).To(Succeed())

originalVMIRawAnnotations, err := cli.GetJSONPath("vmi", vmName, "{.metadata.annotations}")
Expect(err).NotTo(HaveOccurred())

originalVMIAnnotations := map[string]string{}
Expect(json.Unmarshal([]byte(originalVMIRawAnnotations), &originalVMIAnnotations)).To(Succeed())

var vmiCreationOptions []kubevirt.Option
var vmiExpectations []func()
if requestedIPs, hasIPRequests := originalVMIAnnotations[kvIPRequestsAnnot]; hasIPRequests {
vmiCreationOptions = append(
vmiCreationOptions,
kubevirt.WithAnnotations(ipRequests(requestedIPs)),
)
vmiExpectations = append(vmiExpectations, func() {
waitForVMPodEventWithMessage(
cli,
vmNamespace,
duplicateVMName,
"IP is already allocated",
2*time.Minute,
)
})
}

mac, err := cli.GetJSONPath("vmi", vmName, "{.spec.domain.devices.interfaces[0].macAddress}")
Expect(err).NotTo(HaveOccurred())
if len(mac) > 0 {
vmiExpectations = append(vmiExpectations, func() {
waitForVMPodEventWithMessage(
cli,
vmNamespace,
duplicateVMName,
"MAC address already in use",
2*time.Minute,
)
})
}

Expect(cli.CreateVMIFromSpec(vmNamespace, duplicateVMName, vmiSpec, vmiCreationOptions...)).To(Succeed())
for _, expectation := range vmiExpectations {
expectation()
}
}

func waitForVMPodEventWithMessage(vmClient *kubevirt.Client, vmNamespace, vmName, expectedEventMessage string, timeout time.Duration) {
GinkgoHelper()
By(fmt.Sprintf("Waiting for event containing %q on VM %s/%s virt-launcher pod", expectedEventMessage, vmNamespace, vmName))

Eventually(func(g Gomega) []string {
const vmLabelKey = "vm.kubevirt.io/name"
podNames, err := vmClient.GetPodsByLabel(vmLabelKey, vmName)
g.Expect(err).NotTo(HaveOccurred(), "Failed to get pods by label %s=%s", vmLabelKey, vmName)
g.Expect(podNames).To(HaveLen(1), "Expected exactly one virt-launcher pod for VM %s/%s, but found %d pods: %v", vmNamespace, vmName, len(podNames), podNames)

virtLauncherPodName := podNames[0]
eventMessages, err := vmClient.GetEventsForPod(virtLauncherPodName)
g.Expect(err).NotTo(HaveOccurred(), "Failed to get events for pod %s", virtLauncherPodName)

return eventMessages
}).WithPolling(time.Second).WithTimeout(timeout).Should(
ContainElement(ContainSubstring(expectedEventMessage)),
fmt.Sprintf("Expected to find an event containing %q", expectedEventMessage))
}

func waitForPodsCondition(fr *framework.Framework, pods []*corev1.Pod, conditionFn func(g Gomega, pod *corev1.Pod)) {
for _, pod := range pods {
Eventually(func(g Gomega) {
Expand Down Expand Up @@ -744,3 +848,7 @@ func formatAddressesAnnotation(preconfiguredIPs []string) (string, error) {

return string(staticIPs), nil
}

func ipRequests(ips string) map[string]string {
return map[string]string{kvIPRequestsAnnot: ips}
}