github.com/hernad/nomad@v1.6.112/ui/app/models/node.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 { equal } from '@ember/object/computed'; 8 import Model from '@ember-data/model'; 9 import { attr } from '@ember-data/model'; 10 import { hasMany } from '@ember-data/model'; 11 import { fragment, fragmentArray } from 'ember-data-model-fragments/attributes'; 12 import RSVP from 'rsvp'; 13 import shortUUIDProperty from '../utils/properties/short-uuid'; 14 import ipParts from '../utils/ip-parts'; 15 import classic from 'ember-classic-decorator'; 16 17 @classic 18 export default class Node extends Model { 19 // Available from list response 20 @attr('string') name; 21 @attr('string') datacenter; 22 @attr('string') nodeClass; 23 @attr('boolean') isDraining; 24 @attr('string') schedulingEligibility; 25 @attr('string') status; 26 @attr('string') statusDescription; 27 @shortUUIDProperty('id') shortId; 28 @attr('number') modifyIndex; 29 @attr('string') version; 30 @attr('string') nodePool; 31 32 // Available from single response 33 @attr('string') httpAddr; 34 @attr('boolean') tlsEnabled; 35 @fragment('structured-attributes') attributes; 36 @fragment('structured-attributes') meta; 37 @fragment('resources') resources; 38 @fragment('resources') reserved; 39 @fragment('drain-strategy') drainStrategy; 40 41 @equal('schedulingEligibility', 'eligible') isEligible; 42 43 @computed('httpAddr') 44 get address() { 45 return ipParts(this.httpAddr).address; 46 } 47 48 @computed('httpAddr') 49 get port() { 50 return ipParts(this.httpAddr).port; 51 } 52 53 @computed('httpAddr') 54 get isPartial() { 55 return this.httpAddr == null; 56 } 57 58 @hasMany('allocations', { inverse: 'node' }) allocations; 59 60 @computed('allocations.@each.clientStatus') 61 get completeAllocations() { 62 return this.allocations.filterBy('clientStatus', 'complete'); 63 } 64 65 @computed('allocations.@each.isRunning') 66 get runningAllocations() { 67 return this.allocations.filterBy('isRunning'); 68 } 69 70 @computed('allocations.@each.{isMigrating,isRunning}') 71 get migratingAllocations() { 72 return this.allocations.filter( 73 (alloc) => alloc.isRunning && alloc.isMigrating 74 ); 75 } 76 77 @computed('allocations.@each.{isMigrating,isRunning,modifyTime}') 78 get lastMigrateTime() { 79 const allocation = this.allocations 80 .filterBy('isRunning', false) 81 .filterBy('isMigrating') 82 .sortBy('modifyTime') 83 .reverse()[0]; 84 if (allocation) { 85 return allocation.modifyTime; 86 } 87 88 return undefined; 89 } 90 91 @fragmentArray('node-driver') drivers; 92 @fragmentArray('node-event') events; 93 @fragmentArray('host-volume') hostVolumes; 94 95 @computed('drivers.@each.detected') 96 get detectedDrivers() { 97 return this.drivers.filterBy('detected'); 98 } 99 100 @computed('detectedDrivers.@each.healthy') 101 get unhealthyDrivers() { 102 return this.detectedDrivers.filterBy('healthy', false); 103 } 104 105 @computed('unhealthyDrivers.@each.name') 106 get unhealthyDriverNames() { 107 return this.unhealthyDrivers.mapBy('name'); 108 } 109 110 // A status attribute that includes states not included in node status. 111 // Useful for coloring and sorting nodes 112 @computed('isDraining', 'isEligible', 'status') 113 get compositeStatus() { 114 if (this.status === 'down') { 115 return 'down'; 116 } else if (this.isDraining) { 117 return 'draining'; 118 } else if (!this.isEligible) { 119 return 'ineligible'; 120 } else { 121 return this.status; 122 } 123 } 124 125 @computed('isDraining', 'isEligible', 'status') 126 get compositeStatusIcon() { 127 if (this.isDraining || !this.isEligible) { 128 return 'alert-circle-fill'; 129 } else if (this.status === 'down') { 130 return 'cancel-circle-fill'; 131 } else if (this.status === 'initializing') { 132 return 'node-init-circle-fill'; 133 } 134 return 'check-circle-fill'; 135 } 136 137 setEligible() { 138 if (this.isEligible) return RSVP.resolve(); 139 // Optimistically update schedulingEligibility for immediate feedback 140 this.set('schedulingEligibility', 'eligible'); 141 return this.store.adapterFor('node').setEligible(this); 142 } 143 144 setIneligible() { 145 if (!this.isEligible) return RSVP.resolve(); 146 // Optimistically update schedulingEligibility for immediate feedback 147 this.set('schedulingEligibility', 'ineligible'); 148 return this.store.adapterFor('node').setIneligible(this); 149 } 150 151 drain(drainSpec) { 152 return this.store.adapterFor('node').drain(this, drainSpec); 153 } 154 155 forceDrain(drainSpec) { 156 return this.store.adapterFor('node').forceDrain(this, drainSpec); 157 } 158 159 cancelDrain() { 160 return this.store.adapterFor('node').cancelDrain(this); 161 } 162 163 async addMeta(newMeta) { 164 let metaResponse = await this.store 165 .adapterFor('node') 166 .addMeta(this, newMeta); 167 168 if (!this.meta) { 169 this.set('meta', this.store.createFragment('structured-attributes')); 170 } 171 172 this.meta.recomputeRawProperties(metaResponse.Meta); 173 return metaResponse; 174 } 175 }