open-nomad/ui/mirage/factories/job.js
Phil Renaud e03c5a43be Job Services: fixtures and acceptance tests (#14319)
* Added to subnav and basic table implemented

* Existing services become service fragments, and services tab aggregated beneath job route

* Index page within jobs/job/services

* Watchable services

* Lintfixes

* Links to clients and individual services set up

* Child service route

* Keyboard shortcuts on service page

* Model that shows consul services as well, plus level and provider cols

* lintfix

* Level as query param

* Watch job for service name changes too

* Group level service fixtures established

* Progress at task level and job-linked services

* Task and group services on update

* Fixture side-effect cleanup

* Basic acceptance tests for job services

* Testmodel cleanup

* Disabled mirage logging

* New cluster type specifically for services

* Without explicit job-model binding

* Trying to isolate a tostring error

* Account for new tab in keyboardnav

* More test isolation attempts

* Remove skipped tests and link task to parent group by id

ui: add service health viz to table (#14369)

* ui: add service-status-bar

* test: service-status-bar

* refact: update component api for new data struct

* ui: format service health struct

* ui:  add service health viz to table

* temp: add placeholder to remind conditional watcher

* test: write tests for transformation algorithm

* refact: update transformation algo

* ui: conditionally long poll checks endpoint

* refact: add conditional logic for nomad provider

refact: update service-fragment model to include owner info

ui: differentiate between task and group-level in derived state comp

test: add test to document behavior

refact: update tests for api change

refact: update integration test for API change

chore: remove unsused vars

chore: elvis operator to protect mirage

refact: create refId instead of internalModel

refact: update algo

refact: update conditional template logic

refact: update test for api change:

chore: cant use if and not in hbs conditional
2022-09-07 10:24:33 -04:00

379 lines
10 KiB
JavaScript

import { assign } from '@ember/polyfills';
import { Factory, trait } from 'ember-cli-mirage';
import faker from 'nomad-ui/mirage/faker';
import { provide, pickOne } from '../utils';
import { DATACENTERS } from '../common';
import { dasherize } from '@ember/string';
const REF_TIME = new Date();
const JOB_PREFIXES = provide(5, faker.hacker.abbreviation);
const JOB_TYPES = ['service', 'batch', 'system', 'sysbatch'];
const JOB_STATUSES = ['pending', 'running', 'dead'];
export default Factory.extend({
id(i) {
if (this.parameterized && this.parentId) {
const shortUUID = faker.random.uuid().split('-')[0];
const dispatchId = `dispatch-${this.submitTime / 1000}-${shortUUID}`;
return `${this.parentId}/${dispatchId}`;
}
return `${faker.helpers.randomize(JOB_PREFIXES)}-${dasherize(
faker.hacker.noun()
)}-${i}`.toLowerCase();
},
name() {
return this.id;
},
version: 1,
submitTime: () => faker.date.past(2 / 365, REF_TIME) * 1000000,
// When provided, the resourceSpec will inform how many task groups to create
// and how much of each resource that task group reserves.
//
// One task group, 256 MiB memory and 500 MHz cpu
// resourceSpec: ['M: 256, C: 500']
//
// Two task groups
// resourceSpec: ['M: 256, C: 500', 'M: 1024, C: 1200']
resourceSpec: null,
groupsCount() {
return this.resourceSpec
? this.resourceSpec.length
: faker.random.number({ min: 1, max: 2 });
},
region: () => 'global',
type: () => faker.helpers.randomize(JOB_TYPES),
priority: () => faker.random.number(100),
allAtOnce: faker.random.boolean,
status: () => faker.helpers.randomize(JOB_STATUSES),
datacenters: () =>
faker.helpers
.shuffle(DATACENTERS)
.slice(0, faker.random.number({ min: 1, max: 4 })),
childrenCount: () => faker.random.number({ min: 1, max: 2 }),
meta: null,
periodic: trait({
type: 'batch',
periodic: true,
// periodic details object
// serializer update for bool vs details object
periodicDetails: () => ({
Enabled: true,
ProhibitOverlap: true,
Spec: '*/5 * * * * *',
SpecType: 'cron',
TimeZone: 'UTC',
}),
}),
periodicSysbatch: trait({
type: 'sysbatch',
periodic: true,
// periodic details object
// serializer update for bool vs details object
periodicDetails: () => ({
Enabled: true,
ProhibitOverlap: true,
Spec: '*/5 * * * * *',
SpecType: 'cron',
TimeZone: 'UTC',
}),
}),
parameterized: trait({
type: 'batch',
parameterized: true,
// parameterized job object
// serializer update for bool vs details object
parameterizedJob: () => ({
MetaOptional: generateMetaFields(faker.random.number(10), 'optional'),
MetaRequired: generateMetaFields(faker.random.number(10), 'required'),
Payload: faker.random.boolean() ? 'required' : null,
}),
}),
parameterizedSysbatch: trait({
type: 'sysbatch',
parameterized: true,
// parameterized job object
// serializer update for bool vs details object
parameterizedJob: () => ({
MetaOptional: generateMetaFields(faker.random.number(10), 'optional'),
MetaRequired: generateMetaFields(faker.random.number(10), 'required'),
Payload: faker.random.boolean() ? 'required' : null,
}),
}),
periodicChild: trait({
// Periodic children need a parent job,
// It is the Periodic job's responsibility to create
// periodicChild jobs and provide a parent job.
type: 'batch',
}),
periodicSysbatchChild: trait({
// Periodic children need a parent job,
// It is the Periodic job's responsibility to create
// periodicChild jobs and provide a parent job.
type: 'sysbatch',
}),
parameterizedChild: trait({
// Parameterized children need a parent job,
// It is the Parameterized job's responsibility to create
// parameterizedChild jobs and provide a parent job.
type: 'batch',
parameterized: true,
dispatched: true,
payload: window.btoa(faker.lorem.sentence()),
}),
parameterizedSysbatchChild: trait({
// Parameterized children need a parent job,
// It is the Parameterized job's responsibility to create
// parameterizedChild jobs and provide a parent job.
type: 'sysbatch',
parameterized: true,
dispatched: true,
payload: window.btoa(faker.lorem.sentence()),
}),
pack: trait({
meta: () => ({
'pack.name': faker.hacker.noun(),
'pack.version': faker.system.semver(),
}),
}),
createIndex: (i) => i,
modifyIndex: () => faker.random.number({ min: 10, max: 2000 }),
// Directive used to control sub-resources
// When false, no allocations are made
createAllocations: true,
// When true, deployments for the job will never have a 'running' status
noActiveDeployment: false,
// When true, deployments for the job will always have a 'running' status
activeDeployment: false,
// When true, the job will have no versions or deployments (and in turn no latest deployment)
noDeployments: false,
// When true, an evaluation with a high modify index and placement failures is created
failedPlacements: false,
// When true, no evaluations have failed placements
noFailedPlacements: false,
// When true, all task groups get the noHostVolumes trait
noHostVolumes: false,
// When true, allocations for this job will fail and reschedule, randomly succeeding or not
withRescheduling: false,
// When true, task groups will have services
withGroupServices: false,
// When true, tasks will have services
withTaskServices: false,
// When true, dynamic application sizing recommendations will be made
createRecommendations: false,
// When true, only task groups and allocations are made
shallow: false,
afterCreate(job, server) {
if (!job.namespaceId) {
const namespace = server.db.namespaces.length
? pickOne(server.db.namespaces).id
: null;
job.update({
namespace,
namespaceId: namespace,
});
} else {
job.update({
namespace: job.namespaceId,
});
}
const groupProps = {
job,
createAllocations: job.createAllocations,
withRescheduling: job.withRescheduling,
withServices: job.withGroupServices,
withTaskServices: job.withTaskServices,
createRecommendations: job.createRecommendations,
shallow: job.shallow,
};
if (job.groupTaskCount) {
groupProps.count = job.groupTaskCount;
}
let groups;
if (job.noHostVolumes) {
groups = provide(job.groupsCount, (_, idx) =>
server.create('task-group', 'noHostVolumes', {
...groupProps,
resourceSpec:
job.resourceSpec &&
job.resourceSpec.length &&
job.resourceSpec[idx],
})
);
} else {
groups = provide(job.groupsCount, (_, idx) =>
server.create('task-group', {
...groupProps,
resourceSpec:
job.resourceSpec &&
job.resourceSpec.length &&
job.resourceSpec[idx],
})
);
}
job.update({
taskGroupIds: groups.mapBy('id'),
});
const hasChildren = job.periodic || (job.parameterized && !job.parentId);
const jobSummary = server.create(
'job-summary',
hasChildren ? 'withChildren' : 'withSummary',
{
jobId: job.id,
groupNames: groups.mapBy('name'),
namespace: job.namespace,
}
);
job.update({
jobSummaryId: jobSummary.id,
});
const jobScale = server.create('job-scale', {
groupNames: groups.mapBy('name'),
jobId: job.id,
namespace: job.namespace,
shallow: job.shallow,
});
job.update({
jobScaleId: jobScale.id,
});
if (!job.noDeployments) {
Array(faker.random.number({ min: 1, max: 3 }))
.fill(null)
.map((_, index) => {
return server.create('job-version', {
job,
namespace: job.namespace,
version: index,
noActiveDeployment: job.noActiveDeployment,
activeDeployment: job.activeDeployment,
});
});
}
if (!job.shallow) {
const knownEvaluationProperties = {
jobId: job.id,
namespace: job.namespace,
};
server.createList(
'evaluation',
faker.random.number({ min: 1, max: 5 }),
knownEvaluationProperties
);
if (!job.noFailedPlacements) {
server.createList(
'evaluation',
faker.random.number(3),
'withPlacementFailures',
knownEvaluationProperties
);
}
if (job.failedPlacements) {
server.create(
'evaluation',
'withPlacementFailures',
assign(knownEvaluationProperties, {
modifyIndex: 4000,
})
);
}
}
if (job.periodic) {
let childType;
switch (job.type) {
case 'batch':
childType = 'periodicChild';
break;
case 'sysbatch':
childType = 'periodicSysbatchChild';
break;
}
// Create child jobs
server.createList('job', job.childrenCount, childType, {
parentId: job.id,
namespaceId: job.namespaceId,
namespace: job.namespace,
datacenters: job.datacenters,
createAllocations: job.createAllocations,
shallow: job.shallow,
});
}
if (job.parameterized && !job.parentId) {
let childType;
switch (job.type) {
case 'batch':
childType = 'parameterizedChild';
break;
case 'sysbatch':
childType = 'parameterizedSysbatchChild';
break;
}
// Create child jobs
server.createList('job', job.childrenCount, childType, {
parentId: job.id,
namespaceId: job.namespaceId,
namespace: job.namespace,
datacenters: job.datacenters,
createAllocations: job.createAllocations,
shallow: job.shallow,
});
}
},
});
function generateMetaFields(num, prefix = '') {
// Use an object to avoid duplicate meta fields.
// The prefix param helps to avoid duplicate entries across function calls.
let meta = {};
for (let i = 0; i < num; i++) {
const field = `${prefix}-${faker.hacker.noun()}`;
meta[field] = true;
}
return Object.keys(meta);
}