Skip to content

Commit

Permalink
Merge pull request #5594 from hashicorp/f-ui-preemptions
Browse files Browse the repository at this point in the history
UI: Preemptions
  • Loading branch information
DingoEatingFuzz authored Apr 24, 2019
2 parents 93e115e + 4166a71 commit 712e774
Show file tree
Hide file tree
Showing 24 changed files with 545 additions and 12 deletions.
5 changes: 4 additions & 1 deletion ui/app/controllers/allocations/allocation/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { alias } from '@ember/object/computed';
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import { alias } from '@ember/object/computed';
import Sortable from 'nomad-ui/mixins/sortable';
import { lazyClick } from 'nomad-ui/helpers/lazy-click';

Expand All @@ -18,6 +18,9 @@ export default Controller.extend(Sortable, {
listToSort: alias('model.states'),
sortedStates: alias('listSorted'),

// Set in the route
preempter: null,

actions: {
gotoTask(allocation, task) {
this.transitionToRoute('allocations.allocation.task', task);
Expand Down
22 changes: 21 additions & 1 deletion ui/app/controllers/clients/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default Controller.extend(Sortable, Searchable, {
searchTerm: 'search',
sortProperty: 'sort',
sortDescending: 'desc',
onlyPreemptions: 'preemptions',
},

currentPage: 1,
Expand All @@ -20,10 +21,25 @@ export default Controller.extend(Sortable, Searchable, {

searchProps: computed(() => ['shortId', 'name']),

listToSort: alias('model.allocations'),
onlyPreemptions: false,

visibleAllocations: computed(
'model.allocations.[]',
'preemptions.[]',
'onlyPreemptions',
function() {
return this.onlyPreemptions ? this.preemptions : this.model.allocations;
}
),

listToSort: alias('visibleAllocations'),
listToSearch: alias('listSorted'),
sortedAllocations: alias('listSearched'),

preemptions: computed('[email protected]', function() {
return this.model.allocations.filterBy('wasPreempted');
}),

sortedEvents: computed('[email protected]', function() {
return this.get('model.events')
.sortBy('time')
Expand All @@ -38,5 +54,9 @@ export default Controller.extend(Sortable, Searchable, {
gotoAllocation(allocation) {
this.transitionToRoute('allocations.allocation', allocation);
},

setPreemptionFilter(value) {
this.set('onlyPreemptions', value);
},
},
});
14 changes: 10 additions & 4 deletions ui/app/models/allocation.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { computed } from '@ember/object';
import { equal } from '@ember/object/computed';
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
import { belongsTo } from 'ember-data/relationships';
import { belongsTo, hasMany } from 'ember-data/relationships';
import { fragment, fragmentArray } from 'ember-data-model-fragments/attributes';
import intersection from 'lodash.intersection';
import shortUUIDProperty from '../utils/properties/short-uuid';
Expand Down Expand Up @@ -46,6 +46,10 @@ export default Model.extend({
previousAllocation: belongsTo('allocation', { inverse: 'nextAllocation' }),
nextAllocation: belongsTo('allocation', { inverse: 'previousAllocation' }),

preemptedAllocations: hasMany('allocation', { inverse: 'preemptedByAllocation' }),
preemptedByAllocation: belongsTo('allocation', { inverse: 'preemptedAllocations' }),
wasPreempted: attr('boolean'),

followUpEvaluation: belongsTo('evaluation'),

statusClass: computed('clientStatus', function() {
Expand Down Expand Up @@ -88,9 +92,11 @@ export default Model.extend({
'clientStatus',
'followUpEvaluation.content',
function() {
return !this.get('nextAllocation.content') &&
!this.get('followUpEvaluation.content') &&
this.clientStatus === 'failed';
return (
!this.get('nextAllocation.content') &&
!this.get('followUpEvaluation.content') &&
this.clientStatus === 'failed'
);
}
),
});
2 changes: 2 additions & 0 deletions ui/app/models/job-plan.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
import { fragmentArray } from 'ember-data-model-fragments/attributes';
import { hasMany } from 'ember-data/relationships';

export default Model.extend({
diff: attr(),
failedTGAllocs: fragmentArray('placement-failure', { defaultValue: () => [] }),
preemptions: hasMany('allocation'),
});
13 changes: 13 additions & 0 deletions ui/app/routes/allocations/allocation/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Route from '@ember/routing/route';

export default Route.extend({
setupController(controller, model) {
// Suppress the preemptedByAllocation fetch error in the event it's a 404
if (model) {
const setPreempter = () => controller.set('preempter', model.preemptedByAllocation);
model.preemptedByAllocation.then(setPreempter, setPreempter);
}

return this._super(...arguments);
},
});
4 changes: 4 additions & 0 deletions ui/app/serializers/allocation.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ export default ApplicationSerializer.extend({
hash.NextAllocationID = hash.NextAllocation ? hash.NextAllocation : null;
hash.FollowUpEvaluationID = hash.FollowupEvalID ? hash.FollowupEvalID : null;

hash.PreemptedAllocationIDs = hash.PreemptedAllocations || [];
hash.PreemptedByAllocationID = hash.PreemptedByAllocation || null;
hash.WasPreempted = !!hash.PreemptedByAllocationID;

return this._super(typeHash, hash);
},
});
2 changes: 2 additions & 0 deletions ui/app/serializers/job-plan.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { assign } from '@ember/polyfills';
import ApplicationSerializer from './application';
import { get } from '@ember/object';

export default ApplicationSerializer.extend({
normalize(typeHash, hash) {
const failures = hash.FailedTGAllocs || {};
hash.FailedTGAllocs = Object.keys(failures).map(key => {
return assign({ Name: key }, failures[key] || {});
});
hash.PreemptionIDs = (get(hash, 'Annotations.PreemptedAllocs') || []).mapBy('ID');
return this._super(...arguments);
},
});
5 changes: 5 additions & 0 deletions ui/app/styles/components/badge.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
line-height: 1;
border-radius: $radius;
padding: 0.25em 0.75em;
border: none;

@each $name, $pair in $colors {
$color: nth($pair, 1);
Expand Down Expand Up @@ -43,3 +44,7 @@
background: lighten($grey-blue, 10%);
}
}

button.badge {
cursor: pointer;
}
1 change: 0 additions & 1 deletion ui/app/styles/components/page-layout.scss
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@

&.is-right {
margin-left: $gutter-width;
overflow-x: auto;
}

@media #{$mq-hidden-gutter} {
Expand Down
4 changes: 4 additions & 0 deletions ui/app/styles/core/table.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
}
}

&.is-isolated {
margin-bottom: 0;
}

&.with-foot {
margin-bottom: 0;
border-bottom-left-radius: 0;
Expand Down
70 changes: 70 additions & 0 deletions ui/app/templates/allocations/allocation/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,74 @@
</div>
</div>
{{/if}}

{{#if model.wasPreempted}}
<div class="boxed-section is-warning" data-test-was-preempted>
<div class="boxed-section-head">Preempted By</div>
<div class="boxed-section-body">
{{#if (not preempter)}}
<div class="empty-message">
<h3 class="empty-message-headline">Allocation is gone</h3>
<p class="empty-message-body">This allocation has been stopped and garbage collected.</p>
</div>
{{else}}
<div class="boxed-section is-small">
<div class="boxed-section-body inline-definitions">
<span class="pair">
<span data-test-allocation-status class="tag {{preempter.statusClass}}">
{{preempter.clientStatus}}
</span>
</span>
<span class="pair">
<span class="term" data-test-allocation-name>{{preempter.name}}</span>
{{#link-to "allocations.allocation" preempter data-test-allocation-id}}{{preempter.shortId}}{{/link-to}}
</span>
<span class="pair job-link"><span class="term">Job</span>
{{#link-to "jobs.job" preempter.job (query-params jobNamespace=preempter.job.namespace.id) data-test-job-link}}{{preempter.job.name}}{{/link-to}}
</span>
<span class="pair job-priority"><span class="term">Priority</span>
<span data-test-job-priority>{{preempter.job.priority}}</span>
</span>
<span class="pair node-link"><span class="term">Client</span>
{{#link-to "clients.client" preempter.node data-test-client-link}}{{preempter.node.shortId}}{{/link-to}}
</span>
<span class="pair"><span class="term">Reserved CPU</span>
<span data-test-allocation-cpu>{{preempter.resources.cpu}} MHz</span>
</span>
<span class="pair"><span class="term">Reserved Memory</span>
<span data-test-allocation-memory>{{preempter.resources.memory}} MiB</span>
</span>
</div>
</div>
{{/if}}
</div>
</div>
{{/if}}

{{#if (and model.preemptedAllocations.isFulfilled model.preemptedAllocations.length)}}
<div class="boxed-section" data-test-preemptions>
<div class="boxed-section-head">Preempted Allocations</div>
<div class="boxed-section-body">
{{#list-table
source=model.preemptedAllocations
class="allocations is-isolated" as |t|}}
{{#t.head}}
<th class="is-narrow"></th>
<th>ID</th>
<th>Task Group</th>
<th>Created</th>
<th>Modified</th>
<th>Status</th>
<th>Version</th>
<th>Node</th>
<th>CPU</th>
<th>Memory</th>
{{/t.head}}
{{#t.body as |row|}}
{{allocation-row allocation=row.model context="job" data-test-allocation=row.model.id}}
{{/t.body}}
{{/list-table}}
</div>
</div>
{{/if}}
</section>
12 changes: 11 additions & 1 deletion ui/app/templates/clients/client.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,17 @@

<div class="boxed-section">
<div class="boxed-section-head">
<div>Allocations <span class="badge is-white">{{model.allocations.length}}</span></div>
<div>
Allocations
<button role="button" class="badge is-white" onclick={{action "setPreemptionFilter" false}} data-test-filter-all>
{{model.allocations.length}}
</button>
{{#if preemptions.length}}
<button role="button" class="badge is-warning" onclick={{action "setPreemptionFilter" true}} data-test-filter-preemptions>
{{preemptions.length}} {{pluralize "preemption" preemptions.length}}
</button>
{{/if}}
</div>
{{search-box
searchTerm=(mut searchTerm)
onChange=(action resetPagination)
Expand Down
5 changes: 5 additions & 0 deletions ui/app/templates/components/allocation-row.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
{{x-icon "history" class="is-faded"}}
</span>
{{/if}}
{{#if allocation.wasPreempted}}
<span data-test-icon="preemption" class="tooltip text-center" role="tooltip" aria-label="Allocation was preempted">
{{x-icon "boot" class="is-faded"}}
</span>
{{/if}}
</td>
<td data-test-short-id>
{{#link-to "allocations.allocation" allocation class="is-primary"}}
Expand Down
28 changes: 28 additions & 0 deletions ui/app/templates/components/job-editor.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,34 @@
{{/if}}
</div>
</div>
{{#if (and planOutput.preemptions.isFulfilled planOutput.preemptions.length)}}
<div class="boxed-section is-warning" data-test-preemptions>
<div class="boxed-section-head" data-test-preemptions-title>
Preemptions (if you choose to run this job, these allocations will be stopped)
</div>
<div class="boxed-section-body" data-test-preemptions-body>
{{#list-table
source=planOutput.preemptions
class="allocations is-isolated" as |t|}}
{{#t.head}}
<th class="is-narrow"></th>
<th>ID</th>
<th>Task Group</th>
<th>Created</th>
<th>Modified</th>
<th>Status</th>
<th>Version</th>
<th>Node</th>
<th>CPU</th>
<th>Memory</th>
{{/t.head}}
{{#t.body as |row|}}
{{allocation-row allocation=row.model context="job"}}
{{/t.body}}
{{/list-table}}
</div>
</div>
{{/if}}
<div class="content is-associative">
<button class="button is-primary {{if submit.isRunning "is-loading"}}" type="button" onclick={{perform submit}} disabled={{submit.isRunning}} data-test-run>Run</button>
<button class="button is-light" type="button" onclick={{action reset}} data-test-cancel>Cancel</button>
Expand Down
14 changes: 14 additions & 0 deletions ui/mirage/factories/allocation.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,20 @@ export default Factory.extend({
},
}),

preempted: trait({
afterCreate(allocation, server) {
const preempter = server.create('allocation', { preemptedAllocations: [allocation.id] });
allocation.update({ preemptedByAllocation: preempter.id });
},
}),

preempter: trait({
afterCreate(allocation, server) {
const preempted = server.create('allocation', { preemptedByAllocation: allocation.id });
allocation.update({ preemptedAllocations: [preempted.id] });
},
}),

afterCreate(allocation, server) {
Ember.assert(
'[Mirage] No jobs! make sure jobs are created before allocations',
Expand Down
1 change: 1 addition & 0 deletions ui/public/images/icons/boot.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 712e774

Please sign in to comment.