open-nomad/ui/tests/acceptance/job-deployments-test.js

327 lines
9.8 KiB
JavaScript

import { get } from '@ember/object';
import $ from 'jquery';
import { click, findAll, find, visit } from 'ember-native-dom-helpers';
import { test } from 'qunit';
import moment from 'moment';
import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance';
const sum = (list, key) => list.reduce((sum, item) => sum + get(item, key), 0);
let job;
let deployments;
let sortedDeployments;
moduleForAcceptance('Acceptance | job deployments', {
beforeEach() {
server.create('node');
job = server.create('job');
deployments = server.schema.deployments.where({ jobId: job.id });
sortedDeployments = deployments.sort((a, b) => {
const aVersion = server.db.jobVersions.findBy({ jobId: a.jobId, version: a.versionNumber });
const bVersion = server.db.jobVersions.findBy({ jobId: b.jobId, version: b.versionNumber });
if (aVersion.submitTime < bVersion.submitTime) {
return 1;
} else if (aVersion.submitTime > bVersion.submitTime) {
return -1;
}
return 0;
});
},
});
test('/jobs/:id/deployments should list all job deployments', function(assert) {
visit(`/jobs/${job.id}/deployments`);
andThen(() => {
assert.ok(
findAll('[data-test-deployment]').length,
deployments.length,
'Each deployment gets a row in the timeline'
);
});
});
test('each deployment mentions the deployment shortId, status, version, and time since it was submitted', function(
assert
) {
visit(`/jobs/${job.id}/deployments`);
andThen(() => {
const deployment = sortedDeployments.models[0];
const version = server.db.jobVersions.findBy({
jobId: deployment.jobId,
version: deployment.versionNumber,
});
const deploymentRow = $(find('[data-test-deployment]'));
assert.ok(deploymentRow.text().includes(deployment.id.split('-')[0]), 'Short ID');
assert.equal(
deploymentRow.find('[data-test-deployment-status]').text(),
deployment.status,
'Status'
);
assert.ok(
deploymentRow
.find('[data-test-deployment-status]')
.hasClass(classForStatus(deployment.status)),
'Status Class'
);
assert.ok(
deploymentRow
.find('[data-test-deployment-version]')
.text()
.includes(deployment.versionNumber),
'Version #'
);
assert.ok(
deploymentRow
.find('[data-test-deployment-submit-time]')
.text()
.includes(moment(version.submitTime / 1000000).fromNow()),
'Submit time ago'
);
});
});
test('when the deployment is running and needs promotion, the deployment item says so', function(
assert
) {
// Ensure the deployment needs deployment
const deployment = sortedDeployments.models[0];
const taskGroupSummary = deployment.deploymentTaskGroupSummaryIds.map(id =>
server.schema.deploymentTaskGroupSummaries.find(id)
)[0];
deployment.update('status', 'running');
deployment.save();
taskGroupSummary.update({
desiredCanaries: 1,
placedCanaries: 0,
promoted: false,
});
taskGroupSummary.save();
visit(`/jobs/${job.id}/deployments`);
andThen(() => {
const deploymentRow = find('[data-test-deployment]');
assert.ok(
deploymentRow.querySelector('[data-test-promotion-required]'),
'Requires Promotion badge found'
);
});
});
test('each deployment item can be opened to show details', function(assert) {
let deploymentRow;
visit(`/jobs/${job.id}/deployments`);
andThen(() => {
deploymentRow = find('[data-test-deployment]');
assert.notOk(
deploymentRow.querySelector('[data-test-deployment-details]'),
'No deployment body'
);
click(deploymentRow.querySelector('[data-test-deployment-toggle-details]'));
andThen(() => {
assert.ok(
deploymentRow.querySelector('[data-test-deployment-details]'),
'Deployment body found'
);
});
});
});
test('when open, a deployment shows the deployment metrics', function(assert) {
visit(`/jobs/${job.id}/deployments`);
andThen(() => {
const deployment = sortedDeployments.models[0];
const deploymentRow = find('[data-test-deployment]');
const taskGroupSummaries = deployment.deploymentTaskGroupSummaryIds.map(id =>
server.db.deploymentTaskGroupSummaries.find(id)
);
click(deploymentRow.querySelector('[data-test-deployment-toggle-details]'));
andThen(() => {
assert.equal(
find('[data-test-deployment-metric="canaries"]').textContent.trim(),
`${sum(taskGroupSummaries, 'placedCanaries')} / ${sum(
taskGroupSummaries,
'desiredCanaries'
)}`,
'Canaries, both places and desired, are in the metrics'
);
assert.equal(
find('[data-test-deployment-metric="placed"]').textContent.trim(),
sum(taskGroupSummaries, 'placedAllocs'),
'Placed allocs aggregates across task groups'
);
assert.equal(
find('[data-test-deployment-metric="desired"]').textContent.trim(),
sum(taskGroupSummaries, 'desiredTotal'),
'Desired allocs aggregates across task groups'
);
assert.equal(
find('[data-test-deployment-metric="healthy"]').textContent.trim(),
sum(taskGroupSummaries, 'healthyAllocs'),
'Healthy allocs aggregates across task groups'
);
assert.equal(
find('[data-test-deployment-metric="unhealthy"]').textContent.trim(),
sum(taskGroupSummaries, 'unhealthyAllocs'),
'Unhealthy allocs aggregates across task groups'
);
assert.equal(
find('[data-test-deployment-notification]').textContent.trim(),
deployment.statusDescription,
'Status description is in the metrics block'
);
});
});
});
test('when open, a deployment shows a list of all task groups and their respective stats', function(
assert
) {
visit(`/jobs/${job.id}/deployments`);
andThen(() => {
const deployment = sortedDeployments.models[0];
const deploymentRow = find('[data-test-deployment]');
const taskGroupSummaries = deployment.deploymentTaskGroupSummaryIds.map(id =>
server.db.deploymentTaskGroupSummaries.find(id)
);
click(deploymentRow.querySelector('[data-test-deployment-toggle-details]'));
andThen(() => {
const taskGroupTable = deploymentRow.querySelector('[data-test-deployment-task-groups]');
assert.ok(taskGroupTable, 'Task groups found');
assert.equal(
taskGroupTable.querySelectorAll('[data-test-deployment-task-group]').length,
taskGroupSummaries.length,
'One row per task group'
);
const taskGroup = taskGroupSummaries[0];
const taskGroupRow = taskGroupTable.querySelector('[data-test-deployment-task-group]');
assert.equal(
taskGroupRow.querySelector('[data-test-deployment-task-group-name]').textContent.trim(),
taskGroup.name,
'Name'
);
assert.equal(
taskGroupRow
.querySelector('[data-test-deployment-task-group-promotion]')
.textContent.trim(),
promotionTestForTaskGroup(taskGroup),
'Needs Promotion'
);
assert.equal(
taskGroupRow
.querySelector('[data-test-deployment-task-group-auto-revert]')
.textContent.trim(),
taskGroup.autoRevert ? 'Yes' : 'No',
'Auto Revert'
);
assert.equal(
taskGroupRow.querySelector('[data-test-deployment-task-group-canaries]').textContent.trim(),
`${taskGroup.placedCanaries} / ${taskGroup.desiredCanaries}`,
'Canaries'
);
assert.equal(
taskGroupRow.querySelector('[data-test-deployment-task-group-allocs]').textContent.trim(),
`${taskGroup.placedAllocs} / ${taskGroup.desiredTotal}`,
'Allocs'
);
assert.equal(
taskGroupRow.querySelector('[data-test-deployment-task-group-healthy]').textContent.trim(),
taskGroup.healthyAllocs,
'Healthy Allocs'
);
assert.equal(
taskGroupRow
.querySelector('[data-test-deployment-task-group-unhealthy]')
.textContent.trim(),
taskGroup.unhealthyAllocs,
'Unhealthy Allocs'
);
});
});
});
test('when open, a deployment shows a list of all allocations for the deployment', function(
assert
) {
visit(`/jobs/${job.id}/deployments`);
andThen(() => {
const deployment = sortedDeployments.models[0];
const deploymentRow = find('[data-test-deployment]');
// TODO: Make this less brittle. This logic is copied from the mirage config,
// since there is no reference to allocations on the deployment model.
const allocations = server.db.allocations.where({ jobId: deployment.jobId }).slice(0, 3);
click(deploymentRow.querySelector('[data-test-deployment-toggle-details]'));
andThen(() => {
assert.ok(
deploymentRow.querySelector('[data-test-deployment-allocations]'),
'Allocations found'
);
assert.equal(
deploymentRow.querySelectorAll('[data-test-deployment-allocation]').length,
allocations.length,
'One row per allocation'
);
const allocation = allocations[0];
const allocationRow = deploymentRow.querySelector('[data-test-deployment-allocation]');
assert.equal(
allocationRow.querySelector('[data-test-short-id]').textContent.trim(),
allocation.id.split('-')[0],
'Allocation is as expected'
);
});
});
});
function classForStatus(status) {
const classMap = {
running: 'is-running',
successful: 'is-primary',
paused: 'is-light',
failed: 'is-error',
cancelled: 'is-cancelled',
};
return classMap[status] || 'is-dark';
}
function promotionTestForTaskGroup(taskGroup) {
if (taskGroup.desiredCanaries > 0 && taskGroup.promoted === false) {
return 'Yes';
} else if (taskGroup.desiredCanaries > 0) {
return 'No';
}
return 'N/A';
}