New test coverage for the drain capabilities

This commit is contained in:
Michael Lange
2019-12-06 23:45:00 -08:00
parent b11c82caaf
commit 93eaaab77e
6 changed files with 372 additions and 15 deletions

View File

@@ -42,7 +42,7 @@ export default Watchable.extend({
return this.drain(
node,
Object.assign({}, drainSpec, {
Deadline: -1000 * 1000000,
Deadline: -1,
})
);
},

View File

@@ -110,6 +110,7 @@
<div class="toolbar-item is-right-aligned is-top-aligned">
{{#if model.isDraining}}
{{two-step-button
data-test-drain-stop
idleText="Stop Drain"
cancelText="Cancel"
confirmText="Yes, Stop"

View File

@@ -1,8 +1,8 @@
{{#popover-menu
data-test-drain-popover
label=(if client.isDraining "Update" "Drain")
label=(if client.isDraining "Update Drain" "Drain")
triggerClass=(concat "is-small " (if drain.isRunning "is-loading")) as |m|}}
<form onsubmit={{action (queue (action preventDefault) (perform drain m.actions.close))}} class="form is-small">
<form data-test-drain-popover-form onsubmit={{action (queue (action preventDefault) (perform drain m.actions.close))}} class="form is-small">
<h4 class="group-heading">Drain Options</h4>
<div class="field">
<label class="label is-interactive">
@@ -20,6 +20,8 @@
{{#if deadlineEnabled}}
<div class="field is-sub-field">
{{#power-select
data-test-drain-deadline-option-select
tagName="div"
options=durationQuickOptions
selected=selectedDurationQuickOption
onChange=(action (mut selectedDurationQuickOption)) as |opt|}}
@@ -30,6 +32,7 @@
<div class="field is-sub-field">
<label class="label">Deadline</label>
<input
data-test-drain-custom-deadline
type="text"
class="input {{if parseError "is-danger"}}"
placeholder="1h30m"
@@ -45,6 +48,7 @@
<div class="field">
<label class="label is-interactive">
{{#toggle
data-test-force-drain-toggle
isActive=forceDrain
onToggle=(action (mut forceDrain) value="target.checked")}}
Force Drain
@@ -57,6 +61,7 @@
<div class="field">
<label class="label is-interactive">
{{#toggle
data-test-system-jobs-toggle
isActive=drainSystemJobs
onToggle=(action (mut drainSystemJobs) value="target.checked")}}
Drain System Jobs
@@ -68,12 +73,13 @@
</div>
<div class="popover-actions">
<button
data-test-drain-submit
type="button"
class="popover-action is-primary"
onclick={{perform drain m.actions.close}}>
Drain
</button>
<button type="button" class="popover-action" onclick={{action m.actions.close}}>Cancel</button>
<button data-test-drain-cancel type="button" class="popover-action" onclick={{action m.actions.close}}>Cancel</button>
</div>
</form>
{{/popover-menu}}

View File

@@ -1,4 +1,4 @@
import { currentURL } from '@ember/test-helpers';
import { currentURL, waitUntil } from '@ember/test-helpers';
import { assign } from '@ember/polyfills';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
@@ -549,6 +549,348 @@ module('Acceptance | client detail', function(hooks) {
'Drain System Jobs state is shown'
);
});
test('toggling node eligibility disables the toggle and sends the correct POST request', async function(assert) {
node = server.create('node', {
drain: false,
schedulingEligibility: 'eligible',
});
server.pretender.post('/v1/node/:id/eligibility', () => [200, {}, ''], true);
await ClientDetail.visit({ id: node.id });
assert.ok(ClientDetail.eligibilityToggle.isActive);
ClientDetail.eligibilityToggle.toggle();
await waitUntil(() => server.pretender.handledRequests.findBy('method', 'POST'));
assert.ok(ClientDetail.eligibilityToggle.isDisabled);
server.pretender.resolve(server.pretender.requestReferences[0].request);
assert.notOk(ClientDetail.eligibilityToggle.isActive);
assert.notOk(ClientDetail.eligibilityToggle.isDisabled);
const request = server.pretender.handledRequests.findBy('method', 'POST');
assert.equal(request.url, `/v1/node/${node.id}/eligibility`);
assert.deepEqual(JSON.parse(request.requestBody), {
NodeID: node.id,
Eligibility: 'ineligible',
});
ClientDetail.eligibilityToggle.toggle();
await waitUntil(() => server.pretender.handledRequests.filterBy('method', 'POST').length === 2);
server.pretender.resolve(server.pretender.requestReferences[0].request);
assert.ok(ClientDetail.eligibilityToggle.isActive);
const request2 = server.pretender.handledRequests.filterBy('method', 'POST')[1];
assert.equal(request2.url, `/v1/node/${node.id}/eligibility`);
assert.deepEqual(JSON.parse(request2.requestBody), {
NodeID: node.id,
Eligibility: 'eligible',
});
});
test('starting a drain sends the correct POST request', async function(assert) {
let request;
node = server.create('node', {
drain: false,
schedulingEligibility: 'eligible',
});
await ClientDetail.visit({ id: node.id });
await ClientDetail.drainPopover.toggle();
await ClientDetail.drainPopover.submit();
request = server.pretender.handledRequests.filterBy('method', 'POST').pop();
assert.equal(request.url, `/v1/node/${node.id}/drain`);
assert.deepEqual(
JSON.parse(request.requestBody),
{
NodeID: node.id,
DrainSpec: {
Deadline: 0,
IgnoreSystemJobs: false,
},
},
'Drain with default settings'
);
await ClientDetail.drainPopover.toggle();
await ClientDetail.drainPopover.deadlineToggle.toggle();
await ClientDetail.drainPopover.submit();
request = server.pretender.handledRequests.filterBy('method', 'POST').pop();
assert.deepEqual(
JSON.parse(request.requestBody),
{
NodeID: node.id,
DrainSpec: {
Deadline: 60 * 60 * 1000 * 1000000,
IgnoreSystemJobs: false,
},
},
'Drain with deadline toggled'
);
await ClientDetail.drainPopover.toggle();
await ClientDetail.drainPopover.deadlineOptions.open();
await ClientDetail.drainPopover.deadlineOptions.options[1].choose();
await ClientDetail.drainPopover.submit();
request = server.pretender.handledRequests.filterBy('method', 'POST').pop();
assert.deepEqual(
JSON.parse(request.requestBody),
{
NodeID: node.id,
DrainSpec: {
Deadline: 4 * 60 * 60 * 1000 * 1000000,
IgnoreSystemJobs: false,
},
},
'Drain with non-default preset deadline set'
);
await ClientDetail.drainPopover.toggle();
await ClientDetail.drainPopover.deadlineOptions.open();
const optionsCount = ClientDetail.drainPopover.deadlineOptions.options.length;
await ClientDetail.drainPopover.deadlineOptions.options.objectAt(optionsCount - 1).choose();
await ClientDetail.drainPopover.setCustomDeadline('1h40m20s');
await ClientDetail.drainPopover.submit();
request = server.pretender.handledRequests.filterBy('method', 'POST').pop();
assert.deepEqual(
JSON.parse(request.requestBody),
{
NodeID: node.id,
DrainSpec: {
Deadline: ((1 * 60 + 40) * 60 + 20) * 1000 * 1000000,
IgnoreSystemJobs: false,
},
},
'Drain with custom deadline set'
);
await ClientDetail.drainPopover.toggle();
await ClientDetail.drainPopover.deadlineToggle.toggle();
await ClientDetail.drainPopover.forceDrainToggle.toggle();
await ClientDetail.drainPopover.submit();
request = server.pretender.handledRequests.filterBy('method', 'POST').pop();
assert.deepEqual(
JSON.parse(request.requestBody),
{
NodeID: node.id,
DrainSpec: {
Deadline: -1,
IgnoreSystemJobs: false,
},
},
'Drain with force set'
);
await ClientDetail.drainPopover.toggle();
await ClientDetail.drainPopover.systemJobsToggle.toggle();
await ClientDetail.drainPopover.submit();
request = server.pretender.handledRequests.filterBy('method', 'POST').pop();
assert.deepEqual(
JSON.parse(request.requestBody),
{
NodeID: node.id,
DrainSpec: {
Deadline: -1,
IgnoreSystemJobs: true,
},
},
'Drain system jobs unset'
);
});
test('the drain popover cancel button closes the popover', async function(assert) {
node = server.create('node', {
drain: false,
schedulingEligibility: 'eligible',
});
await ClientDetail.visit({ id: node.id });
assert.notOk(ClientDetail.drainPopover.isOpen);
await ClientDetail.drainPopover.toggle();
assert.ok(ClientDetail.drainPopover.isOpen);
await ClientDetail.drainPopover.cancel();
assert.notOk(ClientDetail.drainPopover.isOpen);
assert.equal(server.pretender.handledRequests.filterBy('method', 'POST'), 0);
});
test('toggling eligibility is disabled while a drain is active', async function(assert) {
node = server.create('node', {
drain: true,
schedulingEligibility: 'ineligible',
});
await ClientDetail.visit({ id: node.id });
assert.ok(ClientDetail.eligibilityToggle.isDisabled);
});
test('stopping a drain sends the correct POST request', async function(assert) {
node = server.create('node', {
drain: true,
schedulingEligibility: 'ineligible',
});
await ClientDetail.visit({ id: node.id });
assert.ok(ClientDetail.stopDrainIsPresent);
await ClientDetail.stopDrain.idle();
await ClientDetail.stopDrain.confirm();
const request = server.pretender.handledRequests.findBy('method', 'POST');
assert.equal(request.url, `/v1/node/${node.id}/drain`);
assert.deepEqual(JSON.parse(request.requestBody), {
NodeID: node.id,
DrainSpec: null,
});
});
test('when a drain is active, the "drain" popover is labeled as the "update" popover', async function(assert) {
node = server.create('node', {
drain: true,
schedulingEligibility: 'ineligible',
});
await ClientDetail.visit({ id: node.id });
assert.equal(ClientDetail.drainPopover.label, 'Update Drain');
});
test('forcing a drain sends the correct POST request', async function(assert) {
node = server.create('node', {
drain: true,
schedulingEligibility: 'ineligible',
drainStrategy: {
Deadline: 0,
IgnoreSystemJobs: true,
},
});
await ClientDetail.visit({ id: node.id });
await ClientDetail.drainDetails.force.idle();
await ClientDetail.drainDetails.force.confirm();
const request = server.pretender.handledRequests.findBy('method', 'POST');
assert.equal(request.url, `/v1/node/${node.id}/drain`);
assert.deepEqual(JSON.parse(request.requestBody), {
NodeID: node.id,
DrainSpec: {
Deadline: -1,
IgnoreSystemJobs: true,
},
});
});
test('when stopping a drain fails, an error is shown', async function(assert) {
node = server.create('node', {
drain: true,
schedulingEligibility: 'ineligible',
});
server.pretender.post('/v1/node/:id/drain', () => [500, {}, '']);
await ClientDetail.visit({ id: node.id });
await ClientDetail.stopDrain.idle();
await ClientDetail.stopDrain.confirm();
assert.ok(ClientDetail.stopDrainError.isPresent);
assert.ok(ClientDetail.stopDrainError.title.includes('Stop Drain Error'));
await ClientDetail.stopDrainError.dismiss();
assert.notOk(ClientDetail.stopDrainError.isPresent);
});
test('when starting a drain fails, an error message is shown', async function(assert) {
node = server.create('node', {
drain: false,
schedulingEligibility: 'eligible',
});
server.pretender.post('/v1/node/:id/drain', () => [500, {}, '']);
await ClientDetail.visit({ id: node.id });
await ClientDetail.drainPopover.toggle();
await ClientDetail.drainPopover.submit();
assert.ok(ClientDetail.drainError.isPresent);
assert.ok(ClientDetail.drainError.title.includes('Drain Error'));
await ClientDetail.drainError.dismiss();
assert.notOk(ClientDetail.drainError.isPresent);
});
test('when updating a drain fails, an error message is shown', async function(assert) {
node = server.create('node', {
drain: true,
schedulingEligibility: 'ineligible',
});
server.pretender.post('/v1/node/:id/drain', () => [500, {}, '']);
await ClientDetail.visit({ id: node.id });
await ClientDetail.drainPopover.toggle();
await ClientDetail.drainPopover.submit();
assert.ok(ClientDetail.drainError.isPresent);
assert.ok(ClientDetail.drainError.title.includes('Drain Error'));
await ClientDetail.drainError.dismiss();
assert.notOk(ClientDetail.drainError.isPresent);
});
test('when toggling eligibility fails, an error message is shown', async function(assert) {
node = server.create('node', {
drain: false,
schedulingEligibility: 'eligible',
});
server.pretender.post('/v1/node/:id/eligibility', () => [500, {}, '']);
await ClientDetail.visit({ id: node.id });
await ClientDetail.eligibilityToggle.toggle();
assert.ok(ClientDetail.eligibilityError.isPresent);
assert.ok(ClientDetail.eligibilityError.title.includes('Eligibility Error'));
await ClientDetail.eligibilityError.dismiss();
assert.notOk(ClientDetail.eligibilityError.isPresent);
});
test('when navigating away from a client that has an error message to another client, the error is not shown', async function(assert) {
node = server.create('node', {
drain: false,
schedulingEligibility: 'eligible',
});
const node2 = server.create('node');
server.pretender.post('/v1/node/:id/eligibility', () => [500, {}, '']);
await ClientDetail.visit({ id: node.id });
await ClientDetail.eligibilityToggle.toggle();
assert.ok(ClientDetail.eligibilityError.isPresent);
assert.ok(ClientDetail.eligibilityError.title.includes('Eligibility Error'));
await ClientDetail.visit({ id: node2.id });
assert.notOk(ClientDetail.eligibilityError.isPresent);
});
});
module('Acceptance | client detail (multi-namespace)', function(hooks) {

View File

@@ -12,6 +12,7 @@ import {
import allocations from 'nomad-ui/tests/pages/components/allocations';
import twoStepButton from 'nomad-ui/tests/pages/components/two-step-button';
import notification from 'nomad-ui/tests/pages/components/notification';
import toggle from 'nomad-ui/tests/pages/components/toggle';
export default create({
visit: visitable('/clients/:id'),
@@ -115,19 +116,26 @@ export default create({
},
drainPopover: {
label: text('[data-test-drain-popover] [data-test-popover-trigger]'),
isOpen: isPresent('[data-test-drain-popover-form]'),
toggle: clickable('[data-test-drain-popover] [data-test-popover-trigger]'),
toggleDeadline: clickable('[data-test-drain-deadline-toggle]'),
deadlineToggle: toggle('[data-test-drain-deadline-toggle]'),
deadlineOptions: {
open: clickable('[data-test-drain-deadline-toggle]'),
options: collection('data-test-drain-deadline-options]', {
open: clickable('[data-test-drain-deadline-option-select] .ember-power-select-trigger'),
options: collection('.ember-power-select-option', {
label: text(),
choose: clickable(),
}),
},
setCustomDeadline: fillable('[data-test-drain-custom-deadline]'),
toggleForceDrain: clickable(),
toggleSystemJobs: clickable(),
customDeadline: attribute('value', '[data-test-drain-custom-deadline]'),
forceDrainToggle: toggle('[data-test-force-drain-toggle]'),
systemJobsToggle: toggle('[data-test-system-jobs-toggle]'),
submit: clickable('[data-test-drain-submit]'),
cancel: clickable('[data-test-drain-cancel]'),
setDeadline(label) {
this.deadlineOptions.open();
@@ -138,10 +146,10 @@ export default create({
},
},
stopDrain: twoStepButton('[data-test-stop-drain]'),
stopDrainIsPresent: isPresent('[data-test-stop-drain]'),
stopDrain: twoStepButton('[data-test-drain-stop]'),
stopDrainIsPresent: isPresent('[data-test-drain-stop]'),
toggleEligibility: clickable('[data-test-eligibility-toggle]'),
eligibilityToggle: toggle('[data-test-eligibility-toggle]'),
eligibilityError: notification('[data-test-eligibility-error]'),
stopDrainError: notification('[data-test-stop-drain-error]'),

View File

@@ -189,7 +189,7 @@ module('Unit | Adapter | Node', function(hooks) {
{
NodeID: node.id,
DrainSpec: {
Deadline: -1000000000,
Deadline: -1,
IgnoreSystemJobs: true,
},
},
@@ -212,7 +212,7 @@ module('Unit | Adapter | Node', function(hooks) {
{
NodeID: node.id,
DrainSpec: {
Deadline: -1000000000,
Deadline: -1,
IgnoreSystemJobs: spec.IgnoreSystemJobs,
},
},