github.com/hernad/nomad@v1.6.112/ui/app/models/allocation.js (about)

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