Test coverage for scheduler dry-run addition to the plan page

This commit is contained in:
Michael Lange
2018-08-20 15:04:33 -07:00
parent 223dae27a0
commit 2d0805c453
6 changed files with 113 additions and 29 deletions

View File

@@ -210,7 +210,7 @@ export default Model.extend({
try {
// If the definition is already JSON then it doesn't need to be parsed.
const json = JSON.parse(definition);
this.set('_newDefinitionJSON', definition);
this.set('_newDefinitionJSON', json);
this.setIDByPayload(json);
promise = RSVP.resolve(definition);
} catch (err) {

View File

@@ -74,9 +74,9 @@
{{job-diff data-test-plan-output diff=planOutput.diff verbose=false}}
</div>
</div>
<div class="boxed-section {{if planOutput.failedTGAllocs "is-warning" "is-primary"}}">
<div class="boxed-section-head">Scheduler dry-run</div>
<div class="boxed-section-body">
<div class="boxed-section {{if planOutput.failedTGAllocs "is-warning" "is-primary"}}" data-test-dry-run-message>
<div class="boxed-section-head" data-test-dry-run-title>Scheduler dry-run</div>
<div class="boxed-section-body" data-test-dry-run-body>
{{#if planOutput.failedTGAllocs}}
{{#each planOutput.failedTGAllocs as |placementFailure|}}
{{placement-failure failedTGAlloc=placementFailure}}

View File

@@ -3,6 +3,7 @@ import Response from 'ember-cli-mirage/response';
import { HOSTS } from './common';
import { logFrames, logEncode } from './data/logs';
import { generateDiff } from './factories/job-version';
import { generateTaskGroupFailures } from './factories/evaluation';
const { copy } = Ember;
@@ -56,7 +57,7 @@ export default function() {
})
);
this.post('/jobs', function({ jobs }, req) {
this.post('/jobs', function(schema, req) {
const body = JSON.parse(req.requestBody);
if (!body.Job) return new Response(400, {}, 'Job is a required field on the request payload');
@@ -64,7 +65,7 @@ export default function() {
return okEmpty();
});
this.post('/jobs/parse', function({ jobs }, req) {
this.post('/jobs/parse', function(schema, req) {
const body = JSON.parse(req.requestBody);
if (!body.JobHCL)
@@ -84,13 +85,19 @@ export default function() {
return new Response(200, {}, this.serialize(job));
});
this.post('/job/:id/plan', function({ jobs }, req) {
this.post('/job/:id/plan', function(schema, req) {
const body = JSON.parse(req.requestBody);
if (!body.Job) return new Response(400, {}, 'Job is a required field on the request payload');
if (!body.Diff) return new Response(400, {}, 'Expected Diff to be true');
return new Response(200, {}, JSON.stringify({ Diff: generateDiff(req.params.id) }));
const FailedTGAllocs = body.Job.Unschedulable && generateFailedTGAllocs(body.Job);
return new Response(
200,
{},
JSON.stringify({ FailedTGAllocs, Diff: generateDiff(req.params.id) })
);
});
this.get(
@@ -319,3 +326,16 @@ function filterKeys(object, ...keys) {
function okEmpty() {
return new Response(200, {}, '{}');
}
function generateFailedTGAllocs(job, taskGroups) {
const taskGroupsFromSpec = job.TaskGroups && job.TaskGroups.mapBy('Name');
let tgNames = ['tg-one', 'tg-two'];
if (taskGroupsFromSpec && taskGroupsFromSpec.length) tgNames = taskGroupsFromSpec;
if (taskGroups && taskGroups.length) tgNames = taskGroups;
return tgNames.reduce((hash, tgName) => {
hash[tgName] = generateTaskGroupFailures();
return hash;
}, {});
}

View File

@@ -72,19 +72,7 @@ export default Factory.extend({
}
const placementFailures = failedTaskGroupNames.reduce((hash, name) => {
hash[name] = {
CoalescedFailures: faker.random.number({ min: 1, max: 20 }),
NodesEvaluated: faker.random.number({ min: 1, max: 100 }),
NodesExhausted: faker.random.number({ min: 1, max: 100 }),
NodesAvailable: Math.random() > 0.7 ? generateNodesAvailable() : null,
ClassFiltered: Math.random() > 0.7 ? generateClassFiltered() : null,
ConstraintFiltered: Math.random() > 0.7 ? generateConstraintFiltered() : null,
ClassExhausted: Math.random() > 0.7 ? generateClassExhausted() : null,
DimensionExhausted: Math.random() > 0.7 ? generateDimensionExhausted() : null,
QuotaExhausted: Math.random() > 0.7 ? generateQuotaExhausted() : null,
Scores: Math.random() > 0.7 ? generateScores() : null,
};
hash[name] = generateTaskGroupFailures();
return hash;
}, {});
@@ -111,3 +99,19 @@ function assignJob(evaluation, server) {
job_id: job.id,
});
}
export function generateTaskGroupFailures() {
return {
CoalescedFailures: faker.random.number({ min: 1, max: 20 }),
NodesEvaluated: faker.random.number({ min: 1, max: 100 }),
NodesExhausted: faker.random.number({ min: 1, max: 100 }),
NodesAvailable: Math.random() > 0.7 ? generateNodesAvailable() : null,
ClassFiltered: Math.random() > 0.7 ? generateClassFiltered() : null,
ConstraintFiltered: Math.random() > 0.7 ? generateConstraintFiltered() : null,
ClassExhausted: Math.random() > 0.7 ? generateClassExhausted() : null,
DimensionExhausted: Math.random() > 0.7 ? generateDimensionExhausted() : null,
QuotaExhausted: Math.random() > 0.7 ? generateQuotaExhausted() : null,
Scores: Math.random() > 0.7 ? generateScores() : null,
};
}

View File

@@ -5,6 +5,7 @@ import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance';
import JobRun from 'nomad-ui/tests/pages/jobs/run';
const newJobName = 'new-job';
const newJobTaskGroupName = 'redis';
const jsonJob = overrides => {
return JSON.stringify(
@@ -15,15 +16,17 @@ const jsonJob = overrides => {
Namespace: 'default',
Datacenters: ['dc1'],
Priority: 50,
TaskGroups: {
redis: {
Tasks: {
redis: {
TaskGroups: [
{
Name: newJobTaskGroupName,
Tasks: [
{
Name: 'redis',
Driver: 'docker',
},
},
],
},
},
],
},
overrides
),
@@ -37,7 +40,7 @@ job "${newJobName}" {
namespace = "default"
datacenters = ["dc1"]
task "redis" {
task "${newJobTaskGroupName}" {
driver = "docker"
}
}
@@ -313,3 +316,52 @@ test('when submitting a job to a different namespace, the redirect to the job ov
);
});
});
test('when the scheduler dry-run has warnings, the warnings are shown to the user', function(assert) {
// Unschedulable is a hint to Mirage to respond with warnings from the plan endpoint
const spec = jsonJob({ Unschedulable: true });
JobRun.visit();
andThen(() => {
JobRun.editor.fillIn(spec);
JobRun.plan();
});
andThen(() => {
assert.ok(
JobRun.dryRunMessage.errored,
'The scheduler dry-run message is in the warning state'
);
assert.notOk(
JobRun.dryRunMessage.succeeded,
'The success message is not shown in addition to the warning message'
);
assert.ok(
JobRun.dryRunMessage.body.includes(newJobTaskGroupName),
'The scheduler dry-run message includes the warning from send back by the API'
);
});
});
test('when the scheduler dry-run has no warnings, a success message is shown to the user', function(assert) {
const spec = hclJob();
JobRun.visit();
andThen(() => {
JobRun.editor.fillIn(spec);
JobRun.plan();
});
andThen(() => {
assert.ok(
JobRun.dryRunMessage.succeeded,
'The scheduler dry-run message is in the success state'
);
assert.notOk(
JobRun.dryRunMessage.errored,
'The warning message is not shown in addition to the success message'
);
});
});

View File

@@ -1,4 +1,4 @@
import { clickable, create, isPresent, text, visitable } from 'ember-cli-page-object';
import { clickable, create, hasClass, isPresent, text, visitable } from 'ember-cli-page-object';
import { codeFillable, code } from 'nomad-ui/tests/pages/helpers/codemirror';
import error from 'nomad-ui/tests/pages/components/error';
@@ -35,4 +35,12 @@ export default create({
contents: code('[data-test-editor]'),
fillIn: codeFillable('[data-test-editor]'),
},
dryRunMessage: {
scope: '[data-test-dry-run-message]',
title: text('[data-test-dry-run-title]'),
body: text('[data-test-dry-run-body]'),
errored: hasClass('is-warning'),
succeeded: hasClass('is-primary'),
},
});