Skip to content

Commit

Permalink
Merge pull request #3603 from hashicorp/f-ui-placement-failures
Browse files Browse the repository at this point in the history
UI Placement Failures & Evaluations
  • Loading branch information
DingoEatingFuzz committed Nov 30, 2017
2 parents fef063e + b4ee45a commit 6457735
Show file tree
Hide file tree
Showing 17 changed files with 428 additions and 6 deletions.
4 changes: 4 additions & 0 deletions ui/app/controllers/jobs/job/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export default Controller.extend(Sortable, WithNamespaceResetting, {
listToSort: computed.alias('taskGroups'),
sortedTaskGroups: computed.alias('listSorted'),

sortedEvaluations: computed('model.evaluations.@each.modifyIndex', function() {
return (this.get('model.evaluations') || []).sortBy('modifyIndex').reverse();
}),

actions: {
gotoTaskGroup(taskGroup) {
this.transitionToRoute('jobs.job.task-group', taskGroup.get('job'), taskGroup);
Expand Down
27 changes: 27 additions & 0 deletions ui/app/models/evaluation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Ember from 'ember';
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
import { belongsTo } from 'ember-data/relationships';
import { fragmentArray } from 'ember-data-model-fragments/attributes';
import shortUUIDProperty from '../utils/properties/short-uuid';

const { computed } = Ember;

export default Model.extend({
shortId: shortUUIDProperty('id'),
priority: attr('number'),
type: attr('string'),
triggeredBy: attr('string'),
status: attr('string'),
statusDescription: attr('string'),
failedTGAllocs: fragmentArray('placement-failure', { defaultValue: () => [] }),

hasPlacementFailures: computed.bool('failedTGAllocs.length'),

// TEMPORARY: https://github.com/emberjs/data/issues/5209
originalJobId: attr('string'),

job: belongsTo('job'),

modifyIndex: attr('number'),
});
27 changes: 27 additions & 0 deletions ui/app/models/job.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,35 @@ export default Model.extend({
versions: hasMany('job-versions'),
allocations: hasMany('allocations'),
deployments: hasMany('deployments'),
evaluations: hasMany('evaluations'),
namespace: belongsTo('namespace'),

hasPlacementFailures: computed.bool('latestFailureEvaluation'),

latestEvaluation: computed('evaluations.@each.modifyIndex', 'evaluations.isPending', function() {
const evaluations = this.get('evaluations');
if (!evaluations || evaluations.get('isPending')) {
return null;
}
return evaluations.sortBy('modifyIndex').get('lastObject');
}),

latestFailureEvaluation: computed(
'evaluations.@each.modifyIndex',
'evaluations.isPending',
function() {
const evaluations = this.get('evaluations');
if (!evaluations || evaluations.get('isPending')) {
return null;
}

const failureEvaluations = evaluations.filterBy('hasPlacementFailures');
if (failureEvaluations) {
return failureEvaluations.sortBy('modifyIndex').get('lastObject');
}
}
),

supportsDeployments: computed.equal('type', 'service'),

runningDeployment: computed('deployments.@each.status', function() {
Expand Down
20 changes: 20 additions & 0 deletions ui/app/models/placement-failure.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import attr from 'ember-data/attr';
import Fragment from 'ember-data-model-fragments/fragment';

export default Fragment.extend({
name: attr('string'),

coalescedFailures: attr('number'),

nodesEvaluated: attr('number'),
nodesExhausted: attr('number'),

// Maps keyed by relevant dimension (dc, class, constraint, etc) with count values
nodesAvailable: attr(),
classFiltered: attr(),
constraintFiltered: attr(),
classExhausted: attr(),
dimensionExhausted: attr(),
quotaExhausted: attr(),
scores: attr(),
});
5 changes: 5 additions & 0 deletions ui/app/models/task-group.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ export default Fragment.extend({

reservedEphemeralDisk: attr('number'),

placementFailures: computed('job.latestFailureEvaluation.failedTGAllocs.[]', function() {
const placementFailures = this.get('job.latestFailureEvaluation.failedTGAllocs');
return placementFailures && placementFailures.findBy('name', this.get('name'));
}),

queuedOrStartingAllocs: computed('summary.{queuedAllocs,startingAllocs}', function() {
return this.get('summary.queuedAllocs') + this.get('summary.startingAllocs');
}),
Expand Down
4 changes: 2 additions & 2 deletions ui/app/routes/jobs/job.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Ember from 'ember';
import notifyError from 'nomad-ui/utils/notify-error';

const { Route, inject } = Ember;
const { Route, RSVP, inject } = Ember;

export default Route.extend({
store: inject.service(),
Expand All @@ -17,7 +17,7 @@ export default Route.extend({
return this.get('store')
.findRecord('job', fullId, { reload: true })
.then(job => {
return job.get('allocations').then(() => job);
return RSVP.all([job.get('allocations'), job.get('evaluations')]).then(() => job);
})
.catch(notifyError(this));
},
Expand Down
27 changes: 27 additions & 0 deletions ui/app/serializers/evaluation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Ember from 'ember';
import ApplicationSerializer from './application';

const { inject, get, assign } = Ember;

export default ApplicationSerializer.extend({
system: inject.service(),

normalize(typeHash, hash) {
hash.FailedTGAllocs = Object.keys(hash.FailedTGAllocs || {}).map(key => {
return assign({ Name: key }, get(hash, `FailedTGAllocs.${key}`) || {});
});

hash.PlainJobId = hash.JobID;
hash.Namespace =
hash.Namespace ||
get(hash, 'Job.Namespace') ||
this.get('system.activeNamespace.id') ||
'default';
hash.JobID = JSON.stringify([hash.JobID, hash.Namespace]);

// TEMPORARY: https://github.com/emberjs/data/issues/5209
hash.OriginalJobId = hash.JobID;

return this._super(typeHash, hash);
},
});
7 changes: 6 additions & 1 deletion ui/app/serializers/job.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default ApplicationSerializer.extend({
// Transform the map-based JobSummary object into an array-based
// JobSummary fragment list
hash.TaskGroupSummaries = Object.keys(get(hash, 'JobSummary.Summary') || {}).map(key => {
const allocStats = get(hash, `JobSummary.Summary.${key}`);
const allocStats = get(hash, `JobSummary.Summary.${key}`) || {};
const summary = { Name: key };

Object.keys(allocStats).forEach(
Expand Down Expand Up @@ -65,6 +65,11 @@ export default ApplicationSerializer.extend({
related: buildURL(`${jobURL}/deployments`, { namespace: namespace }),
},
},
evaluations: {
links: {
related: buildURL(`${jobURL}/evaluations`, { namespace: namespace }),
},
},
});
},
});
Expand Down
1 change: 1 addition & 0 deletions ui/app/styles/components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
@import "./components/loading-spinner";
@import "./components/metrics";
@import "./components/node-status-light";
@import "./components/simple-list";
@import "./components/status-text";
@import "./components/timeline";
@import "./components/tooltip";
9 changes: 9 additions & 0 deletions ui/app/styles/components/simple-list.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.simple-list {
list-style: disc;
list-style-position: inside;
margin-left: 1.5rem;

li {
margin-bottom: 0.5em;
}
}
85 changes: 85 additions & 0 deletions ui/app/templates/jobs/job/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,57 @@
</div>
</div>

{{#if model.hasPlacementFailures}}
<div class="boxed-section is-danger placement-failures">
<div class="boxed-section-head">
Placement Failures
</div>
<div class="boxed-section-body">
{{#each model.taskGroups as |taskGroup|}}
{{#if taskGroup.placementFailures}}
{{#with taskGroup.placementFailures as |failures|}}
<h3 class="title is-5">
{{taskGroup.name}}
<span class="badge is-light">{{inc failures.coalescedFailures}} unplaced</span>
</h3>
<ul class="simple-list">
{{#if (eq failures.nodesEvaluated 0)}}
<li>No nodes were eligible for evaluation</li>
{{/if}}
{{#each-in failures.nodesAvailable as |datacenter available|}}
{{#if (eq available 0)}}
<li>No nodes are available in datacenter {{datacenter}}</li>
{{/if}}
{{/each-in}}
{{#each-in failures.classFiltered as |class count|}}
<li>Class {{class}} filtered {{count}} {{pluralize "node" count}}</li>
{{/each-in}}
{{#each-in failures.constraintFiltered as |constraint count|}}
<li>Constraint <code>{{constraint}}</code> filtered {{count}} {{pluralize "node" count}}</li>
{{/each-in}}
{{#if failures.nodesExhausted}}
<li>Resources exhausted on {{failures.nodesExhausted}} {{pluralize "node" failures.nodesExhausted}}</li>
{{/if}}
{{#each-in failures.classExhausted as |class count|}}
<li>Class {{class}} exhausted on {{count}} {{pluralize "node" count}}</li>
{{/each-in}}
{{#each-in failures.dimensionExhausted as |dimension count|}}
<li>Dimension {{dimension}} exhausted on {{count}} {{pluralize "node" count}}</li>
{{/each-in}}
{{#each-in failures.quotaExhausted as |quota dimension|}}
<li>Quota limit hit {{dimension}}</li>
{{/each-in}}
{{#each-in failures.scores as |name score|}}
<li>Score {{name}} = {{score}}</li>
{{/each-in}}
</ul>
{{/with}}
{{/if}}
{{/each}}
</div>
</div>
{{/if}}

{{#if model.runningDeployment}}
<div class="boxed-section is-info active-deployment">
<div class="boxed-section-head">
Expand Down Expand Up @@ -109,5 +160,39 @@
{{/list-pagination}}
</div>
</div>

<div class="boxed-section">
<div class="boxed-section-head">
Evaluations
</div>
<div class="boxed-section-body is-full-bleed evaluations">
{{#list-table source=sortedEvaluations as |t|}}
{{#t.head}}
<th>ID</th>
<th>Priority</th>
<th>Triggered By</th>
<th>Status</th>
<th>Placement Failures</th>
{{/t.head}}
{{#t.body as |row|}}
<tr>
<td>{{row.model.shortId}}</td>
<td>{{row.model.priority}}</td>
<td>{{row.model.triggeredBy}}</td>
<td>{{row.model.status}}</td>
<td>
{{#if (eq row.model.status "blocked")}}
N/A - In Progress
{{else if row.model.hasPlacementFailures}}
True
{{else}}
False
{{/if}}
</td>
</tr>
{{/t.body}}
{{/list-table}}
</div>
</div>
</section>
{{/gutter-menu}}
2 changes: 1 addition & 1 deletion ui/mirage/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ function ipv6() {
for (var i = 0; i < 8; i++) {
var subnet = [];
for (var char = 0; char < 4; char++) {
subnet.push(faker.random.number(16).toString(16));
subnet.push(faker.random.number(15).toString(16));
}
subnets.push(subnet.join(''));
}
Expand Down
6 changes: 6 additions & 0 deletions ui/mirage/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ export default function() {

this.get('/deployment/:id');

this.get('/job/:id/evaluations', function({ evaluations }, { params }) {
return this.serialize(evaluations.where({ jobId: params.id }));
});

this.get('/evaluation/:id');

this.get('/deployment/allocations/:id', function(schema, { params }) {
const job = schema.jobs.find(schema.deployments.find(params.id).jobId);
const allocations = schema.allocations.where({ jobId: job.id });
Expand Down
Loading

0 comments on commit 6457735

Please sign in to comment.