mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
* 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
1373 lines
35 KiB
JavaScript
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 }));
|
|
}
|