From 98c4d0548f4c46a049bc7f10dabb14b34790dc55 Mon Sep 17 00:00:00 2001 From: Justin Pomeroy Date: Wed, 7 Oct 2015 16:27:12 -0500 Subject: [PATCH] Add Metadata page to angular Launch Instance wizard This adds the Metadata page to the angular Launch Instance wizard and allows users to add metadata to an instance when launching. Limits will only be enforced once this patch merges: https://review.openstack.org/209680 Implements: blueprint add-server-metadata Change-Id: If4202e2e0c934d969ebfb1a566cb04848ad1c28e --- .../launch-instance-model.service.js | 28 ++++-- .../launch-instance-model.service.spec.js | 33 ++++++- .../launch-instance-workflow.service.js | 6 ++ .../launch-instance-workflow.service.spec.js | 7 +- .../metadata/metadata.help.html | 17 ++++ .../launch-instance/metadata/metadata.html | 14 +++ .../launch-instance/metadata/metadata.spec.js | 96 +++++++++++++++++++ ...-add-server-metadata-a5d5582966ef25c5.yaml | 5 + 8 files changed, 196 insertions(+), 10 deletions(-) create mode 100644 openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/metadata/metadata.help.html create mode 100644 openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/metadata/metadata.html create mode 100644 openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/metadata/metadata.spec.js create mode 100644 releasenotes/notes/bp-add-server-metadata-a5d5582966ef25c5.yaml diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.js index 7a82dfc36f..5af8412451 100644 --- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.js +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.js @@ -115,7 +115,8 @@ metadataDefs: { flavor: null, image: null, - volume: null + volume: null, + instance: null }, networks: [], neutronEnabled: false, @@ -125,6 +126,7 @@ volumeBootable: false, volumes: [], volumeSnapshots: [], + metadataTree: null, /** * api methods for UI controllers @@ -246,6 +248,7 @@ setFinalSpecNetworks(finalSpec); setFinalSpecKeyPairs(finalSpec); setFinalSpecSecurityGroups(finalSpec); + setFinalSpecMetadata(finalSpec); return novaAPI.createServer(finalSpec).then(successMessage); } @@ -515,13 +518,25 @@ angular.extend(model.novaLimits, data.data); } + // Instance metadata + + function setFinalSpecMetadata(finalSpec) { + if (model.metadataTree) { + var meta = model.metadataTree.getExisting(); + if (!angular.equals({}, meta)) { + angular.forEach(meta, function(value, key) { + meta[key] = value + ''; + }); + finalSpec.meta = meta; + } + } + } + // Metadata Definitions /** - * Metadata definitions provide supplemental information in detail - * rows and should not slow down any of the other load processes. - * All code should be written to treat metadata definitions as - * optional, because they are never guaranteed to exist. + * Metadata definitions provide supplemental information in source image detail + * rows and are used on the metadata tab for adding metadata to the instance. */ function getMetadataDefinitions() { // Metadata definitions often apply to multiple @@ -530,7 +545,8 @@ var resourceTypes = { flavor: 'OS::Nova::Flavor', image: 'OS::Glance::Image', - volume: 'OS::Cinder::Volumes' + volume: 'OS::Cinder::Volumes', + instance: 'OS::Nova::Instance' }; angular.forEach(resourceTypes, applyForResourceType); diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.spec.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.spec.js index a012861e1d..d1599ab9de 100644 --- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.spec.js +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.spec.js @@ -217,7 +217,8 @@ expect(model.metadataDefs.flavor).toBeNull(); expect(model.metadataDefs.image).toBeNull(); expect(model.metadataDefs.volume).toBeNull(); - expect(Object.keys(model.metadataDefs).length).toBe(3); + expect(model.metadataDefs.instance).toBeNull(); + expect(Object.keys(model.metadataDefs).length).toBe(4); }); it('defaults "allow create volume from image" to false', function() { @@ -232,6 +233,10 @@ expect(model.volumeBootable).toBe(false); }); + it('defaults "metadataTree" to null', function() { + expect(model.metadataTree).toBe(null); + }); + it('initializes "nova limits" to empty object', function() { expect(model.novaLimits).toEqual({}); }); @@ -388,6 +393,7 @@ }); describe('Create Instance', function() { + var metadata; beforeEach(function() { // initialize some data @@ -402,6 +408,13 @@ model.newInstanceSpec.vol_delete_on_instance_delete = true; model.newInstanceSpec.vol_device_name = "volTestName"; model.newInstanceSpec.vol_size = 10; + + metadata = {'foo': 'bar'}; + model.metadataTree = { + getExisting: function() { + return metadata; + } + }; }); it('should set final spec in format required by Nova (Neutron disabled)', function() { @@ -525,6 +538,24 @@ var finalSpec = model.createInstance(); expect(finalSpec.block_device_mapping_v2[0].device_name).toBeNull(); }); + + it('should not have meta property if no metadata specified', function() { + metadata = {}; + + var finalSpec = model.createInstance(); + expect(finalSpec.meta).toBeUndefined(); + + model.metadataTree = null; + + finalSpec = model.createInstance(); + expect(finalSpec.meta).toBeUndefined(); + }); + + it('should have meta property if metadata specified', function() { + var finalSpec = model.createInstance(); + expect(finalSpec.meta).toBe(metadata); + }); + }); }); }); diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-workflow.service.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-workflow.service.js index e307817e35..79194662bb 100644 --- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-workflow.service.js +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-workflow.service.js @@ -79,6 +79,12 @@ templateUrl: basePath + 'configuration/configuration.html', helpUrl: basePath + 'configuration/configuration.help.html', formName: 'launchInstanceConfigurationForm' + }, + { + title: gettext('Metadata'), + templateUrl: basePath + 'metadata/metadata.html', + helpUrl: basePath + 'metadata/metadata.help.html', + formName: 'launchInstanceMetadataForm' } ], diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-workflow.service.spec.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-workflow.service.spec.js index 8d73128787..68d52a3133 100644 --- a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-workflow.service.spec.js +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-workflow.service.spec.js @@ -47,9 +47,9 @@ expect(launchInstanceWorkflow.title).toBeDefined(); }); - it('should have the seven steps defined', function () { + it('should have the eight steps defined', function () { expect(launchInstanceWorkflow.steps).toBeDefined(); - expect(launchInstanceWorkflow.steps.length).toBe(7); + expect(launchInstanceWorkflow.steps.length).toBe(8); var forms = [ 'launchInstanceDetailsForm', @@ -58,7 +58,8 @@ 'launchInstanceNetworkForm', 'launchInstanceAccessAndSecurityForm', 'launchInstanceKeypairForm', - 'launchInstanceConfigurationForm' + 'launchInstanceConfigurationForm', + 'launchInstanceMetadataForm' ]; forms.forEach(function(expectedForm, idx) { diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/metadata/metadata.help.html b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/metadata/metadata.help.html new file mode 100644 index 0000000000..91a093d9ba --- /dev/null +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/metadata/metadata.help.html @@ -0,0 +1,17 @@ +
+

