github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/app/models/allocation.js (about) 1 import { inject as service } from '@ember/service'; 2 import { computed } from '@ember/object'; 3 import { equal, none } from '@ember/object/computed'; 4 import Model from '@ember-data/model'; 5 import { attr, belongsTo, hasMany } from '@ember-data/model'; 6 import { fragment, fragmentArray } from 'ember-data-model-fragments/attributes'; 7 import isEqual from 'lodash.isequal'; 8 import intersection from 'lodash.intersection'; 9 import shortUUIDProperty from '../utils/properties/short-uuid'; 10 import classic from 'ember-classic-decorator'; 11 12 const STATUS_ORDER = { 13 pending: 1, 14 running: 2, 15 complete: 3, 16 unknown: 4, 17 failed: 5, 18 lost: 6, 19 }; 20 21 @classic 22 export default class Allocation extends Model { 23 @service token; 24 @service store; 25 26 @shortUUIDProperty('id') shortId; 27 @belongsTo('job') job; 28 @belongsTo('node') node; 29 @attr('string') namespace; 30 @attr('string') name; 31 @attr('string') taskGroupName; 32 @fragment('resources') resources; 33 @fragment('resources') allocatedResources; 34 @attr('number') jobVersion; 35 36 @attr('number') modifyIndex; 37 @attr('date') modifyTime; 38 39 @attr('number') createIndex; 40 @attr('date') createTime; 41 42 @attr('string') clientStatus; 43 @attr('string') desiredStatus; 44 45 @attr healthChecks; 46 47 async getServiceHealth() { 48 const data = await this.store.adapterFor('allocation').check(this); 49 50 // Compare Results 51 if (!isEqual(this.healthChecks, data)) { 52 this.set('healthChecks', data); 53 } 54 } 55 56 @computed('') 57 get plainJobId() { 58 return JSON.parse(this.belongsTo('job').id())[0]; 59 } 60 61 @computed('clientStatus') 62 get statusIndex() { 63 return STATUS_ORDER[this.clientStatus] || 100; 64 } 65 66 @equal('clientStatus', 'running') isRunning; 67 @attr('boolean') isMigrating; 68 69 @computed('clientStatus') 70 get isScheduled() { 71 return ['pending', 'running'].includes(this.clientStatus); 72 } 73 74 // An allocation model created from any allocation list response will be lacking 75 // many properties (some of which can always be null). This is an indicator that 76 // the allocation needs to be reloaded to get the complete allocation state. 77 @none('allocationTaskGroup') isPartial; 78 79 // When allocations are server-side rescheduled, a paper trail 80 // is left linking all reschedule attempts. 81 @belongsTo('allocation', { inverse: 'nextAllocation' }) previousAllocation; 82 @belongsTo('allocation', { inverse: 'previousAllocation' }) nextAllocation; 83 84 @hasMany('allocation', { inverse: 'preemptedByAllocation' }) 85 preemptedAllocations; 86 @belongsTo('allocation', { inverse: 'preemptedAllocations' }) 87 preemptedByAllocation; 88 @attr('boolean') wasPreempted; 89 90 @belongsTo('evaluation') followUpEvaluation; 91 92 @computed('clientStatus') 93 get statusClass() { 94 const classMap = { 95 pending: 'is-pending', 96 running: 'is-primary', 97 complete: 'is-complete', 98 failed: 'is-error', 99 lost: 'is-light', 100 unknown: 'is-unknown', 101 }; 102 103 return classMap[this.clientStatus] || 'is-dark'; 104 } 105 106 @computed('jobVersion', 'job.version') 107 get isOld() { 108 return this.jobVersion !== this.get('job.version'); 109 } 110 111 @computed('isOld', 'jobTaskGroup', 'allocationTaskGroup') 112 get taskGroup() { 113 if (!this.isOld) return this.jobTaskGroup; 114 return this.allocationTaskGroup; 115 } 116 117 @computed('taskGroupName', 'job.taskGroups.[]') 118 get jobTaskGroup() { 119 const taskGroups = this.get('job.taskGroups'); 120 return taskGroups && taskGroups.findBy('name', this.taskGroupName); 121 } 122 123 @fragment('task-group', { defaultValue: null }) allocationTaskGroup; 124 125 @computed('taskGroup.drivers.[]', 'node.unhealthyDriverNames.[]') 126 get unhealthyDrivers() { 127 const taskGroupUnhealthyDrivers = this.get('taskGroup.drivers'); 128 const nodeUnhealthyDrivers = this.get('node.unhealthyDriverNames'); 129 130 if (taskGroupUnhealthyDrivers && nodeUnhealthyDrivers) { 131 return intersection(taskGroupUnhealthyDrivers, nodeUnhealthyDrivers); 132 } 133 134 return []; 135 } 136 137 // When per_alloc is set to true on a volume, the volumes are duplicated between active allocations. 138 // We differentiate them with a [#] suffix, inferred from a volume's allocation's name property. 139 @computed('name') 140 get volumeExtension() { 141 return this.name.substring(this.name.lastIndexOf('[')); 142 } 143 144 @fragmentArray('task-state') states; 145 @fragmentArray('reschedule-event') rescheduleEvents; 146 147 @computed('rescheduleEvents.length', 'nextAllocation') 148 get hasRescheduleEvents() { 149 return this.get('rescheduleEvents.length') > 0 || this.nextAllocation; 150 } 151 152 @computed( 153 'clientStatus', 154 'followUpEvaluation.content', 155 'nextAllocation.content' 156 ) 157 get hasStoppedRescheduling() { 158 return ( 159 !this.get('nextAllocation.content') && 160 !this.get('followUpEvaluation.content') && 161 this.clientStatus === 'failed' 162 ); 163 } 164 165 stop() { 166 return this.store.adapterFor('allocation').stop(this); 167 } 168 169 restart(taskName) { 170 return this.store.adapterFor('allocation').restart(this, taskName); 171 } 172 173 restartAll() { 174 return this.store.adapterFor('allocation').restartAll(this); 175 } 176 177 ls(path) { 178 return this.store.adapterFor('allocation').ls(this, path); 179 } 180 181 stat(path) { 182 return this.store.adapterFor('allocation').stat(this, path); 183 } 184 }