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  }