Files
nomad/ui/mirage/scenarios/default.js
Phil Renaud e1e59455e4 [ui, feature] Job Page Redesign (#16932)
* [ui] Service job status panel (#16134)

* it begins

* Hacky demo enabled

* Still very hacky but seems deece

* Floor of at least 3 must be shown

* Width from on-high

* Other statuses considered

* More sensible allocTypes listing

* Beginnings of a legend

* Total number of allocs running now maps over job.groups

* Lintfix

* base the number of slots to hold open on actual tallies, which should never exceed totalAllocs

* Versions get yer versions here

* Versions lookin like versions

* Mirage fixup

* Adds Remaining as an alloc chart status and adds historical status option

* Get tests passing again by making job status static for a sec

* Historical status panel click actions moved into their own component class

* job detail tests plz chill

* Testing if percy is fickle

* Hyper-specfic on summary distribution bar identifier

* Perhaps the 2nd allocSummary item no longer exists with the more accurate afterCreate data

* UI Test eschewing the page pattern

* Bones of a new acceptance test

* Track width changes explicitly with window-resize

* testlintfix

* Alloc counting tests

* Alloc grouping test

* Alloc grouping with complex resizing

* Refined the list of showable statuses

* PR feedback addressed

* renamed allocation-row to allocation-status-row

* [ui, job status] Make panel status mode a queryParam (#16345)

* queryParam changing

* Test for QP in panel

* Adding @tracked to legacy controller

* Move the job of switching to Historical out to larger context

* integration test mock passed func

* [ui] Service job deployment status panel (#16383)

* A very fast and loose deployment panel

* Removing Unknown status from the panel

* Set up oldAllocs list in constructor, rather than as a getter/tracked var

* Small amount of template cleanup

* Refactored latest-deployment new logic back into panel.js

* Revert now-unused latest-deployment component

* margin bottom when ungrouped also

* Basic integration tests for job deployment status panel

* Updates complete alloc colour to green for new visualizations only (#16618)

* Updates complete alloc colour to green for new visualizations only

* Pale green instead of dark green for viz in general

* [ui] Job Deployment Status: History and Update Props (#16518)

* Deployment history wooooooo

* Styled deployment history

* Update Params

* lintfix

* Types and groups for updateParams

* Live-updating history

* Harden with types, error states, and pending states

* Refactor updateParams to use trigger component

* [ui] Deployment History search (#16608)

* Functioning searchbox

* Some nice animations for history items

* History search test

* Fixing up some old mirage conventions

* some a11y rule override to account for scss keyframes

* Split panel into deploying and steady components

* HandleError passed from job index

* gridified panel elements

* TotalAllocs added to deploying.js

* Width perc to px

* [ui] Splitting deployment allocs by status, health, and canary status (#16766)

* Initial attempt with lots of scratchpad work

* Style mods per UI discussion

* Fix canary overflow bug

* Dont show canary or health for steady/prev-alloc blocks

* Steady state

* Thanks Julie

* Fixes steady-state versions

* Legen, wait for it...

* Test fixes now that we have a minimum block size

* PR prep

* Shimmer effect on pending and unplaced allocs (#16801)

* Shimmer effect on pending and unplaced

* Dont show animation in the legend

* [ui, deployments] Linking allocblocks and legends to allocation / allocations index routes (#16821)

* Conditional link-to component and basic linking to allocations and allocation routes

* Job versions filter added to allocations index page

* Steady state legends link

* Legend links

* Badge count links for versions

* Fix: faded class on steady-state legend items

* version link now wont show completed ones

* Fix a11y violations with link labels

* Combining some template conditional logic

* [ui, deployments] Conversions on long nanosecond update params (#16882)

* Conversions on long nanosecond nums

* Early return in updateParamGroups comp prop

* [ui, deployments] Mirage Actively Deploying Job and Deployment Integration Tests (#16888)

* Start of deployment alloc test scaffolding

* Bit of test cleanup and canary for ungrouped allocs

* Flakey but more robust integrations for deployment panel

* De-flake acceptance tests and add an actively deploying job to mirage

* Jitter-less alloc status distribution removes my bad math

* bugfix caused by summary.desiredTotal non-null

* More interesting mirage active deployment alloc breakdown

* Further tests for previous-allocs row

* Previous alloc legend tests

* Percy snapshots added to integration test

* changelog
2023-04-24 22:45:39 -04:00

689 lines
18 KiB
JavaScript

/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { assign } from '@ember/polyfills';
import config from 'nomad-ui/config/environment';
import * as topoScenarios from './topo';
import * as sysbatchScenarios from './sysbatch';
import { pickOne } from '../utils';
import faker from 'nomad-ui/mirage/faker';
const withNamespaces = getConfigValue('mirageWithNamespaces', false);
const withTokens = getConfigValue('mirageWithTokens', true);
const withRegions = getConfigValue('mirageWithRegions', false);
export const allScenarios = {
smallCluster,
mediumCluster,
largeCluster,
massiveCluster,
allJobTypes,
allNodeTypes,
everyFeature,
emptyCluster,
variableTestCluster,
servicesTestCluster,
policiesTestCluster,
...topoScenarios,
...sysbatchScenarios,
};
const scenario =
getScenarioQueryParameter() ||
getConfigValue('mirageScenario', 'emptyCluster');
export default function (server) {
const activeScenario = allScenarios[scenario];
if (!activeScenario) {
throw new Error(
`Selected Mirage scenario does not exist.\n\n${scenario} not in list: \n\n\t${Object.keys(
allScenarios
).join('\n\t')}`
);
}
if (withNamespaces) createNamespaces(server);
if (withTokens) createTokens(server);
if (withRegions) createRegions(server);
activeScenario(server);
}
// Scenarios
function smallCluster(server) {
faker.seed(1);
server.create('feature', { name: 'Dynamic Application Sizing' });
server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
server.createList('node', 5);
server.create(
'node',
{
name: 'node-with-meta',
meta: { foo: 'bar', baz: 'qux' },
},
'withMeta'
);
server.createList('job', 1, { createRecommendations: true });
server.create('job', {
withGroupServices: true,
withTaskServices: true,
name: 'Service-haver',
id: 'service-haver',
namespaceId: 'default',
});
server.create('job', {
createAllocations: true,
groupTaskCount: 150,
shallow: true,
allocStatusDistribution: {
running: 0.5,
failed: 0.05,
unknown: 0.2,
lost: 0.1,
complete: 0.1,
pending: 0.05,
},
name: 'mixed-alloc-job',
id: 'mixed-alloc-job',
namespaceId: 'default',
type: 'service',
activeDeployment: true,
});
//#region Active Deployment
const activelyDeployingJobGroups = 2;
const activelyDeployingTasksPerGroup = 100;
const activelyDeployingJob = server.create('job', {
createAllocations: true,
groupTaskCount: activelyDeployingTasksPerGroup,
shallow: true,
resourceSpec: Array(activelyDeployingJobGroups).fill(['M: 257, C: 500']),
noDeployments: true, // manually created below
activeDeployment: true,
allocStatusDistribution: {
running: 0.6,
failed: 0.05,
unknown: 0.05,
lost: 0,
complete: 0,
pending: 0.3,
},
name: 'actively-deploying-job',
id: 'actively-deploying-job',
namespaceId: 'default',
type: 'service',
});
server.create('deployment', false, 'active', {
jobId: activelyDeployingJob.id,
groupDesiredTotal: activelyDeployingTasksPerGroup,
versionNumber: 1,
status: 'running',
});
server.createList('allocation', 25, {
jobId: activelyDeployingJob.id,
jobVersion: 0,
clientStatus: 'running',
});
// Manipulate the above job to show a nice distribution of running, canary, etc. allocs
let activelyDeployingJobAllocs = server.schema.allocations
.all()
.filter((a) => a.jobId === activelyDeployingJob.id);
activelyDeployingJobAllocs.models
.filter((a) => a.clientStatus === 'running')
.slice(0, 10)
.forEach((a) =>
a.update({ deploymentStatus: { Healthy: false, Canary: true } })
);
activelyDeployingJobAllocs.models
.filter((a) => a.clientStatus === 'running')
.slice(10, 20)
.forEach((a) =>
a.update({ deploymentStatus: { Healthy: true, Canary: true } })
);
activelyDeployingJobAllocs.models
.filter((a) => a.clientStatus === 'running')
.slice(20, 65)
.forEach((a) =>
a.update({ deploymentStatus: { Healthy: true, Canary: false } })
);
activelyDeployingJobAllocs.models
.filter((a) => a.clientStatus === 'pending')
.slice(0, 10)
.forEach((a) =>
a.update({ deploymentStatus: { Healthy: true, Canary: true } })
);
//#endregion Active Deployment
server.createList('allocFile', 5);
server.create('allocFile', 'dir', { depth: 2 });
server.createList('csi-plugin', 2);
server.createList('variable', 3);
const variableLinkedJob = server.db.jobs[0];
const variableLinkedGroup = server.db.taskGroups.findBy({
jobId: variableLinkedJob.id,
});
const variableLinkedTask = server.db.tasks.findBy({
taskGroupId: variableLinkedGroup.id,
});
[
'a/b/c/foo0',
'a/b/c/bar1',
'a/b/c/d/e/foo2',
'a/b/c/d/e/bar3',
'a/b/c/d/e/f/foo4',
'a/b/c/d/e/f/g/foo5',
'a/b/c/x/y/z/foo6',
'a/b/c/x/y/z/bar7',
'a/b/c/x/y/z/baz8',
'w/x/y/foo9',
'w/x/y/z/foo10',
'w/x/y/z/bar11',
'just some arbitrary file',
'another arbitrary file',
'another arbitrary file again',
].forEach((path) => server.create('variable', { id: path }));
server.create('variable', {
id: `nomad/jobs/${variableLinkedJob.id}/${variableLinkedGroup.name}/${variableLinkedTask.name}`,
namespace: variableLinkedJob.namespace,
});
server.create('variable', {
id: `nomad/jobs/${variableLinkedJob.id}/${variableLinkedGroup.name}`,
namespace: variableLinkedJob.namespace,
});
server.create('variable', {
id: `nomad/jobs/${variableLinkedJob.id}`,
namespace: variableLinkedJob.namespace,
});
const newJobName = 'new-job';
const newJobTaskGroupName = 'redis';
const jsonJob = (overrides) => {
return JSON.stringify(
assign(
{},
{
Name: newJobName,
Namespace: 'default',
Datacenters: ['dc1'],
Priority: 50,
TaskGroups: [
{
Name: newJobTaskGroupName,
Tasks: [
{
Name: 'redis',
Driver: 'docker',
},
],
},
],
},
overrides
),
null,
2
);
};
server.create('variable', {
id: `nomad/job-templates/foo-bar`,
namespace: 'namespace-2',
Items: {
description: 'a description',
template: jsonJob(),
},
});
server.create('variable', {
id: `nomad/job-templates/baz-qud`,
namespace: 'default',
Items: {
description: 'another different description',
template: jsonJob(),
},
});
server.create('variable', {
id: 'Auto-conflicting Variable',
namespace: 'default',
});
// #region evaluations
// Branching: a single eval that relates to N-1 mutually-unrelated evals
const NUM_BRANCHING_EVALUATIONS = 3;
Array(NUM_BRANCHING_EVALUATIONS)
.fill()
.map((_, i) => {
return {
evaluation: server.create('evaluation', {
id: `branching_${i}`,
previousEval: i > 0 ? `branching_0` : '',
jobID: pickOne(server.db.jobs).id,
}),
evaluationStub: server.create('evaluation-stub', {
id: `branching_${i}`,
previousEval: i > 0 ? `branching_0` : '',
status: 'failed',
}),
};
})
.map((x, i, all) => {
x.evaluation.update({
relatedEvals:
i === 0
? all.filter((_, j) => j !== 0).map((e) => e.evaluation)
: all.filter((_, j) => j !== i).map((e) => e.evaluation),
});
return x;
});
// Linear: a long line of N related evaluations
const NUM_LINEAR_EVALUATIONS = 20;
Array(NUM_LINEAR_EVALUATIONS)
.fill()
.map((_, i) => {
return {
evaluation: server.create('evaluation', {
id: `linear_${i}`,
previousEval: i > 0 ? `linear_${i - 1}` : '',
jobID: pickOne(server.db.jobs).id,
}),
evaluationStub: server.create('evaluation-stub', {
id: `linear_${i}`,
previousEval: i > 0 ? `linear_${i - 1}` : '',
nextEval: `linear_${i + 1}`,
status: 'failed',
}),
};
})
.map((x, i, all) => {
x.evaluation.update({
relatedEvals: all.filter((_, j) => i !== j).map((e) => e.evaluation),
});
return x;
});
// #endregion evaluations
const csiAllocations = server.createList('allocation', 5);
const volumes = server.schema.csiVolumes.all().models;
csiAllocations.forEach((alloc) => {
const volume = pickOne(volumes);
volume.writeAllocs.add(alloc);
volume.readAllocs.add(alloc);
volume.save();
});
server.create('auth-method', { name: 'vault' });
server.create('auth-method', { name: 'auth0' });
server.create('auth-method', { name: 'cognito' });
server.create('auth-method', { name: 'JWT-Local', type: 'JWT' });
}
function mediumCluster(server) {
server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
server.createList('node', 50);
server.createList('job', 25);
}
function variableTestCluster(server) {
faker.seed(1);
createTokens(server);
createNamespaces(server);
server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
server.createList('node', 5);
server.createList('job', 3);
server.createList('variable', 3);
// server.createList('allocFile', 5);
// server.create('allocFile', 'dir', { depth: 2 });
// server.createList('csi-plugin', 2);
const variableLinkedJob = server.db.jobs[0];
const variableLinkedGroup = server.db.taskGroups.findBy({
jobId: variableLinkedJob.id,
});
const variableLinkedTask = server.db.tasks.findBy({
taskGroupId: variableLinkedGroup.id,
});
[
'a/b/c/foo0',
'a/b/c/bar1',
'a/b/c/d/e/foo2',
'a/b/c/d/e/bar3',
'a/b/c/d/e/f/foo4',
'a/b/c/d/e/f/g/foo5',
'a/b/c/x/y/z/foo6',
'a/b/c/x/y/z/bar7',
'a/b/c/x/y/z/baz8',
'w/x/y/foo9',
'w/x/y/z/foo10',
'w/x/y/z/bar11',
].forEach((path) => server.create('variable', { id: path }));
server.create('variable', {
id: `nomad/jobs/${variableLinkedJob.id}/${variableLinkedGroup.name}/${variableLinkedTask.name}`,
namespace: variableLinkedJob.namespace,
});
server.create('variable', {
id: `nomad/jobs/${variableLinkedJob.id}/${variableLinkedGroup.name}`,
namespace: variableLinkedJob.namespace,
});
server.create('variable', {
id: `nomad/jobs/${variableLinkedJob.id}`,
namespace: variableLinkedJob.namespace,
});
server.create('variable', {
id: 'just some arbitrary file',
namespace: 'namespace-2',
});
server.create('variable', {
id: 'another arbitrary file',
namespace: 'namespace-2',
});
server.create('variable', {
id: 'another arbitrary file again',
namespace: 'namespace-2',
});
server.create('variable', {
id: 'Auto-conflicting Variable',
namespace: 'default',
});
}
function policiesTestCluster(server) {
faker.seed(1);
createTokens(server);
server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
}
function servicesTestCluster(server) {
faker.seed(1);
server.create('feature', { name: 'Dynamic Application Sizing' });
server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
server.createList('node', 5);
server.createList('job', 1, { createRecommendations: true });
server.create('job', {
withGroupServices: true,
withTaskServices: true,
name: 'Service-haver',
id: 'service-haver',
namespaceId: 'default',
});
server.createList('allocFile', 5);
server.create('allocFile', 'dir', { depth: 2 });
server.createList('csi-plugin', 2);
server.createList('variable', 3);
const variableLinkedJob = server.db.jobs[0];
const variableLinkedGroup = server.db.taskGroups.findBy({
jobId: variableLinkedJob.id,
});
const variableLinkedTask = server.db.tasks.findBy({
taskGroupId: variableLinkedGroup.id,
});
[
'a/b/c/foo0',
'a/b/c/bar1',
'a/b/c/d/e/foo2',
'a/b/c/d/e/bar3',
'a/b/c/d/e/f/foo4',
'a/b/c/d/e/f/g/foo5',
'a/b/c/x/y/z/foo6',
'a/b/c/x/y/z/bar7',
'a/b/c/x/y/z/baz8',
'w/x/y/foo9',
'w/x/y/z/foo10',
'w/x/y/z/bar11',
'just some arbitrary file',
'another arbitrary file',
'another arbitrary file again',
].forEach((path) => server.create('variable', { id: path }));
server.create('variable', {
id: `nomad/jobs/${variableLinkedJob.id}/${variableLinkedGroup.name}/${variableLinkedTask.name}`,
namespace: variableLinkedJob.namespace,
});
server.create('variable', {
id: `nomad/jobs/${variableLinkedJob.id}/${variableLinkedGroup.name}`,
namespace: variableLinkedJob.namespace,
});
server.create('variable', {
id: `nomad/jobs/${variableLinkedJob.id}`,
namespace: variableLinkedJob.namespace,
});
server.create('variable', {
id: 'Auto-conflicting Variable',
namespace: 'default',
});
// #region evaluations
// Branching: a single eval that relates to N-1 mutually-unrelated evals
const NUM_BRANCHING_EVALUATIONS = 3;
Array(NUM_BRANCHING_EVALUATIONS)
.fill()
.map((_, i) => {
return {
evaluation: server.create('evaluation', {
id: `branching_${i}`,
previousEval: i > 0 ? `branching_0` : '',
jobID: pickOne(server.db.jobs).id,
}),
evaluationStub: server.create('evaluation-stub', {
id: `branching_${i}`,
previousEval: i > 0 ? `branching_0` : '',
status: 'failed',
}),
};
})
.map((x, i, all) => {
x.evaluation.update({
relatedEvals:
i === 0
? all.filter((_, j) => j !== 0).map((e) => e.evaluation)
: all.filter((_, j) => j !== i).map((e) => e.evaluation),
});
return x;
});
// Linear: a long line of N related evaluations
const NUM_LINEAR_EVALUATIONS = 20;
Array(NUM_LINEAR_EVALUATIONS)
.fill()
.map((_, i) => {
return {
evaluation: server.create('evaluation', {
id: `linear_${i}`,
previousEval: i > 0 ? `linear_${i - 1}` : '',
jobID: pickOne(server.db.jobs).id,
}),
evaluationStub: server.create('evaluation-stub', {
id: `linear_${i}`,
previousEval: i > 0 ? `linear_${i - 1}` : '',
nextEval: `linear_${i + 1}`,
status: 'failed',
}),
};
})
.map((x, i, all) => {
x.evaluation.update({
relatedEvals: all.filter((_, j) => i !== j).map((e) => e.evaluation),
});
return x;
});
// #endregion evaluations
const csiAllocations = server.createList('allocation', 5);
const volumes = server.schema.csiVolumes.all().models;
csiAllocations.forEach((alloc) => {
const volume = pickOne(volumes);
volume.writeAllocs.add(alloc);
volume.readAllocs.add(alloc);
volume.save();
});
}
// Due to Mirage performance, large cluster scenarios will be slow
function largeCluster(server) {
server.createList('agent', 5);
server.createList('node', 1000);
server.createList('job', 100);
}
function massiveCluster(server) {
server.createList('agent', 7);
server.createList('node', 5000);
server.createList('job', 2000);
}
function allJobTypes(server) {
server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
server.createList('node', 5);
server.create('job', { type: 'service' });
server.create('job', { type: 'batch' });
server.create('job', { type: 'system' });
server.create('job', 'periodic');
server.create('job', 'parameterized');
server.create('job', 'periodicSysbatch');
server.create('job', 'parameterizedSysbatch');
server.create('job', { failedPlacements: true });
}
function allNodeTypes(server) {
server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
server.create('node');
server.create('node', 'forceIPv4');
server.create('node', 'draining');
server.create('node', 'forcedDraining');
server.create('node', 'noDeadlineDraining');
server.create('node', 'withMeta');
server.createList('job', 3);
}
function everyFeature(server) {
server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
server.create('node', 'forceIPv4');
server.create('node', 'draining');
server.create('node', 'forcedDraining');
server.create('node', 'noDeadlineDraining');
server.create('node', 'withMeta');
const job1 = server.create('job', {
type: 'service',
activeDeployment: true,
namespaceId: 'default',
createAllocations: false,
});
server.create('job', {
type: 'batch',
failedPlacements: true,
namespaceId: 'default',
});
server.create('job', { type: 'system', namespaceId: 'default' });
server.create('job', 'periodic', { namespaceId: 'default' });
server.create('job', 'parameterized', { namespaceId: 'default' });
server.create('allocation', 'rescheduled', { jobId: job1.id });
server.create('allocation', 'preempter', { jobId: job1.id });
server.create('allocation', 'preempted', { jobId: job1.id });
}
function emptyCluster(server) {
server.create('agent');
server.create('node');
}
// Behaviors
function createTokens(server) {
server.createList('token', 3);
server.create('token', {
name: 'Secure McVariables',
id: '53cur3-v4r14bl35',
});
server.create('token', {
name: "Safe O'Constants",
id: 'f3w3r-53cur3-v4r14bl35',
});
server.create('token', {
name: 'Lazarus MacMarbh',
id: '3XP1R35-1N-3L3V3N-M1NU735',
});
logTokens(server);
}
function createNamespaces(server) {
server.createList('namespace', 3);
}
function createRegions(server) {
['americas', 'europe', 'asia', 'some-long-name-just-to-test'].forEach(
(id) => {
server.create('region', { id });
}
);
}
/* eslint-disable */
function logTokens(server) {
console.log('TOKENS:');
server.db.tokens.forEach((token) => {
console.log(`
Name: ${token.name}
Secret: ${token.secretId}
Accessor: ${token.accessorId}
`);
console.log(
'Alternatively, log in with a JWT. If it ends with `management`, you have full access. If it ends with `bad`, you`ll get an error. Otherwise, you`ll get a token with limited access.'
);
});
}
function getConfigValue(variableName, defaultValue) {
const value = config.APP[variableName];
if (value !== undefined) return value;
console.warn(
`No ENV.APP value set for "${variableName}". Defaulting to "${defaultValue}". To set a custom value, modify config/environment.js`
);
return defaultValue;
}
function getScenarioQueryParameter() {
const params = new URLSearchParams(window.location.search);
return params.get('mirage-scenario');
}
/* eslint-enable */