From 8c76584356f9c9dfdffc0f2699b0da5206aefd14 Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Tue, 5 May 2020 14:28:15 -0700 Subject: [PATCH] Acceptance test for PluginDetail --- ui/app/templates/csi/plugins/plugin.hbs | 10 +- ui/mirage/factories/csi-plugin.js | 20 ++- ui/tests/acceptance/plugin-detail-test.js | 164 ++++++++++++++++++++++ ui/tests/acceptance/volume-detail-test.js | 2 +- ui/tests/pages/components/allocations.js | 2 + 5 files changed, 181 insertions(+), 17 deletions(-) create mode 100644 ui/tests/acceptance/plugin-detail-test.js diff --git a/ui/app/templates/csi/plugins/plugin.hbs b/ui/app/templates/csi/plugins/plugin.hbs index 51889d9e301..e777a11e983 100644 --- a/ui/app/templates/csi/plugins/plugin.hbs +++ b/ui/app/templates/csi/plugins/plugin.hbs @@ -1,4 +1,4 @@ -{{title "CSI Plugin " model.id}} +{{title "CSI Plugin " model.plainId}}

{{model.plainId}}

@@ -26,7 +26,7 @@
Controller Allocations
-
+
{{#if model.controllers}} {{#list-table source=sortedControllers @@ -46,7 +46,7 @@ {{/t.head}} {{#t.body as |row|}} {{plugin-allocation-row - data-test-controller-allocation=row.model.id + data-test-controller-allocation=row.model.allocID pluginAllocation=row.model}} {{/t.body}} {{/list-table}} @@ -63,7 +63,7 @@
Node Allocations
-
+
{{#if model.nodes}} {{#list-table source=sortedNodes @@ -83,7 +83,7 @@ {{/t.head}} {{#t.body as |row|}} {{plugin-allocation-row - data-test-node-allocation=row.model.id + data-test-node-allocation=row.model.allocID pluginAllocation=row.model}} {{/t.body}} {{/list-table}} diff --git a/ui/mirage/factories/csi-plugin.js b/ui/mirage/factories/csi-plugin.js index 3a96598b391..e57aea40ea7 100644 --- a/ui/mirage/factories/csi-plugin.js +++ b/ui/mirage/factories/csi-plugin.js @@ -12,14 +12,14 @@ export default Factory.extend({ provider: faker.helpers.randomize(STORAGE_PROVIDERS), version: '1.0.1', controllerRequired: faker.random.boolean, - controllersHealthy: () => faker.random.number(10), + controllersHealthy: () => faker.random.number(3), controllersExpected() { - return this.controllersHealthy + faker.random.number(10); + return this.controllersHealthy + faker.random.number({ min: 1, max: 2 }); }, - nodesHealthy: () => faker.random.number(10), + nodesHealthy: () => faker.random.number(3), nodesExpected() { - return this.nodesHealthy + faker.random.number(10); + return this.nodesHealthy + faker.random.number({ min: 1, max: 2 }); }, // Internal property to determine whether or not this plugin @@ -36,20 +36,18 @@ export default Factory.extend({ if (plugin.isMonolith) { const pluginJob = server.create('job', { type: 'service', createAllocations: false }); - const count = faker.random.number({ min: 1, max: 5 }); + const count = plugin.nodesExpected; storageNodes = server.createList('storage-node', count, { job: pluginJob }); storageControllers = server.createList('storage-controller', count, { job: pluginJob }); } else { const controllerJob = server.create('job', { type: 'service', createAllocations: false }); const nodeJob = server.create('job', { type: 'service', createAllocations: false }); - storageNodes = server.createList('storage-node', faker.random.number({ min: 1, max: 5 }), { + storageNodes = server.createList('storage-node', plugin.nodesExpected, { job: nodeJob, }); - storageControllers = server.createList( - 'storage-controller', - faker.random.number({ min: 1, max: 5 }), - { job: controllerJob } - ); + storageControllers = server.createList('storage-controller', plugin.controllersExpected, { + job: controllerJob, + }); } plugin.update({ diff --git a/ui/tests/acceptance/plugin-detail-test.js b/ui/tests/acceptance/plugin-detail-test.js new file mode 100644 index 00000000000..722788d90a0 --- /dev/null +++ b/ui/tests/acceptance/plugin-detail-test.js @@ -0,0 +1,164 @@ +import { module, test } from 'qunit'; +import { currentURL } from '@ember/test-helpers'; +import { setupApplicationTest } from 'ember-qunit'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import moment from 'moment'; +import { formatBytes } from 'nomad-ui/helpers/format-bytes'; +import PluginDetail from 'nomad-ui/tests/pages/storage/plugins/detail'; + +module('Acceptance | plugin detail', function(hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + let plugin; + + hooks.beforeEach(function() { + server.create('node'); + plugin = server.create('csi-plugin'); + }); + + test('/csi/plugins/:id should have a breadcrumb trail linking back to Plugins and Storage', async function(assert) { + await PluginDetail.visit({ id: plugin.id }); + + assert.equal(PluginDetail.breadcrumbFor('csi.index').text, 'Storage'); + assert.equal(PluginDetail.breadcrumbFor('csi.plugins').text, 'Plugins'); + assert.equal(PluginDetail.breadcrumbFor('csi.plugins.plugin').text, plugin.id); + }); + + test('/csi/plugins/:id should show the plugin name in the title', async function(assert) { + await PluginDetail.visit({ id: plugin.id }); + + assert.equal(document.title, `CSI Plugin ${plugin.id} - Nomad`); + assert.equal(PluginDetail.title, plugin.id); + }); + + test('/csi/plugins/:id should list additional details for the plugin below the title', async function(assert) { + await PluginDetail.visit({ id: plugin.id }); + + assert.ok( + PluginDetail.controllerHealth.includes( + `${Math.round((plugin.controllersHealthy / plugin.controllersExpected) * 100)}%` + ) + ); + assert.ok( + PluginDetail.controllerHealth.includes( + `${plugin.controllersHealthy}/${plugin.controllersExpected}` + ) + ); + assert.ok( + PluginDetail.nodeHealth.includes( + `${Math.round((plugin.nodesHealthy / plugin.nodesExpected) * 100)}%` + ) + ); + assert.ok(PluginDetail.nodeHealth.includes(`${plugin.nodesHealthy}/${plugin.nodesExpected}`)); + assert.ok(PluginDetail.provider.includes(plugin.provider)); + }); + + test('/csi/plugins/:id should list all the controller plugin allocations for the plugin', async function(assert) { + await PluginDetail.visit({ id: plugin.id }); + + assert.equal(PluginDetail.controllerAllocations.length, plugin.controllers.length); + plugin.controllers.models + .sortBy('updateTime') + .reverse() + .forEach((allocation, idx) => { + assert.equal(PluginDetail.controllerAllocations.objectAt(idx).id, allocation.allocID); + }); + }); + + test('/csi/plugins/:id should list all the node plugin allocations for the plugin', async function(assert) { + await PluginDetail.visit({ id: plugin.id }); + + assert.equal(PluginDetail.nodeAllocations.length, plugin.nodes.length); + plugin.nodes.models + .sortBy('updateTime') + .reverse() + .forEach((allocation, idx) => { + assert.equal(PluginDetail.nodeAllocations.objectAt(idx).id, allocation.allocID); + }); + }); + + test('each allocation should have high-level details for the allocation', async function(assert) { + const controller = plugin.controllers.models.sortBy('updateTime').reverse()[0]; + const allocation = server.db.allocations.find(controller.allocID); + const allocStats = server.db.clientAllocationStats.find(allocation.id); + const taskGroup = server.db.taskGroups.findBy({ + name: allocation.taskGroup, + jobId: allocation.jobId, + }); + + const tasks = taskGroup.taskIds.map(id => server.db.tasks.find(id)); + const cpuUsed = tasks.reduce((sum, task) => sum + task.Resources.CPU, 0); + const memoryUsed = tasks.reduce((sum, task) => sum + task.Resources.MemoryMB, 0); + + await PluginDetail.visit({ id: plugin.id }); + + PluginDetail.controllerAllocations.objectAt(0).as(allocationRow => { + assert.equal(allocationRow.shortId, allocation.id.split('-')[0], 'Allocation short ID'); + assert.equal( + allocationRow.createTime, + moment(allocation.createTime / 1000000).format('MMM DD') + ); + assert.equal( + allocationRow.createTooltip, + moment(allocation.createTime / 1000000).format('MMM DD HH:mm:ss ZZ') + ); + assert.equal(allocationRow.modifyTime, moment(allocation.modifyTime / 1000000).fromNow()); + assert.equal(allocationRow.health, controller.healthy ? 'Healthy' : 'Unhealthy'); + assert.equal( + allocationRow.client, + server.db.nodes.find(allocation.nodeId).id.split('-')[0], + 'Node ID' + ); + assert.equal(allocationRow.job, server.db.jobs.find(allocation.jobId).name, 'Job name'); + assert.ok(allocationRow.taskGroup, 'Task group name'); + assert.ok(allocationRow.jobVersion, 'Job Version'); + assert.equal( + allocationRow.cpu, + Math.floor(allocStats.resourceUsage.CpuStats.TotalTicks) / cpuUsed, + 'CPU %' + ); + assert.equal( + allocationRow.cpuTooltip, + `${Math.floor(allocStats.resourceUsage.CpuStats.TotalTicks)} / ${cpuUsed} MHz`, + 'Detailed CPU information is in a tooltip' + ); + assert.equal( + allocationRow.mem, + allocStats.resourceUsage.MemoryStats.RSS / 1024 / 1024 / memoryUsed, + 'Memory used' + ); + assert.equal( + allocationRow.memTooltip, + `${formatBytes([allocStats.resourceUsage.MemoryStats.RSS])} / ${memoryUsed} MiB`, + 'Detailed memory information is in a tooltip' + ); + }); + }); + + test('each allocation should link to the allocation detail page', async function(assert) { + const controller = plugin.controllers.models.sortBy('updateTime').reverse()[0]; + + await PluginDetail.visit({ id: plugin.id }); + await PluginDetail.controllerAllocations.objectAt(0).visit(); + + assert.equal(currentURL(), `/allocations/${controller.allocID}`); + }); + + test('when there are no plugin allocations, the tables present empty states', async function(assert) { + const emptyPlugin = server.create('csi-plugin', { + controllersHealthy: 0, + controllersExpected: 0, + nodesHealthy: 0, + nodesExpected: 0, + }); + + await PluginDetail.visit({ id: emptyPlugin.id }); + + assert.ok(PluginDetail.controllerTableIsEmpty); + assert.equal(PluginDetail.controllerEmptyState.headline, 'No Controller Plugin Allocations'); + + assert.ok(PluginDetail.nodeTableIsEmpty); + assert.equal(PluginDetail.nodeEmptyState.headline, 'No Node Plugin Allocations'); + }); +}); diff --git a/ui/tests/acceptance/volume-detail-test.js b/ui/tests/acceptance/volume-detail-test.js index 830f1e4c80e..01139293030 100644 --- a/ui/tests/acceptance/volume-detail-test.js +++ b/ui/tests/acceptance/volume-detail-test.js @@ -28,7 +28,7 @@ module('Acceptance | volume detail', function(hooks) { volume = server.create('csi-volume'); }); - test('/csi/volumes/:id should have a breadcrumb trail linking back to Volumes and CSI', async function(assert) { + test('/csi/volumes/:id should have a breadcrumb trail linking back to Volumes and Storage', async function(assert) { await VolumeDetail.visit({ id: volume.id }); assert.equal(VolumeDetail.breadcrumbFor('csi.index').text, 'Storage'); diff --git a/ui/tests/pages/components/allocations.js b/ui/tests/pages/components/allocations.js index b19f82f70a0..020603cd75d 100644 --- a/ui/tests/pages/components/allocations.js +++ b/ui/tests/pages/components/allocations.js @@ -11,7 +11,9 @@ export default function(selector = '[data-test-allocation]', propKey = 'allocati id: attribute(attr), shortId: text('[data-test-short-id]'), createTime: text('[data-test-create-time]'), + createTooltip: attribute('aria-label', '[data-test-create-time] .tooltip'), modifyTime: text('[data-test-modify-time]'), + health: text('[data-test-health]'), status: text('[data-test-client-status]'), job: text('[data-test-job]'), taskGroup: text('[data-test-task-group]'),