Unit test framework for Ironic-UI API service
This commit is a first step in the development of a unit test framework for the Ironic-UI API service. The approach being used is to provide an emulation of the Ironic backend using a mock that utilizes Angular $httpbackend handlers to intercept requests targeted at the Ironic-UI server side REST endpoints. In addition to demonstrating the mock I have coded some basic tests to show how it is used. At this point I would like to get feedback from the team, and consensus on whether we should continue the process of building additional tests. Change-Id: Iaec83b0e19b5051ebf1257ddc56efcc6f11ee39d
This commit is contained in:
parent
852b70efd8
commit
b55a1004ff
@ -58,6 +58,7 @@
|
|||||||
toxPath + 'xstatic/pkg/rickshaw/data/rickshaw.js',
|
toxPath + 'xstatic/pkg/rickshaw/data/rickshaw.js',
|
||||||
toxPath + 'xstatic/pkg/angular_smart_table/data/smart-table.js',
|
toxPath + 'xstatic/pkg/angular_smart_table/data/smart-table.js',
|
||||||
toxPath + 'xstatic/pkg/angular_lrdragndrop/data/lrdragndrop.js',
|
toxPath + 'xstatic/pkg/angular_lrdragndrop/data/lrdragndrop.js',
|
||||||
|
toxPath + 'xstatic/pkg/angular_fileupload/data/ng-file-upload-all.js',
|
||||||
toxPath + 'xstatic/pkg/spin/data/spin.js',
|
toxPath + 'xstatic/pkg/spin/data/spin.js',
|
||||||
toxPath + 'xstatic/pkg/spin/data/spin.jquery.js',
|
toxPath + 'xstatic/pkg/spin/data/spin.jquery.js',
|
||||||
toxPath + 'xstatic/pkg/tv4/data/tv4.js',
|
toxPath + 'xstatic/pkg/tv4/data/tv4.js',
|
||||||
|
@ -57,14 +57,26 @@
|
|||||||
- adding new properties
|
- adding new properties
|
||||||
- displaying the list of properties in the set
|
- displaying the list of properties in the set
|
||||||
- changing the value of properties
|
- changing the value of properties
|
||||||
|
|
||||||
|
Collection attributes:
|
||||||
|
id: Name of the property inside the node object that is used
|
||||||
|
to store the collection.
|
||||||
|
formId: Name of the controller variable that can be used to
|
||||||
|
access the property collection form.
|
||||||
|
prompt: Label used to prompt the user to add properties
|
||||||
|
to the collection.
|
||||||
|
placeholder: Label used to guide the user in providiing property
|
||||||
|
values.
|
||||||
*/
|
*/
|
||||||
ctrl.propertyCollections = [
|
ctrl.propertyCollections = [
|
||||||
{id: "properties",
|
{id: "properties",
|
||||||
|
formId: "properties_form",
|
||||||
title: gettext("Properties"),
|
title: gettext("Properties"),
|
||||||
addPrompt: gettext("Add Property"),
|
addPrompt: gettext("Add Property"),
|
||||||
placeholder: gettext("Property Name")
|
placeholder: gettext("Property Name")
|
||||||
},
|
},
|
||||||
{id: "extra",
|
{id: "extra",
|
||||||
|
formId: "extra_form",
|
||||||
title: gettext("Extras"),
|
title: gettext("Extras"),
|
||||||
addPrompt: gettext("Add Extra"),
|
addPrompt: gettext("Add Extra"),
|
||||||
placeholder: gettext("Extra Property Name")
|
placeholder: gettext("Extra Property Name")
|
||||||
@ -75,11 +87,13 @@
|
|||||||
name: null,
|
name: null,
|
||||||
driver: null,
|
driver: null,
|
||||||
driver_info: {},
|
driver_info: {},
|
||||||
properties: {},
|
|
||||||
extra: {},
|
|
||||||
network_interface: null
|
network_interface: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
angular.forEach(ctrl.propertyCollections, function(collection) {
|
||||||
|
ctrl.node[collection.id] = {};
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Get the list of currently active Ironic drivers
|
* @description Get the list of currently active Ironic drivers
|
||||||
*
|
*
|
||||||
@ -102,70 +116,6 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Check whether a group contains required properties
|
|
||||||
*
|
|
||||||
* @param {DriverProperty[]} group - Property group
|
|
||||||
* @return {boolean} Return true if the group contains required
|
|
||||||
* properties, false otherwise
|
|
||||||
*/
|
|
||||||
function driverPropertyGroupHasRequired(group) {
|
|
||||||
var hasRequired = false;
|
|
||||||
for (var i = 0; i < group.length; i++) {
|
|
||||||
if (group[i].required) {
|
|
||||||
hasRequired = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasRequired;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Convert array of driver property groups to a string
|
|
||||||
*
|
|
||||||
* @param {array[]} groups - Array for driver property groups
|
|
||||||
* @return {string} Output string
|
|
||||||
*/
|
|
||||||
function driverPropertyGroupsToString(groups) {
|
|
||||||
var output = [];
|
|
||||||
angular.forEach(groups, function(group) {
|
|
||||||
var groupStr = [];
|
|
||||||
angular.forEach(group, function(property) {
|
|
||||||
groupStr.push(property.name);
|
|
||||||
});
|
|
||||||
groupStr = groupStr.join(", ");
|
|
||||||
output.push(['[', groupStr, ']'].join(""));
|
|
||||||
});
|
|
||||||
output = output.join(", ");
|
|
||||||
return ['[', output, ']'].join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Comaprison function used to sort driver property groups
|
|
||||||
*
|
|
||||||
* @param {DriverProperty[]} group1 - First group
|
|
||||||
* @param {DriverProperty[]} group2 - Second group
|
|
||||||
* @return {integer} Return:
|
|
||||||
* < 0 if group1 should precede group2 in an ascending ordering
|
|
||||||
* > 0 if group2 should precede group1
|
|
||||||
* 0 if group1 and group2 are considered equal from ordering perpsective
|
|
||||||
*/
|
|
||||||
function compareDriverPropertyGroups(group1, group2) {
|
|
||||||
var group1HasRequired = driverPropertyGroupHasRequired(group1);
|
|
||||||
var group2HasRequired = driverPropertyGroupHasRequired(group2);
|
|
||||||
|
|
||||||
if (group1HasRequired === group2HasRequired) {
|
|
||||||
if (group1.length === group2.length) {
|
|
||||||
return group1[0].name.localeCompare(group2[0].name);
|
|
||||||
} else {
|
|
||||||
return group1.length - group2.length;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return group1HasRequired ? -1 : 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Order driver properties in the form using the following
|
* @description Order driver properties in the form using the following
|
||||||
* rules:
|
* rules:
|
||||||
@ -176,7 +126,7 @@
|
|||||||
* (2) Required properties with no dependents should be located at the
|
* (2) Required properties with no dependents should be located at the
|
||||||
* top of the form
|
* top of the form
|
||||||
*
|
*
|
||||||
* @return {void}
|
* @return {[]} Ordered list of groups of strongly related properties
|
||||||
*/
|
*/
|
||||||
ctrl._sortDriverProperties = function() {
|
ctrl._sortDriverProperties = function() {
|
||||||
// Build dependency graph between driver properties
|
// Build dependency graph between driver properties
|
||||||
@ -221,10 +171,10 @@
|
|||||||
components.push(component);
|
components.push(component);
|
||||||
},
|
},
|
||||||
groups);
|
groups);
|
||||||
groups.sort(compareDriverPropertyGroups);
|
groups.sort(baseNodeService.compareDriverPropertyGroups);
|
||||||
|
|
||||||
$log.debug("Found the following property groups: " +
|
$log.debug("Found the following property groups: " +
|
||||||
driverPropertyGroupsToString(groups));
|
baseNodeService.driverPropertyGroupsToString(groups));
|
||||||
return groups;
|
return groups;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -297,5 +247,28 @@
|
|||||||
ctrl.isDriverPropertyActive = function(property) {
|
ctrl.isDriverPropertyActive = function(property) {
|
||||||
return property.isActive();
|
return property.isActive();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Check whether the node definition form is ready for
|
||||||
|
* to be submitted.
|
||||||
|
*
|
||||||
|
* @return {boolean} True if the form is ready to be submitted,
|
||||||
|
* otherwise false.
|
||||||
|
*/
|
||||||
|
ctrl.readyToSubmit = function() {
|
||||||
|
var ready = true;
|
||||||
|
if (ctrl.driverProperties) {
|
||||||
|
for (var i = 0; i < ctrl.propertyCollections.length; i++) {
|
||||||
|
var collection = ctrl.propertyCollections[i];
|
||||||
|
if (ctrl[collection.formId].$invalid) {
|
||||||
|
ready = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ready = false;
|
||||||
|
}
|
||||||
|
return ready;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Cray Inc.
|
||||||
|
*
|
||||||
|
* 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('horizon.dashboard.admin.ironic.base-node', function () {
|
||||||
|
var ironicBackendMockService, uibModalInstance;
|
||||||
|
var ctrl = {};
|
||||||
|
|
||||||
|
beforeEach(module('horizon.dashboard.admin.ironic'));
|
||||||
|
|
||||||
|
beforeEach(module('horizon.framework.util'));
|
||||||
|
|
||||||
|
beforeEach(module(function($provide) {
|
||||||
|
$provide.value('$uibModal', {});
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(module(function($provide) {
|
||||||
|
uibModalInstance = {
|
||||||
|
dismiss: jasmine.createSpy()
|
||||||
|
};
|
||||||
|
$provide.value('$uibModalInstance', uibModalInstance);
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(module(function($provide) {
|
||||||
|
$provide.value('horizon.framework.widgets.toast.service',
|
||||||
|
{});
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(module('horizon.app.core.openstack-service-api'));
|
||||||
|
|
||||||
|
beforeEach(inject(function($injector) {
|
||||||
|
ironicBackendMockService =
|
||||||
|
$injector.get('horizon.dashboard.admin.ironic.backend-mock.service');
|
||||||
|
ironicBackendMockService.init();
|
||||||
|
|
||||||
|
var controller = $injector.get('$controller');
|
||||||
|
controller('BaseNodeController', {ctrl: ctrl});
|
||||||
|
}));
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
ironicBackendMockService.postTest();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('controller should be defined', function () {
|
||||||
|
expect(ctrl).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('base construction', function () {
|
||||||
|
expect(ctrl.drivers).toBeNull();
|
||||||
|
expect(ctrl.images).toBeNull();
|
||||||
|
expect(ctrl.loadingDriverProperties).toBe(false);
|
||||||
|
expect(ctrl.driverProperties).toBeNull();
|
||||||
|
expect(ctrl.driverPropertyGroups).toBeNull();
|
||||||
|
expect(ctrl.modalTitle).toBeDefined();
|
||||||
|
angular.forEach(ctrl.propertyCollections, function(collection) {
|
||||||
|
expect(Object.getOwnPropertyNames(collection).sort()).toEqual(
|
||||||
|
PROPERTY_COLLECTION_PROPERTIES.sort());
|
||||||
|
});
|
||||||
|
expect(ctrl.propertyCollections)
|
||||||
|
.toContain(jasmine.objectContaining({id: "properties"}));
|
||||||
|
expect(ctrl.propertyCollections)
|
||||||
|
.toContain(jasmine.objectContaining({id: "extra"}));
|
||||||
|
expect(ctrl.node).toEqual({
|
||||||
|
name: null,
|
||||||
|
driver: null,
|
||||||
|
driver_info: {},
|
||||||
|
properties: {},
|
||||||
|
extra: {},
|
||||||
|
network_interface: null});
|
||||||
|
expect(Object.getOwnPropertyNames(ctrl).sort()).toEqual(
|
||||||
|
BASE_NODE_CONTROLLER_PROPERTIES.sort());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('_loadDrivers', function () {
|
||||||
|
ctrl._loadDrivers();
|
||||||
|
ironicBackendMockService.flush();
|
||||||
|
expect(ctrl.drivers).toEqual(ironicBackendMockService.getDrivers());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('_getImages', function () {
|
||||||
|
ctrl._getImages();
|
||||||
|
ironicBackendMockService.flush();
|
||||||
|
expect(ctrl.images).toEqual(ironicBackendMockService.getImages());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cancel', function () {
|
||||||
|
ctrl.cancel();
|
||||||
|
expect(uibModalInstance.dismiss).toHaveBeenCalledWith('cancel');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
})();
|
@ -125,8 +125,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<form id="{$ collection.id $}-form"
|
<form id="ctrl.{$ collection.formId $}"
|
||||||
name="{$ collection.id $}-form">
|
name="ctrl.{$ collection.formId $}">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="input-group input-group-sm"
|
<div class="input-group input-group-sm"
|
||||||
ng-repeat="(propertyName, propertyValue) in ctrl.node[collection.id]">
|
ng-repeat="(propertyName, propertyValue) in ctrl.node[collection.id]">
|
||||||
@ -253,10 +253,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
ng-disabled="!ctrl.driverProperties ||
|
ng-disabled="!ctrl.readyToSubmit()"
|
||||||
propertiesForm.$invalid ||
|
|
||||||
extraForm.$invalid ||
|
|
||||||
instanceInfoForm.$invalid"
|
|
||||||
ng-click="ctrl.submit()"
|
ng-click="ctrl.submit()"
|
||||||
class="btn btn-primary">
|
class="btn btn-primary">
|
||||||
{$ ::ctrl.submitButtonTitle $}
|
{$ ::ctrl.submitButtonTitle $}
|
||||||
|
@ -64,7 +64,10 @@
|
|||||||
var service = {
|
var service = {
|
||||||
DriverProperty: DriverProperty,
|
DriverProperty: DriverProperty,
|
||||||
PostfixExpr: PostfixExpr,
|
PostfixExpr: PostfixExpr,
|
||||||
Graph: Graph
|
Graph: Graph,
|
||||||
|
driverPropertyGroupHasRequired: driverPropertyGroupHasRequired,
|
||||||
|
driverPropertyGroupsToString: driverPropertyGroupsToString,
|
||||||
|
compareDriverPropertyGroups: compareDriverPropertyGroups
|
||||||
};
|
};
|
||||||
|
|
||||||
var VALID_ADDRESS_HOSTNAME_REGEX = new RegExp(VALID_IPV4_ADDRESS + "|" +
|
var VALID_ADDRESS_HOSTNAME_REGEX = new RegExp(VALID_IPV4_ADDRESS + "|" +
|
||||||
@ -669,6 +672,70 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Check whether a group contains required properties
|
||||||
|
*
|
||||||
|
* @param {DriverProperty[]} group - Property group
|
||||||
|
* @return {boolean} Return true if the group contains required
|
||||||
|
* properties, false otherwise
|
||||||
|
*/
|
||||||
|
function driverPropertyGroupHasRequired(group) {
|
||||||
|
var hasRequired = false;
|
||||||
|
for (var i = 0; i < group.length; i++) {
|
||||||
|
if (group[i].required) {
|
||||||
|
hasRequired = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hasRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Convert array of driver property groups to a string
|
||||||
|
*
|
||||||
|
* @param {array[]} groups - Array of driver property groups
|
||||||
|
* @return {string} Output string
|
||||||
|
*/
|
||||||
|
function driverPropertyGroupsToString(groups) {
|
||||||
|
var output = [];
|
||||||
|
angular.forEach(groups, function(group) {
|
||||||
|
var groupStr = [];
|
||||||
|
angular.forEach(group, function(property) {
|
||||||
|
groupStr.push(property.name);
|
||||||
|
});
|
||||||
|
groupStr = groupStr.join(", ");
|
||||||
|
output.push(['[', groupStr, ']'].join(""));
|
||||||
|
});
|
||||||
|
output = output.join(", ");
|
||||||
|
return ['[', output, ']'].join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Comaprison function used to sort driver property groups
|
||||||
|
*
|
||||||
|
* @param {DriverProperty[]} group1 - First group
|
||||||
|
* @param {DriverProperty[]} group2 - Second group
|
||||||
|
* @return {integer} Return:
|
||||||
|
* < 0 if group1 should precede group2 in an ascending ordering
|
||||||
|
* > 0 if group2 should precede group1
|
||||||
|
* 0 if group1 and group2 are considered equal from ordering perpsective
|
||||||
|
*/
|
||||||
|
function compareDriverPropertyGroups(group1, group2) {
|
||||||
|
var group1HasRequired = driverPropertyGroupHasRequired(group1);
|
||||||
|
var group2HasRequired = driverPropertyGroupHasRequired(group2);
|
||||||
|
|
||||||
|
if (group1HasRequired === group2HasRequired) {
|
||||||
|
if (group1.length === group2.length) {
|
||||||
|
return group1[0].name.localeCompare(group2[0].name);
|
||||||
|
} else {
|
||||||
|
return group1.length - group2.length;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return group1HasRequired ? -1 : 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
return service;
|
return service;
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -224,5 +224,47 @@
|
|||||||
expect(ret[1]).toBe(false);
|
expect(ret[1]).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('DriverPropertyGroup', function() {
|
||||||
|
it('driverPropertyGroupHasRequired', function () {
|
||||||
|
var dp1 = new service.DriverProperty("dp-1", " Required.", []);
|
||||||
|
var dp2 = new service.DriverProperty("dp-2", " ", []);
|
||||||
|
|
||||||
|
expect(service.driverPropertyGroupHasRequired).toBeDefined();
|
||||||
|
expect(service.driverPropertyGroupHasRequired([])).toBe(false);
|
||||||
|
expect(service.driverPropertyGroupHasRequired([dp1])).toBe(true);
|
||||||
|
expect(service.driverPropertyGroupHasRequired([dp2])).toBe(false);
|
||||||
|
expect(service.driverPropertyGroupHasRequired([dp1, dp2])).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('driverPropertyGroupsToString', function () {
|
||||||
|
var dp1 = new service.DriverProperty("dp-1", " Required.", []);
|
||||||
|
var dp2 = new service.DriverProperty("dp-2", " ", []);
|
||||||
|
|
||||||
|
expect(service.driverPropertyGroupsToString).toBeDefined();
|
||||||
|
expect(service.driverPropertyGroupsToString([])).toBe("[]");
|
||||||
|
expect(service.driverPropertyGroupsToString([[dp1]]))
|
||||||
|
.toBe("[[dp-1]]");
|
||||||
|
expect(service.driverPropertyGroupsToString([[dp1], [dp2]]))
|
||||||
|
.toBe("[[dp-1], [dp-2]]");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('compareDriverPropertyGroups', function () {
|
||||||
|
var dp1 = new service.DriverProperty("dp-1", " Required.", []);
|
||||||
|
var dp2 = new service.DriverProperty("dp-2", " ", []);
|
||||||
|
|
||||||
|
expect(service.compareDriverPropertyGroups).toBeDefined();
|
||||||
|
expect(service.compareDriverPropertyGroups([dp1], [dp1])).toBe(0);
|
||||||
|
expect(service.compareDriverPropertyGroups([dp1], [dp2])).toBe(-1);
|
||||||
|
expect(service.compareDriverPropertyGroups([dp2], [dp1])).toBe(1);
|
||||||
|
// smaller group precedes larger group
|
||||||
|
expect(service.compareDriverPropertyGroups([dp1], [dp1, dp2]))
|
||||||
|
.toBe(-1);
|
||||||
|
// group order decided on lexographic comparison of names of first
|
||||||
|
// property
|
||||||
|
expect(service.compareDriverPropertyGroups([dp2, dp1], [dp1, dp2]))
|
||||||
|
.toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2016 Cray Inc.
|
* Copyright 2017 Cray Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -55,16 +55,18 @@
|
|||||||
ctrl.modalTitle = gettext("Edit Node");
|
ctrl.modalTitle = gettext("Edit Node");
|
||||||
ctrl.submitButtonTitle = gettext("Update Node");
|
ctrl.submitButtonTitle = gettext("Update Node");
|
||||||
|
|
||||||
ctrl.node.instance_info = {};
|
|
||||||
|
|
||||||
ctrl.baseNode = null;
|
ctrl.baseNode = null;
|
||||||
|
|
||||||
|
var instanceInfoId = "instance_info";
|
||||||
ctrl.propertyCollections.push(
|
ctrl.propertyCollections.push(
|
||||||
{id: "instance_info",
|
{id: instanceInfoId,
|
||||||
|
formId: "instance_info_form",
|
||||||
title: gettext("Instance Info"),
|
title: gettext("Instance Info"),
|
||||||
addPrompt: gettext("Add Instance Property"),
|
addPrompt: gettext("Add Instance Property"),
|
||||||
placeholder: gettext("Instance Property Name")});
|
placeholder: gettext("Instance Property Name")});
|
||||||
|
|
||||||
|
ctrl.node[instanceInfoId] = {};
|
||||||
|
|
||||||
init(node);
|
init(node);
|
||||||
|
|
||||||
function init(node) {
|
function init(node) {
|
||||||
@ -110,7 +112,7 @@
|
|||||||
* @param {object} targetNode - Target node
|
* @param {object} targetNode - Target node
|
||||||
* @return {object[]} Array of patch instructions
|
* @return {object[]} Array of patch instructions
|
||||||
*/
|
*/
|
||||||
function buildPatch(sourceNode, targetNode) {
|
ctrl.buildPatch = function(sourceNode, targetNode) {
|
||||||
var patcher = new updatePatchService.UpdatePatch();
|
var patcher = new updatePatchService.UpdatePatch();
|
||||||
var PatchItem = function PatchItem(id, path) {
|
var PatchItem = function PatchItem(id, path) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@ -130,7 +132,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
return patcher.getPatch();
|
return patcher.getPatch();
|
||||||
}
|
};
|
||||||
|
|
||||||
ctrl.submit = function() {
|
ctrl.submit = function() {
|
||||||
angular.forEach(ctrl.driverProperties, function(property, name) {
|
angular.forEach(ctrl.driverProperties, function(property, name) {
|
||||||
@ -151,7 +153,7 @@
|
|||||||
$log.info("Updating node " + JSON.stringify(ctrl.baseNode));
|
$log.info("Updating node " + JSON.stringify(ctrl.baseNode));
|
||||||
$log.info("to " + JSON.stringify(ctrl.node));
|
$log.info("to " + JSON.stringify(ctrl.node));
|
||||||
|
|
||||||
var patch = buildPatch(ctrl.baseNode, ctrl.node);
|
var patch = ctrl.buildPatch(ctrl.baseNode, ctrl.node);
|
||||||
$log.info("patch = " + JSON.stringify(patch.patch));
|
$log.info("patch = " + JSON.stringify(patch.patch));
|
||||||
if (patch.status === updatePatchService.UpdatePatch.status.OK) {
|
if (patch.status === updatePatchService.UpdatePatch.status.OK) {
|
||||||
ironic.updateNode(ctrl.baseNode.uuid, patch.patch).then(function(node) {
|
ironic.updateNode(ctrl.baseNode.uuid, patch.patch).then(function(node) {
|
||||||
|
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Hewlett Packard Enterprise Development Company LP
|
||||||
|
* Copyright 2016 Cray Inc.
|
||||||
|
*
|
||||||
|
* 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('horizon.dashboard.admin.ironic.edit-node', function () {
|
||||||
|
var ironicBackendMockService, ctrl, editNode, updatePatchService;
|
||||||
|
|
||||||
|
beforeEach(module('horizon.dashboard.admin.ironic'));
|
||||||
|
|
||||||
|
beforeEach(module('horizon.framework.util'));
|
||||||
|
|
||||||
|
beforeEach(module(function($provide) {
|
||||||
|
$provide.value('$uibModal', {});
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(module(function($provide) {
|
||||||
|
$provide.value('$uibModalInstance', {});
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(module(function($provide) {
|
||||||
|
$provide.value('horizon.framework.widgets.toast.service',
|
||||||
|
{});
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(module('horizon.app.core.openstack-service-api'));
|
||||||
|
|
||||||
|
beforeEach(inject(function($injector) {
|
||||||
|
ironicBackendMockService =
|
||||||
|
$injector.get('horizon.dashboard.admin.ironic.backend-mock.service');
|
||||||
|
ironicBackendMockService.init();
|
||||||
|
|
||||||
|
updatePatchService =
|
||||||
|
$injector.get('horizon.dashboard.admin.ironic.update-patch.service');
|
||||||
|
|
||||||
|
var ironicAPI =
|
||||||
|
$injector.get('horizon.app.core.openstack-service-api.ironic');
|
||||||
|
ironicAPI.createNode(
|
||||||
|
{driver: ironicBackendMockService.params.defaultDriver})
|
||||||
|
.then(function(response) {
|
||||||
|
editNode = response.data;
|
||||||
|
var controller = $injector.get('$controller');
|
||||||
|
ctrl = controller('EditNodeController', {node: editNode});
|
||||||
|
});
|
||||||
|
ironicBackendMockService.flush();
|
||||||
|
}));
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
ironicBackendMockService.postTest();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('controller should be defined', function () {
|
||||||
|
expect(ctrl).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('controller base construction', function () {
|
||||||
|
expect(ctrl.baseNode).toEqual(
|
||||||
|
ironicBackendMockService.getNode(editNode.uuid));
|
||||||
|
expect(ctrl.propertyCollections)
|
||||||
|
.toContain(jasmine.objectContaining({id: "instance_info"}));
|
||||||
|
angular.forEach(ctrl.propertyCollections, function(collection) {
|
||||||
|
expect(Object.getOwnPropertyNames(collection).sort()).toEqual(
|
||||||
|
PROPERTY_COLLECTION_PROPERTIES.sort());
|
||||||
|
});
|
||||||
|
expect(ctrl.node.name).toEqual(editNode.name);
|
||||||
|
expect(ctrl.node.network_interface).toEqual(editNode.network_interface);
|
||||||
|
expect(ctrl.node.properties).toEqual(editNode.properties);
|
||||||
|
expect(ctrl.node.extra).toEqual(editNode.extra);
|
||||||
|
expect(ctrl.node.instance_info).toEqual(editNode.instance_info);
|
||||||
|
expect(ctrl.node.uuid).toEqual(editNode.uuid);
|
||||||
|
var properties = angular.copy(BASE_NODE_CONTROLLER_PROPERTIES);
|
||||||
|
properties.push('baseNode',
|
||||||
|
'buildPatch',
|
||||||
|
'selectedDriver',
|
||||||
|
'submit');
|
||||||
|
|
||||||
|
expect(Object.getOwnPropertyNames(ctrl).sort()).toEqual(
|
||||||
|
properties.sort());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('buildPatch', function () {
|
||||||
|
var patch = ctrl.buildPatch(editNode, editNode);
|
||||||
|
expect(patch.patch).toEqual([]);
|
||||||
|
expect(patch.status).toEqual(updatePatchService.UpdatePatch.status.OK);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Cray Inc.
|
||||||
|
*
|
||||||
|
* 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('horizon.dashboard.admin.ironic.enroll-node', function () {
|
||||||
|
var ironicBackendMockService, rootScope, ironicEvents, uibModalInstance;
|
||||||
|
var ctrl = {};
|
||||||
|
|
||||||
|
beforeEach(module('horizon.dashboard.admin.ironic'));
|
||||||
|
|
||||||
|
beforeEach(module('horizon.framework.util'));
|
||||||
|
|
||||||
|
beforeEach(module(function($provide) {
|
||||||
|
$provide.value('$uibModal', {});
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(module(function($provide) {
|
||||||
|
uibModalInstance = {
|
||||||
|
close: jasmine.createSpy()
|
||||||
|
};
|
||||||
|
$provide.value('$uibModalInstance', uibModalInstance);
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(module(function($provide) {
|
||||||
|
$provide.value('horizon.framework.widgets.toast.service',
|
||||||
|
{});
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(module('horizon.app.core.openstack-service-api'));
|
||||||
|
|
||||||
|
beforeEach(inject(function($injector) {
|
||||||
|
rootScope = $injector.get('$rootScope');
|
||||||
|
ironicEvents = $injector.get('horizon.dashboard.admin.ironic.events');
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(inject(function($injector) {
|
||||||
|
ironicBackendMockService =
|
||||||
|
$injector.get('horizon.dashboard.admin.ironic.backend-mock.service');
|
||||||
|
ironicBackendMockService.init();
|
||||||
|
|
||||||
|
var controller = $injector.get('$controller');
|
||||||
|
ctrl = controller('EnrollNodeController');
|
||||||
|
ironicBackendMockService.flush();
|
||||||
|
}));
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
ironicBackendMockService.postTest();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('controller should be defined', function () {
|
||||||
|
expect(ctrl).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('base construction', function () {
|
||||||
|
var properties = angular.copy(BASE_NODE_CONTROLLER_PROPERTIES);
|
||||||
|
properties.push('submit');
|
||||||
|
expect(Object.getOwnPropertyNames(ctrl).sort()).toEqual(
|
||||||
|
properties.sort());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('submit - success', function () {
|
||||||
|
spyOn(rootScope, '$emit');
|
||||||
|
var nodeName = "node_" + Date.now();
|
||||||
|
ctrl.node.name = nodeName;
|
||||||
|
ctrl.node.driver = ironicBackendMockService.params.defaultDriver;
|
||||||
|
ctrl.submit();
|
||||||
|
ironicBackendMockService.flush();
|
||||||
|
expect(rootScope.$emit)
|
||||||
|
.toHaveBeenCalledWith(ironicEvents.ENROLL_NODE_SUCCESS);
|
||||||
|
expect(uibModalInstance.close)
|
||||||
|
.toHaveBeenCalledWith(ironicBackendMockService.getNode(nodeName));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
@ -0,0 +1,383 @@
|
|||||||
|
/*
|
||||||
|
* © Copyright 2015,2016 Hewlett Packard Enterprise Development Company LP
|
||||||
|
* © Copyright 2016 Cray Inc.
|
||||||
|
*
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Service that provides a mock for the Ironic backend.
|
||||||
|
*/
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('horizon.dashboard.admin.ironic')
|
||||||
|
.factory('horizon.dashboard.admin.ironic.backend-mock.service',
|
||||||
|
ironicBackendMockService);
|
||||||
|
|
||||||
|
ironicBackendMockService.$inject = [
|
||||||
|
'$httpBackend',
|
||||||
|
'horizon.framework.util.uuid.service'
|
||||||
|
];
|
||||||
|
|
||||||
|
function ironicBackendMockService($httpBackend, uuidService) {
|
||||||
|
// Default node object.
|
||||||
|
var defaultNode = {
|
||||||
|
chassis_uuid: null,
|
||||||
|
clean_step: {},
|
||||||
|
console_enabled: false,
|
||||||
|
driver: undefined,
|
||||||
|
driver_info: {},
|
||||||
|
driver_internal_info: {},
|
||||||
|
extra: {},
|
||||||
|
inspection_finished_at: null,
|
||||||
|
inspection_started_at: null,
|
||||||
|
instance_info: {},
|
||||||
|
instance_uuid: null,
|
||||||
|
last_error: null,
|
||||||
|
maintenance: false,
|
||||||
|
maintenance_reason: null,
|
||||||
|
name: null,
|
||||||
|
network_interface: "flat",
|
||||||
|
power_state: null,
|
||||||
|
properties: {},
|
||||||
|
provision_state: "enroll",
|
||||||
|
provision_updated_at: null,
|
||||||
|
raid_config: {},
|
||||||
|
reservation: null,
|
||||||
|
resource_class: null,
|
||||||
|
target_power_state: null,
|
||||||
|
target_provision_state: null,
|
||||||
|
target_raid_config: {},
|
||||||
|
updated_at: null,
|
||||||
|
uuid: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
// Value of the next available system port
|
||||||
|
var nextAvailableSystemPort = 1024;
|
||||||
|
|
||||||
|
// Additional service parameters
|
||||||
|
var params = {
|
||||||
|
// Currently, all nodes have the same boot device.
|
||||||
|
bootDevice: {boot_device: 'pxe', persistent: true},
|
||||||
|
// Console info
|
||||||
|
consoleType: "shellinabox",
|
||||||
|
consoleUrl: "http://localhost:",
|
||||||
|
defaultDriver: "agent_ipmitool"
|
||||||
|
};
|
||||||
|
|
||||||
|
// List of supported drivers
|
||||||
|
var drivers = [{name: params.defaultDriver}];
|
||||||
|
|
||||||
|
// List of images
|
||||||
|
var images = [];
|
||||||
|
|
||||||
|
var service = {
|
||||||
|
params: params,
|
||||||
|
init: init,
|
||||||
|
flush: flush,
|
||||||
|
postTest: postTest,
|
||||||
|
getNode: getNode,
|
||||||
|
nodeGetConsoleUrl: nodeGetConsoleUrl,
|
||||||
|
getDrivers: getDrivers,
|
||||||
|
getImages: getImages
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dictionary of active nodes indexed by node-id (uuid and name)
|
||||||
|
var nodes = {};
|
||||||
|
|
||||||
|
return service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Get and reserve the next available system port.
|
||||||
|
*
|
||||||
|
* @return {int} Port number.
|
||||||
|
*/
|
||||||
|
function getNextAvailableSystemPort() {
|
||||||
|
return nextAvailableSystemPort++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Create a backend managed node.
|
||||||
|
*
|
||||||
|
* @param {object} params - Dictionary of parameters that define
|
||||||
|
* the node to be created.
|
||||||
|
* @return {object | null} Node object, or null if the nde could
|
||||||
|
* not be created.
|
||||||
|
*/
|
||||||
|
function createNode(params) {
|
||||||
|
var node = null;
|
||||||
|
|
||||||
|
if (angular.isDefined(params.driver)) {
|
||||||
|
node = angular.copy(defaultNode);
|
||||||
|
angular.forEach(params, function(value, key) {
|
||||||
|
node[key] = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (angular.isUndefined(node.uuid)) {
|
||||||
|
node.uuid = uuidService.generate();
|
||||||
|
}
|
||||||
|
|
||||||
|
var backendNode = {
|
||||||
|
base: node,
|
||||||
|
consolePort: getNextAvailableSystemPort()
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes[node.uuid] = backendNode;
|
||||||
|
|
||||||
|
if (node.name !== null) {
|
||||||
|
nodes[node.name] = backendNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* description Get a specified node.
|
||||||
|
*
|
||||||
|
* @param {string} nodeId - Uuid or name of the requested node.
|
||||||
|
* @return {object} Base node object.
|
||||||
|
*/
|
||||||
|
function getNode(nodeId) {
|
||||||
|
return angular.isDefined(nodes[nodeId]) ? nodes[nodeId].base : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @description Get the console-url for a specified node.
|
||||||
|
*
|
||||||
|
* @param {string} nodeId - Uuid or name of the node.
|
||||||
|
* @return {string} Console url if the console is enabled, null otherwise.
|
||||||
|
*/
|
||||||
|
function nodeGetConsoleUrl(nodeId) {
|
||||||
|
return nodes[nodeId].base.console_enabled
|
||||||
|
? service.params.consoleUrl + nodes[nodeId].consolePort
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Initialize the Backend-Mock service.
|
||||||
|
* Create the handlers that intercept http requests.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
function init() {
|
||||||
|
// Create node
|
||||||
|
$httpBackend.whenPOST(/\/api\/ironic\/nodes\/$/)
|
||||||
|
.respond(function(method, url, data) {
|
||||||
|
var node = createNode(JSON.parse(data).node);
|
||||||
|
return [node ? 200 : 400, node];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete node
|
||||||
|
$httpBackend.whenDELETE(/\/api\/ironic\/nodes\/$/)
|
||||||
|
.respond(function(method, url, data) {
|
||||||
|
var nodeId = JSON.parse(data).node;
|
||||||
|
var status = 400;
|
||||||
|
if (angular.isDefined(nodes[nodeId])) {
|
||||||
|
var node = nodes[nodeId].base;
|
||||||
|
if (node.name !== null) {
|
||||||
|
delete nodes[node.name];
|
||||||
|
delete nodes[node.uuid];
|
||||||
|
} else {
|
||||||
|
delete nodes[nodeId];
|
||||||
|
}
|
||||||
|
status = 204;
|
||||||
|
}
|
||||||
|
return [status, ""];
|
||||||
|
});
|
||||||
|
|
||||||
|
function _addItem(node, path, value) {
|
||||||
|
var parts = path.substring(1).split("/");
|
||||||
|
var leaf = parts.pop();
|
||||||
|
var obj = node;
|
||||||
|
for (var i = 0; i < parts.length; i++) {
|
||||||
|
var part = parts[i];
|
||||||
|
if (angular.isUndefined(obj[part])) {
|
||||||
|
obj[part] = {};
|
||||||
|
}
|
||||||
|
obj = obj[part];
|
||||||
|
}
|
||||||
|
obj[leaf] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _removeItem(node, path) {
|
||||||
|
var parts = path.substring(1).split("/");
|
||||||
|
var leaf = parts.pop();
|
||||||
|
var obj = node;
|
||||||
|
for (var i = 0; i < parts.length; i++) {
|
||||||
|
obj = obj[parts[i]];
|
||||||
|
}
|
||||||
|
delete obj[leaf];
|
||||||
|
}
|
||||||
|
|
||||||
|
function _replaceItem(node, path, value) {
|
||||||
|
if (path === "/name" &&
|
||||||
|
node.name !== null) {
|
||||||
|
delete nodes[name];
|
||||||
|
if (value !== null) {
|
||||||
|
nodes[value] = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts = path.substring(1).split("/");
|
||||||
|
var leaf = parts.pop();
|
||||||
|
var obj = node;
|
||||||
|
for (var i = 0; i < parts.length; i++) {
|
||||||
|
obj = obj[parts[i]];
|
||||||
|
}
|
||||||
|
obj[leaf] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update node
|
||||||
|
$httpBackend.whenPATCH(/\/api\/ironic\/nodes\/([^\/]+)$/,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
['nodeId'])
|
||||||
|
.respond(function(method, url, data, headers, params) {
|
||||||
|
var status = 400;
|
||||||
|
var node = service.getNode(params.nodeId);
|
||||||
|
if (angular.isDefined(node)) {
|
||||||
|
var patch = JSON.parse(data).patch;
|
||||||
|
angular.forEach(patch, function(operation) {
|
||||||
|
switch (operation.op) {
|
||||||
|
case "add":
|
||||||
|
_addItem(node, operation.path, operation.value);
|
||||||
|
break;
|
||||||
|
case "remove":
|
||||||
|
_removeItem(node, operation.path);
|
||||||
|
break;
|
||||||
|
case "replace":
|
||||||
|
_replaceItem(node, operation.path, operation.value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
});
|
||||||
|
status = 200;
|
||||||
|
}
|
||||||
|
return [status, node];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get node
|
||||||
|
$httpBackend.whenGET(/\/api\/ironic\/nodes\/([^\/]+)$/,
|
||||||
|
undefined,
|
||||||
|
['nodeId'])
|
||||||
|
.respond(function(method, url, data, headers, params) {
|
||||||
|
if (angular.isDefined(nodes[params.nodeId])) {
|
||||||
|
return [200, nodes[params.nodeId].base];
|
||||||
|
} else {
|
||||||
|
return [400, null];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get console
|
||||||
|
$httpBackend.whenGET(/\/api\/ironic\/nodes\/(.+)\/states\/console/,
|
||||||
|
undefined,
|
||||||
|
['nodeId'])
|
||||||
|
.respond(function(method, url, data, headers, params) {
|
||||||
|
var node = nodes[params.nodeId];
|
||||||
|
var consoleEnabled = node.base.console_enabled;
|
||||||
|
var consoleInfo = consoleEnabled
|
||||||
|
? {console_type: service.params.consoleType,
|
||||||
|
url: service.params.consoleUrl + node.consolePort}
|
||||||
|
: null;
|
||||||
|
|
||||||
|
var info = {
|
||||||
|
console_enabled: consoleEnabled,
|
||||||
|
console_info: consoleInfo};
|
||||||
|
return [200, info];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set console
|
||||||
|
$httpBackend.whenPUT(/\/api\/ironic\/nodes\/(.+)\/states\/console/,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
['nodeId'])
|
||||||
|
.respond(function(method, url, data, headers, params) {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
nodes[params.nodeId].base.console_enabled = data.enabled;
|
||||||
|
return [200, {}];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the ports belonging to a specified node
|
||||||
|
$httpBackend.whenGET(/\/api\/ironic\/ports/)
|
||||||
|
.respond(200, []);
|
||||||
|
|
||||||
|
// Get boot device
|
||||||
|
$httpBackend.whenGET(/\/api\/ironic\/nodes\/([^\/]+)\/boot_device$/,
|
||||||
|
undefined,
|
||||||
|
['nodeId'])
|
||||||
|
.respond(200, service.params.bootDevice);
|
||||||
|
|
||||||
|
// Validate the interfaces associated with a specified node
|
||||||
|
$httpBackend.whenGET(/\/api\/ironic\/nodes\/([^\/]+)\/validate$/,
|
||||||
|
undefined,
|
||||||
|
['nodeId'])
|
||||||
|
.respond(200, []);
|
||||||
|
|
||||||
|
// Get the currently available drivers
|
||||||
|
$httpBackend.whenGET(/\/api\/ironic\/drivers\/$/)
|
||||||
|
.respond(200, {drivers: drivers});
|
||||||
|
|
||||||
|
// Get driver properties
|
||||||
|
$httpBackend.whenGET(/\/api\/ironic\/drivers\/([^\/]+)\/properties$/,
|
||||||
|
undefined,
|
||||||
|
['driverName'])
|
||||||
|
.respond(200, []);
|
||||||
|
|
||||||
|
// Get glance images
|
||||||
|
$httpBackend.whenGET(/\/api\/glance\/images/)
|
||||||
|
.respond(200, {items: images});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Get the list of supported drivers
|
||||||
|
*
|
||||||
|
* @return {[]} Array of driver objects
|
||||||
|
*/
|
||||||
|
function getDrivers() {
|
||||||
|
return drivers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Get the list of images
|
||||||
|
*
|
||||||
|
* @return {[]} Array of image objects
|
||||||
|
*/
|
||||||
|
function getImages() {
|
||||||
|
return images;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Flush pending requests
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
function flush() {
|
||||||
|
$httpBackend.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Post test verifications.
|
||||||
|
* This function should be called after completion of a unit test.
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
function postTest() {
|
||||||
|
$httpBackend.verifyNoOutstandingExpectation();
|
||||||
|
$httpBackend.verifyNoOutstandingRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}());
|
@ -52,9 +52,8 @@
|
|||||||
getBootDevice: getBootDevice,
|
getBootDevice: getBootDevice,
|
||||||
nodeGetConsole: nodeGetConsole,
|
nodeGetConsole: nodeGetConsole,
|
||||||
nodeSetConsoleMode: nodeSetConsoleMode,
|
nodeSetConsoleMode: nodeSetConsoleMode,
|
||||||
|
nodeSetMaintenance: nodeSetMaintenance,
|
||||||
nodeSetPowerState: nodeSetPowerState,
|
nodeSetPowerState: nodeSetPowerState,
|
||||||
putNodeInMaintenanceMode: putNodeInMaintenanceMode,
|
|
||||||
removeNodeFromMaintenanceMode: removeNodeFromMaintenanceMode,
|
|
||||||
setNodeProvisionState: setNodeProvisionState,
|
setNodeProvisionState: setNodeProvisionState,
|
||||||
updateNode: updateNode,
|
updateNode: updateNode,
|
||||||
updatePort: updatePort,
|
updatePort: updatePort,
|
||||||
@ -170,49 +169,32 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Put the node in maintenance mode.
|
* @description Set the maintenance state of a node
|
||||||
*
|
*
|
||||||
* http://developer.openstack.org/api-ref/baremetal/#set-maintenance-flag
|
* http://developer.openstack.org/api-ref/baremetal/#set-maintenance-flag
|
||||||
*
|
*
|
||||||
* @param {string} uuid – UUID or logical name of a node.
|
* @param {string} nodeId – UUID or logical name of a node.
|
||||||
* @param {string} reason – Reason for why node is being put into
|
* @param {boolean} mode - True to put the node in maintenance mode,
|
||||||
* maintenance mode
|
* false to remove it from maintenance mode.
|
||||||
|
* @param {string} reason - Reason for putting the node in maintenance.
|
||||||
* @return {promise} Promise
|
* @return {promise} Promise
|
||||||
*/
|
*/
|
||||||
function putNodeInMaintenanceMode(uuid, reason) {
|
function nodeSetMaintenance(nodeId, mode, reason) {
|
||||||
return apiService.patch('/api/ironic/nodes/' + uuid + '/maintenance',
|
var url = '/api/ironic/nodes/' + nodeId + '/maintenance';
|
||||||
{maint_reason: reason
|
var promise = mode
|
||||||
? reason
|
? apiService.patch(url,
|
||||||
: gettext("No reason given.")})
|
{maint_reason: reason ? reason
|
||||||
.catch(function(response) {
|
: gettext("No reason given.")})
|
||||||
var msg = interpolate(
|
: apiService.delete(url);
|
||||||
gettext('Unable to put the Ironic node %s in maintenance mode: %s'),
|
|
||||||
[uuid, response.data],
|
|
||||||
false);
|
|
||||||
toastService.add('error', msg);
|
|
||||||
return $q.reject(msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return promise.catch(function(response) {
|
||||||
* @description Remove the node from maintenance mode.
|
var msg = interpolate(
|
||||||
*
|
gettext('Unable to set Ironic node %s maintenance state: %s'),
|
||||||
* http://developer.openstack.org/api-ref/baremetal/#clear-maintenance-flag
|
[nodeId, response.data],
|
||||||
*
|
false);
|
||||||
* @param {string} uuid – UUID or logical name of a node.
|
toastService.add('error', msg);
|
||||||
* @return {promise} Promise
|
return $q.reject(msg);
|
||||||
*/
|
});
|
||||||
function removeNodeFromMaintenanceMode(uuid) {
|
|
||||||
return apiService.delete('/api/ironic/nodes/' + uuid + '/maintenance')
|
|
||||||
.catch(function(response) {
|
|
||||||
var msg = interpolate(
|
|
||||||
gettext(
|
|
||||||
'Unable to remove the Ironic node %s from maintenance mode: %s'),
|
|
||||||
[uuid, response.data],
|
|
||||||
false);
|
|
||||||
toastService.add('error', msg);
|
|
||||||
return $q.reject(msg);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
322
ironic_ui/static/dashboard/admin/ironic/ironic.service.spec.js
Normal file
322
ironic_ui/static/dashboard/admin/ironic/ironic.service.spec.js
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2016 Cray Inc
|
||||||
|
*
|
||||||
|
* 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";
|
||||||
|
|
||||||
|
var IRONIC_API_PROPERTIES = [
|
||||||
|
'createNode',
|
||||||
|
'createPort',
|
||||||
|
'deleteNode',
|
||||||
|
'deletePort',
|
||||||
|
'getDrivers',
|
||||||
|
'getDriverProperties',
|
||||||
|
'getNode',
|
||||||
|
'getNodes',
|
||||||
|
'getPortsWithNode',
|
||||||
|
'getBootDevice',
|
||||||
|
'nodeGetConsole',
|
||||||
|
'nodeSetConsoleMode',
|
||||||
|
'nodeSetPowerState',
|
||||||
|
'nodeSetMaintenance',
|
||||||
|
'setNodeProvisionState',
|
||||||
|
'updateNode',
|
||||||
|
'updatePort',
|
||||||
|
'validateNode'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Unit tests for the Ironic-UI API service
|
||||||
|
*/
|
||||||
|
|
||||||
|
describe(
|
||||||
|
'horizon.dashboard.admin.ironic.service',
|
||||||
|
|
||||||
|
function() {
|
||||||
|
// Name of default driver used to create nodes.
|
||||||
|
var ironicAPI, ironicBackendMockService, defaultDriver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Create a node.
|
||||||
|
*
|
||||||
|
* @param {object} params - Dictionary of parameters that define the node.
|
||||||
|
* @return {promise} - Promise containing the newly created node.
|
||||||
|
*/
|
||||||
|
function createNode(params) {
|
||||||
|
return ironicAPI.createNode(params)
|
||||||
|
.then(function(response) {
|
||||||
|
return response.data; // node
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Fail the current test
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
function failTest() {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(module('horizon.dashboard.admin.ironic'));
|
||||||
|
|
||||||
|
beforeEach(module('horizon.framework.util'));
|
||||||
|
|
||||||
|
beforeEach(module(function($provide) {
|
||||||
|
$provide.value('horizon.framework.widgets.toast.service', {
|
||||||
|
add: function() {}
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(module('horizon.app.core.openstack-service-api'));
|
||||||
|
|
||||||
|
beforeEach(inject(function($injector) {
|
||||||
|
ironicBackendMockService =
|
||||||
|
$injector.get('horizon.dashboard.admin.ironic.backend-mock.service');
|
||||||
|
ironicBackendMockService.init();
|
||||||
|
defaultDriver = ironicBackendMockService.params.defaultDriver;
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(inject(function($injector) {
|
||||||
|
ironicAPI =
|
||||||
|
$injector.get('horizon.app.core.openstack-service-api.ironic');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('defines the ironicAPI', function() {
|
||||||
|
expect(ironicAPI).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
ironicBackendMockService.postTest();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ironicAPI', function() {
|
||||||
|
it('service API', function() {
|
||||||
|
expect(Object.getOwnPropertyNames(ironicAPI).sort())
|
||||||
|
.toEqual(IRONIC_API_PROPERTIES.sort());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getDrivers', function() {
|
||||||
|
ironicAPI.getDrivers()
|
||||||
|
.then(function(drivers) {
|
||||||
|
expect(drivers.length).toBeGreaterThan(0);
|
||||||
|
angular.forEach(drivers, function(driver) {
|
||||||
|
expect(driver.name).toBeDefined();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(failTest);
|
||||||
|
|
||||||
|
ironicBackendMockService.flush();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('createNode - Minimal input data', function() {
|
||||||
|
createNode({driver: defaultDriver})
|
||||||
|
.then(function(node) {
|
||||||
|
expect(node.driver).toEqual(defaultDriver);
|
||||||
|
expect(node).toEqual(ironicBackendMockService.getNode(node.uuid));
|
||||||
|
})
|
||||||
|
.catch(failTest);
|
||||||
|
|
||||||
|
ironicBackendMockService.flush();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('createNode - Missing input data', function() {
|
||||||
|
createNode({})
|
||||||
|
.then(failTest);
|
||||||
|
|
||||||
|
ironicBackendMockService.flush();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getNode', function() {
|
||||||
|
createNode({driver: defaultDriver})
|
||||||
|
.then(function(node1) {
|
||||||
|
ironicAPI.getNode(node1.uuid).then(function(node2) {
|
||||||
|
expect(node2).toEqual(node1);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(failTest);
|
||||||
|
|
||||||
|
ironicBackendMockService.flush();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deleteNode', function() {
|
||||||
|
createNode({driver: defaultDriver})
|
||||||
|
.then(function(node) {
|
||||||
|
return ironicAPI.deleteNode(node.uuid).then(function() {
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(function(node) {
|
||||||
|
expect(
|
||||||
|
ironicBackendMockService.getNode(node.uuid)).toBe(undefined);
|
||||||
|
})
|
||||||
|
.catch(failTest);
|
||||||
|
|
||||||
|
ironicBackendMockService.flush();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deleteNode - nonexistent node', function() {
|
||||||
|
ironicAPI.deleteNode(0)
|
||||||
|
.then(failTest);
|
||||||
|
|
||||||
|
ironicBackendMockService.flush();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updateNode - resource_class', function() {
|
||||||
|
createNode({driver: defaultDriver})
|
||||||
|
.then(function(node) {
|
||||||
|
return ironicAPI.updateNode(
|
||||||
|
node.uuid,
|
||||||
|
[{op: "replace",
|
||||||
|
path: "/resource_class",
|
||||||
|
value: "some-resource-class"}]).then(
|
||||||
|
function(node) {
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(function(node) {
|
||||||
|
expect(node.resource_class).toEqual("some-resource-class");
|
||||||
|
})
|
||||||
|
.catch(failTest);
|
||||||
|
|
||||||
|
ironicBackendMockService.flush();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('nodeGetConsole - console enabled', function() {
|
||||||
|
createNode({driver: defaultDriver,
|
||||||
|
console_enabled: true})
|
||||||
|
.then(function(node) {
|
||||||
|
expect(node.console_enabled).toEqual(true);
|
||||||
|
return node;
|
||||||
|
})
|
||||||
|
.then(function(node) {
|
||||||
|
return ironicAPI.nodeGetConsole(node.uuid).then(
|
||||||
|
function(consoleData) {
|
||||||
|
return {node: node, consoleData: consoleData};
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(function(data) {
|
||||||
|
expect(data.consoleData.console_enabled).toEqual(true);
|
||||||
|
expect(data.consoleData.console_info.console_type)
|
||||||
|
.toEqual(ironicBackendMockService.params.consoleType);
|
||||||
|
expect(data.consoleData.console_info.url)
|
||||||
|
.toEqual(ironicBackendMockService.nodeGetConsoleUrl(
|
||||||
|
data.node.uuid));
|
||||||
|
})
|
||||||
|
.catch(failTest);
|
||||||
|
|
||||||
|
ironicBackendMockService.flush();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('nodeGetConsole - console not enabled', function() {
|
||||||
|
createNode({driver: defaultDriver,
|
||||||
|
console_enabled: false})
|
||||||
|
.then(function(node) {
|
||||||
|
expect(node.console_enabled).toEqual(false);
|
||||||
|
return node;
|
||||||
|
})
|
||||||
|
.then(function(node) {
|
||||||
|
return ironicAPI.nodeGetConsole(node.uuid);
|
||||||
|
})
|
||||||
|
.then(function(consoleData) {
|
||||||
|
expect(consoleData).toEqual(
|
||||||
|
{console_enabled: false,
|
||||||
|
console_info: null});
|
||||||
|
})
|
||||||
|
.catch(failTest);
|
||||||
|
|
||||||
|
ironicBackendMockService.flush();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('nodeSetConsoleMode - Toggle console mode', function() {
|
||||||
|
createNode({driver: defaultDriver,
|
||||||
|
console_enabled: false})
|
||||||
|
.then(function(node) {
|
||||||
|
expect(node.console_enabled).toEqual(false);
|
||||||
|
return node;
|
||||||
|
})
|
||||||
|
.then(function(node) {
|
||||||
|
ironicAPI.nodeSetConsoleMode(node.uuid, true);
|
||||||
|
return node;
|
||||||
|
})
|
||||||
|
.then(function(node) {
|
||||||
|
return ironicAPI.getNode(node.uuid);
|
||||||
|
})
|
||||||
|
.then(function(node) {
|
||||||
|
expect(node.console_enabled).toEqual(true);
|
||||||
|
return node;
|
||||||
|
})
|
||||||
|
// Toggle back
|
||||||
|
.then(function(node) {
|
||||||
|
ironicAPI.nodeSetConsoleMode(node.uuid, false);
|
||||||
|
return node;
|
||||||
|
})
|
||||||
|
.then(function(node) {
|
||||||
|
return ironicAPI.getNode(node.uuid);
|
||||||
|
})
|
||||||
|
.then(function(node) {
|
||||||
|
expect(node.console_enabled).toEqual(false);
|
||||||
|
return node;
|
||||||
|
})
|
||||||
|
.catch(failTest);
|
||||||
|
|
||||||
|
ironicBackendMockService.flush();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('nodeSetConsoleMode - Redundant console set', function() {
|
||||||
|
createNode({driver: defaultDriver,
|
||||||
|
console_enabled: false})
|
||||||
|
.then(function(node) {
|
||||||
|
expect(node.console_enabled).toEqual(false);
|
||||||
|
return node;
|
||||||
|
})
|
||||||
|
.then(function(node) {
|
||||||
|
ironicAPI.nodeSetConsoleMode(node.uuid, false);
|
||||||
|
return node;
|
||||||
|
})
|
||||||
|
.then(function(node) {
|
||||||
|
return ironicAPI.getNode(node.uuid);
|
||||||
|
})
|
||||||
|
.then(function(node) {
|
||||||
|
expect(node.console_enabled).toEqual(false);
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
|
||||||
|
ironicBackendMockService.flush();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getBootDevice', function() {
|
||||||
|
createNode({driver: defaultDriver})
|
||||||
|
.then(function(node) {
|
||||||
|
expect(node.console_enabled).toEqual(false);
|
||||||
|
return node;
|
||||||
|
})
|
||||||
|
.then(function(node) {
|
||||||
|
return ironicAPI.getBootDevice(node.uuid)
|
||||||
|
.then(function(bootDevice) {
|
||||||
|
return bootDevice;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(function(bootDevice) {
|
||||||
|
expect(bootDevice).toEqual(
|
||||||
|
ironicBackendMockService.params.bootDevice);
|
||||||
|
});
|
||||||
|
|
||||||
|
ironicBackendMockService.flush();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
@ -40,46 +40,32 @@
|
|||||||
};
|
};
|
||||||
return service;
|
return service;
|
||||||
|
|
||||||
/*
|
|
||||||
* @description Put a specified list of nodes into mainenance.
|
|
||||||
* A modal dialog is used to prompt the user for a reason for
|
|
||||||
* putting the nodes in maintenance mode.
|
|
||||||
*
|
|
||||||
* @param {object[]} nodes - List of node objects
|
|
||||||
* @return {promise}
|
|
||||||
*/
|
|
||||||
function putNodeInMaintenanceMode(nodes) {
|
|
||||||
var options = {
|
|
||||||
controller: "MaintenanceController as ctrl",
|
|
||||||
templateUrl: basePath + '/maintenance/maintenance.html'
|
|
||||||
};
|
|
||||||
return $uibModal.open(options).result.then(function(reason) {
|
|
||||||
return nodeActions.putNodeInMaintenanceMode(nodes, reason);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @description Take a specified list of nodes out of mainenance
|
|
||||||
*
|
|
||||||
* @param {object[]} nodes - List of node objects
|
|
||||||
* @return {promise}
|
|
||||||
*/
|
|
||||||
function removeNodeFromMaintenanceMode(nodes) {
|
|
||||||
return nodeActions.removeNodeFromMaintenanceMode(nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @description Set the maintenance mode of a specified list of nodes
|
* @description Set the maintenance mode of a specified list of nodes
|
||||||
*
|
*
|
||||||
|
* If nodes are being put into maintenance mode a modal dialog is used
|
||||||
|
* to prompt the user for a reason.
|
||||||
|
*
|
||||||
* @param {object[]} nodes - List of node objects
|
* @param {object[]} nodes - List of node objects
|
||||||
* @param {boolean} mode - Desired maintenance state.
|
* @param {boolean} mode - Desired maintenance state.
|
||||||
* 'true' -> Node is in maintenance mode
|
* 'true' -> Node is in maintenance mode
|
||||||
* 'false' -> Node is not in maintenance mode
|
* 'false' -> Node is not in maintenance mode
|
||||||
* @return {promise}
|
* @return {promise}
|
||||||
*/
|
*/
|
||||||
function setMaintenance(nodes, mode) {
|
function setMaintenance(nodes, mode) {
|
||||||
return mode ? putNodeInMaintenanceMode(nodes)
|
var promise;
|
||||||
: removeNodeFromMaintenanceMode(nodes);
|
if (mode) {
|
||||||
|
var options = {
|
||||||
|
controller: "MaintenanceController as ctrl",
|
||||||
|
templateUrl: basePath + '/maintenance/maintenance.html'
|
||||||
|
};
|
||||||
|
promise = $uibModal.open(options).result.then(function(reason) {
|
||||||
|
return nodeActions.setMaintenance(nodes, true, reason);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
promise = nodeActions.setMaintenance(nodes, false);
|
||||||
|
}
|
||||||
|
return promise;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -60,8 +60,7 @@
|
|||||||
deleteNode: deleteNode,
|
deleteNode: deleteNode,
|
||||||
deletePort: deletePort,
|
deletePort: deletePort,
|
||||||
setPowerState: setPowerState,
|
setPowerState: setPowerState,
|
||||||
putNodeInMaintenanceMode: putNodeInMaintenanceMode,
|
setMaintenance: setMaintenance,
|
||||||
removeNodeFromMaintenanceMode: removeNodeFromMaintenanceMode,
|
|
||||||
setProvisionState: setProvisionState,
|
setProvisionState: setProvisionState,
|
||||||
getPowerTransitions : getPowerTransitions
|
getPowerTransitions : getPowerTransitions
|
||||||
};
|
};
|
||||||
@ -120,41 +119,37 @@
|
|||||||
|
|
||||||
// maintenance
|
// maintenance
|
||||||
|
|
||||||
function putNodeInMaintenanceMode(nodes, reason) {
|
/**
|
||||||
return applyFuncToNodes(
|
* @description Set the maintenance state of a list of nodes
|
||||||
function(node, reason) {
|
*
|
||||||
if (node.maintenance !== false) {
|
* @param {object[]} nodes - List of node objects
|
||||||
var msg = gettext("Node %s is already in maintenance mode.");
|
* @param {boolean} mode - True if the nodes are to be put in
|
||||||
return $q.reject(interpolate(msg, [node.uuid], false));
|
* maintenance mode, otherwise false.
|
||||||
}
|
* @param {string} [reason] - Optional reason for putting nodes in
|
||||||
return ironic.putNodeInMaintenanceMode(node.uuid, reason).then(
|
* maintenance mode.
|
||||||
|
* @return {promise} promise
|
||||||
|
*/
|
||||||
|
function setMaintenance(nodes, mode, reason) {
|
||||||
|
var promises = [];
|
||||||
|
angular.forEach(nodes, function(node) {
|
||||||
|
var promise;
|
||||||
|
if (node.maintenance === mode) {
|
||||||
|
var msg = gettext(
|
||||||
|
"Node %s is already in target maintenance state.");
|
||||||
|
promise = $q.reject(interpolate(msg, [node.uuid], false));
|
||||||
|
} else {
|
||||||
|
promise = ironic.nodeSetMaintenance(node.uuid, mode, reason).then(
|
||||||
function (result) {
|
function (result) {
|
||||||
node.maintenance = true;
|
node.maintenance = mode;
|
||||||
node.maintenance_reason = reason;
|
node.maintenance_reason =
|
||||||
|
mode && angular.isDefined(reason) ? reason : "";
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
nodes,
|
promises.push(promise);
|
||||||
reason);
|
});
|
||||||
}
|
return $q.all(promises);
|
||||||
|
|
||||||
function removeNodeFromMaintenanceMode(nodes) {
|
|
||||||
return applyFuncToNodes(
|
|
||||||
function(node) {
|
|
||||||
if (node.maintenance !== true) {
|
|
||||||
var msg = gettext("Node %s is not in maintenance mode.");
|
|
||||||
return $q.reject(interpolate(msg, [node.uuid], false));
|
|
||||||
}
|
|
||||||
return ironic.removeNodeFromMaintenanceMode(node.uuid).then(
|
|
||||||
function (result) {
|
|
||||||
node.maintenance = false;
|
|
||||||
node.maintenance_reason = "";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
nodes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -202,30 +197,6 @@
|
|||||||
return deleteModalService.open($rootScope, ports, context);
|
return deleteModalService.open($rootScope, ports, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* @name horizon.dashboard.admin.ironic.actions.applyFuncToNodes
|
|
||||||
* @description Apply a specified function to each member of a
|
|
||||||
* collection of nodes
|
|
||||||
*
|
|
||||||
* @param {function} fn – Function to be applied.
|
|
||||||
* The function should accept a node as the first argument. An optional
|
|
||||||
* second argument can be used to provide additional information.
|
|
||||||
* The function should return a promise.
|
|
||||||
* @param {Array<node>} nodes - Collection of nodes
|
|
||||||
* @param {object} extra - Additional argument passed to the function
|
|
||||||
* @return {promise} - Single promise that represents the combined
|
|
||||||
* return status from all function invocations. The promise is rejected
|
|
||||||
* if any individual call fails.
|
|
||||||
*/
|
|
||||||
function applyFuncToNodes(fn, nodes, extra) {
|
|
||||||
var promises = [];
|
|
||||||
angular.forEach(nodes,
|
|
||||||
function(node) {
|
|
||||||
promises.push(fn(node, extra));
|
|
||||||
});
|
|
||||||
return $q.all(promises);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @name horizon.dashboard.admin.ironic.actions.getPowerTransitions
|
* @name horizon.dashboard.admin.ironic.actions.getPowerTransitions
|
||||||
* @description Get the list of power transitions for a specified
|
* @description Get the list of power transitions for a specified
|
||||||
|
52
ironic_ui/static/dashboard/admin/ironic/test-data.spec.js
Normal file
52
ironic_ui/static/dashboard/admin/ironic/test-data.spec.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Cray Inc.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@description Global data used by unit tests.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* exported BASE_NODE_CONTROLLER_PROPERTIES */
|
||||||
|
|
||||||
|
var BASE_NODE_CONTROLLER_PROPERTIES = [
|
||||||
|
'_getImages',
|
||||||
|
'_loadDrivers',
|
||||||
|
'_sortDriverProperties',
|
||||||
|
'cancel',
|
||||||
|
'collectionCheckPropertyUnique',
|
||||||
|
'collectionDeleteProperty',
|
||||||
|
'driverProperties',
|
||||||
|
'driverPropertyGroups',
|
||||||
|
'drivers',
|
||||||
|
'images',
|
||||||
|
'isDriverPropertyActive',
|
||||||
|
'loadDriverProperties',
|
||||||
|
'loadingDriverProperties',
|
||||||
|
'modalTitle',
|
||||||
|
'node',
|
||||||
|
'propertyCollections',
|
||||||
|
'readyToSubmit',
|
||||||
|
'submitButtonTitle',
|
||||||
|
'validHostNameRegex'];
|
||||||
|
|
||||||
|
/* exported PROPERTY_COLLECTION_PROPERTIES */
|
||||||
|
|
||||||
|
var PROPERTY_COLLECTION_PROPERTIES = [
|
||||||
|
'id',
|
||||||
|
'formId',
|
||||||
|
'title',
|
||||||
|
'addPrompt',
|
||||||
|
'placeholder'
|
||||||
|
];
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ironic-ui",
|
"name": "ironic-ui",
|
||||||
"version": "1.0.0",
|
"version": "0.0.0",
|
||||||
"description": "Horizon plugin for OpenStack Ironic.",
|
"description": "Horizon plugin for OpenStack Ironic.",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
14
releasenotes/notes/unit-test-framework-f61ad7926413bf91.yaml
Normal file
14
releasenotes/notes/unit-test-framework-f61ad7926413bf91.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
A backend mock has been added that enables better unit testing of the
|
||||||
|
Ironic API service and other Ironic-UI components. The mock utilizes
|
||||||
|
Angular $httpbackend handlers to intercept requests targeted at the
|
||||||
|
Ironic-UI server-side REST endpoints, and returns simulated responses.
|
||||||
|
- |
|
||||||
|
A number of unit tests have been developed that illustrate the use
|
||||||
|
of the backend mock.
|
||||||
|
- |
|
||||||
|
Although the backend mock is a work in progress, enough
|
||||||
|
functionality already exists to support test development for
|
||||||
|
the current set of in-progress features.
|
@ -15,6 +15,6 @@ testtools>=1.4.0 # MIT
|
|||||||
# this is required for the docs build jobs
|
# this is required for the docs build jobs
|
||||||
sphinx!=1.6.1,>=1.5.1 # BSD
|
sphinx!=1.6.1,>=1.5.1 # BSD
|
||||||
oslosphinx>=4.7.0 # Apache-2.0
|
oslosphinx>=4.7.0 # Apache-2.0
|
||||||
reno>=1.8.0 # Apache-2.0
|
reno!=2.3.1,>=1.8.0 # Apache-2.0
|
||||||
# Include horizon as test requirement
|
# Include horizon as test requirement
|
||||||
http://tarballs.openstack.org/horizon/horizon-master.tar.gz#egg=horizon
|
http://tarballs.openstack.org/horizon/horizon-master.tar.gz#egg=horizon
|
||||||
|
Loading…
Reference in New Issue
Block a user