2019-03-13 00:08:16 +00:00
|
|
|
import { currentURL } from '@ember/test-helpers';
|
2019-03-13 00:04:16 +00:00
|
|
|
import { module, test } from 'qunit';
|
|
|
|
import { setupApplicationTest } from 'ember-qunit';
|
2019-09-26 18:47:07 +00:00
|
|
|
import { setupMirage } from 'ember-cli-mirage/test-support';
|
2017-10-17 01:47:25 +00:00
|
|
|
import { formatBytes } from 'nomad-ui/helpers/format-bytes';
|
2018-07-11 17:36:33 +00:00
|
|
|
import TaskGroup from 'nomad-ui/tests/pages/jobs/job/task-group';
|
2020-04-01 00:13:44 +00:00
|
|
|
import pageSizeSelect from './behaviors/page-size-select';
|
2017-11-30 23:08:31 +00:00
|
|
|
import moment from 'moment';
|
2017-09-19 14:47:10 +00:00
|
|
|
|
|
|
|
let job;
|
|
|
|
let taskGroup;
|
|
|
|
let tasks;
|
|
|
|
let allocations;
|
|
|
|
|
|
|
|
const sum = (total, n) => total + n;
|
|
|
|
|
2019-03-13 00:04:16 +00:00
|
|
|
module('Acceptance | task group detail', function(hooks) {
|
|
|
|
setupApplicationTest(hooks);
|
2019-03-13 01:09:19 +00:00
|
|
|
setupMirage(hooks);
|
2019-03-13 00:04:16 +00:00
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
hooks.beforeEach(async function() {
|
2017-09-26 01:13:58 +00:00
|
|
|
server.create('agent');
|
2017-09-19 14:47:10 +00:00
|
|
|
server.create('node', 'forceIPv4');
|
|
|
|
|
|
|
|
job = server.create('job', {
|
|
|
|
groupsCount: 2,
|
|
|
|
createAllocations: false,
|
|
|
|
});
|
|
|
|
|
|
|
|
const taskGroups = server.db.taskGroups.where({ jobId: job.id });
|
|
|
|
taskGroup = taskGroups[0];
|
|
|
|
|
|
|
|
tasks = taskGroup.taskIds.map(id => server.db.tasks.find(id));
|
|
|
|
|
|
|
|
server.create('node', 'forceIPv4');
|
|
|
|
|
|
|
|
allocations = server.createList('allocation', 2, {
|
|
|
|
jobId: job.id,
|
|
|
|
taskGroup: taskGroup.name,
|
2018-12-10 23:25:48 +00:00
|
|
|
clientStatus: 'running',
|
2017-09-19 14:47:10 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// Allocations associated to a different task group on the job to
|
|
|
|
// assert that they aren't showing up in on this page in error.
|
|
|
|
server.createList('allocation', 3, {
|
|
|
|
jobId: job.id,
|
|
|
|
taskGroup: taskGroups[1].name,
|
2018-12-10 23:25:48 +00:00
|
|
|
clientStatus: 'running',
|
2017-09-19 14:47:10 +00:00
|
|
|
});
|
|
|
|
|
2017-10-02 19:44:07 +00:00
|
|
|
// Set a static name to make the search test deterministic
|
|
|
|
server.db.allocations.forEach(alloc => {
|
|
|
|
alloc.name = 'aaaaa';
|
|
|
|
});
|
|
|
|
|
2018-05-04 21:31:04 +00:00
|
|
|
// Mark the first alloc as rescheduled
|
|
|
|
allocations[0].update({
|
|
|
|
nextAllocation: allocations[1].id,
|
|
|
|
});
|
|
|
|
allocations[1].update({
|
|
|
|
previousAllocation: allocations[0].id,
|
|
|
|
});
|
|
|
|
|
2020-04-01 00:13:44 +00:00
|
|
|
window.localStorage.clear();
|
2019-03-13 00:04:16 +00:00
|
|
|
});
|
2017-09-19 14:47:10 +00:00
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
test('/jobs/:id/:task-group should list high-level metrics for the allocation', async function(assert) {
|
2019-03-13 00:04:16 +00:00
|
|
|
const totalCPU = tasks.mapBy('Resources.CPU').reduce(sum, 0);
|
|
|
|
const totalMemory = tasks.mapBy('Resources.MemoryMB').reduce(sum, 0);
|
|
|
|
const totalDisk = taskGroup.ephemeralDisk.SizeMB;
|
2017-09-19 14:47:10 +00:00
|
|
|
|
2020-04-01 00:13:44 +00:00
|
|
|
await TaskGroup.visit({ id: job.id, name: taskGroup.name });
|
|
|
|
|
2019-03-13 00:04:16 +00:00
|
|
|
assert.equal(TaskGroup.tasksCount, `# Tasks ${tasks.length}`, '# Tasks');
|
|
|
|
assert.equal(
|
|
|
|
TaskGroup.cpu,
|
|
|
|
`Reserved CPU ${totalCPU} MHz`,
|
|
|
|
'Aggregated CPU reservation for all tasks'
|
|
|
|
);
|
|
|
|
assert.equal(
|
|
|
|
TaskGroup.mem,
|
|
|
|
`Reserved Memory ${totalMemory} MiB`,
|
|
|
|
'Aggregated Memory reservation for all tasks'
|
|
|
|
);
|
|
|
|
assert.equal(
|
|
|
|
TaskGroup.disk,
|
|
|
|
`Reserved Disk ${totalDisk} MiB`,
|
|
|
|
'Aggregated Disk reservation for all tasks'
|
|
|
|
);
|
2019-07-17 20:02:58 +00:00
|
|
|
|
|
|
|
assert.equal(document.title, `Task group ${taskGroup.name} - Job ${job.name} - Nomad`);
|
2019-03-13 00:04:16 +00:00
|
|
|
});
|
2017-09-19 14:47:10 +00:00
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
test('/jobs/:id/:task-group should have breadcrumbs for job and jobs', async function(assert) {
|
2020-04-01 00:13:44 +00:00
|
|
|
await TaskGroup.visit({ id: job.id, name: taskGroup.name });
|
|
|
|
|
2019-03-13 00:04:16 +00:00
|
|
|
assert.equal(TaskGroup.breadcrumbFor('jobs.index').text, 'Jobs', 'First breadcrumb says jobs');
|
|
|
|
assert.equal(
|
|
|
|
TaskGroup.breadcrumbFor('jobs.job.index').text,
|
|
|
|
job.name,
|
|
|
|
'Second breadcrumb says the job name'
|
|
|
|
);
|
|
|
|
assert.equal(
|
|
|
|
TaskGroup.breadcrumbFor('jobs.job.task-group').text,
|
|
|
|
taskGroup.name,
|
|
|
|
'Third breadcrumb says the job name'
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
test('/jobs/:id/:task-group first breadcrumb should link to jobs', async function(assert) {
|
2020-04-01 00:13:44 +00:00
|
|
|
await TaskGroup.visit({ id: job.id, name: taskGroup.name });
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
await TaskGroup.breadcrumbFor('jobs.index').visit();
|
2017-09-19 14:47:10 +00:00
|
|
|
assert.equal(currentURL(), '/jobs', 'First breadcrumb links back to jobs');
|
|
|
|
});
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
test('/jobs/:id/:task-group second breadcrumb should link to the job for the task group', async function(assert) {
|
2020-04-01 00:13:44 +00:00
|
|
|
await TaskGroup.visit({ id: job.id, name: taskGroup.name });
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
await TaskGroup.breadcrumbFor('jobs.job.index').visit();
|
2017-09-19 14:47:10 +00:00
|
|
|
assert.equal(
|
|
|
|
currentURL(),
|
|
|
|
`/jobs/${job.id}`,
|
|
|
|
'Second breadcrumb links back to the job for the task group'
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
test('/jobs/:id/:task-group should list one page of allocations for the task group', async function(assert) {
|
2019-03-13 00:04:16 +00:00
|
|
|
server.createList('allocation', TaskGroup.pageSize, {
|
|
|
|
jobId: job.id,
|
|
|
|
taskGroup: taskGroup.name,
|
|
|
|
clientStatus: 'running',
|
|
|
|
});
|
2017-09-19 14:47:10 +00:00
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
await TaskGroup.visit({ id: job.id, name: taskGroup.name });
|
2017-09-19 14:47:10 +00:00
|
|
|
|
|
|
|
assert.ok(
|
2018-07-11 17:36:33 +00:00
|
|
|
server.db.allocations.where({ jobId: job.id }).length > TaskGroup.pageSize,
|
2017-09-19 14:47:10 +00:00
|
|
|
'There are enough allocations to invoke pagination'
|
|
|
|
);
|
|
|
|
|
|
|
|
assert.equal(
|
2018-07-11 17:36:33 +00:00
|
|
|
TaskGroup.allocations.length,
|
|
|
|
TaskGroup.pageSize,
|
2017-09-19 14:47:10 +00:00
|
|
|
'All allocations for the task group'
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
test('each allocation should show basic information about the allocation', async function(assert) {
|
2020-04-01 00:13:44 +00:00
|
|
|
await TaskGroup.visit({ id: job.id, name: taskGroup.name });
|
|
|
|
|
2019-03-13 00:04:16 +00:00
|
|
|
const allocation = allocations.sortBy('modifyIndex').reverse()[0];
|
|
|
|
const allocationRow = TaskGroup.allocations.objectAt(0);
|
2017-09-19 14:47:10 +00:00
|
|
|
|
2018-07-11 17:36:33 +00:00
|
|
|
assert.equal(allocationRow.shortId, allocation.id.split('-')[0], 'Allocation short id');
|
2018-07-19 20:30:08 +00:00
|
|
|
assert.equal(
|
|
|
|
allocationRow.createTime,
|
2019-02-01 17:19:14 +00:00
|
|
|
moment(allocation.createTime / 1000000).format('MMM DD HH:mm:ss ZZ'),
|
2018-07-19 20:30:08 +00:00
|
|
|
'Allocation create time'
|
|
|
|
);
|
2017-10-18 02:19:02 +00:00
|
|
|
assert.equal(
|
2018-07-11 17:36:33 +00:00
|
|
|
allocationRow.modifyTime,
|
2018-07-19 20:30:08 +00:00
|
|
|
moment(allocation.modifyTime / 1000000).fromNow(),
|
2017-11-30 23:08:31 +00:00
|
|
|
'Allocation modify time'
|
2017-10-18 02:19:02 +00:00
|
|
|
);
|
2018-07-11 17:36:33 +00:00
|
|
|
assert.equal(allocationRow.status, allocation.clientStatus, 'Client status');
|
|
|
|
assert.equal(allocationRow.jobVersion, allocation.jobVersion, 'Job Version');
|
2017-10-18 02:19:02 +00:00
|
|
|
assert.equal(
|
2018-07-11 17:36:33 +00:00
|
|
|
allocationRow.client,
|
2017-10-18 02:19:02 +00:00
|
|
|
server.db.nodes.find(allocation.nodeId).id.split('-')[0],
|
|
|
|
'Node ID'
|
|
|
|
);
|
2020-02-12 19:49:28 +00:00
|
|
|
assert.equal(
|
|
|
|
allocationRow.volume,
|
|
|
|
Object.keys(taskGroup.volumes).length ? 'Yes' : '',
|
|
|
|
'Volumes'
|
|
|
|
);
|
2017-09-26 01:13:58 +00:00
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
await allocationRow.visitClient();
|
2017-09-26 01:13:58 +00:00
|
|
|
|
2017-10-28 01:23:41 +00:00
|
|
|
assert.equal(currentURL(), `/clients/${allocation.nodeId}`, 'Node links to node page');
|
2017-09-26 01:13:58 +00:00
|
|
|
});
|
2017-09-19 14:47:10 +00:00
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
test('each allocation should show stats about the allocation', async function(assert) {
|
2020-04-01 00:13:44 +00:00
|
|
|
await TaskGroup.visit({ id: job.id, name: taskGroup.name });
|
|
|
|
|
2019-03-13 00:04:16 +00:00
|
|
|
const allocation = allocations.sortBy('name')[0];
|
|
|
|
const allocationRow = TaskGroup.allocations.objectAt(0);
|
|
|
|
|
|
|
|
const allocStats = server.db.clientAllocationStats.find(allocation.id);
|
|
|
|
const tasks = taskGroup.taskIds.map(id => server.db.tasks.find(id));
|
|
|
|
|
|
|
|
const cpuUsed = tasks.reduce((sum, task) => sum + task.Resources.CPU, 0);
|
|
|
|
const memoryUsed = tasks.reduce((sum, task) => sum + task.Resources.MemoryMB, 0);
|
|
|
|
|
|
|
|
assert.equal(
|
|
|
|
allocationRow.cpu,
|
|
|
|
Math.floor(allocStats.resourceUsage.CpuStats.TotalTicks) / cpuUsed,
|
|
|
|
'CPU %'
|
|
|
|
);
|
|
|
|
|
|
|
|
assert.equal(
|
|
|
|
allocationRow.cpuTooltip,
|
|
|
|
`${Math.floor(allocStats.resourceUsage.CpuStats.TotalTicks)} / ${cpuUsed} MHz`,
|
|
|
|
'Detailed CPU information is in a tooltip'
|
|
|
|
);
|
|
|
|
|
|
|
|
assert.equal(
|
|
|
|
allocationRow.mem,
|
|
|
|
allocStats.resourceUsage.MemoryStats.RSS / 1024 / 1024 / memoryUsed,
|
|
|
|
'Memory used'
|
|
|
|
);
|
|
|
|
|
|
|
|
assert.equal(
|
|
|
|
allocationRow.memTooltip,
|
|
|
|
`${formatBytes([allocStats.resourceUsage.MemoryStats.RSS])} / ${memoryUsed} MiB`,
|
|
|
|
'Detailed memory information is in a tooltip'
|
|
|
|
);
|
|
|
|
});
|
2017-10-02 19:44:07 +00:00
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
test('when the allocation search has no matches, there is an empty message', async function(assert) {
|
2020-04-01 00:13:44 +00:00
|
|
|
await TaskGroup.visit({ id: job.id, name: taskGroup.name });
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
await TaskGroup.search('zzzzzz');
|
2017-10-02 19:44:07 +00:00
|
|
|
|
2018-07-11 17:36:33 +00:00
|
|
|
assert.ok(TaskGroup.isEmpty, 'Empty state is shown');
|
|
|
|
assert.equal(
|
|
|
|
TaskGroup.emptyState.headline,
|
|
|
|
'No Matches',
|
|
|
|
'Empty state has an appropriate message'
|
|
|
|
);
|
2017-10-02 19:44:07 +00:00
|
|
|
});
|
2018-05-04 21:31:04 +00:00
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
test('when the allocation has reschedule events, the allocation row is denoted with an icon', async function(assert) {
|
2020-04-01 00:13:44 +00:00
|
|
|
await TaskGroup.visit({ id: job.id, name: taskGroup.name });
|
|
|
|
|
2019-03-13 00:04:16 +00:00
|
|
|
const rescheduleRow = TaskGroup.allocationFor(allocations[0].id);
|
|
|
|
const normalRow = TaskGroup.allocationFor(allocations[1].id);
|
2018-05-04 21:31:04 +00:00
|
|
|
|
2019-03-13 00:04:16 +00:00
|
|
|
assert.ok(rescheduleRow.rescheduled, 'Reschedule row has a reschedule icon');
|
|
|
|
assert.notOk(normalRow.rescheduled, 'Normal row has no reschedule icon');
|
|
|
|
});
|
2018-11-03 01:24:33 +00:00
|
|
|
|
2020-02-14 00:44:41 +00:00
|
|
|
test('when the task group depends on volumes, the volumes table is shown', async function(assert) {
|
2020-04-01 00:13:44 +00:00
|
|
|
await TaskGroup.visit({ id: job.id, name: taskGroup.name });
|
|
|
|
|
2020-02-14 00:44:41 +00:00
|
|
|
assert.ok(TaskGroup.hasVolumes);
|
|
|
|
assert.equal(TaskGroup.volumes.length, Object.keys(taskGroup.volumes).length);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('when the task group does not depend on volumes, the volumes table is not shown', async function(assert) {
|
|
|
|
job = server.create('job', { noHostVolumes: true, shallow: true });
|
|
|
|
taskGroup = server.db.taskGroups.where({ jobId: job.id })[0];
|
|
|
|
|
|
|
|
await TaskGroup.visit({ id: job.id, name: taskGroup.name });
|
|
|
|
|
|
|
|
assert.notOk(TaskGroup.hasVolumes);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('each row in the volumes table lists information about the volume', async function(assert) {
|
2020-04-01 00:13:44 +00:00
|
|
|
await TaskGroup.visit({ id: job.id, name: taskGroup.name });
|
|
|
|
|
2020-02-14 00:44:41 +00:00
|
|
|
TaskGroup.volumes[0].as(volumeRow => {
|
|
|
|
const volume = taskGroup.volumes[volumeRow.name];
|
|
|
|
assert.equal(volumeRow.name, volume.Name);
|
|
|
|
assert.equal(volumeRow.type, volume.Type);
|
|
|
|
assert.equal(volumeRow.source, volume.Source);
|
|
|
|
assert.equal(volumeRow.permissions, volume.ReadOnly ? 'Read' : 'Read/Write');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
test('when the job for the task group is not found, an error message is shown, but the URL persists', async function(assert) {
|
|
|
|
await TaskGroup.visit({ id: 'not-a-real-job', name: 'not-a-real-task-group' });
|
2018-11-03 01:24:33 +00:00
|
|
|
|
|
|
|
assert.equal(
|
2020-01-20 20:57:01 +00:00
|
|
|
server.pretender.handledRequests
|
|
|
|
.filter(request => !request.url.includes('policy'))
|
|
|
|
.findBy('status', 404).url,
|
2018-11-03 01:24:33 +00:00
|
|
|
'/v1/job/not-a-real-job',
|
|
|
|
'A request to the nonexistent job is made'
|
|
|
|
);
|
|
|
|
assert.equal(currentURL(), '/jobs/not-a-real-job/not-a-real-task-group', 'The URL persists');
|
|
|
|
assert.ok(TaskGroup.error.isPresent, 'Error message is shown');
|
|
|
|
assert.equal(TaskGroup.error.title, 'Not Found', 'Error message is for 404');
|
|
|
|
});
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
test('when the task group is not found on the job, an error message is shown, but the URL persists', async function(assert) {
|
|
|
|
await TaskGroup.visit({ id: job.id, name: 'not-a-real-task-group' });
|
2018-11-03 01:24:33 +00:00
|
|
|
|
|
|
|
assert.ok(
|
|
|
|
server.pretender.handledRequests
|
|
|
|
.filterBy('status', 200)
|
|
|
|
.mapBy('url')
|
|
|
|
.includes(`/v1/job/${job.id}`),
|
|
|
|
'A request to the job is made and succeeds'
|
|
|
|
);
|
|
|
|
assert.equal(currentURL(), `/jobs/${job.id}/not-a-real-task-group`, 'The URL persists');
|
|
|
|
assert.ok(TaskGroup.error.isPresent, 'Error message is shown');
|
|
|
|
assert.equal(TaskGroup.error.title, 'Not Found', 'Error message is for 404');
|
|
|
|
});
|
2020-04-01 00:13:44 +00:00
|
|
|
|
|
|
|
pageSizeSelect({
|
|
|
|
resourceName: 'allocation',
|
|
|
|
pageObject: TaskGroup,
|
|
|
|
pageObjectList: TaskGroup.allocations,
|
|
|
|
async setup() {
|
|
|
|
server.createList('allocation', TaskGroup.pageSize, {
|
|
|
|
jobId: job.id,
|
|
|
|
taskGroup: taskGroup.name,
|
|
|
|
clientStatus: 'running',
|
|
|
|
});
|
|
|
|
|
|
|
|
await TaskGroup.visit({ id: job.id, name: taskGroup.name });
|
|
|
|
},
|
|
|
|
});
|
2018-11-03 01:24:33 +00:00
|
|
|
});
|