github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/app/utils/properties/job-client-status.js (about)

     1  import { computed } from '@ember/object';
     2  
     3  const STATUS = [
     4    'queued',
     5    'notScheduled',
     6    'starting',
     7    'running',
     8    'complete',
     9    'degraded',
    10    'failed',
    11    'lost',
    12    'unknown',
    13  ];
    14  
    15  // An Ember.Computed property that computes the aggregated status of a job in a
    16  // client based on the desiredStatus of each allocation placed in the client.
    17  //
    18  // ex. clientStaus: jobClientStatus('nodes', 'job'),
    19  export default function jobClientStatus(nodesKey, jobKey) {
    20    return computed(
    21      `${nodesKey}.[]`,
    22      `${jobKey}.{datacenters,status,allocations.@each.clientStatus,taskGroups}`,
    23      function () {
    24        const job = this.get(jobKey);
    25        const nodes = this.get(nodesKey);
    26  
    27        // Filter nodes by the datacenters defined in the job.
    28        const filteredNodes = nodes.filter((n) => {
    29          return job.datacenters.indexOf(n.datacenter) >= 0;
    30        });
    31  
    32        if (job.status === 'pending') {
    33          return allQueued(filteredNodes);
    34        }
    35  
    36        // Group the job allocations by the ID of the client that is running them.
    37        const allocsByNodeID = {};
    38        job.allocations.forEach((a) => {
    39          const nodeId = a.belongsTo('node').id();
    40          if (!allocsByNodeID[nodeId]) {
    41            allocsByNodeID[nodeId] = [];
    42          }
    43          allocsByNodeID[nodeId].push(a);
    44        });
    45  
    46        const result = {
    47          byNode: {},
    48          byStatus: {},
    49          totalNodes: filteredNodes.length,
    50        };
    51        filteredNodes.forEach((n) => {
    52          const status = jobStatus(allocsByNodeID[n.id], job.taskGroups.length);
    53          result.byNode[n.id] = status;
    54  
    55          if (!result.byStatus[status]) {
    56            result.byStatus[status] = [];
    57          }
    58          result.byStatus[status].push(n.id);
    59        });
    60        result.byStatus = canonicalizeStatus(result.byStatus);
    61        return result;
    62      }
    63    );
    64  }
    65  
    66  function allQueued(nodes) {
    67    const nodeIDs = nodes.map((n) => n.id);
    68    return {
    69      byNode: Object.fromEntries(nodeIDs.map((id) => [id, 'queued'])),
    70      byStatus: canonicalizeStatus({ queued: nodeIDs }),
    71      totalNodes: nodes.length,
    72    };
    73  }
    74  
    75  // canonicalizeStatus makes sure all possible statuses are present in the final
    76  // returned object. Statuses missing from the input will be assigned an emtpy
    77  // array.
    78  function canonicalizeStatus(status) {
    79    for (let i = 0; i < STATUS.length; i++) {
    80      const s = STATUS[i];
    81      if (!status[s]) {
    82        status[s] = [];
    83      }
    84    }
    85    return status;
    86  }
    87  
    88  // jobStatus computes the aggregated status of a job in a client.
    89  //
    90  // `allocs` are the list of allocations for a job that are placed in a specific
    91  // client.
    92  // `expected` is the number of allocations the client should have.
    93  function jobStatus(allocs, expected) {
    94    // The `pending` status has already been checked, so if at this point the
    95    // client doesn't have any allocations we assume that it was not considered
    96    // for scheduling for some reason.
    97    if (!allocs) {
    98      return 'notScheduled';
    99    }
   100  
   101    // If there are some allocations, but not how many we expected, the job is
   102    // considered `degraded` since it did fully run in this client.
   103    if (allocs.length < expected) {
   104      return 'degraded';
   105    }
   106  
   107    // Count how many allocations are in each `clientStatus` value.
   108    const summary = allocs
   109      .filter((a) => !a.isOld)
   110      .reduce((acc, a) => {
   111        const status = a.clientStatus;
   112        if (!acc[status]) {
   113          acc[status] = 0;
   114        }
   115        acc[status]++;
   116        return acc;
   117      }, {});
   118  
   119    // Theses statuses are considered terminal, i.e., an allocation will never
   120    // move from this status to another.
   121    // If all of the expected allocations are in one of these statuses, the job
   122    // as a whole is considered to be in the same status.
   123    const terminalStatuses = ['failed', 'lost', 'complete'];
   124    for (let i = 0; i < terminalStatuses.length; i++) {
   125      const s = terminalStatuses[i];
   126      if (summary[s] === expected) {
   127        return s;
   128      }
   129    }
   130  
   131    // It only takes one allocation to be in one of these statuses for the
   132    // entire job to be considered in a given status.
   133    if (summary['failed'] > 0 || summary['lost'] > 0) {
   134      return 'degraded';
   135    }
   136  
   137    if (summary['running'] > 0) {
   138      return 'running';
   139    }
   140  
   141    if (summary['unknown'] > 0) {
   142      return 'unknown';
   143    }
   144  
   145    return 'starting';
   146  }