github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/mirage/factories/task-group.js (about) 1 import { Factory, trait } from 'ember-cli-mirage'; 2 import faker from 'nomad-ui/mirage/faker'; 3 import { provide } from '../utils'; 4 import { generateResources } from '../common'; 5 import { dasherize } from '@ember/string'; 6 7 const DISK_RESERVATIONS = [200, 500, 1000, 2000, 5000, 10000, 100000]; 8 9 export default Factory.extend({ 10 name: (id) => `${dasherize(faker.hacker.noun())}-g-${id}`, 11 count: () => faker.random.number({ min: 1, max: 2 }), 12 13 ephemeralDisk: () => ({ 14 Sticky: faker.random.boolean(), 15 SizeMB: faker.helpers.randomize(DISK_RESERVATIONS), 16 Migrate: faker.random.boolean(), 17 }), 18 19 noHostVolumes: trait({ 20 volumes: () => ({}), 21 }), 22 23 withScaling: faker.random.boolean, 24 25 volumes: makeHostVolumes(), 26 27 // Directive used to control whether or not allocations are automatically 28 // created. 29 createAllocations: true, 30 31 // Directived used to control whether or not the allocation should fail 32 // and reschedule, creating reschedule events. 33 withRescheduling: false, 34 35 // Directive used to control whether the task group should have services. 36 withServices: false, 37 38 // Whether the tasks themselves should have services. 39 withTaskServices: false, 40 41 // Directive used to control whether dynamic application sizing recommendations 42 // should be created. 43 createRecommendations: false, 44 45 // When true, only creates allocations 46 shallow: false, 47 48 // When set, passed into tasks to set resource values 49 resourceSpec: null, 50 51 afterCreate(group, server) { 52 let taskIds = []; 53 let volumes = Object.keys(group.volumes); 54 55 if (group.withScaling) { 56 group.update({ 57 scaling: { 58 Min: 1, 59 Max: 5, 60 Policy: faker.random.boolean() && { 61 EvaluationInterval: '10s', 62 Cooldown: '2m', 63 Check: { 64 avg_conn: { 65 Source: 'prometheus', 66 Query: 67 'scalar(avg((haproxy_server_current_sessions{backend="http_back"}) and (haproxy_server_up{backend="http_back"} == 1)))', 68 Strategy: { 69 'target-value': { 70 target: 20, 71 }, 72 }, 73 }, 74 }, 75 }, 76 }, 77 }); 78 } 79 80 if (!group.shallow) { 81 const resources = 82 group.resourceSpec && 83 divide(group.count, parseResourceSpec(group.resourceSpec)); 84 const tasks = provide(group.count, (_, idx) => { 85 const mounts = faker.helpers 86 .shuffle(volumes) 87 .slice(0, faker.random.number({ min: 1, max: 3 })); 88 89 const maybeResources = {}; 90 if (resources) { 91 maybeResources.originalResources = generateResources(resources[idx]); 92 } 93 return server.create('task', { 94 taskGroupID: group.id, 95 ...maybeResources, 96 withServices: group.withTaskServices, 97 volumeMounts: mounts.map((mount) => ({ 98 Volume: mount, 99 Destination: `/${faker.internet.userName()}/${faker.internet.domainWord()}/${faker.internet.color()}`, 100 PropagationMode: '', 101 ReadOnly: faker.random.boolean(), 102 })), 103 createRecommendations: group.createRecommendations, 104 }); 105 }); 106 taskIds = tasks.mapBy('id'); 107 } 108 109 group.update({ 110 taskIds: taskIds, 111 }); 112 113 if (group.createAllocations) { 114 Array(group.count) 115 .fill(null) 116 .forEach((_, i) => { 117 const props = { 118 jobId: group.job.id, 119 namespace: group.job.namespace, 120 taskGroup: group.name, 121 name: `${group.name}.[${i}]`, 122 rescheduleSuccess: group.withRescheduling 123 ? faker.random.boolean() 124 : null, 125 rescheduleAttempts: group.withRescheduling 126 ? faker.random.number({ min: 1, max: 5 }) 127 : 0, 128 }; 129 130 if (group.withRescheduling) { 131 server.create('allocation', 'rescheduled', props); 132 } else { 133 server.create('allocation', props); 134 } 135 }); 136 } 137 138 if (group.withServices) { 139 const services = server.createList('service-fragment', 5, { 140 taskGroupId: group.id, 141 taskGroup: group, 142 provider: 'nomad', 143 }); 144 145 services.push( 146 server.create('service-fragment', { 147 taskGroupId: group.id, 148 taskGroup: group, 149 provider: 'consul', 150 }) 151 ); 152 153 services.forEach((fragment) => { 154 server.create('service', { 155 serviceName: fragment.name, 156 id: `${faker.internet.domainWord()}-group-${fragment.name}`, 157 }); 158 server.create('service', { 159 serviceName: fragment.name, 160 id: `${faker.internet.domainWord()}-group-${fragment.name}`, 161 }); 162 server.create('service', { 163 serviceName: fragment.name, 164 id: `${faker.internet.domainWord()}-group-${fragment.name}`, 165 }); 166 }); 167 168 group.update({ 169 services, 170 }); 171 } 172 }, 173 }); 174 175 function makeHostVolumes() { 176 const generate = () => ({ 177 Name: faker.internet.domainWord(), 178 Type: 'host', 179 Source: faker.internet.domainWord(), 180 ReadOnly: faker.random.boolean(), 181 }); 182 183 const volumes = provide(faker.random.number({ min: 1, max: 5 }), generate); 184 return volumes.reduce((hash, volume) => { 185 hash[volume.Name] = volume; 186 return hash; 187 }, {}); 188 } 189 190 function parseResourceSpec(spec) { 191 const mapping = { 192 M: 'MemoryMB', 193 C: 'CPU', 194 D: 'DiskMB', 195 I: 'IOPS', 196 }; 197 198 const terms = spec.split(',').map((t) => { 199 const [k, v] = t 200 .trim() 201 .split(':') 202 .map((kv) => kv.trim()); 203 return [k, +v]; 204 }); 205 206 return terms.reduce((hash, term) => { 207 hash[mapping[term[0]]] = term[1]; 208 return hash; 209 }, {}); 210 } 211 212 // Split a single resources object into N resource objects where 213 // the sum of each property of the new resources objects equals 214 // the original resources properties 215 // ex: divide(2, { Mem: 400, Cpu: 250 }) -> [{ Mem: 80, Cpu: 50 }, { Mem: 320, Cpu: 200 }] 216 function divide(count, resources) { 217 const wheel = roulette(1, count); 218 219 const ret = provide(count, (_, idx) => { 220 return Object.keys(resources).reduce((hash, key) => { 221 hash[key] = Math.round(resources[key] * wheel[idx]); 222 return hash; 223 }, {}); 224 }); 225 226 return ret; 227 } 228 229 // Roulette splits a number into N divisions 230 // Variance is a value between 0 and 1 that determines how much each division in 231 // size. At 0 each division is even, at 1, it's entirely random but the sum of all 232 // divisions is guaranteed to equal the total value. 233 function roulette(total, divisions, variance = 0.8) { 234 let roulette = new Array(divisions).fill(total / divisions); 235 roulette.forEach((v, i) => { 236 if (i === roulette.length - 1) return; 237 roulette.splice( 238 i, 239 2, 240 ...rngDistribute(roulette[i], roulette[i + 1], variance) 241 ); 242 }); 243 return roulette; 244 } 245 246 function rngDistribute(a, b, variance = 0.8) { 247 const move = 248 a * faker.random.number({ min: 0, max: variance, precision: 0.01 }); 249 return [a - move, b + move]; 250 }