Metadata Help

+

+ You can add arbitrary metadata to your instance so that you can more easily identify it among other running instances. Metadata is a collection of key-value pairs associated with an instance. The maximum length for each metadata key and value is 255 characters. +

+

+ + The maximum number of key-value pairs that can be supplied per instance is determined by the compute provider. + + + This limit is currently set to {$ model.novaLimits.maxServerMeta $}. + + + This limit is currently not set. + +

+
diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/metadata/metadata.html b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/metadata/metadata.html new file mode 100644 index 0000000000..8033e4749d --- /dev/null +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/metadata/metadata.html @@ -0,0 +1,14 @@ +
+

Metadata

+
+ + +
+
diff --git a/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/metadata/metadata.spec.js b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/metadata/metadata.spec.js new file mode 100644 index 0000000000..ff88b38976 --- /dev/null +++ b/openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/metadata/metadata.spec.js @@ -0,0 +1,96 @@ +/* + * Copyright 2015 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +(function() { + 'use strict'; + + describe('Launch Instance Metadata Step', function() { + + describe('metadata tree', function() { + var $scope, $element, model; + + beforeEach(module('templates')); + beforeEach(module('horizon.dashboard.project.workflow.launch-instance')); + + beforeEach(inject(function($injector) { + var $compile = $injector.get('$compile'); + var $templateCache = $injector.get('$templateCache'); + var basePath = $injector.get('horizon.dashboard.project.workflow.launch-instance.basePath'); + var markup = $templateCache.get(basePath + 'metadata/metadata.html'); + model = { + metadataDefs: { instance: false }, + novaLimits: false + }; + $scope = $injector.get('$rootScope').$new(); + $scope.model = model; + $element = $compile(markup)($scope); + })); + + it('should create metadata tree only after dependencies are received', function() { + expect($element.find('metadata-tree').length).toBe(0); + + model.metadataDefs.instance = {}; + $scope.$apply(); + + expect($element.find('metadata-tree').length).toBe(0); + + model.novaLimits = {}; + $scope.$apply(); + + expect($element.find('metadata-tree').length).toBe(1); + }); + }); + + describe('metadata step help', function() { + var $scope, $element, model; + + beforeEach(module('templates')); + beforeEach(module('horizon.dashboard.project.workflow.launch-instance')); + + beforeEach(inject(function($injector) { + var $compile = $injector.get('$compile'); + var $templateCache = $injector.get('$templateCache'); + var basePath = $injector.get('horizon.dashboard.project.workflow.launch-instance.basePath'); + var markup = $templateCache.get(basePath + 'metadata/metadata.help.html'); + $scope = $injector.get('$rootScope').$new(); + model = { + novaLimits: { + maxServerMeta: null + } + }; + $scope.model = model; + $element = $compile(markup)($scope); + })); + + it('should update message based on nova limit', function() { + expect($element.find('p+p>span').length).toBe(1); + + model.novaLimits.maxServerMeta = -1; + $scope.$apply(); + + expect($element.find('p+p>span').length).toBe(2); + expect($element.find('p+p>span:last-child').text().trim()) + .toBe('This limit is currently not set.'); + + model.novaLimits.maxServerMeta = 5; + $scope.$apply(); + + expect($element.find('p+p>span').length).toBe(2); + expect($element.find('p+p>span:last-child').text().trim()) + .toMatch(/^This limit is currently set to/); + }); + }); + }); +})(); diff --git a/releasenotes/notes/bp-add-server-metadata-a5d5582966ef25c5.yaml b/releasenotes/notes/bp-add-server-metadata-a5d5582966ef25c5.yaml new file mode 100644 index 0000000000..8f512e4d59 --- /dev/null +++ b/releasenotes/notes/bp-add-server-metadata-a5d5582966ef25c5.yaml @@ -0,0 +1,5 @@ +--- +features: + - Added the Metadata tab to the new Launch Instance workflow to allow adding + key-value metadata to an instance at launch. This includes any properties + from the OS::Nova::Instance namespace of the glance metadata definitions. \ No newline at end of file