diff --git a/integration-tests/fixtures/network-vm-navigation-base.yaml b/integration-tests/fixtures/network-vm-navigation-base.yaml new file mode 100644 index 00000000..8d684913 --- /dev/null +++ b/integration-tests/fixtures/network-vm-navigation-base.yaml @@ -0,0 +1,120 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: ocpnetui-14-nad +--- +apiVersion: v1 +kind: Namespace +metadata: + name: ocpnetui-14-udn + labels: + k8s.ovn.org/primary-user-defined-network: '' +--- +apiVersion: v1 +kind: Namespace +metadata: + name: ocpnetui-14-cudn-a + labels: + ocpnetui-14/cudn: member +--- +apiVersion: v1 +kind: Namespace +metadata: + name: ocpnetui-14-cudn-b + labels: + ocpnetui-14/cudn: member +--- +apiVersion: k8s.cni.cncf.io/v1 +kind: NetworkAttachmentDefinition +metadata: + name: nad-empty + namespace: ocpnetui-14-nad +spec: + config: | + { + "cniVersion": "0.3.1", + "name": "nad-empty", + "type": "bridge", + "bridge": "br0" + } +--- +apiVersion: k8s.cni.cncf.io/v1 +kind: NetworkAttachmentDefinition +metadata: + name: nad-with-vms + namespace: ocpnetui-14-nad +spec: + config: | + { + "cniVersion": "0.3.1", + "name": "nad-with-vms", + "type": "bridge", + "bridge": "br0" + } +--- +apiVersion: k8s.cni.cncf.io/v1 +kind: NetworkAttachmentDefinition +metadata: + name: nad-multi + namespace: ocpnetui-14-nad +spec: + config: | + { + "cniVersion": "0.3.1", + "name": "nad-multi", + "type": "bridge", + "bridge": "br0" + } +--- +apiVersion: k8s.cni.cncf.io/v1 +kind: NetworkAttachmentDefinition +metadata: + name: nad-paged + namespace: ocpnetui-14-nad +spec: + config: | + { + "cniVersion": "0.3.1", + "name": "nad-paged", + "type": "bridge", + "bridge": "br0" + } +--- +apiVersion: k8s.ovn.org/v1 +kind: UserDefinedNetwork +metadata: + name: test-udn-empty + namespace: ocpnetui-14-udn +spec: + topology: Layer2 + layer2: + role: Primary + subnets: + - 10.200.0.0/24 +--- +apiVersion: k8s.ovn.org/v1 +kind: UserDefinedNetwork +metadata: + name: test-udn + namespace: ocpnetui-14-udn +spec: + topology: Layer2 + layer2: + role: Primary + subnets: + - 10.201.0.0/24 +--- +apiVersion: k8s.ovn.org/v1 +kind: ClusterUserDefinedNetwork +metadata: + name: test-cudn +spec: + namespaceSelector: + matchLabels: + ocpnetui-14/cudn: member + network: + topology: Layer2 + layer2: + role: Primary + subnets: + - 10.202.0.0/24 diff --git a/integration-tests/fixtures/network-vm-navigation-vms.yaml b/integration-tests/fixtures/network-vm-navigation-vms.yaml new file mode 100644 index 00000000..d135248b --- /dev/null +++ b/integration-tests/fixtures/network-vm-navigation-vms.yaml @@ -0,0 +1,1007 @@ +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-1 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-with-vms + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-a + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-multi + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-b + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-multi + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-c + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-multi + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-udn-1 + namespace: ocpnetui-14-udn +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: test-udn + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-cudn-a + namespace: ocpnetui-14-cudn-a +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: test-cudn + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-cudn-b + namespace: ocpnetui-14-cudn-b +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: test-cudn + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-01 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-02 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-03 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-04 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-05 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-06 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-07 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-08 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-09 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-10 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-11 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-12 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-13 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-14 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-15 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-16 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-17 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-18 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-19 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-20 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: vm-nad-page-21 + namespace: ocpnetui-14-nad +spec: + runStrategy: Halted + template: + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: + - bridge: {} + name: default + - bridge: {} + name: net-iface + machine: + type: q35 + resources: + requests: + memory: 512Mi + networks: + - name: default + pod: {} + - multus: + networkName: ocpnetui-14-nad/nad-paged + name: net-iface + volumes: + - containerDisk: + image: quay.io/kubevirt/cirros-container-disk-demo + name: rootdisk diff --git a/integration-tests/support/commands.ts b/integration-tests/support/commands.ts index 042f2ece..b8d72af6 100644 --- a/integration-tests/support/commands.ts +++ b/integration-tests/support/commands.ts @@ -34,6 +34,7 @@ declare global { namespace Cypress { interface Chainable { applyFixture(fixturePath: string, namespace: string): Chainable; + applyFixtureFile(fixturePath: string): Chainable; assertEndpointHealth( name: string, status: EndpointHealthStatus, @@ -72,10 +73,13 @@ Cypress.Commands.add('ensureNamespace', (namespace: string) => { }); Cypress.Commands.add('deleteNamespace', (namespace: string) => { - cy.exec(`oc delete namespace ${namespace} --ignore-not-found=true --wait=false`, { - failOnNonZeroExit: false, - timeout: 2 * MINUTE, - }); + cy.exec( + `oc delete namespace ${namespace} --ignore-not-found=true --wait=false --request-timeout=30s`, + { + failOnNonZeroExit: false, + timeout: MINUTE, + }, + ); }); Cypress.Commands.add('applyFixture', (fixturePath: string, namespace: string) => { @@ -87,6 +91,15 @@ Cypress.Commands.add('applyFixture', (fixturePath: string, namespace: string) => }); }); +Cypress.Commands.add('applyFixtureFile', (fixturePath: string) => { + cy.exec(`oc apply -f "${fixturePath}"`, { + failOnNonZeroExit: false, + timeout: 5 * MINUTE, + }).then((result) => { + assertOcSuccess(result, 'oc apply failed'); + }); +}); + Cypress.Commands.add('filterByName', (name: string) => { cy.get(itemFilter, { timeout: MINUTE }).should('be.visible').clear(); cy.get(itemFilter, { timeout: MINUTE }).type(`${name}{enter}`); diff --git a/integration-tests/support/index.ts b/integration-tests/support/index.ts index 06cd9406..908b8baf 100644 --- a/integration-tests/support/index.ts +++ b/integration-tests/support/index.ts @@ -21,6 +21,14 @@ const isIgnorableWindowError = (raw: unknown): boolean => { return true; } + if ( + message.includes('loadPlugin failed') || + message.includes('plugin-manifest.json timed out') || + message.includes('Failed to load plugin manifest') + ) { + return true; + } + const parts = message .split(';') .map((part) => part.trim()) @@ -32,7 +40,15 @@ const isIgnorableWindowError = (raw: unknown): boolean => { return parts.every((part) => { const match = part.match(/Missing i18n key ".+" in namespace "([^"]+)"/); - return match !== null && match[1] !== NETWORKING_I18N_NS; + if (match !== null && match[1] !== NETWORKING_I18N_NS) { + return true; + } + + return ( + part.includes('loadPlugin failed') || + part.includes('plugin-manifest.json timed out') || + part.includes('Failed to load plugin manifest') + ); }); }; diff --git a/integration-tests/support/network-vm-navigation.ts b/integration-tests/support/network-vm-navigation.ts new file mode 100644 index 00000000..fbbcabf2 --- /dev/null +++ b/integration-tests/support/network-vm-navigation.ts @@ -0,0 +1,186 @@ +import { MINUTE } from './commands'; + +export const NAD_NS = 'ocpnetui-14-nad'; +export const UDN_NS = 'ocpnetui-14-udn'; +export const CUDN_NS_A = 'ocpnetui-14-cudn-a'; +export const CUDN_NS_B = 'ocpnetui-14-cudn-b'; + +export const NAD_EMPTY = 'nad-empty'; +export const NAD_WITH_VMS = 'nad-with-vms'; +export const NAD_MULTI = 'nad-multi'; +export const NAD_PAGED = 'nad-paged'; + +export const UDN_EMPTY = 'test-udn-empty'; +export const UDN_WITH_VM = 'test-udn'; +export const CUDN_NAME = 'test-cudn'; + +const nadDetailsPath = (name: string, tab = ''): string => + `/k8s/ns/${NAD_NS}/k8s.cni.cncf.io~v1~NetworkAttachmentDefinition/${name}${tab ? `/${tab}` : ''}`; + +const udnDetailsPath = (name: string, tab = ''): string => + `/k8s/ns/${UDN_NS}/k8s.ovn.org~v1~UserDefinedNetwork/${name}${tab ? `/${tab}` : ''}`; + +const cudnDetailsPath = (name: string, tab = ''): string => + `/k8s/cluster/k8s.ovn.org~v1~ClusterUserDefinedNetwork/${name}${tab ? `/${tab}` : ''}`; + +const vmDetailsPathPattern = (name: string, namespace: string): RegExp => + new RegExp(`/k8s/(cnv/)?ns/${namespace}/kubevirt\\.io~v1~VirtualMachine/${name}`); + +const vmTableBody = 'table tbody'; + +const waitForVirtualMachinesTabReady = (): void => { + cy.contains('button', 'Add virtual machines', { timeout: 2 * MINUTE }).should('be.visible'); + cy.get('body').then(($body) => { + if ($body.text().includes('Sorry, we could not load')) { + cy.reload(); + cy.dismissWelcomeTourIfPresent(); + cy.contains('button', 'Add virtual machines', { timeout: 2 * MINUTE }).should('be.visible'); + } + }); +}; + +export const waitForVmTableLoaded = (minRows = 1): void => { + waitForVirtualMachinesTabReady(); + cy.contains('h4', 'Error loading virtual machines', { timeout: 2 * MINUTE }).should('not.exist'); + cy.get(vmTableBody, { timeout: 2 * MINUTE }).should('exist'); + if (minRows > 0) { + cy.get(`${vmTableBody} tr`, { timeout: 2 * MINUTE }).should('have.length.at.least', minRows); + } +}; + +export const visitNADVirtualMachinesTab = (nadName: string, query = ''): void => { + cy.visit(`${nadDetailsPath(nadName, 'virtual-machines')}${query}`, { + failOnStatusCode: false, + timeout: 2 * MINUTE, + }); + cy.dismissWelcomeTourIfPresent(); + waitForVirtualMachinesTabReady(); + assertVirtualMachinesTabSelected(); +}; + +export const visitUDNVirtualMachinesTab = (udnName: string, query = ''): void => { + cy.visit(`${udnDetailsPath(udnName, 'virtual-machines')}${query}`, { + failOnStatusCode: false, + timeout: 2 * MINUTE, + }); + cy.dismissWelcomeTourIfPresent(); + waitForVirtualMachinesTabReady(); + assertVirtualMachinesTabSelected(); +}; + +export const visitCUDNVirtualMachinesTab = (cudnName: string, query = ''): void => { + cy.visit(`${cudnDetailsPath(cudnName, 'virtual-machines')}${query}`, { + failOnStatusCode: false, + timeout: 2 * MINUTE, + }); + cy.dismissWelcomeTourIfPresent(); + waitForVirtualMachinesTabReady(); + assertVirtualMachinesTabSelected(); +}; + +export const visitNADDetails = (nadName: string): void => { + cy.visit(nadDetailsPath(nadName), { failOnStatusCode: false }); + cy.dismissWelcomeTourIfPresent(); +}; + +export const assertVirtualMachinesTabSelected = (): void => { + cy.contains('a[role="tab"], button[role="tab"]', 'Virtual Machines', { timeout: MINUTE }).should( + 'have.attr', + 'aria-selected', + 'true', + ); +}; + +export const assertVirtualMachinesTabVisible = (expectedCount?: number): void => { + cy.contains('a[role="tab"], button[role="tab"]', 'Virtual Machines', { timeout: MINUTE }).should( + 'be.visible', + ); + + if (expectedCount !== undefined) { + cy.contains('a[role="tab"], button[role="tab"]', 'Virtual Machines') + .find('.pf-v6-c-badge, .pf-c-badge') + .should('contain.text', String(expectedCount)); + } +}; + +export const assertVirtualMachinesTabHidden = (): void => { + cy.get('body').then(($body) => { + const tabs = $body.find('a[role="tab"], button[role="tab"]'); + const vmTab = tabs.filter((_, el) => el.textContent?.includes('Virtual Machines')); + expect(vmTab.length, 'Virtual Machines tab should not be present').to.eq(0); + }); +}; + +export const assertVMEmptyState = (): void => { + cy.contains('h4', 'No virtual machines found', { timeout: MINUTE }).should('be.visible'); + cy.contains('button', 'Add virtual machines').should('be.visible'); +}; + +export const assertVMListUnavailableWithoutKubevirt = (): void => { + cy.contains('h4', 'Error loading virtual machines', { timeout: MINUTE }).should('be.visible'); +}; + +const vmNameLink = 'table a, [data-test="resource-link"]'; + +export const assertVMListed = (vmName: string): void => { + cy.get(vmTableBody, { timeout: MINUTE }).contains(vmName).as('vmRow'); + cy.get('@vmRow').scrollIntoView(); + cy.get('@vmRow').should('be.visible'); +}; + +export const assertVMNotListed = (vmName: string): void => { + cy.get(`${vmTableBody} tr:visible`, { timeout: MINUTE }).should('not.contain', vmName); +}; + +export const clickVMInTable = (vmName: string): void => { + cy.contains(vmNameLink, vmName, { timeout: MINUTE }).first().as('vmLink'); + cy.get('@vmLink').scrollIntoView(); + cy.get('@vmLink').click(); +}; + +export const assertVMDetailsPage = (vmName: string, namespace: string): void => { + cy.url({ timeout: 2 * MINUTE }).should('match', vmDetailsPathPattern(vmName, namespace)); + cy.dismissWelcomeTourIfPresent(); + cy.contains('[data-test="page-heading"], h1', vmName, { timeout: 2 * MINUTE }).should( + 'be.visible', + ); +}; + +export const filterVMsByName = (name: string): void => { + waitForVmTableLoaded(1); + + cy.get('input[placeholder="Search by name..."]').filter(':visible').first().as('vmNameFilter'); + + cy.get('@vmNameFilter').focus(); + cy.get('@vmNameFilter').clear({ force: true }); + cy.get('@vmNameFilter').type(name, { delay: 50, force: true }); + cy.get('@vmNameFilter').type('{enter}', { force: true }); + + cy.get(vmTableBody, { timeout: MINUTE }).should('contain', name); + assertVMListed(name); +}; + +export const assertPaginationVisible = (): void => { + cy.get('.pf-v6-c-pagination, .pf-c-pagination', { timeout: MINUTE }) + .filter(':visible') + .first() + .should('be.visible'); +}; + +export const goToNextPaginationPage = (): void => { + cy.get('button[aria-label="Go to next page"], button[aria-label^="Go to next page"]', { + timeout: MINUTE, + }) + .filter(':visible') + .first() + .should('not.be.disabled') + .click(); + + // PatternFly pagination updates URL search params asynchronously. + cy.url({ timeout: MINUTE }).should('match', /page=2/); +}; + +export const isKubevirtInstalled = (): Cypress.Chainable => + cy + .exec('oc api-resources --api-group=kubevirt.io -o name', { failOnNonZeroExit: false }) + .then((result) => (result.stdout || '').includes('virtualmachines.kubevirt.io')); diff --git a/integration-tests/tests/network-vm-navigation.cy.ts b/integration-tests/tests/network-vm-navigation.cy.ts new file mode 100644 index 00000000..3b55b520 --- /dev/null +++ b/integration-tests/tests/network-vm-navigation.cy.ts @@ -0,0 +1,241 @@ +import { checkErrors } from '../support'; +import { MINUTE } from '../support/commands'; +import { + assertPaginationVisible, + assertVirtualMachinesTabHidden, + assertVirtualMachinesTabSelected, + assertVirtualMachinesTabVisible, + assertVMDetailsPage, + assertVMEmptyState, + assertVMListed, + assertVMListUnavailableWithoutKubevirt, + assertVMNotListed, + clickVMInTable, + CUDN_NAME, + CUDN_NS_A, + CUDN_NS_B, + filterVMsByName, + isKubevirtInstalled, + NAD_EMPTY, + NAD_MULTI, + NAD_NS, + NAD_PAGED, + NAD_WITH_VMS, + UDN_EMPTY, + UDN_NS, + UDN_WITH_VM, + visitCUDNVirtualMachinesTab, + visitNADDetails, + visitNADVirtualMachinesTab, + visitUDNVirtualMachinesTab, + waitForVmTableLoaded, +} from '../support/network-vm-navigation'; + +const BASE_FIXTURE = 'fixtures/network-vm-navigation-base.yaml'; +const VM_FIXTURE = 'fixtures/network-vm-navigation-vms.yaml'; + +const TEST_NAMESPACES = [NAD_NS, UDN_NS, CUDN_NS_A, CUDN_NS_B]; + +describe('OCPNETUI-14: network-to-VM cross-navigation', { testIsolation: false }, () => { + let kubevirtInstalled = false; + + before(() => { + cy.login(); + isKubevirtInstalled().then((installed) => { + kubevirtInstalled = installed; + cy.applyFixtureFile(BASE_FIXTURE); + if (installed) { + cy.applyFixtureFile(VM_FIXTURE); + } + }); + }); + + afterEach(() => { + checkErrors(); + }); + + after(() => { + cy.exec(`oc delete clusteruserdefinednetwork ${CUDN_NAME} --ignore-not-found`, { + failOnNonZeroExit: false, + timeout: MINUTE, + }); + cy.exec( + `oc delete namespace ${TEST_NAMESPACES.join(' ')} --ignore-not-found --wait=false --request-timeout=30s`, + { failOnNonZeroExit: false, timeout: MINUTE }, + ); + cy.logout(); + }); + + it('connects to the OpenShift console', () => { + cy.visit('/dashboards'); + cy.dismissWelcomeTourIfPresent(); + cy.get('[data-test="user-dropdown"], [data-test="user-dropdown-toggle"]', { + timeout: MINUTE, + }).should('exist'); + }); + + context('UDN Virtual Machines tab', () => { + it('shows the Virtual Machines tab on UDN details', () => { + visitUDNVirtualMachinesTab(UDN_WITH_VM); + assertVirtualMachinesTabSelected(); + assertVirtualMachinesTabVisible(kubevirtInstalled ? 1 : 0); + cy.contains('button', 'Add virtual machines').should('be.visible'); + + if (kubevirtInstalled) { + assertVMListed('vm-udn-1'); + } else { + assertVMListUnavailableWithoutKubevirt(); + } + }); + + it('shows an empty state when no VMs are attached', function () { + if (!kubevirtInstalled) { + this.skip(); + } + + visitUDNVirtualMachinesTab(UDN_EMPTY); + assertVirtualMachinesTabVisible(0); + assertVMEmptyState(); + }); + + it('navigates to the VM details page when a VM name is clicked', function () { + if (!kubevirtInstalled) { + this.skip(); + } + + visitUDNVirtualMachinesTab(UDN_WITH_VM); + clickVMInTable('vm-udn-1'); + assertVMDetailsPage('vm-udn-1', UDN_NS); + }); + }); + + context('CUDN Virtual Machines tab', () => { + it('shows the Virtual Machines tab on CUDN details', () => { + visitCUDNVirtualMachinesTab(CUDN_NAME); + assertVirtualMachinesTabSelected(); + assertVirtualMachinesTabVisible(kubevirtInstalled ? 2 : 0); + cy.contains('button', 'Add virtual machines').should('be.visible'); + + if (!kubevirtInstalled) { + assertVMListUnavailableWithoutKubevirt(); + } + }); + + it('lists VMs attached across member namespaces', function () { + if (!kubevirtInstalled) { + this.skip(); + } + + visitCUDNVirtualMachinesTab(CUDN_NAME); + waitForVmTableLoaded(2); + assertVirtualMachinesTabVisible(2); + assertVMListed('vm-cudn-a'); + assertVMListed('vm-cudn-b'); + }); + + it('navigates to a VM in another namespace from the CUDN tab', function () { + if (!kubevirtInstalled) { + this.skip(); + } + + visitCUDNVirtualMachinesTab(CUDN_NAME); + waitForVmTableLoaded(1); + clickVMInTable('vm-cudn-b'); + assertVMDetailsPage('vm-cudn-b', CUDN_NS_B); + }); + }); + + context('NAD Virtual Machines tab', () => { + it('omits the Virtual Machines tab when KubeVirt is not installed', function () { + if (kubevirtInstalled) { + this.skip(); + } + + visitNADDetails(NAD_EMPTY); + assertVirtualMachinesTabHidden(); + }); + + it('shows the Virtual Machines tab when KubeVirt is installed', function () { + if (!kubevirtInstalled) { + this.skip(); + } + + visitNADVirtualMachinesTab(NAD_WITH_VMS); + assertVirtualMachinesTabVisible(1); + }); + + it('shows an empty state when no VMs use the NAD', function () { + if (!kubevirtInstalled) { + this.skip(); + } + + visitNADVirtualMachinesTab(NAD_EMPTY); + assertVirtualMachinesTabVisible(0); + assertVMEmptyState(); + }); + + it('lists a single attached VM and navigates to its details page', function () { + if (!kubevirtInstalled) { + this.skip(); + } + + visitNADVirtualMachinesTab(NAD_WITH_VMS); + assertVMListed('vm-nad-1'); + clickVMInTable('vm-nad-1'); + assertVMDetailsPage('vm-nad-1', NAD_NS); + }); + + it('lists multiple attached VMs on the same NAD', function () { + if (!kubevirtInstalled) { + this.skip(); + } + + visitNADVirtualMachinesTab(NAD_MULTI); + waitForVmTableLoaded(3); + assertVirtualMachinesTabVisible(3); + assertVMListed('vm-nad-a'); + assertVMListed('vm-nad-b'); + assertVMListed('vm-nad-c'); + }); + + it('filters VMs by name in the table search', function () { + if (!kubevirtInstalled) { + this.skip(); + } + + visitNADVirtualMachinesTab(NAD_MULTI); + filterVMsByName('vm-nad-b'); + cy.get('table tbody tr:visible', { timeout: MINUTE }).should('have.length', 1); + assertVMListed('vm-nad-b'); + assertVMNotListed('vm-nad-a'); + assertVMNotListed('vm-nad-c'); + }); + + it('paginates when more than 20 VMs are attached', function () { + if (!kubevirtInstalled) { + this.skip(); + } + + visitNADVirtualMachinesTab(NAD_PAGED, '?perPage=20&page=1'); + waitForVmTableLoaded(20); + assertVirtualMachinesTabVisible(21); + assertPaginationVisible(); + cy.get('.pf-v6-c-pagination, .pf-c-pagination') + .filter(':visible') + .first() + .should('contain.text', 'of 21'); + assertVMListed('vm-nad-page-01'); + assertVMNotListed('vm-nad-page-21'); + + visitNADVirtualMachinesTab(NAD_PAGED, '?perPage=20&page=2'); + waitForVmTableLoaded(1); + cy.get('.pf-v6-c-pagination, .pf-c-pagination') + .filter(':visible') + .first() + .should('contain.text', '21') + .and('contain.text', 'of 21'); + cy.get('table tbody tr', { timeout: MINUTE }).should('have.length', 1); + assertVMNotListed('vm-nad-page-01'); + }); + }); +});