open-nomad/ui/app/models/allocation.js
Phil Renaud 15872cc2d4
[ui] Disconnected Clients: "Unknown" allocations in the UI (#12544)
* Unknown status for allocations accounted for

* Canary string removed

* Test cleanup

* Generate unknown in mirage

* aacidentally oovervoowled

* Update ui/app/components/allocation-status-bar.js

Co-authored-by: Derek Strickland <1111455+DerekStrickland@users.noreply.github.com>

* Disconnected state on job status in client

* Renaming Disconnected to Unknown in the job-status-in-client

* Unknown accounted for on job rows filtering and testsfix

* Adding lostAllocs as a computed dependency

* Unknown client status within acceptance test

* Swatches updated and PR comments addressed

* Unknown and disconnected added to test fixtures

Co-authored-by: Derek Strickland <1111455+DerekStrickland@users.noreply.github.com>
2022-04-22 11:25:02 -04:00

168 lines
4.7 KiB
JavaScript

import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
import { equal, none } from '@ember/object/computed';
import Model from '@ember-data/model';
import { attr, belongsTo, hasMany } from '@ember-data/model';
import { fragment, fragmentArray } from 'ember-data-model-fragments/attributes';
import intersection from 'lodash.intersection';
import shortUUIDProperty from '../utils/properties/short-uuid';
import classic from 'ember-classic-decorator';
const STATUS_ORDER = {
pending: 1,
running: 2,
complete: 3,
unknown: 4,
failed: 5,
lost: 6,
};
@classic
export default class Allocation extends Model {
@service token;
@shortUUIDProperty('id') shortId;
@belongsTo('job') job;
@belongsTo('node') node;
@attr('string') namespace;
@attr('string') name;
@attr('string') taskGroupName;
@fragment('resources') resources;
@fragment('resources') allocatedResources;
@attr('number') jobVersion;
@attr('number') modifyIndex;
@attr('date') modifyTime;
@attr('number') createIndex;
@attr('date') createTime;
@attr('string') clientStatus;
@attr('string') desiredStatus;
@computed('')
get plainJobId() {
return JSON.parse(this.belongsTo('job').id())[0];
}
@computed('clientStatus')
get statusIndex() {
return STATUS_ORDER[this.clientStatus] || 100;
}
@equal('clientStatus', 'running') isRunning;
@attr('boolean') isMigrating;
@computed('clientStatus')
get isScheduled() {
return ['pending', 'running'].includes(this.clientStatus);
}
// An allocation model created from any allocation list response will be lacking
// many properties (some of which can always be null). This is an indicator that
// the allocation needs to be reloaded to get the complete allocation state.
@none('allocationTaskGroup') isPartial;
// When allocations are server-side rescheduled, a paper trail
// is left linking all reschedule attempts.
@belongsTo('allocation', { inverse: 'nextAllocation' }) previousAllocation;
@belongsTo('allocation', { inverse: 'previousAllocation' }) nextAllocation;
@hasMany('allocation', { inverse: 'preemptedByAllocation' })
preemptedAllocations;
@belongsTo('allocation', { inverse: 'preemptedAllocations' })
preemptedByAllocation;
@attr('boolean') wasPreempted;
@belongsTo('evaluation') followUpEvaluation;
@computed('clientStatus')
get statusClass() {
const classMap = {
pending: 'is-pending',
running: 'is-primary',
complete: 'is-complete',
failed: 'is-error',
lost: 'is-light',
unknown: 'is-unknown',
};
return classMap[this.clientStatus] || 'is-dark';
}
@computed('jobVersion', 'job.version')
get isOld() {
return this.jobVersion !== this.get('job.version');
}
@computed('isOld', 'jobTaskGroup', 'allocationTaskGroup')
get taskGroup() {
if (!this.isOld) return this.jobTaskGroup;
return this.allocationTaskGroup;
}
@computed('taskGroupName', 'job.taskGroups.[]')
get jobTaskGroup() {
const taskGroups = this.get('job.taskGroups');
return taskGroups && taskGroups.findBy('name', this.taskGroupName);
}
@fragment('task-group', { defaultValue: null }) allocationTaskGroup;
@computed('taskGroup.drivers.[]', 'node.unhealthyDriverNames.[]')
get unhealthyDrivers() {
const taskGroupUnhealthyDrivers = this.get('taskGroup.drivers');
const nodeUnhealthyDrivers = this.get('node.unhealthyDriverNames');
if (taskGroupUnhealthyDrivers && nodeUnhealthyDrivers) {
return intersection(taskGroupUnhealthyDrivers, nodeUnhealthyDrivers);
}
return [];
}
// When per_alloc is set to true on a volume, the volumes are duplicated between active allocations.
// We differentiate them with a [#] suffix, inferred from a volume's allocation's name property.
@computed('name')
get volumeExtension() {
return this.name.substring(this.name.lastIndexOf('['));
}
@fragmentArray('task-state') states;
@fragmentArray('reschedule-event') rescheduleEvents;
@computed('rescheduleEvents.length', 'nextAllocation')
get hasRescheduleEvents() {
return this.get('rescheduleEvents.length') > 0 || this.nextAllocation;
}
@computed(
'clientStatus',
'followUpEvaluation.content',
'nextAllocation.content'
)
get hasStoppedRescheduling() {
return (
!this.get('nextAllocation.content') &&
!this.get('followUpEvaluation.content') &&
this.clientStatus === 'failed'
);
}
stop() {
return this.store.adapterFor('allocation').stop(this);
}
restart(taskName) {
return this.store.adapterFor('allocation').restart(this, taskName);
}
ls(path) {
return this.store.adapterFor('allocation').ls(this, path);
}
stat(path) {
return this.store.adapterFor('allocation').stat(this, path);
}
}