ui: create node pool model (#17301)

Co-authored-by: Phil Renaud <phil@riotindustries.com>
Co-authored-by: Luiz Aoqui <luiz@hashicorp.com>
This commit is contained in:
Jai
2023-06-22 13:11:44 -04:00
committed by GitHub
parent 085a0b0883
commit 4da63e3ded
90 changed files with 604 additions and 30 deletions

View File

@@ -0,0 +1,17 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import ApplicationAdapter from './application';
import classic from 'ember-classic-decorator';
import { pluralize } from 'ember-inflector';
@classic
export default class NodePoolAdapter extends ApplicationAdapter {
urlForFindAll(modelName) {
let [relationshipResource, resource] = modelName.split('-');
resource = pluralize(resource);
return `/v1/${relationshipResource}/${resource}`;
}
}

View File

@@ -57,6 +57,9 @@ export default class IndexController extends Controller.extend(
{
qpVolume: 'volume',
},
{
qpNodePool: 'nodePool',
},
];
currentPage = 1;
@@ -75,12 +78,14 @@ export default class IndexController extends Controller.extend(
qpDatacenter = '';
qpVersion = '';
qpVolume = '';
qpNodePool = '';
@selection('qpClass') selectionClass;
@selection('qpState') selectionState;
@selection('qpDatacenter') selectionDatacenter;
@selection('qpVersion') selectionVersion;
@selection('qpVolume') selectionVolume;
@selection('qpNodePool') selectionNodePool;
@computed('nodes.[]', 'selectionClass')
get optionsClass() {
@@ -164,11 +169,37 @@ export default class IndexController extends Controller.extend(
return volumes.sort().map((volume) => ({ key: volume, label: volume }));
}
@computed('selectionNodePool', 'model.nodePools.[]')
get optionsNodePool() {
const availableNodePools = this.model.nodePools.filter(
(p) => p.name !== 'all'
);
scheduleOnce('actions', () => {
// eslint-disable-next-line ember/no-side-effects
this.set(
'qpNodePool',
serialize(
intersection(
availableNodePools.map(({ name }) => name),
this.selectionNodePool
)
)
);
});
return availableNodePools.map((nodePool) => ({
key: nodePool.name,
label: nodePool.name,
}));
}
@computed(
'nodes.[]',
'selectionClass',
'selectionState',
'selectionDatacenter',
'selectionNodePool',
'selectionVersion',
'selectionVolume'
)
@@ -177,6 +208,7 @@ export default class IndexController extends Controller.extend(
selectionClass: classes,
selectionState: states,
selectionDatacenter: datacenters,
selectionNodePool: nodePools,
selectionVersion: versions,
selectionVolume: volumes,
} = this;
@@ -201,6 +233,9 @@ export default class IndexController extends Controller.extend(
!node.hostVolumes.find((volume) => volumes.includes(volume.name))
)
return false;
if (nodePools.length && !nodePools.includes(node.get('nodePool'))) {
return false;
}
if (onlyIneligible && node.get('isEligible')) return false;
if (onlyDraining && !node.get('isDraining')) return false;

View File

@@ -57,6 +57,9 @@ export default class IndexController extends Controller.extend(
{
qpNamespace: 'namespace',
},
{
qpNodePool: 'nodePool',
},
];
currentPage = 1;
@@ -81,11 +84,13 @@ export default class IndexController extends Controller.extend(
qpStatus = '';
qpDatacenter = '';
qpPrefix = '';
qpNodePool = '';
@selection('qpType') selectionType;
@selection('qpStatus') selectionStatus;
@selection('qpDatacenter') selectionDatacenter;
@selection('qpPrefix') selectionPrefix;
@selection('qpNodePool') selectionNodePool;
@computed
get optionsType() {
@@ -194,6 +199,29 @@ export default class IndexController extends Controller.extend(
return availableNamespaces;
}
@computed('selectionNodePool', 'model.nodePools.[]')
get optionsNodePool() {
const availableNodePools = this.model.nodePools;
scheduleOnce('actions', () => {
// eslint-disable-next-line ember/no-side-effects
this.set(
'qpNodePool',
serialize(
intersection(
availableNodePools.map(({ name }) => name),
this.selectionNodePool
)
)
);
});
return availableNodePools.map((nodePool) => ({
key: nodePool.name,
label: nodePool.name,
}));
}
/**
Visible jobs are those that match the selected namespace and aren't children
of periodic or parameterized jobs.
@@ -212,6 +240,7 @@ export default class IndexController extends Controller.extend(
'selectionType',
'selectionStatus',
'selectionDatacenter',
'selectionNodePool',
'selectionPrefix'
)
get filteredJobs() {
@@ -220,6 +249,7 @@ export default class IndexController extends Controller.extend(
selectionStatus: statuses,
selectionDatacenter: datacenters,
selectionPrefix: prefixes,
selectionNodePool: nodePools,
} = this;
// A job must match ALL filter facets, but it can match ANY selection within a facet
@@ -246,6 +276,10 @@ export default class IndexController extends Controller.extend(
return false;
}
if (nodePools.length && !nodePools.includes(job.get('nodePool'))) {
return false;
}
const name = job.get('name');
if (
prefixes.length &&

View File

@@ -44,6 +44,9 @@ export default class TopologyControllers extends Controller.extend(Searchable) {
{
qpDatacenter: 'dc',
},
{
qpNodePool: 'nodePool',
},
];
@tracked searchTerm = '';
@@ -51,6 +54,7 @@ export default class TopologyControllers extends Controller.extend(Searchable) {
qpVersion = '';
qpClass = '';
qpDatacenter = '';
qpNodePool = '';
setFacetQueryParam(queryParam, selection) {
this.set(queryParam, serialize(selection));
@@ -59,6 +63,7 @@ export default class TopologyControllers extends Controller.extend(Searchable) {
@selection('qpState') selectionState;
@selection('qpClass') selectionClass;
@selection('qpDatacenter') selectionDatacenter;
@selection('qpNodePool') selectionNodePool;
@selection('qpVersion') selectionVersion;
@computed
@@ -109,6 +114,29 @@ export default class TopologyControllers extends Controller.extend(Searchable) {
return datacenters.sort().map((dc) => ({ key: dc, label: dc }));
}
@computed('model.nodePools.[]', 'selectionNodePool')
get optionsNodePool() {
const availableNodePools = this.model.nodePools;
scheduleOnce('actions', () => {
// eslint-disable-next-line ember/no-side-effects
this.set(
'qpNodePool',
serialize(
intersection(
availableNodePools.map(({ name }) => name),
this.selectionNodePool
)
)
);
});
return availableNodePools.sort().map((nodePool) => ({
key: nodePool.name,
label: nodePool.name,
}));
}
@computed('model.nodes', 'nodes.[]', 'selectionVersion')
get optionsVersion() {
const versions = Array.from(
@@ -140,6 +168,7 @@ export default class TopologyControllers extends Controller.extend(Searchable) {
selectionVersion,
selectionDatacenter,
selectionClass,
selectionNodePool,
} = this;
return (
(selectionState.length ? selectionState.includes(node.status) : true) &&
@@ -152,6 +181,9 @@ export default class TopologyControllers extends Controller.extend(Searchable) {
(selectionClass.length
? selectionClass.includes(node.nodeClass)
: true) &&
(selectionNodePool.length
? selectionNodePool.includes(node.nodePool)
: true) &&
(node.name.includes(searchTerm) ||
node.datacenter.includes(searchTerm) ||
node.nodeClass.includes(searchTerm))

View File

@@ -28,6 +28,7 @@ export default class Job extends Model {
@attr('number') createIndex;
@attr('number') modifyIndex;
@attr('date') submitTime;
@attr('string') nodePool; // Jobs are related to Node Pools either directly or via its Namespace, but no relationship.
@fragment('structured-attributes') meta;

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import Model from '@ember-data/model';
import { attr } from '@ember-data/model';
import { computed } from '@ember/object';
import classic from 'ember-classic-decorator';
@classic
export default class NodePool extends Model {
@attr('string') name;
@attr('string') description;
@attr() meta;
@attr() schedulerConfiguration;
@computed('schedulerConfiguration.SchedulerAlgorithm')
get schedulerAlgorithm() {
return this.get('schedulerConfiguration.SchedulerAlgorithm');
}
@computed('schedulerConfiguration.MemoryOversubscriptionEnabled')
get memoryOversubscriptionEnabled() {
return this.get('schedulerConfiguration.MemoryOversubscriptionEnabled');
}
}

View File

@@ -27,6 +27,7 @@ export default class Node extends Model {
@shortUUIDProperty('id') shortId;
@attr('number') modifyIndex;
@attr('string') version;
@attr('string') nodePool;
// Available from single response
@attr('string') httpAddr;

View File

@@ -23,6 +23,7 @@ export default class ClientsRoute extends Route.extend(WithForbiddenState) {
return RSVP.hash({
nodes: this.store.findAll('node'),
agents: this.store.findAll('agent'),
nodePools: this.store.findAll('node-pool'),
}).catch(notifyForbidden(this));
}
}

View File

@@ -30,6 +30,7 @@ export default class IndexRoute extends Route.extend(
.query('job', { namespace: params.qpNamespace, meta: true })
.catch(notifyForbidden(this)),
namespaces: this.store.findAll('namespace'),
nodePools: this.store.findAll('node-pool'),
});
}

View File

@@ -23,6 +23,10 @@ export default class TopologyRoute extends Route.extend(WithForbiddenState) {
namespace: '*',
}),
nodes: this.store.query('node', { resources: true }),
// Nodes are not allowed to be in the 'all' node pool, so filter it out.
nodePools: this.store
.findAll('node-pool')
.then((pools) => pools.filter((p) => p.name !== 'all')),
}).catch(notifyForbidden(this));
}
@@ -32,6 +36,7 @@ export default class TopologyRoute extends Route.extend(WithForbiddenState) {
controller.model = {
allocations: [],
nodes: [],
nodePools: [],
};
}

View File

@@ -0,0 +1,12 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import ApplicationSerializer from './application';
import classic from 'ember-classic-decorator';
@classic
export default class NodePool extends ApplicationSerializer {
primaryKey = 'Name';
}

View File

@@ -17,6 +17,12 @@
font-weight: $weight-bold;
font-size: $size-3;
&.justify {
display: flex;
flex-direction: column;
align-items: center;
}
.metric-units {
font-size: $size-4;
}

View File

@@ -243,6 +243,12 @@
</span>
{{this.model.datacenter}}
</span>
<span class="pair" data-test-node-pool>
<span class="term">
Node Pool
</span>
{{this.model.nodePool}}
</span>
{{#if this.model.nodeClass}}
<span class="pair" data-test-node-class>
<span class="term">
@@ -561,7 +567,7 @@
</t.head>
<t.body as |row|>
<AllocationRow
{{keyboard-shortcut
{{keyboard-shortcut
enumerated=true
action=(action "gotoAllocation" row.model)
}}
@@ -885,7 +891,7 @@
type="button"
class="button is-primary"
{{on "click" (action (mut this.editingMetadata) true)}}
{{keyboard-shortcut
{{keyboard-shortcut
label="Add Dynamic Node Metadata"
pattern=(array "m" "e" "t" "a")
action=(action (mut this.editingMetadata) true)

View File

@@ -20,6 +20,13 @@
</div>
<div class="toolbar-item is-right-aligned is-mobile-full-width">
<div class="button-bar">
<MultiSelectDropdown
data-test-node-pool-facet
@label="Node Pool"
@options={{this.optionsNodePool}}
@selection={{this.selectionNodePool}}
@onSelect={{action this.setFacetQueryParam "qpNodePool"}}
/>
<MultiSelectDropdown
data-test-class-facet
@label="Class"
@@ -81,6 +88,7 @@
>Name</t.sort-by>
<t.sort-by @prop="compositeStatus">State</t.sort-by>
<th class="is-200px is-truncatable">Address</th>
<t.sort-by @prop="nodePool">Node Pool</t.sort-by>
<t.sort-by @prop="datacenter">Datacenter</t.sort-by>
<t.sort-by @prop="version">Version</t.sort-by>
<th># Volumes</th>
@@ -91,7 +99,7 @@
data-test-client-node-row
@node={{row.model}}
@onClick={{action "gotoNode" row.model}}
{{keyboard-shortcut
{{keyboard-shortcut
enumerated=true
action=(action "gotoNode" row.model)
}}

View File

@@ -18,6 +18,7 @@
</span>
</td>
<td data-test-client-address class="is-200px is-truncatable">{{this.node.httpAddr}}</td>
<td data-test-client-node-pool title="{{this.node.nodePool}}">{{this.node.nodePool}}</td>
<td data-test-client-datacenter>{{this.node.datacenter}}</td>
<td data-test-client-version>{{this.node.version}}</td>
<td data-test-client-volumes>{{if this.node.hostVolumes.length this.node.hostVolumes.length}}</td>

View File

@@ -31,6 +31,10 @@
{{@job.namespace.name}}
</span>
{{/if}}
<span class="pair" data-test-job-stat="node-pool">
<span class="term">Node Pool</span>
{{@job.nodePool}}
</span>
{{yield to="after-namespace"}}
</div>

View File

@@ -31,6 +31,9 @@
{{this.job.namespace.name}}
</td>
{{/if}}
<td data-test-job-node-pool>
{{this.job.nodePool}}
</td>
{{/if}}
{{#if (eq @context "child")}}
<td data-test-job-submit-time>

View File

@@ -13,6 +13,7 @@
{{/if}}
<span class="tooltip" aria-label="Node Name"><strong><LinkTo @route="clients.client" @model={{@node.node.id}}>{{@node.node.name}}</LinkTo></strong></span>
<span class="bumper-left tooltip" aria-label="Number of Allocations">{{this.count}} Allocs</span>
<span class="bumper-left is-faded tooltip" aria-label="Node Pool">{{@node.node.nodePool}}</span>
<span class="bumper-left is-faded">
<span class="tooltip" aria-label="Node Memory">{{format-scheduled-bytes @node.memory start="MiB"}}</span>,
<span class="tooltip" aria-label="Node CPU">{{format-scheduled-hertz @node.cpu}}</span>

View File

@@ -58,6 +58,13 @@
@selection={{this.selectionType}}
@onSelect={{action this.setFacetQueryParam "qpType"}}
/>
<MultiSelectDropdown
data-test-node-pool-facet
@label="Node Pool"
@options={{this.optionsNodePool}}
@selection={{this.selectionNodePool}}
@onSelect={{action this.setFacetQueryParam "qpNodePool"}}
/>
<MultiSelectDropdown
data-test-status-facet
@label="Status"
@@ -89,7 +96,7 @@
@query={{hash namespace=this.qpNamespace}}
data-test-run-job
class="button is-primary"
{{keyboard-shortcut
{{keyboard-shortcut
label="Run Job"
pattern=(array "r" "u" "n")
action=(action this.goToRun)
@@ -136,6 +143,9 @@
Namespace
</t.sort-by>
{{/if}}
<t.sort-by @prop="nodePool">
Node Pool
</t.sort-by>
<t.sort-by @prop="status">
Status
</t.sort-by>

View File

@@ -370,7 +370,7 @@
{{else}}
<div class="columns is-flush">
<div class="dashboard-metric column">
<p data-test-node-count class="metric">
<p data-test-node-count class="metric justify">
{{this.model.nodes.length}}
<span class="metric-label">
Clients
@@ -378,13 +378,21 @@
</p>
</div>
<div class="dashboard-metric column">
<p data-test-alloc-count class="metric">
<p data-test-alloc-count class="metric justify">
{{this.scheduledAllocations.length}}
<span class="metric-label">
Allocations
</span>
</p>
</div>
<div class="dashboard-metric column">
<p data-test-node-pool-count class="metric justify">
{{this.model.nodePools.length}}
<span class="metric-label">
Node Pools
</span>
</p>
</div>
</div>
<div class="dashboard-metric with-divider">
<p class="metric">
@@ -479,6 +487,13 @@
</div>
<div class="toolbar-item is-right-aligned is-mobile-full-width">
<div class="button-bar">
<MultiSelectDropdown
data-test-node-pool-facet
@label="Node Pool"
@options={{this.optionsNodePool}}
@selection={{this.selectionNodePool}}
@onSelect={{action this.setFacetQueryParam "qpNodePool"}}
/>
<MultiSelectDropdown
data-test-datacenter-facet
@label="Datacenter"

View File

@@ -338,6 +338,10 @@ export default function () {
return this.serialize(nodes.find(params.id));
});
this.get('/node/pools', function ({ nodePools }) {
return this.serialize(nodePools.all());
});
this.get('/allocations');
this.get('/allocation/:id');

View File

@@ -200,6 +200,11 @@ export default Factory.extend({
shallow: false,
afterCreate(job, server) {
Ember.assert(
'[Mirage] No node pools! make sure node pools are created before jobs',
server.db.nodePools.length
);
if (!job.namespaceId) {
const namespace = server.db.namespaces.length
? pickOne(server.db.namespaces).id
@@ -214,6 +219,12 @@ export default Factory.extend({
});
}
if (!job.nodePool) {
job.update({
nodePool: pickOne(server.db.nodePools).name,
});
}
const groupProps = {
job,
createAllocations: job.createAllocations,

View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { Factory } from 'ember-cli-mirage';
export default Factory.extend({
name: (i) => `node-pool-${i}`,
description: (i) => `describe node-pool-${i}`,
meta: {},
schedulerConfiguration: {},
});

View File

@@ -5,28 +5,33 @@
import { Factory, trait } from 'ember-cli-mirage';
import faker from 'nomad-ui/mirage/faker';
import { provide } from '../utils';
import { provide, pickOne } from '../utils';
import { DATACENTERS, HOSTS, generateResources } from '../common';
import moment from 'moment';
const UUIDS = provide(100, faker.random.uuid.bind(faker.random));
const NODE_STATUSES = ['initializing', 'ready', 'down'];
const NODE_CLASSES = provide(7, faker.company.bsBuzz.bind(faker.company));
const NODE_VERSIONS = ['1.1.0-beta', '1.0.2-alpha+ent', ...provide(5, faker.system.semver)];
const NODE_VERSIONS = [
'1.1.0-beta',
'1.0.2-alpha+ent',
...provide(5, faker.system.semver),
];
const REF_DATE = new Date();
export default Factory.extend({
id: i => (i / 100 >= 1 ? `${UUIDS[i]}-${i}` : UUIDS[i]),
name: i => `nomad@${HOSTS[i % HOSTS.length]}`,
id: (i) => (i / 100 >= 1 ? `${UUIDS[i]}-${i}` : UUIDS[i]),
name: (i) => `nomad@${HOSTS[i % HOSTS.length]}`,
datacenter: () => faker.helpers.randomize(DATACENTERS),
nodeClass: () => faker.helpers.randomize(NODE_CLASSES),
drain: faker.random.boolean,
status: () => faker.helpers.randomize(NODE_STATUSES),
tlsEnabled: faker.random.boolean,
schedulingEligibility: () => (faker.random.boolean() ? 'eligible' : 'ineligible'),
schedulingEligibility: () =>
faker.random.boolean() ? 'eligible' : 'ineligible',
createIndex: i => i,
createIndex: (i) => i,
modifyIndex: () => faker.random.number({ min: 10, max: 2000 }),
version: () => faker.helpers.randomize(NODE_VERSIONS),
@@ -35,8 +40,8 @@ export default Factory.extend({
},
forceIPv4: trait({
name: i => {
const ipv4Hosts = HOSTS.filter(h => !h.startsWith('['));
name: (i) => {
const ipv4Hosts = HOSTS.filter((h) => !h.startsWith('['));
return `nomad@${ipv4Hosts[i % ipv4Hosts.length]}`;
},
}),
@@ -45,8 +50,13 @@ export default Factory.extend({
drain: true,
schedulingEligibility: 'ineligible',
drainStrategy: {
Deadline: faker.random.number({ min: 30 * 1000, max: 5 * 60 * 60 * 1000 }) * 1000000,
ForceDeadline: moment(REF_DATE).add(faker.random.number({ min: 1, max: 5 }), 'd'),
Deadline:
faker.random.number({ min: 30 * 1000, max: 5 * 60 * 60 * 1000 }) *
1000000,
ForceDeadline: moment(REF_DATE).add(
faker.random.number({ min: 1, max: 5 }),
'd'
),
IgnoreSystemJobs: faker.random.boolean(),
},
}),
@@ -129,17 +139,28 @@ export default Factory.extend({
}),
afterCreate(node, server) {
Ember.assert(
'[Mirage] No node pools! make sure node pools are created before nodes',
server.db.nodePools.length
);
// Each node has a corresponding client stat resource that's queried via node IP.
// Create that record, even though it's not a relationship.
server.create('client-stat', {
id: node.httpAddr,
});
const events = server.createList('node-event', faker.random.number({ min: 1, max: 10 }), {
nodeId: node.id,
});
const events = server.createList(
'node-event',
faker.random.number({ min: 1, max: 10 }),
{ nodeId: node.id }
);
const nodePool = node.nodePool
? server.db.nodePools.findBy({ name: node.nodePool })
: pickOne(server.db.nodePools, (pool) => pool.name !== 'all');
node.update({
nodePool: nodePool.name,
eventIds: events.mapBy('id'),
});
@@ -150,7 +171,7 @@ export default Factory.extend({
});
function makeDrivers() {
const generate = name => {
const generate = (name) => {
const detected = faker.random.number(10) >= 3;
const healthy = detected && faker.random.number(10) >= 3;
const attributes = {

View File

@@ -0,0 +1,8 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { Model } from 'ember-cli-mirage';
export default Model.extend({});

View File

@@ -44,9 +44,12 @@ export default function (server) {
);
}
// Make sure built-in node pools exist.
createBuiltInNodePools(server);
if (withNamespaces) createNamespaces(server);
if (withTokens) createTokens(server);
if (withRegions) createRegions(server);
activeScenario(server);
}
@@ -56,6 +59,7 @@ function smallCluster(server) {
faker.seed(1);
server.create('feature', { name: 'Dynamic Application Sizing' });
server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
server.createList('node-pool', 2);
server.createList('node', 5);
server.create(
'node',
@@ -347,6 +351,7 @@ function smallCluster(server) {
function mediumCluster(server) {
server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
server.createList('node-pool', 5);
server.createList('node', 50);
server.createList('job', 25);
}
@@ -356,6 +361,7 @@ function variableTestCluster(server) {
createTokens(server);
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);
@@ -431,6 +437,7 @@ 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', {
@@ -563,12 +570,14 @@ function servicesTestCluster(server) {
// 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);
}
@@ -602,6 +611,7 @@ function allNodeTypes(server) {
function everyFeature(server) {
server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
server.createList('node-pool', 3);
server.create('node', 'forceIPv4');
server.create('node', 'draining');
@@ -636,6 +646,11 @@ function emptyCluster(server) {
// 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', {

View File

@@ -16,6 +16,7 @@ const genResources = (CPU, Memory) => ({
export function topoSmall(server) {
server.createList('agent', 3, 'withConsulLink', 'withVaultLink');
server.createList('node-pool', 4);
server.createList('node', 12, {
datacenter: 'dc1',
status: 'ready',
@@ -35,7 +36,7 @@ export function topoSmall(server) {
['M: 512, C: 250', 'M: 600, C: 200'],
];
jobResources.forEach(spec => {
jobResources.forEach((spec) => {
server.create('job', {
status: 'running',
datacenters: ['dc1'],
@@ -98,7 +99,7 @@ export function topoMedium(server) {
['M: 512, C: 250', 'M: 600, C: 200'],
];
jobResources.forEach(spec => {
jobResources.forEach((spec) => {
server.create('job', {
status: 'running',
datacenters: ['dc1'],

View File

@@ -16,8 +16,13 @@ export function provider() {
return () => provide(...arguments);
}
export function pickOne(list) {
return list[faker.random.number(list.length - 1)];
export function pickOne(list, filterFn) {
let candidates = list;
if (filterFn) {
candidates = list.filter(filterFn);
}
return candidates[faker.random.number(candidates.length - 1)];
}
export function arrToObj(prop, alias = '') {
@@ -31,7 +36,6 @@ export function arrToObj(prop, alias = '') {
}
export const generateAcceptanceTestEvalMock = (id) => {
return {
CreateIndex: 20,
CreateTime: 1647899150314738000,

View File

@@ -28,6 +28,7 @@ module('Acceptance | allocation detail', function (hooks) {
hooks.beforeEach(async function () {
server.create('agent');
server.create('node-pool');
node = server.create('node');
job = server.create('job', {
groupsCount: 1,
@@ -479,6 +480,7 @@ module('Acceptance | allocation detail (rescheduled)', function (hooks) {
hooks.beforeEach(async function () {
server.create('agent');
server.create('node-pool');
node = server.create('node');
job = server.create('job', { createAllocations: false });
allocation = server.create('allocation', 'rescheduled');
@@ -501,6 +503,7 @@ module('Acceptance | allocation detail (not running)', function (hooks) {
hooks.beforeEach(async function () {
server.create('agent');
server.create('node-pool');
node = server.create('node');
job = server.create('job', { createAllocations: false });
allocation = server.create('allocation', { clientStatus: 'pending' });
@@ -531,6 +534,7 @@ module('Acceptance | allocation detail (preemptions)', function (hooks) {
hooks.beforeEach(async function () {
server.create('agent');
server.create('node-pool');
node = server.create('node');
job = server.create('job', { createAllocations: false });
});
@@ -669,6 +673,7 @@ module('Acceptance | allocation detail (services)', function (hooks) {
hooks.beforeEach(async function () {
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 });
const job = server.create('job', {

View File

@@ -20,6 +20,7 @@ module('Acceptance | allocation fs', function (hooks) {
hooks.beforeEach(async function () {
server.create('agent');
server.create('node-pool');
server.create('node', 'forceIPv4');
const job = server.create('job', { createAllocations: false });

View File

@@ -21,6 +21,7 @@ module('Acceptance | application errors ', function (hooks) {
hooks.beforeEach(function () {
faker.seed(1);
server.create('agent');
server.create('node-pool');
server.create('node');
server.create('job');
});

View File

@@ -46,6 +46,7 @@ module('Acceptance | client detail', function (hooks) {
hooks.beforeEach(function () {
window.localStorage.clear();
server.create('node-pool');
server.create('node', 'forceIPv4', { schedulingEligibility: 'eligible' });
node = server.db.nodes[0];
@@ -1257,6 +1258,7 @@ module('Acceptance | client detail', function (hooks) {
return Array.from(new Set(allocs.mapBy('jobId'))).sort();
},
async beforeEach() {
server.create('node-pool');
server.createList('job', 5);
await ClientDetail.visit({ id: node.id });
},
@@ -1275,6 +1277,7 @@ module('Acceptance | client detail', function (hooks) {
'Unknown',
],
async beforeEach() {
server.create('node-pool');
server.createList('job', 5, { createAllocations: false });
['pending', 'running', 'complete', 'failed', 'lost', 'unknown'].forEach(
(s) => {
@@ -1306,6 +1309,7 @@ module('Acceptance | client detail (multi-namespace)', function (hooks) {
setupMirage(hooks);
hooks.beforeEach(function () {
server.create('node-pool');
server.create('node', 'forceIPv4', { schedulingEligibility: 'eligible' });
node = server.db.nodes[0];

View File

@@ -21,6 +21,7 @@ module('Acceptance | client monitor', function (hooks) {
setupMirage(hooks);
hooks.beforeEach(function () {
server.create('node-pool');
node = server.create('node');
managementToken = server.create('token');

View File

@@ -20,6 +20,7 @@ module('Acceptance | clients list', function (hooks) {
hooks.beforeEach(function () {
window.localStorage.clear();
server.createList('node-pool', 3);
});
test('it passes an accessibility audit', async function (assert) {
@@ -73,6 +74,7 @@ module('Acceptance | clients list', function (hooks) {
assert.equal(nodeRow.id, node.id.split('-')[0], 'ID');
assert.equal(nodeRow.name, node.name, 'Name');
assert.equal(nodeRow.nodePool, node.nodePool, 'Node Pool');
assert.equal(
nodeRow.compositeStatus.text,
'draining',
@@ -324,6 +326,29 @@ module('Acceptance | clients list', function (hooks) {
},
});
testFacet('Node Pools', {
facet: ClientsList.facets.nodePools,
paramName: 'nodePool',
expectedOptions() {
return server.db.nodePools
.filter((p) => p.name !== 'all') // The node pool 'all' should not be a filter.
.map((p) => p.name);
},
async beforeEach() {
server.create('agent');
server.create('node-pool', { name: 'all' });
server.create('node-pool', { name: 'default' });
server.createList('node-pool', 10);
// Make sure each node pool has at least one node.
server.db.nodePools.forEach((p) => {
server.createList('node', 2, { nodePool: p.name });
});
await ClientsList.visit();
},
filter: (node, selection) => selection.includes(node.nodePool),
});
testFacet('Datacenters', {
facet: ClientsList.facets.datacenter,
paramName: 'dc',

View File

@@ -643,6 +643,7 @@ module('Acceptance | evaluations list', function (hooks) {
module('resource linking', function () {
test('it should generate a link to the job resource', async function (assert) {
server.create('node-pool');
server.create('node');
const job = server.create('job', { id: 'example', shallow: true });
server.create('evaluation', { jobId: job.id });
@@ -662,6 +663,7 @@ module('Acceptance | evaluations list', function (hooks) {
});
test('it should generate a link to the node resource', async function (assert) {
server.create('node-pool');
const node = server.create('node');
server.create('evaluation', { nodeId: node.id });
await visit('/evaluations');

View File

@@ -26,6 +26,7 @@ module('Acceptance | exec', function (hooks) {
faker.seed(1);
server.create('agent');
server.create('node-pool');
server.create('node');
this.job = server.create('job', {

View File

@@ -30,6 +30,7 @@ module('Acceptance | job allocations', function (hooks) {
setupMirage(hooks);
hooks.beforeEach(function () {
server.create('node-pool');
server.create('node');
job = server.create('job', {
@@ -211,6 +212,7 @@ module('Acceptance | job allocations', function (hooks) {
).sort();
},
async beforeEach() {
server.create('node-pool');
job = server.create('job', {
type: 'service',
status: 'running',

View File

@@ -43,6 +43,7 @@ module('Acceptance | job clients', function (hooks) {
},
});
server.createList('node-pool', 5);
clients = server.createList('node', 12, {
datacenter: 'dc1',
status: 'ready',

View File

@@ -21,6 +21,7 @@ module('Acceptance | job definition', function (hooks) {
setupCodeMirror(hooks);
hooks.beforeEach(async function () {
server.create('node-pool');
server.create('node');
server.create('job');
job = server.db.jobs[0];
@@ -137,12 +138,13 @@ module('Acceptance | job definition', function (hooks) {
});
});
module('display and edit using full specification', function (hooks) {
module('Acceptance | job definition | full specification', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
setupCodeMirror(hooks);
hooks.beforeEach(async function () {
server.create('node-pool');
server.create('node');
server.create('job');
job = server.db.jobs[0];

View File

@@ -24,6 +24,7 @@ module('Acceptance | job deployments', function (hooks) {
setupMirage(hooks);
hooks.beforeEach(function () {
server.create('node-pool');
server.create('node');
job = server.create('job');
deployments = server.schema.deployments.where({ jobId: job.id });

View File

@@ -175,6 +175,7 @@ moduleForJob(
job,
assert
) {
assert.notOk(JobDetail.jobsHeader.hasNodePool);
assert.notOk(JobDetail.jobsHeader.hasPriority);
assert.notOk(JobDetail.jobsHeader.hasType);
},
@@ -226,6 +227,7 @@ moduleForJob(
job,
assert
) {
assert.notOk(JobDetail.jobsHeader.hasNodePool);
assert.notOk(JobDetail.jobsHeader.hasPriority);
assert.notOk(JobDetail.jobsHeader.hasType);
},
@@ -319,6 +321,7 @@ module('Acceptance | job detail (with namespaces)', function (hooks) {
hooks.beforeEach(function () {
server.createList('namespace', 2);
server.create('node-pool');
server.create('node');
job = server.create('job', {
type: 'service',

View File

@@ -47,6 +47,7 @@ function moduleForJobDispatch(title, jobFactory) {
hooks.beforeEach(function () {
// Required for placing allocations (a result of dispatching jobs)
server.create('node-pool');
server.create('node');
job = jobFactory();

View File

@@ -19,6 +19,7 @@ module('Acceptance | job evaluations', function (hooks) {
setupMirage(hooks);
hooks.beforeEach(async function () {
server.create('node-pool');
job = server.create('job', {
noFailedPlacements: true,
createAllocations: false,

View File

@@ -66,6 +66,7 @@ module('Acceptance | job run', function (hooks) {
hooks.beforeEach(function () {
// Required for placing allocations (a result of creating jobs)
server.create('node-pool');
server.create('node');
managementToken = server.create('token');

View File

@@ -28,6 +28,7 @@ module('Acceptance | job status panel', function (hooks) {
setupMirage(hooks);
hooks.beforeEach(async function () {
server.create('node-pool');
server.create('node');
});

View File

@@ -23,6 +23,7 @@ module('Acceptance | job versions', function (hooks) {
setupMirage(hooks);
hooks.beforeEach(async function () {
server.create('node-pool');
server.create('namespace');
namespace = server.create('namespace');
@@ -176,6 +177,7 @@ module('Acceptance | job versions (with client token)', function (hooks) {
setupMirage(hooks);
hooks.beforeEach(async function () {
server.create('node-pool');
job = server.create('job', { createAllocations: false });
versions = server.db.jobVersions.where({ jobId: job.id });

View File

@@ -22,6 +22,7 @@ module('Acceptance | jobs list', function (hooks) {
hooks.beforeEach(function () {
// Required for placing allocations (a result of creating jobs)
server.create('node-pool');
server.create('node');
managementToken = server.create('token');
@@ -70,6 +71,7 @@ module('Acceptance | jobs list', function (hooks) {
assert.equal(jobRow.name, job.name, 'Name');
assert.notOk(jobRow.hasNamespace);
assert.equal(jobRow.nodePool, job.nodePool, 'Node Pool');
assert.equal(jobRow.link, `/ui/jobs/${job.id}@default`, 'Detail Link');
assert.equal(jobRow.status, job.status, 'Status');
assert.equal(jobRow.type, typeForJob(job), 'Type');

View File

@@ -248,6 +248,7 @@ module('Acceptance | keyboard', function (hooks) {
module('Dynamic Nav', function (dynamicHooks) {
dynamicHooks.beforeEach(async function () {
server.create('node-pool');
server.create('node');
});
test('Dynamic Table Nav', async function (assert) {

View File

@@ -40,6 +40,7 @@ module('Acceptance | optimize', function (hooks) {
hooks.beforeEach(async function () {
server.create('feature', { name: 'Dynamic Application Sizing' });
server.create('node-pool');
server.create('node');
server.createList('namespace', 2);
@@ -440,6 +441,7 @@ module('Acceptance | optimize search and facets', function (hooks) {
hooks.beforeEach(async function () {
server.create('feature', { name: 'Dynamic Application Sizing' });
server.create('node-pool');
server.create('node');
server.createList('namespace', 2);

View File

@@ -19,6 +19,7 @@ module('Acceptance | plugin allocations', function (hooks) {
let plugin;
hooks.beforeEach(function () {
server.create('node-pool');
server.create('node');
window.localStorage.clear();
});

View File

@@ -21,6 +21,7 @@ module('Acceptance | plugin detail', function (hooks) {
let plugin;
hooks.beforeEach(function () {
server.create('node-pool');
server.create('node');
plugin = server.create('csi-plugin', { controllerRequired: true });
});

View File

@@ -17,6 +17,7 @@ module('Acceptance | plugins list', function (hooks) {
setupMirage(hooks);
hooks.beforeEach(function () {
server.create('node-pool');
server.create('node');
window.localStorage.clear();
});

View File

@@ -22,6 +22,7 @@ module('Acceptance | regions (only one)', function (hooks) {
hooks.beforeEach(function () {
server.create('agent');
server.create('node-pool');
server.create('node');
server.createList('job', 2, {
createAllocations: false,
@@ -88,6 +89,7 @@ module('Acceptance | regions (many)', function (hooks) {
hooks.beforeEach(function () {
server.create('agent');
server.create('node-pool');
server.create('node');
server.createList('job', 2, {
createAllocations: false,

View File

@@ -19,6 +19,7 @@ module('Acceptance | search', function (hooks) {
setupMirage(hooks);
test('search exposes and navigates to results from the fuzzy search endpoint', async function (assert) {
server.create('node-pool');
server.create('node', { name: 'xyz' });
const otherNode = server.create('node', { name: 'ghi' });
@@ -184,6 +185,7 @@ module('Acceptance | search', function (hooks) {
});
test('results are truncated at 10 per group', async function (assert) {
server.create('node-pool');
server.create('node', { name: 'xyz' });
for (let i = 0; i < 11; i++) {
@@ -203,6 +205,7 @@ module('Acceptance | search', function (hooks) {
});
test('server-side truncation is indicated in the group label', async function (assert) {
server.create('node-pool');
server.create('node', { name: 'xyz' });
for (let i = 0; i < 21; i++) {
@@ -241,6 +244,7 @@ module('Acceptance | search', function (hooks) {
});
test('pressing slash when an input element is focused does not start a search', async function (assert) {
server.create('node-pool');
server.create('node');
server.create('job');

View File

@@ -17,6 +17,7 @@ import faker from 'nomad-ui/mirage/faker';
const minimumSetup = () => {
faker.seed(1);
server.createList('node-pool', 1);
server.createList('node', 1);
server.createList('agent', 1);
};
@@ -42,6 +43,7 @@ module('Acceptance | servers list', function (hooks) {
test('/servers should list all servers', async function (assert) {
faker.seed(1);
server.createList('node-pool', 1);
server.createList('node', 1);
server.createList('agent', 10);

View File

@@ -22,6 +22,7 @@ module('Acceptance | task detail', function (hooks) {
hooks.beforeEach(async function () {
server.create('agent');
server.create('node-pool');
server.create('node');
server.create('job', { createAllocations: false });
allocation = server.create('allocation', 'withTaskWithPorts', {
@@ -337,6 +338,7 @@ module('Acceptance | task detail (no addresses)', function (hooks) {
hooks.beforeEach(async function () {
server.create('agent');
server.create('node-pool');
server.create('node');
server.create('job');
allocation = server.create('allocation', 'withoutTaskWithPorts', {
@@ -354,6 +356,7 @@ module('Acceptance | task detail (different namespace)', function (hooks) {
hooks.beforeEach(async function () {
server.create('agent');
server.create('node-pool');
server.create('node');
server.create('namespace');
server.create('namespace', { id: 'other-namespace' });
@@ -412,6 +415,7 @@ module('Acceptance | task detail (not running)', function (hooks) {
hooks.beforeEach(async function () {
server.create('agent');
server.create('node-pool');
server.create('node');
server.create('namespace');
server.create('namespace', { id: 'other-namespace' });
@@ -447,6 +451,7 @@ module('Acceptance | proxy task detail', function (hooks) {
hooks.beforeEach(async function () {
server.create('agent');
server.create('node-pool');
server.create('node');
server.create('job', { createAllocations: false });
allocation = server.create('allocation', 'withTaskWithPorts', {

View File

@@ -21,6 +21,7 @@ module('Acceptance | task fs', function (hooks) {
hooks.beforeEach(async function () {
server.create('agent');
server.create('node-pool');
server.create('node', 'forceIPv4');
const job = server.create('job', { createAllocations: false });

View File

@@ -35,6 +35,7 @@ module('Acceptance | task group detail', function (hooks) {
hooks.beforeEach(async function () {
server.create('agent');
server.create('node-pool');
server.create('node', 'forceIPv4');
job = server.create('job', {

View File

@@ -25,6 +25,7 @@ module('Acceptance | task logs', function (hooks) {
hooks.beforeEach(async function () {
faker.seed(1);
server.create('agent');
server.create('node-pool');
server.create('node', 'forceIPv4');
job = server.create('job', { createAllocations: false });

View File

@@ -38,6 +38,7 @@ module('Acceptance | tokens', function (hooks) {
faker.seed(1);
server.create('agent');
server.create('node-pool');
node = server.create('node');
job = server.create('job');
managementToken = server.create('token');

View File

@@ -29,6 +29,7 @@ module('Acceptance | topology', function (hooks) {
setupMirage(hooks);
hooks.beforeEach(function () {
server.createList('node-pool', 5);
server.create('job', { createAllocations: false });
});
@@ -44,6 +45,7 @@ module('Acceptance | topology', function (hooks) {
test('by default the info panel shows cluster aggregate stats', async function (assert) {
faker.seed(1);
server.create('node-pool', { name: 'all' });
server.createList('node', 3);
server.createList('allocation', 5);
@@ -68,6 +70,15 @@ module('Acceptance | topology', function (hooks) {
`${scheduledAllocs.length} Allocations`
);
// Node pool count ignores 'all'.
const nodePools = server.schema.nodePools
.all()
.models.filter((p) => p.name !== 'all');
assert.equal(
Topology.clusterInfoPanel.nodePoolCount,
`${nodePools.length} Node Pools`
);
const nodeResources = server.schema.nodes
.all()
.models.mapBy('nodeResources');
@@ -322,22 +333,41 @@ module('Acceptance | topology', function (hooks) {
server.createList('node', 2, {
nodeClass: 'foo-bar-baz',
});
// Create node pool exclusive for these nodes.
server.create('node-pool', { name: 'test-node-pool' });
server.createList('node', 3, {
nodePool: 'test-node-pool',
});
server.createList('allocation', 5);
await Topology.visit();
assert.dom('[data-test-topo-viz-node]').exists({ count: 12 });
assert.dom('[data-test-topo-viz-node]').exists({ count: 15 });
await typeIn('input.node-search', server.schema.nodes.first().name);
assert.dom('[data-test-topo-viz-node]').exists({ count: 1 });
await typeIn('input.node-search', server.schema.nodes.first().name);
assert.dom('[data-test-topo-viz-node]').doesNotExist();
await click('[title="Clear search"]');
assert.dom('[data-test-topo-viz-node]').exists({ count: 12 });
assert.dom('[data-test-topo-viz-node]').exists({ count: 15 });
await Topology.facets.class.toggle();
await Topology.facets.class.options
.findOneBy('label', 'foo-bar-baz')
.toggle();
assert.dom('[data-test-topo-viz-node]').exists({ count: 2 });
await Topology.facets.class.options
.findOneBy('label', 'foo-bar-baz')
.toggle();
await Topology.facets.nodePool.toggle();
await Topology.facets.nodePool.options
.findOneBy('label', 'test-node-pool')
.toggle();
assert.dom('[data-test-topo-viz-node]').exists({ count: 3 });
await Topology.facets.nodePool.options
.findOneBy('label', 'test-node-pool')
.toggle();
});
});

View File

@@ -33,6 +33,7 @@ module('Acceptance | volume detail', function (hooks) {
let volume;
hooks.beforeEach(function () {
server.create('node-pool');
server.create('node');
server.create('csi-plugin', { createVolumes: false });
volume = server.create('csi-volume');
@@ -243,6 +244,7 @@ module('Acceptance | volume detail (with namespaces)', function (hooks) {
hooks.beforeEach(function () {
server.createList('namespace', 2);
server.create('node-pool');
server.create('node');
server.create('csi-plugin', { createVolumes: false });
volume = server.create('csi-volume');

View File

@@ -32,6 +32,7 @@ module('Acceptance | volumes list', function (hooks) {
hooks.beforeEach(function () {
faker.seed(1);
server.create('node-pool');
server.create('node');
server.create('csi-plugin', { createVolumes: false });
window.localStorage.clear();

View File

@@ -41,6 +41,7 @@ export default function moduleForJob(
});
hooks.beforeEach(async function () {
server.create('node-pool');
server.create('node');
job = jobFactory();
if (!job.namespace || job.namespace === 'default') {
@@ -103,6 +104,19 @@ export default function moduleForJob(
}
});
test('page header displays job information', async function (assert) {
assert.equal(JobDetail.statFor('type').text, `Type ${job.type}`);
assert.equal(
JobDetail.statFor('priority').text,
`Priority ${job.priority}`
);
assert.equal(JobDetail.statFor('version').text, `Version ${job.version}`);
assert.equal(
JobDetail.statFor('node-pool').text,
`Node Pool ${job.nodePool}`
);
});
if (context === 'allocations') {
test('allocations for the job are shown in the overview', async function (assert) {
if (jobTypesWithStatusPanel.includes(job.type)) {
@@ -252,6 +266,7 @@ export function moduleForJobWithClientStatus(
setupMirage(hooks);
hooks.beforeEach(async function () {
server.createList('node-pool', 3);
const clients = server.createList('node', 3, {
datacenter: 'dc1',
status: 'ready',

View File

@@ -21,6 +21,7 @@ module('Integration | Component | allocation row', function (hooks) {
this.store = this.owner.lookup('service:store');
this.server = startMirage();
this.server.create('namespace');
this.server.create('node-pool');
this.server.create('node');
this.server.create('job', { createAllocations: false });
});

View File

@@ -31,6 +31,7 @@ module('Integration | Component | job-editor', function (hooks) {
this.server = startMirage();
// Required for placing allocations (a result of creating jobs)
this.server.create('node-pool');
this.server.create('node');
});

View File

@@ -19,6 +19,7 @@ module('Integration | Component | job-page/parts/children', function (hooks) {
this.store = this.owner.lookup('service:store');
this.server = startMirage();
this.server.create('namespace');
this.server.create('node-pool');
});
hooks.afterEach(function () {

View File

@@ -24,6 +24,7 @@ module(
this.store = this.owner.lookup('service:store');
this.server = startMirage();
this.server.create('namespace');
this.server.create('node-pool');
});
hooks.afterEach(function () {

View File

@@ -20,6 +20,7 @@ module('Integration | Component | job-page/parts/summary', function (hooks) {
this.store = this.owner.lookup('service:store');
this.server = startMirage();
this.server.create('namespace');
this.server.create('node-pool');
});
hooks.afterEach(function () {

View File

@@ -25,6 +25,7 @@ module(
this.store = this.owner.lookup('service:store');
this.server = startMirage();
this.server.create('namespace');
this.server.create('node-pool');
});
hooks.afterEach(function () {

View File

@@ -39,6 +39,7 @@ module('Integration | Component | job-page/periodic', function (hooks) {
this.store = this.owner.lookup('service:store');
this.server = startMirage();
this.server.create('namespace');
this.server.create('node-pool');
});
hooks.afterEach(function () {

View File

@@ -31,6 +31,7 @@ module('Integration | Component | job-page/service', function (hooks) {
this.store = this.owner.lookup('service:store');
this.server = startMirage();
this.server.create('namespace');
this.server.create('node-pool');
});
hooks.afterEach(function () {

View File

@@ -22,6 +22,7 @@ module(
window.localStorage.clear();
this.store = this.owner.lookup('service:store');
this.server = startMirage();
this.server.create('node-pool');
this.server.create('namespace');
});

View File

@@ -18,6 +18,7 @@ module('Integration | Component | plugin allocation row', function (hooks) {
fragmentSerializerInitializer(this.owner);
this.store = this.owner.lookup('service:store');
this.server = startMirage();
this.server.create('node-pool');
this.server.create('node');
});

View File

@@ -27,6 +27,7 @@ module('Integration | Component | PrimaryMetric::Allocation', function (hooks) {
this.store = this.owner.lookup('service:store');
this.server = startMirage();
this.server.create('namespace');
this.server.create('node-pool');
this.server.create('node');
this.server.create('job', {
groupsCount: 1,

View File

@@ -22,6 +22,7 @@ module('Integration | Component | PrimaryMetric::Node', function (hooks) {
this.store = this.owner.lookup('service:store');
this.server = startMirage();
this.server.create('namespace');
this.server.create('node-pool');
this.server.create('node');
});

View File

@@ -27,6 +27,7 @@ module('Integration | Component | PrimaryMetric::Task', function (hooks) {
this.store = this.owner.lookup('service:store');
this.server = startMirage();
this.server.create('namespace');
this.server.create('node-pool');
this.server.create('node');
const job = this.server.create('job', {
groupsCount: 1,

View File

@@ -18,6 +18,7 @@ module('Integration | Component | reschedule event timeline', function (hooks) {
this.store = this.owner.lookup('service:store');
this.server = startMirage();
this.server.create('namespace');
this.server.create('node-pool');
this.server.create('node');
this.server.create('job', { createAllocations: false });
});

View File

@@ -20,6 +20,7 @@ module('Integration | Component | scale-events-accordion', function (hooks) {
fragmentSerializerInitializer(this.owner);
this.store = this.owner.lookup('service:store');
this.server = startMirage();
this.server.create('node-pool');
this.server.create('node');
this.taskGroupWithEvents = async function (events) {
const job = this.server.create('job', { createAllocations: false });

View File

@@ -58,6 +58,7 @@ module('Integration | Component | task group row', function (hooks) {
this.store = this.owner.lookup('service:store');
this.token = this.owner.lookup('service:token');
this.server = startMirage();
this.server.create('node-pool');
this.server.create('node');
managementToken = this.server.create('token');

View File

@@ -50,6 +50,7 @@ export default create({
},
address: text('[data-test-client-address]'),
nodePool: text('[data-test-client-node-pool]'),
datacenter: text('[data-test-client-datacenter]'),
version: text('[data-test-client-version]'),
allocations: text('[data-test-client-allocations]'),
@@ -75,6 +76,7 @@ export default create({
},
facets: {
nodePools: multiFacet('[data-test-node-pool-facet]'),
class: multiFacet('[data-test-class-facet]'),
state: multiFacet('[data-test-state-facet]'),
datacenter: multiFacet('[data-test-datacenter-facet]'),

View File

@@ -108,6 +108,7 @@ export default create({
scope: '[data-test-jobs-header]',
hasSubmitTime: isPresent('[data-test-jobs-submit-time-header]'),
hasNamespace: isPresent('[data-test-jobs-namespace-header]'),
hasNodePool: isPresent('[data-test-jobs-node-pool-header]'),
hasType: isPresent('[data-test-jobs-type-header]'),
hasPriority: isPresent('[data-test-jobs-priority-header]'),
},
@@ -115,8 +116,9 @@ export default create({
jobs: collection('[data-test-job-row]', {
id: attribute('data-test-job-row'),
name: text('[data-test-job-name]'),
namespace: text('[data-test-job-namespace]'),
link: attribute('href', '[data-test-job-name] a'),
namespace: text('[data-test-job-namespace]'),
nodePool: text('[data-test-job-node-pool]'),
submitTime: text('[data-test-job-submit-time]'),
status: text('[data-test-job-status]'),
type: text('[data-test-job-type]'),

View File

@@ -36,8 +36,9 @@ export default create({
jobs: collection('[data-test-job-row]', {
id: attribute('data-test-job-row'),
name: text('[data-test-job-name]'),
namespace: text('[data-test-job-namespace]'),
link: attribute('href', '[data-test-job-name] a'),
namespace: text('[data-test-job-namespace]'),
nodePool: text('[data-test-job-node-pool]'),
status: text('[data-test-job-status]'),
type: text('[data-test-job-type]'),
priority: text('[data-test-job-priority]'),

View File

@@ -26,6 +26,7 @@ export default create({
viz: TopoViz('[data-test-topo-viz]'),
facets: {
nodePool: multiFacet('[data-test-node-pool-facet]'),
datacenter: multiFacet('[data-test-datacenter-facet]'),
class: multiFacet('[data-test-class-facet]'),
state: multiFacet('[data-test-state-facet]'),
@@ -36,6 +37,7 @@ export default create({
scope: '[data-test-info-panel]',
nodeCount: text('[data-test-node-count]'),
allocCount: text('[data-test-alloc-count]'),
nodePoolCount: text('[data-test-node-pool-count]'),
memoryProgressValue: attribute('value', '[data-test-memory-progress-bar]'),
memoryAbsoluteValue: text('[data-test-memory-absolute-value]'),

View File

@@ -25,6 +25,7 @@ module('Unit | Adapter | Allocation', function (hooks) {
this.server.create('region', { id: 'region-1' });
this.server.create('region', { id: 'region-2' });
this.server.create('node-pool');
this.server.create('node');
this.server.create('job', { createAllocations: false });
this.server.create('allocation', { id: 'alloc-1' });

View File

@@ -25,6 +25,7 @@ module('Unit | Adapter | Deployment', function (hooks) {
this.server.create('region', { id: 'region-1' });
this.server.create('region', { id: 'region-2' });
this.server.create('node-pool');
this.server.create('node');
const job = this.server.create('job', { createAllocations: false });
const deploymentRecord = server.schema.deployments.where({

View File

@@ -34,6 +34,7 @@ module('Unit | Adapter | Job', function (hooks) {
this.server.create('namespace');
this.server.create('namespace', { id: 'some-namespace' });
this.server.create('node-pool');
this.server.create('node');
this.server.create('job', { id: 'job-1', namespaceId: 'default' });
this.server.create('job', { id: 'job-2', namespaceId: 'some-namespace' });

View File

@@ -23,6 +23,7 @@ module('Unit | Adapter | Node', function (hooks) {
this.server.create('region', { id: 'region-1' });
this.server.create('region', { id: 'region-2' });
this.server.create('node-pool');
this.server.create('node', { id: 'node-1' });
this.server.create('node', { id: 'node-2' });
this.server.create('job', { id: 'job-1', createAllocations: false });

View File

@@ -25,6 +25,7 @@ module('Unit | Adapter | Volume', function (hooks) {
this.initializeUI = async () => {
this.server.create('namespace');
this.server.create('namespace', { id: 'some-namespace' });
this.server.create('node-pool');
this.server.create('node');
this.server.create('job', { id: 'job-1', namespaceId: 'default' });
this.server.create('csi-plugin', 2);

View File

@@ -0,0 +1,121 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { setupTest } from 'ember-qunit';
import { module, test } from 'qunit';
module('Unit | Serializer | NodePool', function (hooks) {
setupTest(hooks);
hooks.beforeEach(function () {
this.store = this.owner.lookup('service:store');
this.subject = () => this.store.serializerFor('node-pool');
});
test('should serialize a NodePool', function (assert) {
const testCases = [
{
name: 'full node pool',
input: {
name: 'prod-eng',
description: 'Production workloads',
meta: {
env: 'production',
team: 'engineering',
},
schedulerConfiguration: {
SchedulerAlgorithm: 'spread',
MemoryOversubscriptionEnabled: true,
},
},
expected: {
Name: 'prod-eng',
Description: 'Production workloads',
Meta: {
env: 'production',
team: 'engineering',
},
SchedulerConfiguration: {
SchedulerAlgorithm: 'spread',
MemoryOversubscriptionEnabled: true,
},
},
},
{
name: 'node pool without scheduler configuration',
input: {
name: 'prod-eng',
description: 'Production workloads',
meta: {
env: 'production',
team: 'engineering',
},
},
expected: {
Name: 'prod-eng',
Description: 'Production workloads',
Meta: {
env: 'production',
team: 'engineering',
},
SchedulerConfiguration: undefined,
},
},
{
name: 'node pool with null scheduler configuration',
input: {
name: 'prod-eng',
description: 'Production workloads',
meta: {
env: 'production',
team: 'engineering',
},
schedulerConfiguration: null,
},
expected: {
Name: 'prod-eng',
Description: 'Production workloads',
Meta: {
env: 'production',
team: 'engineering',
},
SchedulerConfiguration: null,
},
},
{
name: 'node pool with empty scheduler configuration',
input: {
name: 'prod-eng',
description: 'Production workloads',
meta: {
env: 'production',
team: 'engineering',
},
schedulerConfiguration: {},
},
expected: {
Name: 'prod-eng',
Description: 'Production workloads',
Meta: {
env: 'production',
team: 'engineering',
},
SchedulerConfiguration: {},
},
},
];
assert.expect(testCases.length);
for (const tc of testCases) {
const nodePool = this.store.createRecord('node-pool', tc.input);
const got = this.subject().serialize(nodePool._createSnapshot());
assert.deepEqual(
got,
tc.expected,
`${tc.name} failed, got ${JSON.stringify(got)}`
);
}
});
});