Revert "UI Placement failures""
This reverts commits 141ecd8d9170f56c8302b5c776532e6d287a40e3 d2d838c2de08aac716a9431d9720b5c12bb19ba1 8099db433641429816e8479c6d23116269f744c0 86262b3ceff607627a9c9e0e25eb81b9b5ee739c eb4104ec528982e0ee6ae9a05fb0460e53139f3f 0e0e23e238017815bdb6dcfbc056275b3deaacca 6d45658d77fb4c40760a63cced71b74757356e48 b52a8176e85b9c6f13ec012f7fce2ec3df1c8751
This commit is contained in:
parent
961926880a
commit
f747cc79e4
17
ansi.nomad
17
ansi.nomad
|
@ -1,17 +0,0 @@
|
||||||
job "ansi-test" {
|
|
||||||
datacenters = ["dc1"]
|
|
||||||
type = "service"
|
|
||||||
|
|
||||||
group "ansi-test" {
|
|
||||||
count = 1
|
|
||||||
|
|
||||||
task "ansi-test" {
|
|
||||||
driver = "raw_exec"
|
|
||||||
|
|
||||||
config {
|
|
||||||
command = "node"
|
|
||||||
args = [ "/Users/michael/work/ansi-test/index.js" ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -30,10 +30,6 @@ export default Controller.extend(Sortable, WithNamespaceResetting, {
|
||||||
listToSort: computed.alias('taskGroups'),
|
listToSort: computed.alias('taskGroups'),
|
||||||
sortedTaskGroups: computed.alias('listSorted'),
|
sortedTaskGroups: computed.alias('listSorted'),
|
||||||
|
|
||||||
sortedEvaluations: computed('model.evaluations.@each.modifyIndex', function() {
|
|
||||||
return (this.get('model.evaluations') || []).sortBy('modifyIndex').reverse();
|
|
||||||
}),
|
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
gotoTaskGroup(taskGroup) {
|
gotoTaskGroup(taskGroup) {
|
||||||
this.transitionToRoute('jobs.job.task-group', taskGroup.get('job'), taskGroup);
|
this.transitionToRoute('jobs.job.task-group', taskGroup.get('job'), taskGroup);
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
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'),
|
|
||||||
});
|
|
|
@ -53,35 +53,8 @@ export default Model.extend({
|
||||||
versions: hasMany('job-versions'),
|
versions: hasMany('job-versions'),
|
||||||
allocations: hasMany('allocations'),
|
allocations: hasMany('allocations'),
|
||||||
deployments: hasMany('deployments'),
|
deployments: hasMany('deployments'),
|
||||||
evaluations: hasMany('evaluations'),
|
|
||||||
namespace: belongsTo('namespace'),
|
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'),
|
supportsDeployments: computed.equal('type', 'service'),
|
||||||
|
|
||||||
runningDeployment: computed('deployments.@each.status', function() {
|
runningDeployment: computed('deployments.@each.status', function() {
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
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(),
|
|
||||||
});
|
|
|
@ -24,11 +24,6 @@ export default Fragment.extend({
|
||||||
|
|
||||||
reservedEphemeralDisk: attr('number'),
|
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() {
|
queuedOrStartingAllocs: computed('summary.{queuedAllocs,startingAllocs}', function() {
|
||||||
return this.get('summary.queuedAllocs') + this.get('summary.startingAllocs');
|
return this.get('summary.queuedAllocs') + this.get('summary.startingAllocs');
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
import notifyError from 'nomad-ui/utils/notify-error';
|
import notifyError from 'nomad-ui/utils/notify-error';
|
||||||
|
|
||||||
const { Route, RSVP, inject } = Ember;
|
const { Route, inject } = Ember;
|
||||||
|
|
||||||
export default Route.extend({
|
export default Route.extend({
|
||||||
store: inject.service(),
|
store: inject.service(),
|
||||||
|
@ -17,7 +17,7 @@ export default Route.extend({
|
||||||
return this.get('store')
|
return this.get('store')
|
||||||
.findRecord('job', fullId, { reload: true })
|
.findRecord('job', fullId, { reload: true })
|
||||||
.then(job => {
|
.then(job => {
|
||||||
return RSVP.all([job.get('allocations'), job.get('evaluations')]).then(() => job);
|
return job.get('allocations').then(() => job);
|
||||||
})
|
})
|
||||||
.catch(notifyError(this));
|
.catch(notifyError(this));
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
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);
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -19,7 +19,7 @@ export default ApplicationSerializer.extend({
|
||||||
// Transform the map-based JobSummary object into an array-based
|
// Transform the map-based JobSummary object into an array-based
|
||||||
// JobSummary fragment list
|
// JobSummary fragment list
|
||||||
hash.TaskGroupSummaries = Object.keys(get(hash, 'JobSummary.Summary') || {}).map(key => {
|
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 };
|
const summary = { Name: key };
|
||||||
|
|
||||||
Object.keys(allocStats).forEach(
|
Object.keys(allocStats).forEach(
|
||||||
|
@ -65,11 +65,6 @@ export default ApplicationSerializer.extend({
|
||||||
related: buildURL(`${jobURL}/deployments`, { namespace: namespace }),
|
related: buildURL(`${jobURL}/deployments`, { namespace: namespace }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
evaluations: {
|
|
||||||
links: {
|
|
||||||
related: buildURL(`${jobURL}/evaluations`, { namespace: namespace }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
@import "./components/loading-spinner";
|
@import "./components/loading-spinner";
|
||||||
@import "./components/metrics";
|
@import "./components/metrics";
|
||||||
@import "./components/node-status-light";
|
@import "./components/node-status-light";
|
||||||
@import "./components/simple-list";
|
|
||||||
@import "./components/status-text";
|
@import "./components/status-text";
|
||||||
@import "./components/timeline";
|
@import "./components/timeline";
|
||||||
@import "./components/tooltip";
|
@import "./components/tooltip";
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
.simple-list {
|
|
||||||
list-style: disc;
|
|
||||||
list-style-position: inside;
|
|
||||||
margin-left: 1.5rem;
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -47,57 +47,6 @@
|
||||||
</div>
|
</div>
|
||||||
</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}}
|
{{#if model.runningDeployment}}
|
||||||
<div class="boxed-section is-info active-deployment">
|
<div class="boxed-section is-info active-deployment">
|
||||||
<div class="boxed-section-head">
|
<div class="boxed-section-head">
|
||||||
|
@ -160,39 +109,5 @@
|
||||||
{{/list-pagination}}
|
{{/list-pagination}}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</section>
|
||||||
{{/gutter-menu}}
|
{{/gutter-menu}}
|
||||||
|
|
|
@ -71,7 +71,7 @@ function ipv6() {
|
||||||
for (var i = 0; i < 8; i++) {
|
for (var i = 0; i < 8; i++) {
|
||||||
var subnet = [];
|
var subnet = [];
|
||||||
for (var char = 0; char < 4; char++) {
|
for (var char = 0; char < 4; char++) {
|
||||||
subnet.push(faker.random.number(15).toString(16));
|
subnet.push(faker.random.number(16).toString(16));
|
||||||
}
|
}
|
||||||
subnets.push(subnet.join(''));
|
subnets.push(subnet.join(''));
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,12 +59,6 @@ export default function() {
|
||||||
|
|
||||||
this.get('/deployment/:id');
|
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 }) {
|
this.get('/deployment/allocations/:id', function(schema, { params }) {
|
||||||
const job = schema.jobs.find(schema.deployments.find(params.id).jobId);
|
const job = schema.jobs.find(schema.deployments.find(params.id).jobId);
|
||||||
const allocations = schema.allocations.where({ jobId: job.id });
|
const allocations = schema.allocations.where({ jobId: job.id });
|
||||||
|
|
|
@ -1,110 +0,0 @@
|
||||||
import Ember from 'ember';
|
|
||||||
import { Factory, faker, trait } from 'ember-cli-mirage';
|
|
||||||
import { provide, pickOne } from '../utils';
|
|
||||||
import { DATACENTERS } from '../common';
|
|
||||||
|
|
||||||
const EVAL_TYPES = ['system', 'service', 'batch'];
|
|
||||||
const EVAL_STATUSES = ['blocked', 'pending', 'complete', 'failed', 'canceled'];
|
|
||||||
const EVAL_TRIGGERED_BY = [
|
|
||||||
'job-register',
|
|
||||||
'job-deregister',
|
|
||||||
'periodic-job',
|
|
||||||
'node-update',
|
|
||||||
'scheduled',
|
|
||||||
'roling-update',
|
|
||||||
'deployment-watcher',
|
|
||||||
'failed-follow-up',
|
|
||||||
'max-plan-attempts',
|
|
||||||
];
|
|
||||||
|
|
||||||
const generateCountMap = (keysCount, list) => () => {
|
|
||||||
const sample = Array(keysCount)
|
|
||||||
.fill(null)
|
|
||||||
.map(() => pickOne(list))
|
|
||||||
.uniq();
|
|
||||||
return sample.reduce((hash, key) => {
|
|
||||||
hash[key] = faker.random.number({ min: 1, max: 5 });
|
|
||||||
return hash;
|
|
||||||
}, {});
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateNodesAvailable = generateCountMap(5, DATACENTERS);
|
|
||||||
const generateClassFiltered = generateCountMap(3, provide(10, faker.hacker.abbreviation));
|
|
||||||
const generateClassExhausted = generateClassFiltered;
|
|
||||||
const generateDimensionExhausted = generateCountMap(1, ['cpu', 'mem', 'disk', 'iops']);
|
|
||||||
const generateQuotaExhausted = generateDimensionExhausted;
|
|
||||||
const generateScores = generateCountMap(1, ['binpack', 'job-anti-affinity']);
|
|
||||||
const generateConstraintFiltered = generateCountMap(2, [
|
|
||||||
'prop = val',
|
|
||||||
'driver = docker',
|
|
||||||
'arch = x64',
|
|
||||||
]);
|
|
||||||
|
|
||||||
export default Factory.extend({
|
|
||||||
id: () => faker.random.uuid(),
|
|
||||||
|
|
||||||
priority: () => faker.random.number(100),
|
|
||||||
|
|
||||||
type: faker.list.random(...EVAL_TYPES),
|
|
||||||
triggeredBy: faker.list.random(...EVAL_TRIGGERED_BY),
|
|
||||||
status: faker.list.random(...EVAL_STATUSES),
|
|
||||||
statusDescription: () => faker.lorem.sentence(),
|
|
||||||
|
|
||||||
failedTGAllocs: null,
|
|
||||||
|
|
||||||
modifyIndex: () => faker.random.number({ min: 10, max: 2000 }),
|
|
||||||
|
|
||||||
withPlacementFailures: trait({
|
|
||||||
status: faker.list.random(...EVAL_STATUSES.without('blocked')),
|
|
||||||
afterCreate(evaluation, server) {
|
|
||||||
assignJob(evaluation, server);
|
|
||||||
const taskGroups = server.db.taskGroups.where({ jobId: evaluation.jobId });
|
|
||||||
|
|
||||||
const taskGroupNames = taskGroups.mapBy('name');
|
|
||||||
const failedTaskGroupsCount = faker.random.number({ min: 1, max: taskGroupNames.length });
|
|
||||||
const failedTaskGroupNames = [];
|
|
||||||
for (let i = 0; i < failedTaskGroupsCount; i++) {
|
|
||||||
failedTaskGroupNames.push(
|
|
||||||
...taskGroupNames.splice(faker.random.number(taskGroupNames.length), 1)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const placementFailures = failedTaskGroupNames.reduce((hash, name) => {
|
|
||||||
hash[name] = {
|
|
||||||
CoalescedFailures: faker.random.number({ min: 1, max: 20 }),
|
|
||||||
NodesEvaluated: faker.random.number({ min: 1, max: 100 }),
|
|
||||||
NodesExhausted: faker.random.number({ min: 1, max: 100 }),
|
|
||||||
|
|
||||||
NodesAvailable: Math.random() > 0.7 ? generateNodesAvailable() : null,
|
|
||||||
ClassFiltered: Math.random() > 0.7 ? generateClassFiltered() : null,
|
|
||||||
ConstraintFiltered: Math.random() > 0.7 ? generateConstraintFiltered() : null,
|
|
||||||
ClassExhausted: Math.random() > 0.7 ? generateClassExhausted() : null,
|
|
||||||
DimensionExhausted: Math.random() > 0.7 ? generateDimensionExhausted() : null,
|
|
||||||
QuotaExhausted: Math.random() > 0.7 ? generateQuotaExhausted() : null,
|
|
||||||
Scores: Math.random() > 0.7 ? generateScores() : null,
|
|
||||||
};
|
|
||||||
return hash;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
evaluation.update({
|
|
||||||
failedTGAllocs: placementFailures,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
|
|
||||||
afterCreate(evaluation, server) {
|
|
||||||
assignJob(evaluation, server);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function assignJob(evaluation, server) {
|
|
||||||
Ember.assert(
|
|
||||||
'[Mirage] No jobs! make sure jobs are created before evaluations',
|
|
||||||
server.db.jobs.length
|
|
||||||
);
|
|
||||||
|
|
||||||
const job = evaluation.jobId ? server.db.jobs.find(evaluation.jobId) : pickOne(server.db.jobs);
|
|
||||||
evaluation.update({
|
|
||||||
jobId: job.id,
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -14,7 +14,7 @@ export default Factory.extend({
|
||||||
|
|
||||||
region: () => 'global',
|
region: () => 'global',
|
||||||
type: faker.list.random(...JOB_TYPES),
|
type: faker.list.random(...JOB_TYPES),
|
||||||
priority: () => faker.random.number(100),
|
priority: () => faker.random.number(200),
|
||||||
all_at_once: faker.random.boolean,
|
all_at_once: faker.random.boolean,
|
||||||
status: faker.list.random(...JOB_STATUSES),
|
status: faker.list.random(...JOB_STATUSES),
|
||||||
datacenters: provider(
|
datacenters: provider(
|
||||||
|
@ -41,12 +41,6 @@ export default Factory.extend({
|
||||||
// When true, deployments for the job will always have a 'running' status
|
// When true, deployments for the job will always have a 'running' status
|
||||||
activeDeployment: false,
|
activeDeployment: false,
|
||||||
|
|
||||||
// When true, an evaluation with a high modify index and placement failures is created
|
|
||||||
failedPlacements: false,
|
|
||||||
|
|
||||||
// When true, no evaluations have failed placements
|
|
||||||
noFailedPlacements: false,
|
|
||||||
|
|
||||||
afterCreate(job, server) {
|
afterCreate(job, server) {
|
||||||
const groups = server.createList('task-group', job.groupsCount, {
|
const groups = server.createList('task-group', job.groupsCount, {
|
||||||
job,
|
job,
|
||||||
|
@ -90,17 +84,5 @@ export default Factory.extend({
|
||||||
activeDeployment: job.activeDeployment,
|
activeDeployment: job.activeDeployment,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
server.createList('evaluation', faker.random.number({ min: 1, max: 5 }), { job });
|
|
||||||
if (!job.noFailedPlacements) {
|
|
||||||
server.createList('evaluation', faker.random.number(3), 'withPlacementFailures', { job });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (job.failedPlacements) {
|
|
||||||
server.create('evaluation', 'withPlacementFailures', {
|
|
||||||
job,
|
|
||||||
modifyIndex: 4000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,8 +4,7 @@ export default function(server) {
|
||||||
|
|
||||||
server.createList('namespace', 3);
|
server.createList('namespace', 3);
|
||||||
|
|
||||||
server.createList('job', 10);
|
server.createList('job', 15);
|
||||||
server.createList('job', 5, { failedPlacements: true });
|
|
||||||
|
|
||||||
server.createList('token', 3);
|
server.createList('token', 3);
|
||||||
logTokens(server);
|
logTokens(server);
|
||||||
|
|
|
@ -296,83 +296,6 @@ test('the active deployment section can be expanded to show task groups and allo
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('the evaluations table lists evaluations sorted by modify index', function(assert) {
|
|
||||||
job = server.create('job');
|
|
||||||
const evaluations = server.db.evaluations
|
|
||||||
.where({ jobId: job.id })
|
|
||||||
.sortBy('modifyIndex')
|
|
||||||
.reverse();
|
|
||||||
|
|
||||||
visit(`/jobs/${job.id}`);
|
|
||||||
|
|
||||||
andThen(() => {
|
|
||||||
assert.equal(
|
|
||||||
findAll('.evaluations tbody tr').length,
|
|
||||||
evaluations.length,
|
|
||||||
'A row for each evaluation'
|
|
||||||
);
|
|
||||||
|
|
||||||
evaluations.forEach((evaluation, index) => {
|
|
||||||
const row = $(findAll('.evaluations tbody tr')[index]);
|
|
||||||
assert.equal(
|
|
||||||
row.find('td:eq(0)').text(),
|
|
||||||
evaluation.id.split('-')[0],
|
|
||||||
`Short ID, row ${index}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const firstEvaluation = evaluations[0];
|
|
||||||
const row = $(findAll('.evaluations tbody tr')[0]);
|
|
||||||
assert.equal(row.find('td:eq(1)').text(), '' + firstEvaluation.priority, 'Priority');
|
|
||||||
assert.equal(row.find('td:eq(2)').text(), firstEvaluation.triggeredBy, 'Triggered By');
|
|
||||||
assert.equal(row.find('td:eq(3)').text(), firstEvaluation.status, 'Status');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when the job has placement failures, they are called out', function(assert) {
|
|
||||||
job = server.create('job', { failedPlacements: true });
|
|
||||||
const failedEvaluation = server.db.evaluations
|
|
||||||
.where({ jobId: job.id })
|
|
||||||
.filter(evaluation => evaluation.failedTGAllocs)
|
|
||||||
.sortBy('modifyIndex')
|
|
||||||
.reverse()[0];
|
|
||||||
|
|
||||||
const failedTaskGroupNames = Object.keys(failedEvaluation.failedTGAllocs);
|
|
||||||
|
|
||||||
visit(`/jobs/${job.id}`);
|
|
||||||
|
|
||||||
andThen(() => {
|
|
||||||
assert.ok(find('.placement-failures'), 'Placement failures section found');
|
|
||||||
|
|
||||||
const taskGroupLabels = findAll('.placement-failures h3.title').map(title =>
|
|
||||||
title.textContent.trim()
|
|
||||||
);
|
|
||||||
failedTaskGroupNames.forEach(name => {
|
|
||||||
assert.ok(
|
|
||||||
taskGroupLabels.find(label => label.includes(name)),
|
|
||||||
`${name} included in placement failures list`
|
|
||||||
);
|
|
||||||
assert.ok(
|
|
||||||
taskGroupLabels.find(label =>
|
|
||||||
label.includes(failedEvaluation.failedTGAllocs[name].CoalescedFailures + 1)
|
|
||||||
),
|
|
||||||
'The number of unplaced allocs = CoalescedFailures + 1'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when the job has no placement failures, the placement failures section is gone', function(
|
|
||||||
assert
|
|
||||||
) {
|
|
||||||
job = server.create('job', { noFailedPlacements: true });
|
|
||||||
visit(`/jobs/${job.id}`);
|
|
||||||
|
|
||||||
andThen(() => {
|
|
||||||
assert.notOk(find('.placement-failures'), 'Placement failures section not found');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when the job is not found, an error message is shown, but the URL persists', function(
|
test('when the job is not found, an error message is shown, but the URL persists', function(
|
||||||
assert
|
assert
|
||||||
) {
|
) {
|
||||||
|
|
Loading…
Reference in a new issue