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
This commit is contained in:
Justin Pomeroy 2015-10-07 16:27:12 -05:00
parent 1178757445
commit 98c4d0548f
8 changed files with 196 additions and 10 deletions

View File

@ -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);

View File

@ -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);
});
});
});
});

View File

@ -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'
}
],

View File

@ -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) {

View File

@ -0,0 +1,17 @@
<div>
<h1 translate>Metadata Help</h1>
<p translate>
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.
</p>
<p>
<span translate>
The maximum number of key-value pairs that can be supplied per instance is determined by the compute provider.
</span>
<span ng-if="model.novaLimits.maxServerMeta > -1" translate>
This limit is currently set to {$ model.novaLimits.maxServerMeta $}.
</span>
<span ng-if="model.novaLimits.maxServerMeta <= -1" translate>
This limit is currently not set.
</span>
</p>
</div>

View File

@ -0,0 +1,14 @@
<div>
<h1 translate>Metadata</h1>
<div class="content">
<metadata-tree
ng-if="model.metadataDefs.instance && model.novaLimits"
available="::model.metadataDefs.instance"
existing="{}"
max-key-length="255"
max-value-length="255"
max-item-count="::model.novaLimits.maxServerMeta"
model="::model.metadataTree">
</metadata-tree>
</div>
</div>

View File

@ -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/);
});
});
});
})();

View File

@ -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.