Skip to content
This repository was archived by the owner on Mar 1, 2024. It is now read-only.

Commit 5466b69

Browse files
author
Radim Hrazdil
committed
Added tests for vm list/vies actions (start, stop, ...)
1 parent 514e47a commit 5466b69

File tree

7 files changed

+278
-93
lines changed

7 files changed

+278
-93
lines changed

frontend/integration-tests/protractor.conf.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export const config: Config = {
101101
catalog: ['tests/base.scenario.ts', 'tests/catalog.scenario.ts'],
102102
marketplace: ['tests/base.scenario.ts', 'tests/marketplace/kubernetes-marketplace.scenario.ts'],
103103
overview: ['tests/base.scenario.ts', 'tests/overview/overview.scenario.ts'],
104-
kubevirt: ['tests/base.scenario.ts', 'tests/kubevirt/vm.wizard.scenario.ts'],
104+
kubevirt: ['tests/base.scenario.ts', 'tests/kubevirt/vm.wizard.scenario.ts', 'tests/kubevirt/vm.actions.scenario.ts'],
105105
all: ['tests/base.scenario.ts',
106106
'tests/crud.scenario.ts',
107107
'tests/overview/overview.scenareio.ts',
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/* eslint-disable no-undef */
2+
import { testName } from '../../protractor.conf';
3+
4+
const testLabel = 'automatedTestName';
5+
6+
export const testVM = {
7+
apiVersion: 'kubevirt.io/v1alpha2',
8+
kind: 'VirtualMachine',
9+
metadata: {
10+
name: `vm-${testName}`,
11+
namespace: testName,
12+
labels: {[testLabel]: testName},
13+
},
14+
spec: {
15+
running: false,
16+
template: {
17+
spec: {
18+
domain: {
19+
cpu: {
20+
cores: 1,
21+
},
22+
devices: {
23+
disks: [
24+
{
25+
bootOrder: 1,
26+
disk: {
27+
bus: 'virtio',
28+
},
29+
name: 'rootdisk',
30+
volumeName: 'rootdisk',
31+
},
32+
],
33+
interfaces: [
34+
{
35+
bridge: {},
36+
name: 'eth0',
37+
},
38+
],
39+
},
40+
resources: {
41+
requests: {
42+
memory: '1G',
43+
},
44+
},
45+
},
46+
networks: [
47+
{
48+
name: 'eth0',
49+
pod: {},
50+
},
51+
],
52+
volumes: [
53+
{
54+
containerDisk: {
55+
image: 'kubevirt/cirros-registry-disk-demo:latest',
56+
},
57+
name: 'rootdisk',
58+
},
59+
],
60+
},
61+
},
62+
},
63+
};
64+
65+
export const testNAD = {
66+
apiVersion: 'k8s.cni.cncf.io/v1',
67+
kind: 'NetworkAttachmentDefinition',
68+
metadata: {
69+
name: `ovs-net-1${testName}-${testName}`,
70+
namespace: testName,
71+
labels: {[testLabel]: testName},
72+
},
73+
spec: {
74+
config: '{ "cniVersion": "0.3.1", "type": "ovs", "bridge": "br0" }',
75+
},
76+
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/* eslint-disable no-undef */
2+
import { execSync } from 'child_process';
3+
4+
export function removeLeakedResources(leakedResources: Set<string>){
5+
const leakedArray: Array<string> = [...leakedResources];
6+
if (leakedArray.length > 0) {
7+
console.error(`Leaked ${leakedArray.join()}`);
8+
leakedArray.map(r => JSON.parse(r) as {name: string, namespace: string, kind: string})
9+
.forEach(({name, namespace, kind}) => {
10+
try {
11+
execSync(`kubectl delete -n ${namespace} --cascade ${kind} ${name}`);
12+
} catch (error) {
13+
console.error(`Failed to delete ${kind} ${name}:\n${error}`);
14+
}
15+
});
16+
}
17+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/* eslint-disable no-undef */
2+
3+
import { execSync } from 'child_process';
4+
import { browser, ExpectedConditions as until } from 'protractor';
5+
6+
import { appHost, testName } from '../../protractor.conf';
7+
import { resourceRowsPresent, filterForName, isLoaded } from '../../views/crud.view';
8+
import { testVM } from './mocks';
9+
import { removeLeakedResources } from './utils';
10+
import * as vmActionsView from '../../views/kubevirt/vm.actions.view';
11+
12+
const VM_BOOTUP_TIMEOUT = 60000;
13+
const VM_ACTIONS_TIMEOUT = 90000;
14+
15+
describe('Test VM actions', () => {
16+
const leakedResources = new Set<string>();
17+
18+
afterAll(async() => {
19+
removeLeakedResources(leakedResources);
20+
});
21+
22+
describe('Test VM list view kebab actions', () => {
23+
const vmName = `vm-list-view-actions-${testName}`;
24+
beforeAll(async() => {
25+
testVM.metadata.name = vmName;
26+
execSync(`echo '${JSON.stringify(testVM)}' | kubectl create -f -`);
27+
leakedResources.add(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
28+
});
29+
30+
it('Navigates to VMs', async() => {
31+
await browser.get(`${appHost}/k8s/all-namespaces/virtualmachines`);
32+
await isLoaded();
33+
await filterForName(vmName);
34+
await resourceRowsPresent();
35+
});
36+
37+
it('Starts VM', async() => {
38+
await vmActionsView.listViewAction(vmName)('Start');
39+
await browser.wait(until.textToBePresentInElement(vmActionsView.listViewVMmStatus(vmName), 'Running'), VM_BOOTUP_TIMEOUT);
40+
});
41+
42+
it('Restarts VM', async() => {
43+
await vmActionsView.listViewAction(vmName)('Restart');
44+
await browser.wait(until.textToBePresentInElement(vmActionsView.listViewVMmStatus(vmName), 'Starting'), VM_BOOTUP_TIMEOUT);
45+
await browser.wait(until.textToBePresentInElement(vmActionsView.listViewVMmStatus(vmName), 'Running'), VM_BOOTUP_TIMEOUT);
46+
}, VM_ACTIONS_TIMEOUT);
47+
48+
it('Stops VM', async() => {
49+
await vmActionsView.listViewAction(vmName)('Stop');
50+
await browser.wait(until.textToBePresentInElement(vmActionsView.listViewVMmStatus(vmName), 'Off'), 10000);
51+
});
52+
53+
it('Deletes VM', async() => {
54+
await vmActionsView.listViewAction(vmName)('Delete');
55+
await browser.wait(until.not(until.presenceOf(vmActionsView.listViewVMmStatus(vmName))));
56+
leakedResources.delete(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
57+
});
58+
});
59+
60+
describe('Test VM detail view kebab actions', () => {
61+
const vmName = `vm-detail-view-actions-${testName}`;
62+
beforeAll(async() => {
63+
testVM.metadata.name = vmName;
64+
execSync(`echo '${JSON.stringify(testVM)}' | kubectl create -f -`);
65+
leakedResources.add(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
66+
});
67+
68+
it('Navigates to VMs detail page', async() => {
69+
await browser.get(`${appHost}/k8s/all-namespaces/virtualmachines/${vmName}`);
70+
await isLoaded();
71+
});
72+
73+
it('Starts VM', async() => {
74+
await vmActionsView.detailViewAction('Start');
75+
await browser.wait(until.textToBePresentInElement(vmActionsView.detailViewVMmStatus, 'Running'), VM_BOOTUP_TIMEOUT);
76+
});
77+
78+
it('Restarts VM', async() => {
79+
await vmActionsView.detailViewAction('Restart');
80+
await browser.wait(until.textToBePresentInElement(vmActionsView.detailViewVMmStatus, 'Starting'), VM_BOOTUP_TIMEOUT);
81+
await browser.wait(until.textToBePresentInElement(vmActionsView.detailViewVMmStatus, 'Running'), VM_BOOTUP_TIMEOUT);
82+
}, VM_ACTIONS_TIMEOUT);
83+
84+
it('Stops VM', async() => {
85+
await vmActionsView.detailViewAction('Stop');
86+
await browser.wait(until.textToBePresentInElement(vmActionsView.detailViewVMmStatus, 'Off'), 10000);
87+
});
88+
89+
it('Deletes VM', async() => {
90+
await vmActionsView.detailViewAction('Delete');
91+
leakedResources.delete(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
92+
});
93+
});
94+
});

frontend/integration-tests/tests/kubevirt/vm.wizard.scenario.ts

Lines changed: 60 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
import { execSync } from 'child_process';
44
import { browser, by, ExpectedConditions as until } from 'protractor';
5-
import { appHost, testName } from '../../protractor.conf';
6-
import { resourceRowsPresent, filterForName, deleteRow, isLoaded, createItemButton, errorMessage } from '../../views/crud.view';
7-
import * as vmView from '../../views/kubevirt/vm.view';
85
import { OrderedMap } from 'immutable';
96

7+
import { appHost, testName } from '../../protractor.conf';
8+
import { resourceRowsPresent, filterForName, deleteRow, createItemButton, isLoaded, errorMessage } from '../../views/crud.view';
9+
import { removeLeakedResources } from './utils';
10+
import { testNAD } from './mocks';
11+
import * as vmView from '../../views/kubevirt/vm.view';
1012

1113
describe('Kubevirt create VM using wizard', () => {
1214
const leakedResources = new Set<string>();
@@ -16,19 +18,7 @@ describe('Kubevirt create VM using wizard', () => {
1618
const workloadProfile = 'generic';
1719
const sourceURL = 'https://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img';
1820
const sourceContainer = 'kubevirt/cirros-registry-disk-demo:latest';
19-
const networkDefinitionName = `${testName}-ovs-net-1`;
2021
const pxeInterface = 'eth1';
21-
const testNAD = {
22-
apiVersion: 'k8s.cni.cncf.io/v1',
23-
kind: 'NetworkAttachmentDefinition',
24-
metadata: {
25-
name: networkDefinitionName,
26-
namespace: testName,
27-
},
28-
spec: {
29-
config: '{ "cniVersion": "0.3.1", "type": "ovs", "bridge": "br0" }',
30-
},
31-
};
3222
const provisionMethods = OrderedMap<string, (provisionSource: string) => void>()
3323
.set('PXE', async function(provisionSource) {
3424
await vmView.provisionSourceButton.click();
@@ -47,96 +37,74 @@ describe('Kubevirt create VM using wizard', () => {
4737
await vmView.provisionSourceURL.sendKeys(sourceURL);
4838
});
4939

50-
beforeAll(async() => {
51-
execSync(`echo '${JSON.stringify(testNAD)}' | kubectl create -f -`);
52-
});
53-
54-
afterAll(async() => {
55-
execSync(`kubectl delete -n ${testName} net-attach-def ${networkDefinitionName}`);
56-
const leakedArray: Array<string> = [...leakedResources];
57-
if (leakedArray.length > 0) {
58-
console.error(`Leaked ${leakedArray.join()}`);
59-
leakedArray.map(r => JSON.parse(r) as {name: string, namespace: string, kind: string})
60-
.forEach(({name, namespace, kind}) => {
61-
try {
62-
execSync(`kubectl delete -n ${namespace} --cascade ${kind} ${name}`);
63-
} catch (error) {
64-
console.error(`Failed to delete ${kind} ${name}:\n${error}`);
65-
}
66-
});
67-
}
68-
});
69-
70-
provisionMethods.forEach((provisionMethod, methodName) => {
71-
describe(`Using ${methodName} method.`, () => {
72-
it('Navigates to VMs', async() => {
73-
await browser.get(`${appHost}/k8s/all-namespaces/virtualmachines`);
74-
await isLoaded();
75-
});
76-
77-
it('Opens VM wizard', async() => {
78-
await createItemButton.click().then(() => vmView.createWithWizardLink.click());
79-
});
80-
81-
it('Configures VM Basic Settings', async() => {
82-
await browser.wait(until.presenceOf(vmView.nameInput), 10000);
83-
await vmView.nameInput.sendKeys(vmName);
40+
async function fillBasicSettings(provisionMethod: (provisionSource: string) => void, provisionSourceName: string){
41+
await browser.wait(until.presenceOf(vmView.nameInput), 10000);
42+
await vmView.nameInput.sendKeys(vmName);
8443

85-
await vmView.namespaceButton.click();
86-
await vmView.namespaceMenu.element(by.linkText(testName)).click();
44+
await vmView.namespaceButton.click();
45+
await vmView.namespaceMenu.element(by.linkText(testName)).click();
8746

88-
await provisionMethod(methodName);
47+
await provisionMethod(provisionSourceName);
8948

90-
await vmView.operatingSystemButton.click();
91-
await vmView.operatingSystemMenu.element(by.linkText(operatingSystem)).click();
49+
await vmView.operatingSystemButton.click();
50+
await vmView.operatingSystemMenu.element(by.linkText(operatingSystem)).click();
9251

93-
await vmView.flavorButton.click();
94-
await vmView.flavorSourceMenu.element(by.linkText(flavor)).click();
52+
await vmView.flavorButton.click();
53+
await vmView.flavorSourceMenu.element(by.linkText(flavor)).click();
9554

96-
await vmView.workloadProfileButton.click();
97-
await vmView.workloadProfileMenu.element(by.linkText(workloadProfile)).click();
55+
await vmView.workloadProfileButton.click();
56+
await vmView.workloadProfileMenu.element(by.linkText(workloadProfile)).click();
9857

99-
await vmView.startVMOnCreation.click();
58+
await vmView.startVMOnCreation.click();
10059

101-
await vmView.nextButton.click();
102-
});
60+
await vmView.nextButton.click();
61+
}
10362

104-
it('Configures VM Networking', async() => {
105-
if (methodName === 'PXE'){
106-
await vmView.createNIC.click();
63+
async function fillVMNetworking(provisionSourceName: string){
64+
if (provisionSourceName === 'PXE'){
65+
await vmView.createNIC.click();
10766

108-
await vmView.networkDefinitionButton.click();
109-
await vmView.networkDefinitionMenu.element(by.linkText(networkDefinitionName)).click();
67+
await vmView.networkDefinitionButton.click();
68+
await vmView.networkDefinitionMenu.element(by.linkText(testNAD.metadata.name)).click();
11069

111-
await vmView.pxeNICButton.click();
112-
await vmView.pxeNICMenu.element(by.linkText(pxeInterface)).click();
113-
await vmView.applyButton.click();
114-
}
115-
await vmView.nextButton.click();
116-
});
117-
118-
it('Configures VM Storage', async() => {
119-
await vmView.nextButton.click();
120-
});
121-
122-
it('Confirms to create VM', async() => {
123-
await browser.wait(until.elementToBeClickable(vmView.nextButton), 5000).then(() => vmView.nextButton.click());
70+
await vmView.pxeNICButton.click();
71+
await vmView.pxeNICMenu.element(by.linkText(pxeInterface)).click();
72+
await vmView.applyButton.click();
73+
}
74+
await vmView.nextButton.click();
75+
}
12476

125-
expect(errorMessage.isPresent()).toBe(false);
126-
leakedResources.add(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
127-
});
77+
beforeAll(async() => {
78+
console.log(testNAD);
79+
execSync(`echo '${JSON.stringify(testNAD)}' | kubectl create -f -`);
80+
});
12881

129-
it('Verifies created VM', async() => {
130-
await browser.wait(until.invisibilityOf(vmView.wizardHeader), 5000);
131-
await filterForName(vmName);
132-
await resourceRowsPresent();
133-
await browser.wait(until.textToBePresentInElement(vmView.firstRowVMStatus, 'Running'), 20000);
134-
});
82+
afterAll(async() => {
83+
execSync(`kubectl delete -n ${testName} net-attach-def ${testNAD.metadata.name}`);
84+
removeLeakedResources(leakedResources);
85+
});
13586

136-
it('Removes created VM', async() => {
137-
await deleteRow('VirtualMachine')(vmName);
138-
leakedResources.delete(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
139-
});
87+
provisionMethods.forEach((provisionMethod, methodName) => {
88+
it(`Using ${methodName} provision source.`, async() => {
89+
await browser.get(`${appHost}/k8s/all-namespaces/virtualmachines`);
90+
await isLoaded();
91+
await createItemButton.click().then(() => vmView.createWithWizardLink.click());
92+
await fillBasicSettings(provisionMethod, methodName);
93+
await fillVMNetworking(methodName);
94+
// Use default storage settings
95+
await vmView.nextButton.click();
96+
// Confirm to create VM
97+
await browser.wait(until.elementToBeClickable(vmView.nextButton), 5000).then(() => vmView.nextButton.click());
98+
expect(errorMessage.isPresent()).toBe(false);
99+
leakedResources.add(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
100+
// Verify VM is created and running
101+
await browser.wait(until.invisibilityOf(vmView.wizardHeader), 5000);
102+
await filterForName(vmName);
103+
await resourceRowsPresent();
104+
await browser.wait(until.textToBePresentInElement(vmView.firstRowVMStatus, 'Running'), 20000);
105+
// Delete VM
106+
await deleteRow('VirtualMachine')(vmName);
107+
leakedResources.delete(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
140108
});
141109
});
142110
});

frontend/integration-tests/views/crud.view.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export const saveChangesBtn = $('#save-changes');
1313
export const reloadBtn = $('#reload-object');
1414
export const cancelBtn = $('#cancel');
1515

16+
export const confirmAction = () => browser.wait(until.presenceOf($('#confirm-action'))).then(() => $('#confirm-action').click());
17+
1618
/**
1719
* Returns a promise that resolves after the loading spinner is not present.
1820
*/

0 commit comments

Comments
 (0)