-
+
{{allocation.clientStatus}}
@@ -21,16 +24,16 @@
Allocation
{{#if linkToAllocation}}
{{#link-to "allocations.allocation" allocation.id}}
-
{{allocation.shortId}}
+
{{allocation.shortId}}
{{/link-to}}
{{else}}
-
{{allocation.shortId}}
+
{{allocation.shortId}}
{{/if}}
Client
- {{#link-to "clients.client" allocation.node.id}}
+ {{#link-to "clients.client" data-test-node-link allocation.node.id}}
{{allocation.node.id}}
{{/link-to}}
diff --git a/ui/app/templates/components/reschedule-event-timeline.hbs b/ui/app/templates/components/reschedule-event-timeline.hbs
index 8a75433c2..1f2f5385c 100644
--- a/ui/app/templates/components/reschedule-event-timeline.hbs
+++ b/ui/app/templates/components/reschedule-event-timeline.hbs
@@ -6,18 +6,17 @@
time=allocation.nextAllocation.modifyTime}}
{{/if}}
{{#if allocation.hasStoppedRescheduling}}
-
+
{{x-icon "warning" class="is-warning is-text"}} Nomad has stopped attempting to reschedule this allocation.
{{/if}}
{{#if (and allocation.followUpEvaluation.waitUntil (not allocation.nextAllocation))}}
-
+
{{x-icon "clock" class="is-info is-text"}} Nomad will attempt to reschedule
{{moment-from-now allocation.followUpEvaluation.waitUntil interval=1000}}
{{/if}}
{{reschedule-event-row
- label="This Allocation"
allocation=allocation
linkToAllocation=false
time=allocation.modifyTime}}
diff --git a/ui/mirage/factories/allocation.js b/ui/mirage/factories/allocation.js
index 11bdff4d7..6c1c20393 100644
--- a/ui/mirage/factories/allocation.js
+++ b/ui/mirage/factories/allocation.js
@@ -66,7 +66,7 @@ export default Factory.extend({
// After rescheduleAttempts hits zero, a final allocation is made with no nextAllocation and
// a clientStatus of failed or running, depending on rescheduleSuccess
afterCreate(allocation, server) {
- const attempts = allocation.rescheduleAttempts;
+ const attempts = allocation.rescheduleAttempts - 1;
const previousEvents =
(allocation.rescheduleTracker && allocation.rescheduleTracker.Events) || [];
@@ -91,9 +91,9 @@ export default Factory.extend({
};
let nextAllocation;
- if (attempts) {
+ if (attempts > 0) {
nextAllocation = server.create('allocation', 'rescheduled', {
- rescheduleAttempts: Math.max(attempts - 1, 0),
+ rescheduleAttempts: Math.max(attempts, 0),
rescheduleSuccess: allocation.rescheduleSuccess,
previousAllocation: allocation.id,
clientStatus: 'failed',
diff --git a/ui/tests/integration/reschedule-event-timeline-test.js b/ui/tests/integration/reschedule-event-timeline-test.js
new file mode 100644
index 000000000..4bed776a0
--- /dev/null
+++ b/ui/tests/integration/reschedule-event-timeline-test.js
@@ -0,0 +1,198 @@
+import { getOwner } from '@ember/application';
+import { test, moduleForComponent } from 'ember-qunit';
+import { find, findAll } from 'ember-native-dom-helpers';
+import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage';
+import wait from 'ember-test-helpers/wait';
+import hbs from 'htmlbars-inline-precompile';
+import moment from 'moment';
+
+moduleForComponent(
+ 'reschedule-event-timeline',
+ 'Integration | Component | reschedule event timeline',
+ {
+ integration: true,
+ beforeEach() {
+ this.store = getOwner(this).lookup('service:store');
+ this.server = startMirage();
+ this.server.create('namespace');
+ this.server.create('node');
+ this.server.create('job', { createAllocations: false });
+ },
+ afterEach() {
+ this.server.shutdown();
+ },
+ }
+);
+
+const commonTemplate = hbs`
+ {{reschedule-event-timeline allocation=allocation}}
+`;
+
+test('when the allocation is running, the timeline shows past allocations', function(assert) {
+ const attempts = 2;
+
+ this.server.create('allocation', 'rescheduled', {
+ rescheduleAttempts: attempts,
+ rescheduleSuccess: true,
+ });
+
+ this.store.findAll('allocation');
+ let allocation;
+
+ return wait()
+ .then(() => {
+ allocation = this.store
+ .peekAll('allocation')
+ .find(alloc => !alloc.get('nextAllocation.content'));
+
+ this.set('allocation', allocation);
+ this.render(commonTemplate);
+
+ return wait();
+ })
+ .then(() => {
+ assert.equal(
+ findAll('[data-test-allocation]').length,
+ attempts + 1,
+ 'Total allocations equals current allocation plus all past allocations'
+ );
+ assert.equal(
+ find('[data-test-allocation]'),
+ find(`[data-test-allocation="${allocation.id}"]`),
+ 'First allocation is the current allocation'
+ );
+
+ assert.notOk(find('[data-test-stop-warning]'), 'No stop warning');
+ assert.notOk(find('[data-test-attempt-notice]'), 'No attempt notice');
+
+ assert.equal(
+ find(
+ `[data-test-allocation="${allocation.id}"] [data-test-allocation-link]`
+ ).textContent.trim(),
+ allocation.get('shortId'),
+ 'The "this" allocation is correct'
+ );
+ assert.equal(
+ find(
+ `[data-test-allocation="${allocation.id}"] [data-test-allocation-status]`
+ ).textContent.trim(),
+ allocation.get('clientStatus'),
+ 'Allocation shows the status'
+ );
+ });
+});
+
+test('when the allocation has failed and there is a follow up evaluation, a note with a time is shown', function(assert) {
+ const attempts = 2;
+
+ this.server.create('allocation', 'rescheduled', {
+ rescheduleAttempts: attempts,
+ rescheduleSuccess: false,
+ });
+
+ this.store.findAll('allocation');
+ let allocation;
+
+ return wait()
+ .then(() => {
+ allocation = this.store
+ .peekAll('allocation')
+ .find(alloc => !alloc.get('nextAllocation.content'));
+
+ this.set('allocation', allocation);
+ this.render(commonTemplate);
+
+ return wait();
+ })
+ .then(() => {
+ assert.ok(
+ find('[data-test-stop-warning]'),
+ 'Stop warning is shown since the last allocation failed'
+ );
+ assert.notOk(find('[data-test-attempt-notice]'), 'Reschdule attempt notice is not shown');
+ });
+});
+
+test('when the allocation has failed and there is no follow up evaluation, a warning is shown', function(assert) {
+ const attempts = 2;
+
+ this.server.create('allocation', 'rescheduled', {
+ rescheduleAttempts: attempts,
+ rescheduleSuccess: false,
+ });
+
+ const lastAllocation = server.schema.allocations.findBy({ nextAllocation: undefined });
+ lastAllocation.update({
+ followupEvalId: server.create('evaluation', {
+ waitUntil: moment()
+ .add(2, 'hours')
+ .toDate(),
+ }).id,
+ });
+
+ this.store.findAll('allocation');
+ let allocation;
+
+ return wait()
+ .then(() => {
+ allocation = this.store
+ .peekAll('allocation')
+ .find(alloc => !alloc.get('nextAllocation.content'));
+
+ this.set('allocation', allocation);
+ this.render(commonTemplate);
+
+ return wait();
+ })
+ .then(() => {
+ assert.ok(
+ find('[data-test-attempt-notice]'),
+ 'Reschedule notice is shown since the follow up eval says so'
+ );
+ assert.notOk(find('[data-test-stop-warning]'), 'Stop warning is not shown');
+ });
+});
+
+test('when the allocation has a next allocation already, it is shown in the timeline', function(assert) {
+ const attempts = 2;
+
+ const originalAllocation = this.server.create('allocation', 'rescheduled', {
+ rescheduleAttempts: attempts,
+ rescheduleSuccess: true,
+ });
+
+ this.store.findAll('allocation');
+ let allocation;
+
+ return wait()
+ .then(() => {
+ allocation = this.store.peekAll('allocation').findBy('id', originalAllocation.id);
+
+ this.set('allocation', allocation);
+ this.render(commonTemplate);
+
+ return wait();
+ })
+ .then(() => {
+ assert.ok(
+ find('[data-test-reschedule-label]').textContent.trim(),
+ 'Next Allocation',
+ 'The first allocation is the next allocation and labeled as such'
+ );
+
+ assert.equal(
+ find('[data-test-allocation] [data-test-allocation-link]').textContent.trim(),
+ allocation.get('nextAllocation.shortId'),
+ 'The next allocation item is for the correct allocation'
+ );
+
+ assert.equal(
+ findAll('[data-test-allocation]')[1],
+ find(`[data-test-allocation="${allocation.id}"]`),
+ 'Second allocation is the current allocation'
+ );
+
+ assert.notOk(find('[data-test-stop-warning]'), 'No stop warning');
+ assert.notOk(find('[data-test-attempt-notice]'), 'No attempt notice');
+ });
+});