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 }