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

Commit 9c440a2

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

File tree

7 files changed

+292
-93
lines changed

7 files changed

+292
-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: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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 {detailViewAction, detailViewVMmStatus, listViewAction, listViewVMmStatus} 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+
afterAll(async() => {
18+
removeLeakedResources(leakedResources);
19+
});
20+
21+
describe('Test VM list view kebab actions', () => {
22+
const vmName = `vm-list-view-actions-${testName}`;
23+
beforeAll(async() => {
24+
testVM.metadata.name = vmName;
25+
execSync(`echo '${JSON.stringify(testVM)}' | kubectl create -f -`);
26+
leakedResources.add(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
27+
});
28+
29+
// Workaround for https://github.com/kubevirt/web-ui/issues/177, remove when resolved
30+
afterEach(async() => await browser.sleep(1000));
31+
32+
it('Navigates to VMs', async() => {
33+
await browser.get(`${appHost}/k8s/all-namespaces/virtualmachines`);
34+
await isLoaded();
35+
await filterForName(vmName);
36+
await resourceRowsPresent();
37+
});
38+
39+
it('Starts VM', async() => {
40+
await listViewAction(vmName)('Start');
41+
await browser.wait(until.textToBePresentInElement(listViewVMmStatus(vmName), 'Running'), VM_BOOTUP_TIMEOUT);
42+
});
43+
44+
it('Restarts VM', async() => {
45+
await listViewAction(vmName)('Restart');
46+
await browser.wait(until.textToBePresentInElement(listViewVMmStatus(vmName), 'Starting'), VM_BOOTUP_TIMEOUT);
47+
await browser.wait(until.textToBePresentInElement(listViewVMmStatus(vmName), 'Running'), VM_BOOTUP_TIMEOUT);
48+
}, VM_ACTIONS_TIMEOUT);
49+
50+
it('Stops VM', async() => {
51+
await listViewAction(vmName)('Stop');
52+
await browser.wait(until.textToBePresentInElement(listViewVMmStatus(vmName), 'Off'), 10000);
53+
});
54+
55+
it('Deletes VM', async() => {
56+
await listViewAction(vmName)('Delete');
57+
await browser.wait(until.not(until.presenceOf(listViewVMmStatus(vmName))));
58+
leakedResources.delete(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
59+
});
60+
});
61+
62+
describe('Test VM detail view kebab actions', () => {
63+
const vmName = `vm-detail-view-actions-${testName}`;
64+
beforeAll(async() => {
65+
testVM.metadata.name = vmName;
66+
execSync(`echo '${JSON.stringify(testVM)}' | kubectl create -f -`);
67+
leakedResources.add(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
68+
});
69+
70+
it('Navigates to VMs detail page', async() => {
71+
await browser.get(`${appHost}/k8s/all-namespaces/virtualmachines/${vmName}`);
72+
await isLoaded();
73+
});
74+
75+
it('Starts VM', async() => {
76+
await detailViewAction('Start');
77+
await browser.wait(until.textToBePresentInElement(detailViewVMmStatus, 'Running'), VM_BOOTUP_TIMEOUT);
78+
});
79+
80+
it('Restarts VM', async() => {
81+
await detailViewAction('Restart');
82+
await browser.wait(until.textToBePresentInElement(detailViewVMmStatus, 'Starting'), VM_BOOTUP_TIMEOUT);
83+
await browser.wait(until.textToBePresentInElement(detailViewVMmStatus, 'Running'), VM_BOOTUP_TIMEOUT);
84+
}, VM_ACTIONS_TIMEOUT);
85+
86+
it('Stops VM', async() => {
87+
await detailViewAction('Stop');
88+
await browser.wait(until.textToBePresentInElement(detailViewVMmStatus, 'Off'), 10000);
89+
});
90+
91+
it('Deletes VM', async() => {
92+
await detailViewAction('Delete');
93+
leakedResources.delete(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
94+
});
95+
});
96+
});

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

Lines changed: 59 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,73 @@ 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+
execSync(`echo '${JSON.stringify(testNAD)}' | kubectl create -f -`);
79+
});
12880

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-
});
81+
afterAll(async() => {
82+
execSync(`kubectl delete -n ${testName} net-attach-def ${testNAD.metadata.name}`);
83+
removeLeakedResources(leakedResources);
84+
});
13585

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

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)