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