Files
nomad/ui/mirage/scenarios/default.js
Phil Renaud 1976202cd6 Feature: Dynamic Host Volumes in the UI (#25224)
* DHV UI init

* /csi routes to /storage routes and a routeRedirector util (#25163)

* /csi routes to /storage routes and a routeRedirector util

* Tests and routes move csi/ to storage/

* Changelog added

* [ui] Storage UI overhaul + Dynamic Host Volumes UI (#25226)

* Storage index page and DHV model properties

* Naive version of a storage overview page

* Experimental fetch of alloc data dirs

* Fetch ephemeral disks and static host volumes as an ember concurrency task and nice table stylings

* Playing nice with section header labels to make eslint happy even though wcag was already cool with it

* inlined the storage type explainers and reordered things, plus tooltips and keynav

* Bones of a dynamic host volume individual page

* Woooo dynamic host volume model, adapter, and serializer with embedded alloc relationships

* Couple test fixes

* async:false relationship for dhv.hasMany('alloc') to prevent a ton of xhr requests

* DHV request type at index routemodel and better serialization

* Pagination and searching and query params oh my

* Test retrofits for csi volumes

* Really fantastic flake gets fixed

* DHV detail page acceptance test and a bunch of mirage hooks

* Seed so that the actions test has a guaranteed task

* removed ephemeral disk and static host volume manual scanning

* CapacityBytes and capabilities table added to DHV detail page

* Debugging actions flyout test

* was becoming clear that faker.seed editing was causing havoc elsewhere so might as well not boil the ocean and just tell this test to do what I want it to

* Post-create job gets taskCount instead of count

* CSI volumes now get /csi route prefix at detail level

* lazyclick method for unused keynav removed

* keyboard nav and table-watcher for DHV added

* Addressed PR comments, changed up capabilities table and id references, etc.

* Capabilities table for DHV and ID in details header

* Testfixes for pluginID and capabilities table on DHV page
2025-03-10 14:46:02 -04:00

1373 lines
35 KiB
JavaScript

/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
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,
rolesTestCluster,
namespacesTestCluster,
jobsIndexTestCluster,
...topoScenarios,
...sysbatchScenarios,
};
export 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')}`
);
}
// Make sure built-in node pools exist.
createBuiltInNodePools(server);
if (withNamespaces) createNamespaces(server);
if (withTokens) createTokens(server);
if (withRegions) createRegions(server);
activeScenario(server);
}
// Scenarios
function jobsIndexTestCluster(server) {
faker.seed(1);
server.createList('agent', 1, 'withConsulLink', 'withVaultLink');
server.createList('node', 1);
server.create('node-pool');
const jobsToCreate = 55;
for (let i = 0; i < jobsToCreate; i++) {
let groupCount = Math.floor(Math.random() * 2) + 1;
server.create('job', {
name: `Job ${i + 1}`,
resourceSpec: Array(groupCount).fill('M: 256, C: 500'),
groupAllocCount: Math.floor(Math.random() * 3) + 1,
modifyIndex: i + 1,
});
}
server.create('job', 'periodic', {
name: 'Periodic Job',
modifyIndex: jobsToCreate + 1,
childrenCount: 3,
});
server.create('job', 'parameterized', {
name: 'Parameterized Job',
modifyIndex: jobsToCreate + 2,
childrenCount: 5,
});
}
function smallCluster(server) {
faker.seed(1);
server.create('feature', { name: 'Dynamic Application Sizing' });
server.create('feature', { name: 'Sentinel Policies' });
server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
if (withRegions) {
server.db.agents[0].member.Tags.region = server.db.regions[0].id;
}
server.createList('node-pool', 2);
server.createList('node', 5);
server.create(
'node',
{
name: 'node-with-meta',
meta: { foo: 'bar', baz: 'qux' },
},
'withMeta'
);
server.createList('job', 10, { createRecommendations: true });
server.create('job', {
withGroupServices: true,
withTaskServices: true,
name: 'Service-haver',
id: 'service-haver',
namespaceId: 'default',
});
server.create('job', {
createAllocations: true,
groupAllocCount: 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,
});
// action-having job
const actionsJob = server.create('job', {
createAllocations: true,
resourceSpec: Array(2).fill('M: 257, C: 500'),
groupAllocCount: 5,
groupTaskCount: 2,
shallow: false,
name: 'actionable-job',
id: 'actionable-job',
namespaceId: 'default',
type: 'service',
activeDeployment: false,
noDeployments: true,
allocStatusDistribution: {
running: 1,
},
noFailedPlacements: true,
status: 'running',
withActions: true,
});
// A third task group in the Actions job with a single task/alloc
const actionsGroup = server.create('task-group', {
jobId: actionsJob.id,
name: 'actionable-group',
count: 1,
});
// make sure the allocation generated by that group is running
server.schema.allocations.findBy({ taskGroup: actionsGroup.name }).update({
clientStatus: 'running',
});
// Set its task state to running
server.schema.allocations
.all()
.filter((x) => x.taskGroup === actionsGroup.name)
.models[0].taskStates.models[0]?.update({
state: 'running',
});
server.create('policy', {
id: 'client-reader',
name: 'client-reader',
description: "Can read nodes and that's about it",
rulesJSON: {
Node: {
Policy: 'read',
},
},
rules: `# Allow node read access`,
});
server.create('policy', {
id: 'client-writer',
name: 'client-writer',
description: 'Can write to nodes',
rulesJSON: {
Node: {
Policy: 'write',
},
},
rules: `# Allow node write access`,
});
server.create('policy', {
id: 'job-reader',
name: 'job-reader',
description: "Can read jobs and that's about it",
rulesJSON: {
namespace: {
'*': {
policy: 'read',
},
},
},
rules: `# Job read access`,
});
server.create('policy', {
id: 'job-writer',
name: 'job-writer',
description: 'Can write jobs',
rulesJSON: {
Namespaces: [
{
Name: '*',
Policy: '',
Capabilities: ['submit-job'],
Variables: null,
},
],
},
rules: `# Job write access`,
});
server.create('policy', {
id: 'variable-lister',
name: 'variable-lister',
description: 'Can list variables',
rulesJSON: {
namespace: {
'*': {
variables: {
path: {
capabilities: ['list'],
pathspec: '*',
},
},
},
},
},
rules: `# Variable list access`,
});
server.create('role', {
id: 'operator',
name: 'operator',
description: 'Can operate',
policyIds: ['client-reader', 'client-writer', 'job-reader', 'job-writer'],
});
server.create('role', {
id: 'sysadmin',
name: 'sysadmin',
description: 'Can modify nodes',
policyIds: ['client-reader', 'client-writer'],
});
server.create('token', {
type: 'client',
name: 'Tiarna Riarthóir',
id: 'administrator-token',
roleIds: ['operator', 'sysadmin'],
policyIds: ['variable-lister'],
});
//#region Active Deployment
const activelyDeployingJobGroups = 2;
const activelyDeployingTasksPerGroup = 100;
const activelyDeployingJob = server.create('job', {
createAllocations: true,
groupAllocCount: 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 } })
);
activelyDeployingJobAllocs.models
.filter((a) => a.clientStatus === 'failed')
.slice(0, 5)
.forEach((a) =>
a.update({ deploymentStatus: { Healthy: true, Canary: false } })
);
//#endregion Active Deployment
// #region Version Tags
const versionTaggedJob = server.create('job', {
name: 'version-tag-job',
id: 'version-tag-job',
namespaceId: 'default',
noDeployments: true,
});
server.create('job-version', {
job: versionTaggedJob,
namespace: 'default',
version: 0,
});
server.create('job-version', {
job: versionTaggedJob,
namespace: 'default',
version: 1,
versionTag: {
Name: 'burrito',
Description: 'A delicious version',
},
});
server.create('job-version', {
job: versionTaggedJob,
namespace: 'default',
version: 2,
versionTag: {
Name: 'enchilada',
Description: 'A version with just a hint of spice',
},
});
// #endregion Version Tags
createRestartableJobs(server);
server.create('job', {
name: 'hcl-definition-job',
id: 'display-hcl',
namespaceId: 'default',
});
server.create('job', {
name: 'ui-block-job',
id: 'ui-block-job',
ui: {
Links: [
{
Label: 'HashiCorp',
Url: 'https://hashicorp.com',
},
{
Label: 'Nomad',
Url: 'https://nomadproject.io',
},
],
Description:
'A job with a UI-block defined description and links. It has **bold text** and everything!',
},
});
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',
'nomad/jobs',
].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('dynamic-host-volume', {
name: 'dynamic-host-volume',
namespaceId: 'default',
createTime: new Date().getTime() * 1000000,
modifyTime: new Date().getTime() * 1000000,
allocations: csiAllocations,
});
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-pool', 5);
server.createList('node', 50);
server.createList('job', 25);
}
function variableTestCluster(server) {
faker.seed(1);
createTokens(server);
server.create('token', {
name: 'Novars Murphy',
id: 'n0-v4r5-4cc355',
type: 'client',
});
createNamespaces(server);
server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
server.createList('node-pool', 3);
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, options = { sentinel: false }) {
if (options.sentinel) {
server.create('feature', { name: 'Sentinel Policies' });
server.create('sentinel-policy', {
id: 'policy-1',
name: 'policy-1',
description: 'A sentinel policy generated by Mirage',
enforcementLevel: 'soft-mandatory',
policy:
'import "time"\n\nis_weekday = rule { time.day not in ["friday", "saturday", "sunday"] }\nis_open_hours = rule { time.hour > 8 and time.hour < 16 }\n\nmain = rule { is_open_hours and is_weekday }',
scope: 'submit-job',
});
server.createList('sentinel-policy', 5);
}
faker.seed(1);
createTokens(server);
server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
}
function rolesTestCluster(server) {
faker.seed(1);
server.create('namespace', {
id: 'default',
name: 'default',
});
server.createList('namespace', 4);
server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
server.createList('node-pool', 2);
server.createList('node', 5);
server.createList('job', 5);
// createTokens(server);
// Create policies
const clientReaderPolicy = server.create('policy', {
id: 'client-reader',
name: 'client-reader',
description: "Can read nodes and that's about it",
rulesJSON: {
Node: {
Policy: 'read',
},
},
});
const clientWriterPolicy = server.create('policy', {
id: 'client-writer',
name: 'client-writer',
description: 'Can write to nodes',
rulesJSON: {
Node: {
Policy: 'write',
},
},
});
const clientDenierPolicy = server.create('policy', {
id: 'client-denier',
name: 'client-denier',
description: "Can't do anything with Clients",
rulesJSON: {
Node: {
Policy: 'deny',
},
},
});
const jobDenierPolicy = server.create('policy', {
id: 'job-denier',
name: 'job-denier',
description: "Can't do anything with Jobs",
rulesJSON: {
namespace: {
'*': {
policy: 'deny',
},
},
},
});
const operatorPolicy = server.create('policy', {
id: 'operator',
name: 'operator',
description: 'Can operate',
rulesJSON: {
operator: {
policy: 'write',
},
},
});
const jobReaderPolicy = server.create('policy', {
id: 'job-reader',
name: 'job-reader',
description: 'Can learn about jobs',
rulesJSON: {
namespace: {
'*': {
policy: 'read',
},
},
},
});
const highLevelJobPolicy = server.create('policy', {
id: 'job-writer',
name: 'job-writer',
description: 'Can do lots with jobs',
rulesJSON: {
Namespaces: [
{
Name: '*',
Policy: '',
Capabilities: ['submit-job'],
Variables: null,
},
],
},
});
// Create roles
const editorRole = server.create('role', {
id: 'editor',
name: 'editor',
description: 'Can edit things',
policyIds: [clientWriterPolicy.id],
});
const highLevelRole = server.create('role', {
id: 'high-level',
name: 'high-level',
description: 'Can do lots of things',
policyIds: [highLevelJobPolicy.id],
});
const readerRole = server.create('role', {
id: 'reader',
name: 'reader',
description: 'Can read things',
policyIds: [clientReaderPolicy.id, jobReaderPolicy.id],
});
const denierRole = server.create('role', {
id: 'denier',
name: 'denier',
description: "Can't do anything",
policyIds: [clientDenierPolicy.id, jobDenierPolicy.id],
});
// Create tokens
let managementToken = server.create('token', {
type: 'management',
name: 'Management Token',
});
let clientReaderToken = server.create('token', {
type: 'client',
name: "N. O'DeReader",
policyIds: [clientReaderPolicy.id],
});
let clientWriterToken = server.create('token', {
type: 'client',
name: "N. O'DeWriter",
policyIds: [clientWriterPolicy.id],
});
let dualPolicyToken = server.create('token', {
type: 'client',
name: 'Multi-policy Token',
policyIds: [clientReaderPolicy.id, clientWriterPolicy.id],
});
let highLevelViaPolicyToken = server.create('token', {
type: 'client',
name: 'High Level Policy Token',
policyIds: [highLevelJobPolicy.id],
});
let highLevelViaRoleToken = server.create('token', {
type: 'client',
name: 'High Level Role Token',
roleIds: [highLevelRole.id],
});
let policyAndRoleToken = server.create('token', {
type: 'client',
name: 'Policy And Role Token',
policyIds: [operatorPolicy.id],
roleIds: [readerRole.id],
});
let multiRoleToken = server.create('token', {
type: 'client',
name: 'Multi Role Token',
roleIds: [editorRole.id, highLevelRole.id],
});
let multiRoleAndPolicyToken = server.create('token', {
type: 'client',
name: 'Multi Role And Policy Token',
roleIds: [editorRole.id, highLevelRole.id],
policyIds: [clientWriterPolicy.id], // also included within editorRole, so redundant here.
});
let noClientsViaPolicyToken = server.create('token', {
type: 'client',
name: 'Clientless Policy Token',
policyIds: [clientDenierPolicy.id],
});
let noClientsViaRoleToken = server.create('token', {
type: 'client',
name: 'Clientless Role Token',
roleIds: [denierRole.id],
});
// malleable test token
server.create('token', {
name: 'Clay-Token',
id: 'cl4y-t0k3n',
type: 'client',
policyIds: [clientReaderPolicy.id, operatorPolicy.id],
roleIds: [editorRole.id],
expirationTime: new Date(new Date().getTime() + 60 * 60 * 1000),
});
logTokens(server);
server.create('auth-method', { name: 'vault' });
server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
}
function namespacesTestCluster(server, opts = { enterprise: true }) {
faker.seed(1);
createTokens(server);
if (opts.enterprise) {
server.create('feature', { name: 'Node Pools Governance' });
server.create('feature', { name: 'Resource Quotas' });
}
createNamespaces(server);
let nsWithVariable = server.create('namespace', {
name: 'with-variables',
id: 'with-variables',
});
server.create('variable', {
id: `some/variable/path`,
namespace: nsWithVariable.id,
});
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-pool', 3);
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-pool', 10);
server.createList('node', 1000);
server.createList('job', 100);
}
function massiveCluster(server) {
server.createList('agent', 7);
server.createList('node-pool', 100);
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.createList('node-pool', 3);
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 createBuiltInNodePools(server) {
server.create('node-pool', { name: 'default' });
server.create('node-pool', { name: 'all' });
}
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.'
);
console.log('=====================================');
}
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);
const mirageScenario = params.get('mirage-scenario');
if (mirageScenario && !(mirageScenario in allScenarios)) {
console.error(
new Error(
`Selected Mirage scenario does not exist.\n\n${mirageScenario} not in list: \n\n\t${Object.keys(
allScenarios
).join('\n\t')}`
)
);
return 'smallCluster';
}
return mirageScenario;
}
/* eslint-enable */
export function createRestartableJobs(server) {
const restartableJob = server.create('job', {
name: 'restartable-job',
stopped: true,
status: 'dead',
noDeployments: true,
shallow: true,
createAllocations: false,
groupAllocCount: 0,
});
const revertableJob = server.create('job', {
name: 'revertable-job',
stopped: false,
status: 'dead',
noDeployments: true,
shallow: true,
createAllocations: false,
groupAllocCount: 0,
type: 'service',
});
const nonRevertableJob = server.create('job', {
name: 'non-revertable-job',
stopped: false,
status: 'dead',
shallow: true,
createAllocations: false,
groupAllocCount: 0,
type: 'service',
});
const revertableBatchJob = server.create('job', {
name: 'revertable-batch-job',
stopped: false,
status: 'dead',
noDeployments: true,
shallow: true,
type: 'batch',
});
// So it shows up as "Failed" instead of "Scaled Down"
restartableJob.taskGroups.models[0].update({
count: 1,
});
revertableJob.taskGroups.models[0].update({
count: 1,
});
nonRevertableJob.taskGroups.models[0].update({
count: 1,
});
// Remove all job-versions inherently created
server.schema.jobVersions
.all()
.filter((v) => v.jobId === restartableJob.id)
.models.forEach((v) => v.destroy());
server.schema.jobVersions
.all()
.filter((v) => v.jobId === revertableJob.id)
.models.forEach((v) => v.destroy());
server.schema.jobVersions
.all()
.filter((v) => v.jobId === nonRevertableJob.id)
.models.forEach((v) => v.destroy());
server.schema.jobVersions
.all()
.filter((v) => v.jobId === revertableBatchJob.id)
.models.forEach((v) => v.destroy());
server.create('job-version', {
job: revertableJob,
namespace: revertableJob.namespace,
version: 0,
stable: false,
versionTag: {
Name: 'v0',
Description: 'The first version',
},
});
server.create('job-version', {
job: revertableJob,
namespace: revertableJob.namespace,
version: 1,
stable: true,
versionTag: {
Name: 'v1',
Description: 'The second version',
},
});
server.create('job-version', {
job: revertableJob,
namespace: revertableJob.namespace,
version: 2,
stable: false,
versionTag: {
Name: 'v2',
Description: 'The third version',
},
});
server.create('job-version', {
job: nonRevertableJob,
namespace: nonRevertableJob.namespace,
version: 0,
stable: false,
noActiveDeployment: true,
});
server.create('job-version', {
job: nonRevertableJob,
namespace: nonRevertableJob.namespace,
version: 1,
stable: false,
noActiveDeployment: true,
});
server.create('job-version', {
job: revertableBatchJob,
namespace: revertableBatchJob.namespace,
version: 0,
stable: false, // <--- ignored by the UI by way of job.hasVersionStability
noActiveDeployment: true,
});
server.create('job-version', {
job: revertableBatchJob,
namespace: revertableBatchJob.namespace,
version: 1,
stable: false, // <--- ignored by the UI by way of job.hasVersionStability
noActiveDeployment: true,
});
server.schema.jobVersions
.all()
.filter((v) => v.jobId === revertableJob.id)
.models.forEach((v) => v.update({ stable: true }));
server.schema.jobVersions
.all()
.filter((v) => v.jobId === nonRevertableJob.id)
.models.forEach((v) => v.update({ stable: false }));
}