Merge "Add Metadata page to angular Launch Instance wizard"
This commit is contained in:
@@ -113,7 +113,8 @@
|
|||||||
metadataDefs: {
|
metadataDefs: {
|
||||||
flavor: null,
|
flavor: null,
|
||||||
image: null,
|
image: null,
|
||||||
volume: null
|
volume: null,
|
||||||
|
instance: null
|
||||||
},
|
},
|
||||||
networks: [],
|
networks: [],
|
||||||
neutronEnabled: false,
|
neutronEnabled: false,
|
||||||
@@ -123,6 +124,7 @@
|
|||||||
volumeBootable: false,
|
volumeBootable: false,
|
||||||
volumes: [],
|
volumes: [],
|
||||||
volumeSnapshots: [],
|
volumeSnapshots: [],
|
||||||
|
metadataTree: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* api methods for UI controllers
|
* api methods for UI controllers
|
||||||
@@ -244,6 +246,7 @@
|
|||||||
setFinalSpecNetworks(finalSpec);
|
setFinalSpecNetworks(finalSpec);
|
||||||
setFinalSpecKeyPairs(finalSpec);
|
setFinalSpecKeyPairs(finalSpec);
|
||||||
setFinalSpecSecurityGroups(finalSpec);
|
setFinalSpecSecurityGroups(finalSpec);
|
||||||
|
setFinalSpecMetadata(finalSpec);
|
||||||
|
|
||||||
return novaAPI.createServer(finalSpec).then(successMessage);
|
return novaAPI.createServer(finalSpec).then(successMessage);
|
||||||
}
|
}
|
||||||
@@ -513,13 +516,25 @@
|
|||||||
angular.extend(model.novaLimits, data.data);
|
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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata definitions provide supplemental information in detail
|
* Metadata definitions provide supplemental information in source image detail
|
||||||
* rows and should not slow down any of the other load processes.
|
* rows and are used on the metadata tab for adding metadata to the instance.
|
||||||
* All code should be written to treat metadata definitions as
|
|
||||||
* optional, because they are never guaranteed to exist.
|
|
||||||
*/
|
*/
|
||||||
function getMetadataDefinitions() {
|
function getMetadataDefinitions() {
|
||||||
// Metadata definitions often apply to multiple
|
// Metadata definitions often apply to multiple
|
||||||
@@ -528,7 +543,8 @@
|
|||||||
var resourceTypes = {
|
var resourceTypes = {
|
||||||
flavor: 'OS::Nova::Flavor',
|
flavor: 'OS::Nova::Flavor',
|
||||||
image: 'OS::Glance::Image',
|
image: 'OS::Glance::Image',
|
||||||
volume: 'OS::Cinder::Volumes'
|
volume: 'OS::Cinder::Volumes',
|
||||||
|
instance: 'OS::Nova::Instance'
|
||||||
};
|
};
|
||||||
|
|
||||||
angular.forEach(resourceTypes, applyForResourceType);
|
angular.forEach(resourceTypes, applyForResourceType);
|
||||||
|
@@ -215,7 +215,8 @@
|
|||||||
expect(model.metadataDefs.flavor).toBeNull();
|
expect(model.metadataDefs.flavor).toBeNull();
|
||||||
expect(model.metadataDefs.image).toBeNull();
|
expect(model.metadataDefs.image).toBeNull();
|
||||||
expect(model.metadataDefs.volume).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() {
|
it('defaults "allow create volume from image" to false', function() {
|
||||||
@@ -230,6 +231,10 @@
|
|||||||
expect(model.volumeBootable).toBe(false);
|
expect(model.volumeBootable).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('defaults "metadataTree" to null', function() {
|
||||||
|
expect(model.metadataTree).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
it('initializes "nova limits" to empty object', function() {
|
it('initializes "nova limits" to empty object', function() {
|
||||||
expect(model.novaLimits).toEqual({});
|
expect(model.novaLimits).toEqual({});
|
||||||
});
|
});
|
||||||
@@ -386,6 +391,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Create Instance', function() {
|
describe('Create Instance', function() {
|
||||||
|
var metadata;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
// initialize some data
|
// initialize some data
|
||||||
@@ -400,6 +406,13 @@
|
|||||||
model.newInstanceSpec.vol_delete_on_instance_delete = true;
|
model.newInstanceSpec.vol_delete_on_instance_delete = true;
|
||||||
model.newInstanceSpec.vol_device_name = "volTestName";
|
model.newInstanceSpec.vol_device_name = "volTestName";
|
||||||
model.newInstanceSpec.vol_size = 10;
|
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() {
|
it('should set final spec in format required by Nova (Neutron disabled)', function() {
|
||||||
@@ -523,6 +536,24 @@
|
|||||||
var finalSpec = model.createInstance();
|
var finalSpec = model.createInstance();
|
||||||
expect(finalSpec.block_device_mapping_v2[0].device_name).toBeNull();
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -79,6 +79,12 @@
|
|||||||
templateUrl: basePath + 'configuration/configuration.html',
|
templateUrl: basePath + 'configuration/configuration.html',
|
||||||
helpUrl: basePath + 'configuration/configuration.help.html',
|
helpUrl: basePath + 'configuration/configuration.help.html',
|
||||||
formName: 'launchInstanceConfigurationForm'
|
formName: 'launchInstanceConfigurationForm'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: gettext('Metadata'),
|
||||||
|
templateUrl: basePath + 'metadata/metadata.html',
|
||||||
|
helpUrl: basePath + 'metadata/metadata.help.html',
|
||||||
|
formName: 'launchInstanceMetadataForm'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@@ -47,9 +47,9 @@
|
|||||||
expect(launchInstanceWorkflow.title).toBeDefined();
|
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).toBeDefined();
|
||||||
expect(launchInstanceWorkflow.steps.length).toBe(7);
|
expect(launchInstanceWorkflow.steps.length).toBe(8);
|
||||||
|
|
||||||
var forms = [
|
var forms = [
|
||||||
'launchInstanceDetailsForm',
|
'launchInstanceDetailsForm',
|
||||||
@@ -58,7 +58,8 @@
|
|||||||
'launchInstanceNetworkForm',
|
'launchInstanceNetworkForm',
|
||||||
'launchInstanceAccessAndSecurityForm',
|
'launchInstanceAccessAndSecurityForm',
|
||||||
'launchInstanceKeypairForm',
|
'launchInstanceKeypairForm',
|
||||||
'launchInstanceConfigurationForm'
|
'launchInstanceConfigurationForm',
|
||||||
|
'launchInstanceMetadataForm'
|
||||||
];
|
];
|
||||||
|
|
||||||
forms.forEach(function(expectedForm, idx) {
|
forms.forEach(function(expectedForm, idx) {
|
||||||
|
@@ -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>
|
@@ -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>
|
@@ -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/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
@@ -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.
|
Reference in New Issue
Block a user