From 4da63e3dedf8138759844734940d30b8b79337d5 Mon Sep 17 00:00:00 2001 From: Jai <41024828+ChaiWithJai@users.noreply.github.com> Date: Thu, 22 Jun 2023 13:11:44 -0400 Subject: [PATCH] ui: create node pool model (#17301) Co-authored-by: Phil Renaud Co-authored-by: Luiz Aoqui --- ui/app/adapters/node-pool.js | 17 +++ ui/app/controllers/clients/index.js | 35 +++++ ui/app/controllers/jobs/index.js | 34 +++++ ui/app/controllers/topology.js | 32 +++++ ui/app/models/job.js | 1 + ui/app/models/node-pool.js | 27 ++++ ui/app/models/node.js | 1 + ui/app/routes/clients.js | 1 + ui/app/routes/jobs/index.js | 1 + ui/app/routes/topology.js | 5 + ui/app/serializers/node-pool.js | 12 ++ .../styles/components/dashboard-metric.scss | 6 + ui/app/templates/clients/client/index.hbs | 10 +- ui/app/templates/clients/index.hbs | 10 +- .../templates/components/client-node-row.hbs | 1 + .../components/job-page/parts/stats-box.hbs | 4 + ui/app/templates/components/job-row.hbs | 3 + ui/app/templates/components/topo-viz/node.hbs | 1 + ui/app/templates/jobs/index.hbs | 12 +- ui/app/templates/topology.hbs | 19 ++- ui/mirage/config.js | 4 + ui/mirage/factories/job.js | 11 ++ ui/mirage/factories/node-pool.js | 13 ++ ui/mirage/factories/node.js | 49 +++++-- ui/mirage/models/node-pool.js | 8 ++ ui/mirage/scenarios/default.js | 15 +++ ui/mirage/scenarios/topo.js | 5 +- ui/mirage/utils.js | 10 +- ui/tests/acceptance/allocation-detail-test.js | 5 + ui/tests/acceptance/allocation-fs-test.js | 1 + .../acceptance/application-errors-test.js | 1 + ui/tests/acceptance/client-detail-test.js | 4 + ui/tests/acceptance/client-monitor-test.js | 1 + ui/tests/acceptance/clients-list-test.js | 25 ++++ ui/tests/acceptance/evaluations-test.js | 2 + ui/tests/acceptance/exec-test.js | 1 + ui/tests/acceptance/job-allocations-test.js | 2 + ui/tests/acceptance/job-clients-test.js | 1 + ui/tests/acceptance/job-definition-test.js | 4 +- ui/tests/acceptance/job-deployments-test.js | 1 + ui/tests/acceptance/job-detail-test.js | 3 + ui/tests/acceptance/job-dispatch-test.js | 1 + ui/tests/acceptance/job-evaluations-test.js | 1 + ui/tests/acceptance/job-run-test.js | 1 + ui/tests/acceptance/job-status-panel-test.js | 1 + ui/tests/acceptance/job-versions-test.js | 2 + ui/tests/acceptance/jobs-list-test.js | 2 + ui/tests/acceptance/keyboard-test.js | 1 + ui/tests/acceptance/optimize-test.js | 2 + .../acceptance/plugin-allocations-test.js | 1 + ui/tests/acceptance/plugin-detail-test.js | 1 + ui/tests/acceptance/plugins-list-test.js | 1 + ui/tests/acceptance/regions-test.js | 2 + ui/tests/acceptance/search-test.js | 4 + ui/tests/acceptance/servers-list-test.js | 2 + ui/tests/acceptance/task-detail-test.js | 5 + ui/tests/acceptance/task-fs-test.js | 1 + ui/tests/acceptance/task-group-detail-test.js | 1 + ui/tests/acceptance/task-logs-test.js | 1 + ui/tests/acceptance/token-test.js | 1 + ui/tests/acceptance/topology-test.js | 34 ++++- ui/tests/acceptance/volume-detail-test.js | 2 + ui/tests/acceptance/volumes-list-test.js | 1 + ui/tests/helpers/module-for-job.js | 15 +++ .../components/allocation-row-test.js | 1 + .../integration/components/job-editor-test.js | 1 + .../job-page/parts/children-test.js | 1 + .../job-page/parts/placement-failures-test.js | 1 + .../components/job-page/parts/summary-test.js | 1 + .../job-page/parts/task-groups-test.js | 1 + .../components/job-page/periodic-test.js | 1 + .../components/job-page/service-test.js | 1 + .../components/job-status-panel-test.js | 1 + .../components/plugin-allocation-row-test.js | 1 + .../primary-metric/allocation-test.js | 1 + .../components/primary-metric/node-test.js | 1 + .../components/primary-metric/task-test.js | 1 + .../reschedule-event-timeline-test.js | 1 + .../components/scale-events-accordion-test.js | 1 + .../components/task-group-row-test.js | 1 + ui/tests/pages/clients/list.js | 2 + ui/tests/pages/jobs/detail.js | 4 +- ui/tests/pages/jobs/list.js | 3 +- ui/tests/pages/topology.js | 2 + ui/tests/unit/adapters/allocation-test.js | 1 + ui/tests/unit/adapters/deployment-test.js | 1 + ui/tests/unit/adapters/job-test.js | 1 + ui/tests/unit/adapters/node-test.js | 1 + ui/tests/unit/adapters/volume-test.js | 1 + ui/tests/unit/serializers/node-pool-test.js | 121 ++++++++++++++++++ 90 files changed, 604 insertions(+), 30 deletions(-) create mode 100644 ui/app/adapters/node-pool.js create mode 100644 ui/app/models/node-pool.js create mode 100644 ui/app/serializers/node-pool.js create mode 100644 ui/mirage/factories/node-pool.js create mode 100644 ui/mirage/models/node-pool.js create mode 100644 ui/tests/unit/serializers/node-pool-test.js diff --git a/ui/app/adapters/node-pool.js b/ui/app/adapters/node-pool.js new file mode 100644 index 000000000..93943b0f0 --- /dev/null +++ b/ui/app/adapters/node-pool.js @@ -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}`; + } +} diff --git a/ui/app/controllers/clients/index.js b/ui/app/controllers/clients/index.js index 672b36bd4..085077fa2 100644 --- a/ui/app/controllers/clients/index.js +++ b/ui/app/controllers/clients/index.js @@ -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; diff --git a/ui/app/controllers/jobs/index.js b/ui/app/controllers/jobs/index.js index 9e5648065..bb3a59bf0 100644 --- a/ui/app/controllers/jobs/index.js +++ b/ui/app/controllers/jobs/index.js @@ -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 && diff --git a/ui/app/controllers/topology.js b/ui/app/controllers/topology.js index fee99a39f..4612c4bf4 100644 --- a/ui/app/controllers/topology.js +++ b/ui/app/controllers/topology.js @@ -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)) diff --git a/ui/app/models/job.js b/ui/app/models/job.js index 35f7ddd02..18c8f8541 100644 --- a/ui/app/models/job.js +++ b/ui/app/models/job.js @@ -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; diff --git a/ui/app/models/node-pool.js b/ui/app/models/node-pool.js new file mode 100644 index 000000000..57730ccb8 --- /dev/null +++ b/ui/app/models/node-pool.js @@ -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'); + } +} diff --git a/ui/app/models/node.js b/ui/app/models/node.js index bd55a1661..1600f55f6 100644 --- a/ui/app/models/node.js +++ b/ui/app/models/node.js @@ -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; diff --git a/ui/app/routes/clients.js b/ui/app/routes/clients.js index bdc4fe199..bde64750b 100644 --- a/ui/app/routes/clients.js +++ b/ui/app/routes/clients.js @@ -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)); } } diff --git a/ui/app/routes/jobs/index.js b/ui/app/routes/jobs/index.js index 815d4e103..5bbd8a07d 100644 --- a/ui/app/routes/jobs/index.js +++ b/ui/app/routes/jobs/index.js @@ -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'), }); } diff --git a/ui/app/routes/topology.js b/ui/app/routes/topology.js index 68be3a282..26da72fa9 100644 --- a/ui/app/routes/topology.js +++ b/ui/app/routes/topology.js @@ -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: [], }; } diff --git a/ui/app/serializers/node-pool.js b/ui/app/serializers/node-pool.js new file mode 100644 index 000000000..ebc575b3d --- /dev/null +++ b/ui/app/serializers/node-pool.js @@ -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'; +} diff --git a/ui/app/styles/components/dashboard-metric.scss b/ui/app/styles/components/dashboard-metric.scss index 15c2ac0f9..7a8d76af2 100644 --- a/ui/app/styles/components/dashboard-metric.scss +++ b/ui/app/styles/components/dashboard-metric.scss @@ -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; } diff --git a/ui/app/templates/clients/client/index.hbs b/ui/app/templates/clients/client/index.hbs index 8da47176e..8ef064f1c 100644 --- a/ui/app/templates/clients/client/index.hbs +++ b/ui/app/templates/clients/client/index.hbs @@ -243,6 +243,12 @@ {{this.model.datacenter}} + + + Node Pool + + {{this.model.nodePool}} + {{#if this.model.nodeClass}} @@ -561,7 +567,7 @@
+ Name State Address + Node Pool Datacenter Version # Volumes @@ -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) }} diff --git a/ui/app/templates/components/client-node-row.hbs b/ui/app/templates/components/client-node-row.hbs index 3e1b7cc9d..000761d54 100644 --- a/ui/app/templates/components/client-node-row.hbs +++ b/ui/app/templates/components/client-node-row.hbs @@ -18,6 +18,7 @@ {{this.node.httpAddr}} +{{this.node.nodePool}} {{this.node.datacenter}} {{this.node.version}} {{if this.node.hostVolumes.length this.node.hostVolumes.length}} diff --git a/ui/app/templates/components/job-page/parts/stats-box.hbs b/ui/app/templates/components/job-page/parts/stats-box.hbs index 769501dfe..1db2330c5 100644 --- a/ui/app/templates/components/job-page/parts/stats-box.hbs +++ b/ui/app/templates/components/job-page/parts/stats-box.hbs @@ -31,6 +31,10 @@ {{@job.namespace.name}} {{/if}} + + Node Pool + {{@job.nodePool}} + {{yield to="after-namespace"}}
diff --git a/ui/app/templates/components/job-row.hbs b/ui/app/templates/components/job-row.hbs index 37d3300da..35c34df7a 100644 --- a/ui/app/templates/components/job-row.hbs +++ b/ui/app/templates/components/job-row.hbs @@ -31,6 +31,9 @@ {{this.job.namespace.name}} {{/if}} + + {{this.job.nodePool}} + {{/if}} {{#if (eq @context "child")}} diff --git a/ui/app/templates/components/topo-viz/node.hbs b/ui/app/templates/components/topo-viz/node.hbs index bc3e215b9..4d356b8b2 100644 --- a/ui/app/templates/components/topo-viz/node.hbs +++ b/ui/app/templates/components/topo-viz/node.hbs @@ -13,6 +13,7 @@ {{/if}} {{@node.node.name}} {{this.count}} Allocs + {{@node.node.nodePool}} {{format-scheduled-bytes @node.memory start="MiB"}}, {{format-scheduled-hertz @node.cpu}} diff --git a/ui/app/templates/jobs/index.hbs b/ui/app/templates/jobs/index.hbs index 1385c2994..6e1a7d1eb 100644 --- a/ui/app/templates/jobs/index.hbs +++ b/ui/app/templates/jobs/index.hbs @@ -58,6 +58,13 @@ @selection={{this.selectionType}} @onSelect={{action this.setFacetQueryParam "qpType"}} /> + {{/if}} + + Node Pool + Status diff --git a/ui/app/templates/topology.hbs b/ui/app/templates/topology.hbs index 30256061f..864470a50 100644 --- a/ui/app/templates/topology.hbs +++ b/ui/app/templates/topology.hbs @@ -370,7 +370,7 @@ {{else}}
-

+

{{this.model.nodes.length}} Clients @@ -378,13 +378,21 @@

-

+

{{this.scheduledAllocations.length}} Allocations

+
+

+ {{this.model.nodePools.length}} + + Node Pools + +

+

@@ -479,6 +487,13 @@

+ `node-pool-${i}`, + description: (i) => `describe node-pool-${i}`, + meta: {}, + schedulerConfiguration: {}, +}); diff --git a/ui/mirage/factories/node.js b/ui/mirage/factories/node.js index 3e2ec5fe6..dec7e0d02 100644 --- a/ui/mirage/factories/node.js +++ b/ui/mirage/factories/node.js @@ -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 = { diff --git a/ui/mirage/models/node-pool.js b/ui/mirage/models/node-pool.js new file mode 100644 index 000000000..8566ec638 --- /dev/null +++ b/ui/mirage/models/node-pool.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import { Model } from 'ember-cli-mirage'; + +export default Model.extend({}); diff --git a/ui/mirage/scenarios/default.js b/ui/mirage/scenarios/default.js index 5216e9274..f55ffbad4 100644 --- a/ui/mirage/scenarios/default.js +++ b/ui/mirage/scenarios/default.js @@ -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', { diff --git a/ui/mirage/scenarios/topo.js b/ui/mirage/scenarios/topo.js index b96370aca..723747572 100644 --- a/ui/mirage/scenarios/topo.js +++ b/ui/mirage/scenarios/topo.js @@ -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'], diff --git a/ui/mirage/utils.js b/ui/mirage/utils.js index 4cc11b375..8d613607e 100644 --- a/ui/mirage/utils.js +++ b/ui/mirage/utils.js @@ -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, diff --git a/ui/tests/acceptance/allocation-detail-test.js b/ui/tests/acceptance/allocation-detail-test.js index b358c9453..340da9014 100644 --- a/ui/tests/acceptance/allocation-detail-test.js +++ b/ui/tests/acceptance/allocation-detail-test.js @@ -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', { diff --git a/ui/tests/acceptance/allocation-fs-test.js b/ui/tests/acceptance/allocation-fs-test.js index 3da48499b..f167719b6 100644 --- a/ui/tests/acceptance/allocation-fs-test.js +++ b/ui/tests/acceptance/allocation-fs-test.js @@ -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 }); diff --git a/ui/tests/acceptance/application-errors-test.js b/ui/tests/acceptance/application-errors-test.js index c6d0041da..130ee3651 100644 --- a/ui/tests/acceptance/application-errors-test.js +++ b/ui/tests/acceptance/application-errors-test.js @@ -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'); }); diff --git a/ui/tests/acceptance/client-detail-test.js b/ui/tests/acceptance/client-detail-test.js index 5d657b112..9900f4ec3 100644 --- a/ui/tests/acceptance/client-detail-test.js +++ b/ui/tests/acceptance/client-detail-test.js @@ -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]; diff --git a/ui/tests/acceptance/client-monitor-test.js b/ui/tests/acceptance/client-monitor-test.js index 59bbf909d..a33523b53 100644 --- a/ui/tests/acceptance/client-monitor-test.js +++ b/ui/tests/acceptance/client-monitor-test.js @@ -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'); diff --git a/ui/tests/acceptance/clients-list-test.js b/ui/tests/acceptance/clients-list-test.js index 5479a7e09..a50acd78f 100644 --- a/ui/tests/acceptance/clients-list-test.js +++ b/ui/tests/acceptance/clients-list-test.js @@ -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', diff --git a/ui/tests/acceptance/evaluations-test.js b/ui/tests/acceptance/evaluations-test.js index 9397d8a39..0593558be 100644 --- a/ui/tests/acceptance/evaluations-test.js +++ b/ui/tests/acceptance/evaluations-test.js @@ -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'); diff --git a/ui/tests/acceptance/exec-test.js b/ui/tests/acceptance/exec-test.js index fbf182283..1240189dd 100644 --- a/ui/tests/acceptance/exec-test.js +++ b/ui/tests/acceptance/exec-test.js @@ -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', { diff --git a/ui/tests/acceptance/job-allocations-test.js b/ui/tests/acceptance/job-allocations-test.js index f3883a375..837cb692c 100644 --- a/ui/tests/acceptance/job-allocations-test.js +++ b/ui/tests/acceptance/job-allocations-test.js @@ -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', diff --git a/ui/tests/acceptance/job-clients-test.js b/ui/tests/acceptance/job-clients-test.js index 384f0e1ef..c9876dba0 100644 --- a/ui/tests/acceptance/job-clients-test.js +++ b/ui/tests/acceptance/job-clients-test.js @@ -43,6 +43,7 @@ module('Acceptance | job clients', function (hooks) { }, }); + server.createList('node-pool', 5); clients = server.createList('node', 12, { datacenter: 'dc1', status: 'ready', diff --git a/ui/tests/acceptance/job-definition-test.js b/ui/tests/acceptance/job-definition-test.js index 8caa9c9ba..a6b260a24 100644 --- a/ui/tests/acceptance/job-definition-test.js +++ b/ui/tests/acceptance/job-definition-test.js @@ -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]; diff --git a/ui/tests/acceptance/job-deployments-test.js b/ui/tests/acceptance/job-deployments-test.js index ae3f9cca6..401945336 100644 --- a/ui/tests/acceptance/job-deployments-test.js +++ b/ui/tests/acceptance/job-deployments-test.js @@ -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 }); diff --git a/ui/tests/acceptance/job-detail-test.js b/ui/tests/acceptance/job-detail-test.js index 905888d27..4517df0e4 100644 --- a/ui/tests/acceptance/job-detail-test.js +++ b/ui/tests/acceptance/job-detail-test.js @@ -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', diff --git a/ui/tests/acceptance/job-dispatch-test.js b/ui/tests/acceptance/job-dispatch-test.js index 074d03e8a..b72538a00 100644 --- a/ui/tests/acceptance/job-dispatch-test.js +++ b/ui/tests/acceptance/job-dispatch-test.js @@ -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(); diff --git a/ui/tests/acceptance/job-evaluations-test.js b/ui/tests/acceptance/job-evaluations-test.js index a974da9d0..79c252ac6 100644 --- a/ui/tests/acceptance/job-evaluations-test.js +++ b/ui/tests/acceptance/job-evaluations-test.js @@ -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, diff --git a/ui/tests/acceptance/job-run-test.js b/ui/tests/acceptance/job-run-test.js index 8f683f0eb..14a3943af 100644 --- a/ui/tests/acceptance/job-run-test.js +++ b/ui/tests/acceptance/job-run-test.js @@ -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'); diff --git a/ui/tests/acceptance/job-status-panel-test.js b/ui/tests/acceptance/job-status-panel-test.js index ea1b65e3a..179dac8d9 100644 --- a/ui/tests/acceptance/job-status-panel-test.js +++ b/ui/tests/acceptance/job-status-panel-test.js @@ -28,6 +28,7 @@ module('Acceptance | job status panel', function (hooks) { setupMirage(hooks); hooks.beforeEach(async function () { + server.create('node-pool'); server.create('node'); }); diff --git a/ui/tests/acceptance/job-versions-test.js b/ui/tests/acceptance/job-versions-test.js index ea7c91df4..cf6238526 100644 --- a/ui/tests/acceptance/job-versions-test.js +++ b/ui/tests/acceptance/job-versions-test.js @@ -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 }); diff --git a/ui/tests/acceptance/jobs-list-test.js b/ui/tests/acceptance/jobs-list-test.js index 9ce183430..9831c8655 100644 --- a/ui/tests/acceptance/jobs-list-test.js +++ b/ui/tests/acceptance/jobs-list-test.js @@ -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'); diff --git a/ui/tests/acceptance/keyboard-test.js b/ui/tests/acceptance/keyboard-test.js index 877a3d670..ab42c40a2 100644 --- a/ui/tests/acceptance/keyboard-test.js +++ b/ui/tests/acceptance/keyboard-test.js @@ -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) { diff --git a/ui/tests/acceptance/optimize-test.js b/ui/tests/acceptance/optimize-test.js index 0eb508c04..481d279df 100644 --- a/ui/tests/acceptance/optimize-test.js +++ b/ui/tests/acceptance/optimize-test.js @@ -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); diff --git a/ui/tests/acceptance/plugin-allocations-test.js b/ui/tests/acceptance/plugin-allocations-test.js index 9eec71c04..fff39a3ef 100644 --- a/ui/tests/acceptance/plugin-allocations-test.js +++ b/ui/tests/acceptance/plugin-allocations-test.js @@ -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(); }); diff --git a/ui/tests/acceptance/plugin-detail-test.js b/ui/tests/acceptance/plugin-detail-test.js index f53478489..d6f6df5b4 100644 --- a/ui/tests/acceptance/plugin-detail-test.js +++ b/ui/tests/acceptance/plugin-detail-test.js @@ -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 }); }); diff --git a/ui/tests/acceptance/plugins-list-test.js b/ui/tests/acceptance/plugins-list-test.js index 819a1055a..aea711fd1 100644 --- a/ui/tests/acceptance/plugins-list-test.js +++ b/ui/tests/acceptance/plugins-list-test.js @@ -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(); }); diff --git a/ui/tests/acceptance/regions-test.js b/ui/tests/acceptance/regions-test.js index 717375f5a..39dd64f1e 100644 --- a/ui/tests/acceptance/regions-test.js +++ b/ui/tests/acceptance/regions-test.js @@ -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, diff --git a/ui/tests/acceptance/search-test.js b/ui/tests/acceptance/search-test.js index c0322b25e..6c0912cd0 100644 --- a/ui/tests/acceptance/search-test.js +++ b/ui/tests/acceptance/search-test.js @@ -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'); diff --git a/ui/tests/acceptance/servers-list-test.js b/ui/tests/acceptance/servers-list-test.js index 3217f90cf..5d2ac9755 100644 --- a/ui/tests/acceptance/servers-list-test.js +++ b/ui/tests/acceptance/servers-list-test.js @@ -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); diff --git a/ui/tests/acceptance/task-detail-test.js b/ui/tests/acceptance/task-detail-test.js index b40cebc04..e5b8a459f 100644 --- a/ui/tests/acceptance/task-detail-test.js +++ b/ui/tests/acceptance/task-detail-test.js @@ -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', { diff --git a/ui/tests/acceptance/task-fs-test.js b/ui/tests/acceptance/task-fs-test.js index 0739fe045..521a9d8f5 100644 --- a/ui/tests/acceptance/task-fs-test.js +++ b/ui/tests/acceptance/task-fs-test.js @@ -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 }); diff --git a/ui/tests/acceptance/task-group-detail-test.js b/ui/tests/acceptance/task-group-detail-test.js index 74e82762c..04a39d8be 100644 --- a/ui/tests/acceptance/task-group-detail-test.js +++ b/ui/tests/acceptance/task-group-detail-test.js @@ -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', { diff --git a/ui/tests/acceptance/task-logs-test.js b/ui/tests/acceptance/task-logs-test.js index e6f2b6f42..45432adae 100644 --- a/ui/tests/acceptance/task-logs-test.js +++ b/ui/tests/acceptance/task-logs-test.js @@ -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 }); diff --git a/ui/tests/acceptance/token-test.js b/ui/tests/acceptance/token-test.js index 55ca88dc4..e6ce82838 100644 --- a/ui/tests/acceptance/token-test.js +++ b/ui/tests/acceptance/token-test.js @@ -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'); diff --git a/ui/tests/acceptance/topology-test.js b/ui/tests/acceptance/topology-test.js index 2018c2d2d..d541e804c 100644 --- a/ui/tests/acceptance/topology-test.js +++ b/ui/tests/acceptance/topology-test.js @@ -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(); }); }); diff --git a/ui/tests/acceptance/volume-detail-test.js b/ui/tests/acceptance/volume-detail-test.js index 85e1cc496..7831aae19 100644 --- a/ui/tests/acceptance/volume-detail-test.js +++ b/ui/tests/acceptance/volume-detail-test.js @@ -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'); diff --git a/ui/tests/acceptance/volumes-list-test.js b/ui/tests/acceptance/volumes-list-test.js index 75a27519e..a91667655 100644 --- a/ui/tests/acceptance/volumes-list-test.js +++ b/ui/tests/acceptance/volumes-list-test.js @@ -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(); diff --git a/ui/tests/helpers/module-for-job.js b/ui/tests/helpers/module-for-job.js index 788b2d157..bface5526 100644 --- a/ui/tests/helpers/module-for-job.js +++ b/ui/tests/helpers/module-for-job.js @@ -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', diff --git a/ui/tests/integration/components/allocation-row-test.js b/ui/tests/integration/components/allocation-row-test.js index 8a9f05352..fc63c2848 100644 --- a/ui/tests/integration/components/allocation-row-test.js +++ b/ui/tests/integration/components/allocation-row-test.js @@ -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 }); }); diff --git a/ui/tests/integration/components/job-editor-test.js b/ui/tests/integration/components/job-editor-test.js index c1c14bf85..18d817f8a 100644 --- a/ui/tests/integration/components/job-editor-test.js +++ b/ui/tests/integration/components/job-editor-test.js @@ -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'); }); diff --git a/ui/tests/integration/components/job-page/parts/children-test.js b/ui/tests/integration/components/job-page/parts/children-test.js index 1b37955b0..f3bd2aadd 100644 --- a/ui/tests/integration/components/job-page/parts/children-test.js +++ b/ui/tests/integration/components/job-page/parts/children-test.js @@ -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 () { diff --git a/ui/tests/integration/components/job-page/parts/placement-failures-test.js b/ui/tests/integration/components/job-page/parts/placement-failures-test.js index 96930aae9..1b764e4de 100644 --- a/ui/tests/integration/components/job-page/parts/placement-failures-test.js +++ b/ui/tests/integration/components/job-page/parts/placement-failures-test.js @@ -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 () { diff --git a/ui/tests/integration/components/job-page/parts/summary-test.js b/ui/tests/integration/components/job-page/parts/summary-test.js index 1dda95c92..c8c24f3e3 100644 --- a/ui/tests/integration/components/job-page/parts/summary-test.js +++ b/ui/tests/integration/components/job-page/parts/summary-test.js @@ -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 () { diff --git a/ui/tests/integration/components/job-page/parts/task-groups-test.js b/ui/tests/integration/components/job-page/parts/task-groups-test.js index 5cb3b6dba..69fc3d533 100644 --- a/ui/tests/integration/components/job-page/parts/task-groups-test.js +++ b/ui/tests/integration/components/job-page/parts/task-groups-test.js @@ -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 () { diff --git a/ui/tests/integration/components/job-page/periodic-test.js b/ui/tests/integration/components/job-page/periodic-test.js index 3e775747b..207d1ab7f 100644 --- a/ui/tests/integration/components/job-page/periodic-test.js +++ b/ui/tests/integration/components/job-page/periodic-test.js @@ -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 () { diff --git a/ui/tests/integration/components/job-page/service-test.js b/ui/tests/integration/components/job-page/service-test.js index db1bc79cc..aeae07779 100644 --- a/ui/tests/integration/components/job-page/service-test.js +++ b/ui/tests/integration/components/job-page/service-test.js @@ -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 () { diff --git a/ui/tests/integration/components/job-status-panel-test.js b/ui/tests/integration/components/job-status-panel-test.js index 94843cbea..54c37ddff 100644 --- a/ui/tests/integration/components/job-status-panel-test.js +++ b/ui/tests/integration/components/job-status-panel-test.js @@ -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'); }); diff --git a/ui/tests/integration/components/plugin-allocation-row-test.js b/ui/tests/integration/components/plugin-allocation-row-test.js index 552ff34a0..66835dbfc 100644 --- a/ui/tests/integration/components/plugin-allocation-row-test.js +++ b/ui/tests/integration/components/plugin-allocation-row-test.js @@ -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'); }); diff --git a/ui/tests/integration/components/primary-metric/allocation-test.js b/ui/tests/integration/components/primary-metric/allocation-test.js index 76aeb80cf..1b7f75ff6 100644 --- a/ui/tests/integration/components/primary-metric/allocation-test.js +++ b/ui/tests/integration/components/primary-metric/allocation-test.js @@ -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, diff --git a/ui/tests/integration/components/primary-metric/node-test.js b/ui/tests/integration/components/primary-metric/node-test.js index 96dace8b5..ae93a69e1 100644 --- a/ui/tests/integration/components/primary-metric/node-test.js +++ b/ui/tests/integration/components/primary-metric/node-test.js @@ -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'); }); diff --git a/ui/tests/integration/components/primary-metric/task-test.js b/ui/tests/integration/components/primary-metric/task-test.js index 1b1348974..11e906aa2 100644 --- a/ui/tests/integration/components/primary-metric/task-test.js +++ b/ui/tests/integration/components/primary-metric/task-test.js @@ -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, diff --git a/ui/tests/integration/components/reschedule-event-timeline-test.js b/ui/tests/integration/components/reschedule-event-timeline-test.js index 79fbdc017..ac9fd46e3 100644 --- a/ui/tests/integration/components/reschedule-event-timeline-test.js +++ b/ui/tests/integration/components/reschedule-event-timeline-test.js @@ -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 }); }); diff --git a/ui/tests/integration/components/scale-events-accordion-test.js b/ui/tests/integration/components/scale-events-accordion-test.js index 46aeffe00..51c964b05 100644 --- a/ui/tests/integration/components/scale-events-accordion-test.js +++ b/ui/tests/integration/components/scale-events-accordion-test.js @@ -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 }); diff --git a/ui/tests/integration/components/task-group-row-test.js b/ui/tests/integration/components/task-group-row-test.js index 7fdcea883..e6468be52 100644 --- a/ui/tests/integration/components/task-group-row-test.js +++ b/ui/tests/integration/components/task-group-row-test.js @@ -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'); diff --git a/ui/tests/pages/clients/list.js b/ui/tests/pages/clients/list.js index f99ac65d3..b7eebbd27 100644 --- a/ui/tests/pages/clients/list.js +++ b/ui/tests/pages/clients/list.js @@ -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]'), diff --git a/ui/tests/pages/jobs/detail.js b/ui/tests/pages/jobs/detail.js index cdab2e815..91465c6ef 100644 --- a/ui/tests/pages/jobs/detail.js +++ b/ui/tests/pages/jobs/detail.js @@ -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]'), diff --git a/ui/tests/pages/jobs/list.js b/ui/tests/pages/jobs/list.js index db0782b0a..f8de52c70 100644 --- a/ui/tests/pages/jobs/list.js +++ b/ui/tests/pages/jobs/list.js @@ -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]'), diff --git a/ui/tests/pages/topology.js b/ui/tests/pages/topology.js index de093b471..bb3f830bf 100644 --- a/ui/tests/pages/topology.js +++ b/ui/tests/pages/topology.js @@ -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]'), diff --git a/ui/tests/unit/adapters/allocation-test.js b/ui/tests/unit/adapters/allocation-test.js index 9af24c1f7..e18a5cb0e 100644 --- a/ui/tests/unit/adapters/allocation-test.js +++ b/ui/tests/unit/adapters/allocation-test.js @@ -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' }); diff --git a/ui/tests/unit/adapters/deployment-test.js b/ui/tests/unit/adapters/deployment-test.js index 22f072b01..11374e2d2 100644 --- a/ui/tests/unit/adapters/deployment-test.js +++ b/ui/tests/unit/adapters/deployment-test.js @@ -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({ diff --git a/ui/tests/unit/adapters/job-test.js b/ui/tests/unit/adapters/job-test.js index 9a6f0a9dd..9e1e9384d 100644 --- a/ui/tests/unit/adapters/job-test.js +++ b/ui/tests/unit/adapters/job-test.js @@ -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' }); diff --git a/ui/tests/unit/adapters/node-test.js b/ui/tests/unit/adapters/node-test.js index 98f1b4427..2dd5a4382 100644 --- a/ui/tests/unit/adapters/node-test.js +++ b/ui/tests/unit/adapters/node-test.js @@ -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 }); diff --git a/ui/tests/unit/adapters/volume-test.js b/ui/tests/unit/adapters/volume-test.js index 58d78a7f8..095c57190 100644 --- a/ui/tests/unit/adapters/volume-test.js +++ b/ui/tests/unit/adapters/volume-test.js @@ -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); diff --git a/ui/tests/unit/serializers/node-pool-test.js b/ui/tests/unit/serializers/node-pool-test.js new file mode 100644 index 000000000..149caff6c --- /dev/null +++ b/ui/tests/unit/serializers/node-pool-test.js @@ -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)}` + ); + } + }); +});