github.com/hernad/nomad@v1.6.112/ui/app/components/job-status/panel/steady.js (about)

     1  /**
     2   * Copyright (c) HashiCorp, Inc.
     3   * SPDX-License-Identifier: MPL-2.0
     4   */
     5  
     6  // @ts-check
     7  import Component from '@glimmer/component';
     8  import { alias } from '@ember/object/computed';
     9  import { jobAllocStatuses } from '../../../utils/allocation-client-statuses';
    10  
    11  export default class JobStatusPanelSteadyComponent extends Component {
    12    @alias('args.job') job;
    13  
    14    get allocTypes() {
    15      return jobAllocStatuses[this.args.job.type].map((type) => {
    16        return {
    17          label: type,
    18        };
    19      });
    20    }
    21  
    22    /**
    23     * @typedef {Object} HealthStatus
    24     * @property {Array} nonCanary
    25     * @property {Array} canary
    26     */
    27  
    28    /**
    29     * @typedef {Object} AllocationStatus
    30     * @property {HealthStatus} healthy
    31     * @property {HealthStatus} unhealthy
    32     * @property {HealthStatus} health unknown
    33     */
    34  
    35    /**
    36     * @typedef {Object} AllocationBlock
    37     * @property {AllocationStatus} [running]
    38     * @property {AllocationStatus} [pending]
    39     * @property {AllocationStatus} [failed]
    40     * @property {AllocationStatus} [lost]
    41     * @property {AllocationStatus} [unplaced]
    42     * @property {AllocationStatus} [complete]
    43     */
    44  
    45    /**
    46     * Looks through running/pending allocations with the aim of filling up your desired number of allocations.
    47     * If any desired remain, it will walk backwards through job versions and other allocation types to build
    48     * a picture of the job's overall status.
    49     *
    50     * @returns {AllocationBlock} An object containing healthy non-canary allocations
    51     *                            for each clientStatus.
    52     */
    53    get allocBlocks() {
    54      let availableSlotsToFill = this.totalAllocs;
    55  
    56      // Initialize allocationsOfShowableType with empty arrays for each clientStatus
    57      /**
    58       * @type {AllocationBlock}
    59       */
    60      let allocationsOfShowableType = this.allocTypes.reduce(
    61        (accumulator, type) => {
    62          accumulator[type.label] = { healthy: { nonCanary: [] } };
    63          return accumulator;
    64        },
    65        {}
    66      );
    67  
    68      // First accumulate the Running/Pending allocations
    69      for (const alloc of this.job.allocations.filter(
    70        (a) => a.clientStatus === 'running' || a.clientStatus === 'pending'
    71      )) {
    72        if (availableSlotsToFill === 0) {
    73          break;
    74        }
    75  
    76        const status = alloc.clientStatus;
    77        allocationsOfShowableType[status].healthy.nonCanary.push(alloc);
    78        availableSlotsToFill--;
    79      }
    80  
    81      // Sort all allocs by jobVersion in descending order
    82      const sortedAllocs = this.args.job.allocations
    83        .filter(
    84          (a) => a.clientStatus !== 'running' && a.clientStatus !== 'pending'
    85        )
    86        .sort((a, b) => {
    87          // First sort by jobVersion
    88          if (a.jobVersion > b.jobVersion) return 1;
    89          if (a.jobVersion < b.jobVersion) return -1;
    90  
    91          // If jobVersion is the same, sort by status order
    92          if (a.jobVersion === b.jobVersion) {
    93            return (
    94              jobAllocStatuses[this.args.job.type].indexOf(b.clientStatus) -
    95              jobAllocStatuses[this.args.job.type].indexOf(a.clientStatus)
    96            );
    97          } else {
    98            return 0;
    99          }
   100        })
   101        .reverse();
   102  
   103      // Iterate over the sorted allocs
   104      for (const alloc of sortedAllocs) {
   105        if (availableSlotsToFill === 0) {
   106          break;
   107        }
   108  
   109        const status = alloc.clientStatus;
   110        // If the alloc has another clientStatus, add it to the corresponding list
   111        // as long as we haven't reached the totalAllocs limit for that clientStatus
   112        if (
   113          this.allocTypes.map(({ label }) => label).includes(status) &&
   114          allocationsOfShowableType[status].healthy.nonCanary.length <
   115            this.totalAllocs
   116        ) {
   117          allocationsOfShowableType[status].healthy.nonCanary.push(alloc);
   118          availableSlotsToFill--;
   119        }
   120      }
   121  
   122      // Handle unplaced allocs
   123      if (availableSlotsToFill > 0) {
   124        allocationsOfShowableType['unplaced'] = {
   125          healthy: {
   126            nonCanary: Array(availableSlotsToFill)
   127              .fill()
   128              .map(() => {
   129                return { clientStatus: 'unplaced' };
   130              }),
   131          },
   132        };
   133      }
   134  
   135      return allocationsOfShowableType;
   136    }
   137  
   138    get nodes() {
   139      return this.args.nodes;
   140    }
   141  
   142    get totalAllocs() {
   143      if (this.args.job.type === 'service' || this.args.job.type === 'batch') {
   144        return this.args.job.taskGroups.reduce((sum, tg) => sum + tg.count, 0);
   145      } else if (this.atMostOneAllocPerNode) {
   146        return this.args.job.allocations.uniqBy('nodeID').length;
   147      } else {
   148        return this.args.job.count; // TODO: this is probably not the correct totalAllocs count for any type.
   149      }
   150    }
   151  
   152    get totalNonCompletedAllocs() {
   153      return this.totalAllocs - this.completedAllocs.length;
   154    }
   155  
   156    get allAllocsComplete() {
   157      return this.completedAllocs.length && this.totalNonCompletedAllocs === 0;
   158    }
   159  
   160    get atMostOneAllocPerNode() {
   161      return this.args.job.type === 'system' || this.args.job.type === 'sysbatch';
   162    }
   163  
   164    get versions() {
   165      const versions = Object.values(this.allocBlocks)
   166        .flatMap((allocType) => Object.values(allocType))
   167        .flatMap((allocHealth) => Object.values(allocHealth))
   168        .flatMap((allocCanary) => Object.values(allocCanary))
   169        .map((a) => (!isNaN(a?.jobVersion) ? a.jobVersion : 'unknown')) // "starting" allocs, GC'd allocs, etc. do not have a jobVersion
   170        .sort((a, b) => a - b)
   171        .reduce((result, item) => {
   172          const existingVersion = result.find((v) => v.version === item);
   173          if (existingVersion) {
   174            existingVersion.allocations.push(item);
   175          } else {
   176            result.push({ version: item, allocations: [item] });
   177          }
   178          return result;
   179        }, []);
   180      return versions;
   181    }
   182  
   183    get rescheduledAllocs() {
   184      return this.job.allocations.filter((a) => !a.isOld && a.hasBeenRescheduled);
   185    }
   186  
   187    get restartedAllocs() {
   188      return this.job.allocations.filter((a) => !a.isOld && a.hasBeenRestarted);
   189    }
   190  
   191    get completedAllocs() {
   192      return this.job.allocations.filter(
   193        (a) => !a.isOld && a.clientStatus === 'complete'
   194      );
   195    }
   196  
   197    get supportsRescheduling() {
   198      return this.job.type !== 'system';
   199    }
   200  
   201    get latestVersionAllocations() {
   202      return this.job.allocations.filter((a) => !a.isOld);
   203    }
   204  
   205    /**
   206     * @typedef {Object} CurrentStatus
   207     * @property {"Healthy"|"Failed"|"Degraded"|"Recovering"|"Complete"|"Running"} label - The current status of the job
   208     * @property {"highlight"|"success"|"warning"|"critical"} state -
   209     */
   210  
   211    /**
   212     * A general assessment for how a job is going, in a non-deployment state
   213     * @returns {CurrentStatus}
   214     */
   215    get currentStatus() {
   216      // If all allocs are running, the job is Healthy
   217      const totalAllocs = this.totalAllocs;
   218  
   219      if (this.job.type === 'batch' || this.job.type === 'sysbatch') {
   220        // If all the allocs are complete, the job is Complete
   221        const completeAllocs = this.allocBlocks.complete?.healthy?.nonCanary;
   222        if (completeAllocs?.length === totalAllocs) {
   223          return { label: 'Complete', state: 'success' };
   224        }
   225  
   226        // If any allocations are running the job is "Running"
   227        const healthyAllocs = this.allocBlocks.running?.healthy?.nonCanary;
   228        if (healthyAllocs?.length + completeAllocs?.length === totalAllocs) {
   229          return { label: 'Running', state: 'success' };
   230        }
   231      }
   232  
   233      const healthyAllocs = this.allocBlocks.running?.healthy?.nonCanary;
   234      if (healthyAllocs?.length === totalAllocs) {
   235        return { label: 'Healthy', state: 'success' };
   236      }
   237  
   238      // If any allocations are pending the job is "Recovering"
   239      const pendingAllocs = this.allocBlocks.pending?.healthy?.nonCanary;
   240      if (pendingAllocs?.length > 0) {
   241        return { label: 'Recovering', state: 'highlight' };
   242      }
   243  
   244      // If any allocations are failed, lost, or unplaced in a steady state, the job is "Degraded"
   245      const failedOrLostAllocs = [
   246        ...this.allocBlocks.failed?.healthy?.nonCanary,
   247        ...this.allocBlocks.lost?.healthy?.nonCanary,
   248        ...this.allocBlocks.unplaced?.healthy?.nonCanary,
   249      ];
   250  
   251      if (failedOrLostAllocs.length === totalAllocs) {
   252        return { label: 'Failed', state: 'critical' };
   253      } else {
   254        return { label: 'Degraded', state: 'warning' };
   255      }
   256    }
   257  }