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

Added tests for vm list/detail vm view actions (start, stop, restart, delete) #163

Merged
merged 1 commit into from
Jan 17, 2019
Merged
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
2 changes: 1 addition & 1 deletion frontend/integration-tests/protractor.conf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export const config: Config = {
catalog: ['tests/base.scenario.ts', 'tests/catalog.scenario.ts'],
marketplace: ['tests/base.scenario.ts', 'tests/marketplace/kubernetes-marketplace.scenario.ts'],
overview: ['tests/base.scenario.ts', 'tests/overview/overview.scenario.ts'],
kubevirt: ['tests/base.scenario.ts', 'tests/kubevirt/vm.wizard.scenario.ts'],
kubevirt: ['tests/base.scenario.ts', 'tests/kubevirt/vm.wizard.scenario.ts', 'tests/kubevirt/vm.actions.scenario.ts'],
all: ['tests/base.scenario.ts',
'tests/crud.scenario.ts',
'tests/overview/overview.scenareio.ts',
Expand Down
76 changes: 76 additions & 0 deletions frontend/integration-tests/tests/kubevirt/mocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* eslint-disable no-undef */
import { testName } from '../../protractor.conf';

const testLabel = 'automatedTestName';

export const testVM = {
apiVersion: 'kubevirt.io/v1alpha2',
kind: 'VirtualMachine',
metadata: {
name: `vm-${testName}`,
namespace: testName,
labels: {[testLabel]: testName},
},
spec: {
running: false,
template: {
spec: {
domain: {
cpu: {
cores: 1,
},
devices: {
disks: [
{
bootOrder: 1,
disk: {
bus: 'virtio',
},
name: 'rootdisk',
volumeName: 'rootdisk',
},
],
interfaces: [
{
bridge: {},
name: 'eth0',
},
],
},
resources: {
requests: {
memory: '1G',
},
},
},
networks: [
{
name: 'eth0',
pod: {},
},
],
volumes: [
{
containerDisk: {
image: 'kubevirt/cirros-registry-disk-demo:latest',
},
name: 'rootdisk',
},
],
},
},
},
};

export const testNAD = {
apiVersion: 'k8s.cni.cncf.io/v1',
kind: 'NetworkAttachmentDefinition',
metadata: {
name: `ovs-net-1${testName}-${testName}`,
namespace: testName,
labels: {[testLabel]: testName},
},
spec: {
config: '{ "cniVersion": "0.3.1", "type": "ovs", "bridge": "br0" }',
},
};
17 changes: 17 additions & 0 deletions frontend/integration-tests/tests/kubevirt/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* eslint-disable no-undef */
import { execSync } from 'child_process';

export function removeLeakedResources(leakedResources: Set<string>){
const leakedArray: Array<string> = [...leakedResources];
if (leakedArray.length > 0) {
console.error(`Leaked ${leakedArray.join()}`);
leakedArray.map(r => JSON.parse(r) as {name: string, namespace: string, kind: string})
.forEach(({name, namespace, kind}) => {
try {
execSync(`kubectl delete -n ${namespace} --cascade ${kind} ${name}`);
} catch (error) {
console.error(`Failed to delete ${kind} ${name}:\n${error}`);
}
});
}
}
96 changes: 96 additions & 0 deletions frontend/integration-tests/tests/kubevirt/vm.actions.scenario.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/* eslint-disable no-undef */

import { execSync } from 'child_process';
import { browser, ExpectedConditions as until } from 'protractor';

import { appHost, testName } from '../../protractor.conf';
import { resourceRowsPresent, filterForName, isLoaded } from '../../views/crud.view';
import { testVM } from './mocks';
import { removeLeakedResources } from './utils';
import {detailViewAction, detailViewVMmStatus, listViewAction, listViewVMmStatus} from '../../views/kubevirt/vm.actions.view';

const VM_BOOTUP_TIMEOUT = 60000;
const VM_ACTIONS_TIMEOUT = 90000;

describe('Test VM actions', () => {
const leakedResources = new Set<string>();
afterAll(async() => {
rhrazdil marked this conversation as resolved.
Show resolved Hide resolved
removeLeakedResources(leakedResources);
});

describe('Test VM list view kebab actions', () => {
const vmName = `vm-list-view-actions-${testName}`;
beforeAll(async() => {
testVM.metadata.name = vmName;
execSync(`echo '${JSON.stringify(testVM)}' | kubectl create -f -`);
leakedResources.add(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
});

// Workaround for https://github.com/kubevirt/web-ui/issues/177, remove when resolved
afterEach(async() => await browser.sleep(1000));

it('Navigates to VMs', async() => {
await browser.get(`${appHost}/k8s/all-namespaces/virtualmachines`);
await isLoaded();
await filterForName(vmName);
await resourceRowsPresent();
});

it('Starts VM', async() => {
await listViewAction(vmName)('Start');
await browser.wait(until.textToBePresentInElement(listViewVMmStatus(vmName), 'Running'), VM_BOOTUP_TIMEOUT);
});

it('Restarts VM', async() => {
await listViewAction(vmName)('Restart');
await browser.wait(until.textToBePresentInElement(listViewVMmStatus(vmName), 'Starting'), VM_BOOTUP_TIMEOUT);
await browser.wait(until.textToBePresentInElement(listViewVMmStatus(vmName), 'Running'), VM_BOOTUP_TIMEOUT);
}, VM_ACTIONS_TIMEOUT);

it('Stops VM', async() => {
await listViewAction(vmName)('Stop');
await browser.wait(until.textToBePresentInElement(listViewVMmStatus(vmName), 'Off'), 10000);
});

it('Deletes VM', async() => {
await listViewAction(vmName)('Delete');
await browser.wait(until.not(until.presenceOf(listViewVMmStatus(vmName))));
leakedResources.delete(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
});
});

describe('Test VM detail view kebab actions', () => {
const vmName = `vm-detail-view-actions-${testName}`;
beforeAll(async() => {
testVM.metadata.name = vmName;
execSync(`echo '${JSON.stringify(testVM)}' | kubectl create -f -`);
leakedResources.add(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
});

it('Navigates to VMs detail page', async() => {
await browser.get(`${appHost}/k8s/all-namespaces/virtualmachines/${vmName}`);
await isLoaded();
});

it('Starts VM', async() => {
await detailViewAction('Start');
await browser.wait(until.textToBePresentInElement(detailViewVMmStatus, 'Running'), VM_BOOTUP_TIMEOUT);
});

it('Restarts VM', async() => {
await detailViewAction('Restart');
await browser.wait(until.textToBePresentInElement(detailViewVMmStatus, 'Starting'), VM_BOOTUP_TIMEOUT);
await browser.wait(until.textToBePresentInElement(detailViewVMmStatus, 'Running'), VM_BOOTUP_TIMEOUT);
}, VM_ACTIONS_TIMEOUT);

it('Stops VM', async() => {
await detailViewAction('Stop');
await browser.wait(until.textToBePresentInElement(detailViewVMmStatus, 'Off'), 10000);
});

it('Deletes VM', async() => {
await detailViewAction('Delete');
leakedResources.delete(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
});
});
});
151 changes: 59 additions & 92 deletions frontend/integration-tests/tests/kubevirt/vm.wizard.scenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

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

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

describe('Kubevirt create VM using wizard', () => {
const leakedResources = new Set<string>();
Expand All @@ -16,19 +18,7 @@ describe('Kubevirt create VM using wizard', () => {
const workloadProfile = 'generic';
const sourceURL = 'https://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img';
const sourceContainer = 'kubevirt/cirros-registry-disk-demo:latest';
const networkDefinitionName = `${testName}-ovs-net-1`;
const pxeInterface = 'eth1';
const testNAD = {
apiVersion: 'k8s.cni.cncf.io/v1',
kind: 'NetworkAttachmentDefinition',
metadata: {
name: networkDefinitionName,
namespace: testName,
},
spec: {
config: '{ "cniVersion": "0.3.1", "type": "ovs", "bridge": "br0" }',
},
};
const provisionMethods = OrderedMap<string, (provisionSource: string) => void>()
.set('PXE', async function(provisionSource) {
await vmView.provisionSourceButton.click();
Expand All @@ -47,96 +37,73 @@ describe('Kubevirt create VM using wizard', () => {
await vmView.provisionSourceURL.sendKeys(sourceURL);
});

beforeAll(async() => {
execSync(`echo '${JSON.stringify(testNAD)}' | kubectl create -f -`);
});

afterAll(async() => {
execSync(`kubectl delete -n ${testName} net-attach-def ${networkDefinitionName}`);
const leakedArray: Array<string> = [...leakedResources];
if (leakedArray.length > 0) {
console.error(`Leaked ${leakedArray.join()}`);
leakedArray.map(r => JSON.parse(r) as {name: string, namespace: string, kind: string})
.forEach(({name, namespace, kind}) => {
try {
execSync(`kubectl delete -n ${namespace} --cascade ${kind} ${name}`);
} catch (error) {
console.error(`Failed to delete ${kind} ${name}:\n${error}`);
}
});
}
});

provisionMethods.forEach((provisionMethod, methodName) => {
describe(`Using ${methodName} method.`, () => {
it('Navigates to VMs', async() => {
await browser.get(`${appHost}/k8s/all-namespaces/virtualmachines`);
await isLoaded();
});

it('Opens VM wizard', async() => {
await createItemButton.click().then(() => vmView.createWithWizardLink.click());
});

it('Configures VM Basic Settings', async() => {
await browser.wait(until.presenceOf(vmView.nameInput), 10000);
await vmView.nameInput.sendKeys(vmName);
async function fillBasicSettings(provisionMethod: (provisionSource: string) => void, provisionSourceName: string){
await browser.wait(until.presenceOf(vmView.nameInput), 10000);
await vmView.nameInput.sendKeys(vmName);

await vmView.namespaceButton.click();
await vmView.namespaceMenu.element(by.linkText(testName)).click();
await vmView.namespaceButton.click();
await vmView.namespaceMenu.element(by.linkText(testName)).click();

await provisionMethod(methodName);
await provisionMethod(provisionSourceName);

await vmView.operatingSystemButton.click();
await vmView.operatingSystemMenu.element(by.linkText(operatingSystem)).click();
await vmView.operatingSystemButton.click();
await vmView.operatingSystemMenu.element(by.linkText(operatingSystem)).click();

await vmView.flavorButton.click();
await vmView.flavorSourceMenu.element(by.linkText(flavor)).click();
await vmView.flavorButton.click();
await vmView.flavorSourceMenu.element(by.linkText(flavor)).click();

await vmView.workloadProfileButton.click();
await vmView.workloadProfileMenu.element(by.linkText(workloadProfile)).click();
await vmView.workloadProfileButton.click();
await vmView.workloadProfileMenu.element(by.linkText(workloadProfile)).click();

await vmView.startVMOnCreation.click();
await vmView.startVMOnCreation.click();

await vmView.nextButton.click();
});
await vmView.nextButton.click();
}

it('Configures VM Networking', async() => {
if (methodName === 'PXE'){
await vmView.createNIC.click();
async function fillVMNetworking(provisionSourceName: string){
if (provisionSourceName === 'PXE'){
await vmView.createNIC.click();

await vmView.networkDefinitionButton.click();
await vmView.networkDefinitionMenu.element(by.linkText(networkDefinitionName)).click();
await vmView.networkDefinitionButton.click();
await vmView.networkDefinitionMenu.element(by.linkText(testNAD.metadata.name)).click();

await vmView.pxeNICButton.click();
await vmView.pxeNICMenu.element(by.linkText(pxeInterface)).click();
await vmView.applyButton.click();
}
await vmView.nextButton.click();
});

it('Configures VM Storage', async() => {
await vmView.nextButton.click();
});

it('Confirms to create VM', async() => {
await browser.wait(until.elementToBeClickable(vmView.nextButton), 5000).then(() => vmView.nextButton.click());
await vmView.pxeNICButton.click();
await vmView.pxeNICMenu.element(by.linkText(pxeInterface)).click();
await vmView.applyButton.click();
}
await vmView.nextButton.click();
}

expect(errorMessage.isPresent()).toBe(false);
leakedResources.add(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
});
beforeAll(async() => {
execSync(`echo '${JSON.stringify(testNAD)}' | kubectl create -f -`);
});

it('Verifies created VM', async() => {
await browser.wait(until.invisibilityOf(vmView.wizardHeader), 5000);
await filterForName(vmName);
await resourceRowsPresent();
await browser.wait(until.textToBePresentInElement(vmView.firstRowVMStatus, 'Running'), 20000);
});
afterAll(async() => {
execSync(`kubectl delete -n ${testName} net-attach-def ${testNAD.metadata.name}`);
removeLeakedResources(leakedResources);
});

it('Removes created VM', async() => {
await deleteRow('VirtualMachine')(vmName);
leakedResources.delete(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
});
provisionMethods.forEach((provisionMethod, methodName) => {
it(`Using ${methodName} provision source.`, async() => {
await browser.get(`${appHost}/k8s/all-namespaces/virtualmachines`);
await isLoaded();
await createItemButton.click().then(() => vmView.createWithWizardLink.click());
await fillBasicSettings(provisionMethod, methodName);
await fillVMNetworking(methodName);
// Use default storage settings
await vmView.nextButton.click();
// Confirm to create VM
await browser.wait(until.elementToBeClickable(vmView.nextButton), 5000).then(() => vmView.nextButton.click());
expect(errorMessage.isPresent()).toBe(false);
leakedResources.add(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
// Verify VM is created and running
await browser.wait(until.invisibilityOf(vmView.wizardHeader), 5000);
await filterForName(vmName);
await resourceRowsPresent();
await browser.wait(until.textToBePresentInElement(vmView.firstRowVMStatus, 'Running'), 20000);
// Delete VM
await deleteRow('VirtualMachine')(vmName);
leakedResources.delete(JSON.stringify({name: vmName, namespace: testName, kind: 'vm'}));
});
});
});
2 changes: 2 additions & 0 deletions frontend/integration-tests/views/crud.view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const saveChangesBtn = $('#save-changes');
export const reloadBtn = $('#reload-object');
export const cancelBtn = $('#cancel');

export const confirmAction = () => browser.wait(until.presenceOf($('#confirm-action'))).then(() => $('#confirm-action').click());

/**
* Returns a promise that resolves after the loading spinner is not present.
*/
Expand Down
Loading