Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI: Preemptions #5594

Merged
merged 14 commits into from
Apr 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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('model.allocations.@each.wasPreempted', function() {
return this.model.allocations.filterBy('wasPreempted');
}),

sortedEvents: computed('model.events.@each.time', 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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if this boolean casting would be done by the attr('boolean') in the model?

Also I had a quick scan over the docs here:

https://www.nomadproject.io/docs/internals/scheduling/preemption.html#preemptedbyallocid

Does hash.PreemptedByAllocationID == PreemptedByAllocID? (Allocation vs Alloc). Thinking it could be something wrong in the docs, thought I'd mention just incase?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The boolean casting is probably taken care of by the boolean transformer, yes, but I'm not sure I want to rely on a transformer for it.

Yes, the docs are wrong in this case. Here's the go struct to confirm: https://github.com/hashicorp/nomad/blob/master/nomad/structs/structs.go#L7261-L7267


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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to include this here? Would this mean you can't pan on mobile anymore?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did mean to include this. It was causing the icon tooltip to be clipped on the left side where it goes out of the bounds of the page-column.

As for panning on mobile, good question. I just checked and it appears to still work.

}

@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