diff --git a/ui/app/styles/core/columns.scss b/ui/app/styles/core/columns.scss index d68425e9a88..d912a37c31c 100644 --- a/ui/app/styles/core/columns.scss +++ b/ui/app/styles/core/columns.scss @@ -14,4 +14,13 @@ &.is-bottom-aligned { align-items: flex-end; } + + &.is-max-half { + max-width: 50%; + } + + &.is-centered { + margin-left: auto; + margin-right: auto; + } } diff --git a/ui/app/templates/csi/plugins/index.hbs b/ui/app/templates/csi/plugins/index.hbs index 7d4f5f1f1f9..2eab1929145 100644 --- a/ui/app/templates/csi/plugins/index.hbs +++ b/ui/app/templates/csi/plugins/index.hbs @@ -42,8 +42,12 @@ {{#link-to "csi.plugins.plugin" row.model.plainId class="is-primary"}}{{row.model.plainId}}{{/link-to}} - {{if (gt row.model.controllersHealthy 0) "Healthy" "Unhealthy"}} - ({{row.model.controllersHealthy}}/{{row.model.controllersExpected}}) + {{#if row.model.controllerRequired}} + {{if (gt row.model.controllersHealthy 0) "Healthy" "Unhealthy"}} + ({{row.model.controllersHealthy}}/{{row.model.controllersExpected}}) + {{else}} + Node Only + {{/if}} {{if (gt row.model.nodesHealthy 0) "Healthy" "Unhealthy"}} diff --git a/ui/app/templates/csi/plugins/plugin.hbs b/ui/app/templates/csi/plugins/plugin.hbs index bcd2c8873de..7c11e183784 100644 --- a/ui/app/templates/csi/plugins/plugin.hbs +++ b/ui/app/templates/csi/plugins/plugin.hbs @@ -5,11 +5,13 @@
Plugin Details - - Controller Health - {{format-percentage model.controllersHealthy total=model.controllersExpected}} - ({{model.controllersHealthy}}/{{model.controllersExpected}}) - + {{#if model.controllerRequired}} + + Controller Health + {{format-percentage model.controllersHealthy total=model.controllersExpected}} + ({{model.controllersHealthy}}/{{model.controllersExpected}}) + + {{/if}} Node Health {{format-percentage model.nodesHealthy total=model.nodesExpected}} @@ -23,38 +25,40 @@
-
-
-
Controller Health
-
-
-
- {{gauge-chart - label="Availability" - value=model.controllersHealthy - total=model.controllersExpected}} -
-
-
-

Available

-

{{model.controllersHealthy}}

+ {{#if model.controllerRequired}} +
+
+
Controller Health
+
+
+
+ {{gauge-chart + label="Availability" + value=model.controllersHealthy + total=model.controllersExpected}}
-
-
-
-

Expected

-

{{model.controllersExpected}}

+
+
+

Available

+

{{model.controllersHealthy}}

+
+
+
+
+

Expected

+

{{model.controllersExpected}}

+
-
+ {{/if}}
-
+
Node Health
-
+
{{gauge-chart label="Availability" @@ -79,42 +83,44 @@
-
-
- Controller Allocations -
-
- {{#if model.controllers}} - {{#list-table - source=sortedControllers - class="with-foot" as |t|}} - {{#t.head}} - - ID - Created - Modified - Health - Client - Job - Version - Volumes - CPU - Memory - {{/t.head}} - {{#t.body key="model.allocID" as |row|}} - {{plugin-allocation-row - data-test-controller-allocation=row.model.allocID - pluginAllocation=row.model}} - {{/t.body}} - {{/list-table}} - {{else}} -
-

No Controller Plugin Allocations

-

No allocations are providing controller plugin service.

-
- {{/if}} + {{#if model.controllerRequired}} +
+
+ Controller Allocations +
+
+ {{#if model.controllers}} + {{#list-table + source=sortedControllers + class="with-foot" as |t|}} + {{#t.head}} + + ID + Created + Modified + Health + Client + Job + Version + Volumes + CPU + Memory + {{/t.head}} + {{#t.body key="model.allocID" as |row|}} + {{plugin-allocation-row + data-test-controller-allocation=row.model.allocID + pluginAllocation=row.model}} + {{/t.body}} + {{/list-table}} + {{else}} +
+

No Controller Plugin Allocations

+

No allocations are providing controller plugin service.

+
+ {{/if}} +
-
+ {{/if}}
diff --git a/ui/mirage/factories/csi-plugin.js b/ui/mirage/factories/csi-plugin.js index b0f95354532..6e462267993 100644 --- a/ui/mirage/factories/csi-plugin.js +++ b/ui/mirage/factories/csi-plugin.js @@ -11,10 +11,19 @@ export default Factory.extend({ provider: faker.helpers.randomize(STORAGE_PROVIDERS), version: '1.0.1', + controllerRequired: faker.random.boolean, - controllersHealthy: () => faker.random.number(3), + + controllersHealthy() { + if (!this.controllerRequired) return 0; + return faker.random.number(3); + }, controllersExpected() { - return this.controllersHealthy + faker.random.number({ min: 1, max: 2 }); + // This property must be read before the conditional + // or Mirage will incorrectly sort dependent properties. + const healthy = this.controllersHealthy; + if (!this.controllerRequired) return 0; + return healthy + faker.random.number({ min: 1, max: 2 }); }, nodesHealthy: () => faker.random.number(3), @@ -25,7 +34,10 @@ export default Factory.extend({ // Internal property to determine whether or not this plugin // Should create one or two Jobs to represent Node and // Controller plugins. - isMonolith: faker.random.boolean, + isMonolith() { + if (!this.controllerRequired) return false; + return faker.random.boolean; + }, // When false, the plugin will not make its own volumes createVolumes: true, @@ -49,11 +61,13 @@ export default Factory.extend({ shallow: plugin.shallow, }); } else { - const controllerJob = server.create('job', { - type: 'service', - createAllocations: false, - shallow: plugin.shallow, - }); + const controllerJob = + plugin.controllerRequired && + server.create('job', { + type: 'service', + createAllocations: false, + shallow: plugin.shallow, + }); const nodeJob = server.create('job', { type: 'service', createAllocations: false, @@ -63,10 +77,12 @@ export default Factory.extend({ job: nodeJob, shallow: plugin.shallow, }); - storageControllers = server.createList('storage-controller', plugin.controllersExpected, { - job: controllerJob, - shallow: plugin.shallow, - }); + storageControllers = + plugin.controllerRequired && + server.createList('storage-controller', plugin.controllersExpected, { + job: controllerJob, + shallow: plugin.shallow, + }); } plugin.update({ diff --git a/ui/tests/acceptance/plugin-detail-test.js b/ui/tests/acceptance/plugin-detail-test.js index b97cdfb8a9b..d2403ad0591 100644 --- a/ui/tests/acceptance/plugin-detail-test.js +++ b/ui/tests/acceptance/plugin-detail-test.js @@ -14,7 +14,7 @@ module('Acceptance | plugin detail', function(hooks) { hooks.beforeEach(function() { server.create('node'); - plugin = server.create('csi-plugin'); + plugin = server.create('csi-plugin', { controllerRequired: true }); }); test('/csi/plugins/:id should have a breadcrumb trail linking back to Plugins and Storage', async function(assert) { @@ -147,6 +147,7 @@ module('Acceptance | plugin detail', function(hooks) { test('when there are no plugin allocations, the tables present empty states', async function(assert) { const emptyPlugin = server.create('csi-plugin', { + controllerRequired: true, controllersHealthy: 0, controllersExpected: 0, nodesHealthy: 0, @@ -161,4 +162,16 @@ module('Acceptance | plugin detail', function(hooks) { assert.ok(PluginDetail.nodeTableIsEmpty); assert.equal(PluginDetail.nodeEmptyState.headline, 'No Node Plugin Allocations'); }); + + test('when the plugin is node-only, the controller information is omitted', async function(assert) { + const nodeOnlyPlugin = server.create('csi-plugin', { controllerRequired: false }); + + await PluginDetail.visit({ id: nodeOnlyPlugin.id }); + + assert.notOk(PluginDetail.controllerAvailabilityIsPresent); + assert.ok(PluginDetail.nodeAvailabilityIsPresent); + + assert.notOk(PluginDetail.controllerHealthIsPresent); + assert.notOk(PluginDetail.controllerTableIsPresent); + }); }); diff --git a/ui/tests/acceptance/plugins-list-test.js b/ui/tests/acceptance/plugins-list-test.js index f86e2d66120..22710fa2b69 100644 --- a/ui/tests/acceptance/plugins-list-test.js +++ b/ui/tests/acceptance/plugins-list-test.js @@ -35,7 +35,7 @@ module('Acceptance | plugins list', function(hooks) { }); test('each plugin row should contain information about the plugin', async function(assert) { - const plugin = server.create('csi-plugin', { shallow: true }); + const plugin = server.create('csi-plugin', { shallow: true, controllerRequired: true }); await PluginsList.visit(); @@ -55,6 +55,23 @@ module('Acceptance | plugins list', function(hooks) { assert.equal(pluginRow.provider, plugin.provider); }); + test('node only plugins explain that there is no controller health for this plugin type', async function(assert) { + const plugin = server.create('csi-plugin', { shallow: true, controllerRequired: false }); + + await PluginsList.visit(); + + const pluginRow = PluginsList.plugins.objectAt(0); + const nodeHealthStr = plugin.nodesHealthy > 0 ? 'Healthy' : 'Unhealthy'; + + assert.equal(pluginRow.id, plugin.id); + assert.equal(pluginRow.controllerHealth, 'Node Only'); + assert.equal( + pluginRow.nodeHealth, + `${nodeHealthStr} (${plugin.nodesHealthy}/${plugin.nodesExpected})` + ); + assert.equal(pluginRow.provider, plugin.provider); + }); + test('each plugin row should link to the corresponding plugin', async function(assert) { const plugin = server.create('csi-plugin', { shallow: true }); diff --git a/ui/tests/integration/plugin-allocation-row-test.js b/ui/tests/integration/plugin-allocation-row-test.js index e11aee8016f..b85d2d492b4 100644 --- a/ui/tests/integration/plugin-allocation-row-test.js +++ b/ui/tests/integration/plugin-allocation-row-test.js @@ -20,7 +20,7 @@ module('Integration | Component | plugin allocation row', function(hooks) { }); test('Plugin allocation row immediately fetches the plugin allocation', async function(assert) { - const plugin = this.server.create('csi-plugin', { id: 'plugin' }); + const plugin = this.server.create('csi-plugin', { id: 'plugin', controllerRequired: true }); const storageController = plugin.controllers.models[0]; const pluginRecord = await this.store.find('plugin', 'csi/plugin'); @@ -42,7 +42,7 @@ module('Integration | Component | plugin allocation row', function(hooks) { }); test('After the plugin allocation row fetches the plugin allocation, allocation stats are fetched', async function(assert) { - const plugin = this.server.create('csi-plugin', { id: 'plugin' }); + const plugin = this.server.create('csi-plugin', { id: 'plugin', controllerRequired: true }); const storageController = plugin.controllers.models[0]; const pluginRecord = await this.store.find('plugin', 'csi/plugin'); @@ -66,6 +66,7 @@ module('Integration | Component | plugin allocation row', function(hooks) { const plugin = this.server.create('csi-plugin', { id: 'plugin', isMonolith: false, + controllerRequired: true, controllersExpected: 2, }); const storageController = plugin.controllers.models[0]; diff --git a/ui/tests/pages/storage/plugins/detail.js b/ui/tests/pages/storage/plugins/detail.js index 2c148d0e696..1191c0be3ff 100644 --- a/ui/tests/pages/storage/plugins/detail.js +++ b/ui/tests/pages/storage/plugins/detail.js @@ -15,10 +15,14 @@ export default create({ title: text('[data-test-title]'), + controllerHealthIsPresent: isPresent('[data-test-plugin-controller-health]'), controllerHealth: text('[data-test-plugin-controller-health]'), nodeHealth: text('[data-test-plugin-node-health]'), provider: text('[data-test-plugin-provider]'), + controllerAvailabilityIsPresent: isPresent('[data-test-plugin-controller-availability]'), + nodeAvailabilityIsPresent: isPresent('[data-test-plugin-node-availability]'), + breadcrumbs: collection('[data-test-breadcrumb]', { id: attribute('data-test-breadcrumb'), text: text(), @@ -32,6 +36,8 @@ export default create({ ...allocations('[data-test-controller-allocation]', 'controllerAllocations'), ...allocations('[data-test-node-allocation]', 'nodeAllocations'), + controllerTableIsPresent: isPresent('[data-test-controller-allocations]'), + controllerTableIsEmpty: isPresent('[data-test-empty-controller-allocations]'), controllerEmptyState: { headline: text('[data-test-empty-controller-allocations-headline]'),