Added functionality to enroll a node, and delete node(s)
These changes add functionality to enroll a node into the Ironic database, and delete node(s) from the database. Change-Id: Id0837b75e946ff702e81a47b9f8c3656beb5a783
This commit is contained in:
parent
fd86177443
commit
3a8ff7b3c8
@ -117,3 +117,52 @@ def node_set_maintenance(request, node_id, state, maint_reason=None):
|
||||
node_id,
|
||||
state,
|
||||
maint_reason=maint_reason)
|
||||
|
||||
|
||||
def node_create(request, params):
|
||||
"""Create a node
|
||||
|
||||
:param request: HTTP request.
|
||||
:param params: Dictionary of node parameters
|
||||
"""
|
||||
node_manager = ironicclient(request).node
|
||||
node = node_manager.create(**params)
|
||||
field_list = ['chassis_uuid',
|
||||
'driver',
|
||||
'driver_info',
|
||||
'properties',
|
||||
'extra',
|
||||
'uuid',
|
||||
'name']
|
||||
return dict([(f, getattr(node, f, '')) for f in field_list])
|
||||
|
||||
|
||||
def node_delete(request, node_id):
|
||||
"""Delete a node from inventory.
|
||||
|
||||
:param request: HTTP request.
|
||||
:param node_id: The UUID of the node.
|
||||
:return: node.
|
||||
|
||||
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.delete
|
||||
"""
|
||||
return ironicclient(request).node.delete(node_id)
|
||||
|
||||
|
||||
def driver_list(request):
|
||||
"""Retrieve a list of drivers.
|
||||
|
||||
:param request: HTTP request.
|
||||
:return: A list of drivers.
|
||||
"""
|
||||
return ironicclient(request).driver.list()
|
||||
|
||||
|
||||
def driver_properties(request, driver_name):
|
||||
"""Retrieve the properties of a specified driver
|
||||
|
||||
:param request: HTTP request
|
||||
:param driver_name: Name of the driver
|
||||
:return: Property list
|
||||
"""
|
||||
return ironicclient(request).driver.properties(driver_name)
|
||||
|
@ -40,6 +40,24 @@ class Nodes(generic.View):
|
||||
'items': [i.to_dict() for i in items],
|
||||
}
|
||||
|
||||
@rest_utils.ajax(data_required=True)
|
||||
def post(self, request):
|
||||
"""Create an Ironic node
|
||||
|
||||
:param request: HTTP request
|
||||
"""
|
||||
params = request.DATA.get('node')
|
||||
return ironic.node_create(request, params)
|
||||
|
||||
@rest_utils.ajax(data_required=True)
|
||||
def delete(self, request):
|
||||
"""Delete an Ironic node from inventory
|
||||
|
||||
:param request: HTTP request
|
||||
"""
|
||||
params = request.DATA.get('node')
|
||||
return ironic.node_delete(request, params)
|
||||
|
||||
|
||||
@urls.register
|
||||
class Node(generic.View):
|
||||
@ -122,3 +140,37 @@ class Maintenance(generic.View):
|
||||
:return: Return code
|
||||
"""
|
||||
return ironic.node_set_maintenance(request, node_id, 'off')
|
||||
|
||||
|
||||
@urls.register
|
||||
class Drivers(generic.View):
|
||||
|
||||
url_regex = r'ironic/drivers/$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request):
|
||||
"""Get the list of drivers
|
||||
|
||||
:param request: HTTP request
|
||||
:return: drivers
|
||||
"""
|
||||
items = ironic.driver_list(request)
|
||||
return {
|
||||
'items': [i.to_dict() for i in items]
|
||||
}
|
||||
|
||||
|
||||
@urls.register
|
||||
class DriverProperties(generic.View):
|
||||
|
||||
url_regex = r'ironic/drivers/(?P<driver_name>[0-9a-zA-Z_-]+)/properties$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request, driver_name):
|
||||
"""Get the properties associated with a specified driver
|
||||
|
||||
:param request: HTTP request
|
||||
:param driver_name: Driver name
|
||||
:return: Dictionary of properties
|
||||
"""
|
||||
return ironic.driver_properties(request, driver_name)
|
||||
|
@ -0,0 +1,179 @@
|
||||
/*
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* Controller used to enroll a node in the Ironic database
|
||||
*/
|
||||
angular
|
||||
.module('horizon.dashboard.admin.ironic')
|
||||
.controller('EnrollNodeController', EnrollNodeController);
|
||||
|
||||
EnrollNodeController.$inject = [
|
||||
'$rootScope',
|
||||
'$modalInstance',
|
||||
'horizon.app.core.openstack-service-api.ironic',
|
||||
'horizon.dashboard.admin.ironic.enroll-node.service',
|
||||
'$log'
|
||||
];
|
||||
|
||||
function EnrollNodeController($rootScope,
|
||||
$modalInstance,
|
||||
ironic,
|
||||
enrollNodeService,
|
||||
$log) {
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.drivers = null;
|
||||
ctrl.loadingDriverProperties = false;
|
||||
// Object containing the set of properties associated with the currently
|
||||
// selected driver
|
||||
ctrl.driverProperties = null;
|
||||
|
||||
// Paramater object that defines the node to be enrolled
|
||||
ctrl.node = {
|
||||
name: null,
|
||||
driver: null,
|
||||
driver_info: {},
|
||||
properties: {},
|
||||
extra: {}
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
function init() {
|
||||
loadDrivers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of currently active Ironic drivers
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
function loadDrivers() {
|
||||
ironic.getDrivers().then(function(response) {
|
||||
ctrl.drivers = response.data.items;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the properties associated with a specified driver
|
||||
*
|
||||
* @param {string} driverName - Name of driver
|
||||
* @return {void}
|
||||
*/
|
||||
ctrl.loadDriverProperties = function(driverName) {
|
||||
ctrl.node.driver = driverName;
|
||||
ctrl.node.driver_info = {};
|
||||
|
||||
ctrl.loadingDriverProperties = true;
|
||||
ctrl.driverProperties = null;
|
||||
|
||||
ironic.getDriverProperties(driverName).then(function(response) {
|
||||
ctrl.driverProperties = {};
|
||||
angular.forEach(response.data, function(desc, property) {
|
||||
ctrl.driverProperties[property] =
|
||||
new enrollNodeService.DriverProperty(property,
|
||||
desc,
|
||||
ctrl.driverProperties);
|
||||
});
|
||||
ctrl.loadingDriverProperties = false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Cancel the node enrollment process
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
ctrl.cancel = function() {
|
||||
$modalInstance.dismiss('cancel');
|
||||
};
|
||||
|
||||
/**
|
||||
* Enroll the defined node
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
ctrl.enroll = function() {
|
||||
$log.debug(">> EnrollNodeController.enroll()");
|
||||
angular.forEach(ctrl.driverProperties, function(property, name) {
|
||||
$log.debug(name +
|
||||
", required = " + property.isRequired() +
|
||||
", active = " + property.isActive() +
|
||||
", input value = " + property.inputValue);
|
||||
if (property.isActive() && property.inputValue) {
|
||||
$log.debug("Setting driver property " + name + " to " +
|
||||
property.inputValue);
|
||||
ctrl.node.driver_info[name] = property.inputValue;
|
||||
}
|
||||
});
|
||||
|
||||
ironic.createNode(ctrl.node).then(
|
||||
function() {
|
||||
$modalInstance.close();
|
||||
$rootScope.$emit('ironic-ui:new-node');
|
||||
},
|
||||
function() {
|
||||
// No additional error processing for now
|
||||
});
|
||||
$log.debug("<< EnrollNodeController.enroll()");
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a node property
|
||||
*
|
||||
* @param {string} propertyName - Name of the property
|
||||
* @return {void}
|
||||
*/
|
||||
ctrl.deleteProperty = function(propertyName) {
|
||||
delete ctrl.node.properties[propertyName];
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether the specified node property already exists
|
||||
*
|
||||
* @param {string} propertyName - Name of the property
|
||||
* @return {boolean} True if the property already exists,
|
||||
* otherwise false
|
||||
*/
|
||||
ctrl.checkPropertyUnique = function(propertyName) {
|
||||
return !(propertyName in ctrl.node.properties);
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a node metadata property
|
||||
*
|
||||
* @param {string} propertyName - Name of the property
|
||||
* @return {void}
|
||||
*/
|
||||
ctrl.deleteExtra = function(propertyName) {
|
||||
delete ctrl.node.extra[propertyName];
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether the specified node metadata property already exists
|
||||
*
|
||||
* @param {string} propertyName - Name of the metadata property
|
||||
* @return {boolean} True if the property already exists,
|
||||
* otherwise false
|
||||
*/
|
||||
ctrl.checkExtraUnique = function(propertyName) {
|
||||
return !(propertyName in ctrl.node.extra);
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,207 @@
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title" translate>Enroll Node</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h4 class="modal-title" translate>General</h4>
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label for="name"
|
||||
class="col-sm-3 control-label"
|
||||
translate>Node Name</label>
|
||||
|
||||
<div class="col-sm-9">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
ng-model="ctrl.node.name"
|
||||
id="name"
|
||||
name="name"
|
||||
placeholder="{$ 'A unique node name. Optional.' | translate $}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="driver"
|
||||
class="col-sm-3 control-label"
|
||||
translate>Node Driver</label>
|
||||
<div class="col-sm-9">
|
||||
<select id="driver"
|
||||
class="form-control"
|
||||
ng-options="driver as driver.name for driver in ctrl.drivers"
|
||||
ng-model="ctrl.selectedDriver"
|
||||
ng-change="ctrl.loadDriverProperties(ctrl.selectedDriver.name)">
|
||||
<option value="" disabled selected translate>Select a Driver</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div ng-if="!ctrl.loadingDriverProperties && ctrl.driverProperties">
|
||||
<hr/>
|
||||
<h4 class="modal-title" translate>Driver Info</h4>
|
||||
</div>
|
||||
|
||||
<form class="form-horizontal"
|
||||
name="DriverInfoForm"
|
||||
id="DriverInfoForm">
|
||||
<p class="text-center"
|
||||
ng-if="ctrl.loadingDriverProperties">
|
||||
<small><em><i class="fa fa-spin fa-refresh"></i></em></small>
|
||||
</p>
|
||||
<div class="form-group"
|
||||
ng-repeat="(name, property) in ctrl.driverProperties"
|
||||
ng-show="property.isActive()">
|
||||
<label for="{$ name $}"
|
||||
class="col-sm-3 control-label"
|
||||
style="white-space: nowrap"
|
||||
translate>
|
||||
{$ name $}
|
||||
<span class="help-icon"
|
||||
data-container="body"
|
||||
title=""
|
||||
data-toggle="tooltip"
|
||||
data-original-title="{$ property.getDescription() | translate $}">
|
||||
<span class="fa fa-question-circle"></span>
|
||||
</span>
|
||||
</label>
|
||||
<div ng-if="!property.getSelectOptions()" class="col-sm-9">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="{$ name $}"
|
||||
name="{$ name $}"
|
||||
ng-model="property.inputValue"
|
||||
placeholder="{$ property.getDescription() | translate $}"
|
||||
ng-required="property.isRequired()"/>
|
||||
</div>
|
||||
<div ng-if="property.getSelectOptions()" class="col-sm-9">
|
||||
<select id="{$ name $}"
|
||||
class="form-control"
|
||||
ng-options="opt for opt in property.getSelectOptions()"
|
||||
ng-model="property.inputValue"
|
||||
ng-required="property.isRequired()">
|
||||
<option value=""
|
||||
disabled
|
||||
selected
|
||||
translate>{$ property.getDescription() $}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr/>
|
||||
<h4 class="modal-title" translate>Properties</h4>
|
||||
|
||||
<form class="form-horizontal"
|
||||
id="AddPropertyForm"
|
||||
name="AddPropertyForm"
|
||||
style="margin-bottom:10px;">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-addon"
|
||||
style="width:25%;text-align:right">
|
||||
Property</span>
|
||||
<input class="form-control"
|
||||
type="text"
|
||||
ng-model="propertyName"
|
||||
validate-unique="ctrl.checkPropertyUnique"
|
||||
placeholder="{$ 'Property Name' | translate $}"/>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-primary"
|
||||
type="button"
|
||||
ng-disabled="!propertyName || AddPropertyForm.$invalid"
|
||||
ng-click="ctrl.node.properties[propertyName] = null;
|
||||
propertyName = null">
|
||||
<span class="fa fa-plus"> </span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form class="form-horizontal"
|
||||
id="PropertiesForm"
|
||||
name="PropertiesForm">
|
||||
<div class="input-group input-group-sm"
|
||||
ng-repeat="(propertyName, propertyValue) in ctrl.node.properties">
|
||||
<span class="input-group-addon"
|
||||
style="width:25%;text-align:right">
|
||||
{$ propertyName $}
|
||||
</span>
|
||||
<input class="form-control"
|
||||
type="text"
|
||||
name="{$ propertyName $}"
|
||||
ng-model="ctrl.node.properties[propertyName]"
|
||||
ng-required="true"/>
|
||||
<div class="input-group-btn">
|
||||
<a class="btn btn-default"
|
||||
ng-click="ctrl.deleteProperty(propertyName)">
|
||||
<span class="fa fa-minus"> </span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr/>
|
||||
<h4 class="modal-title" translate>Extra</h4>
|
||||
|
||||
<form class="form-horizontal"
|
||||
id="AddExtraForm"
|
||||
name="AddExtraForm"
|
||||
style="margin-bottom:10px;">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-addon"
|
||||
style="width:25%;text-align:right">
|
||||
Extra</span>
|
||||
<input class="form-control"
|
||||
type="text"
|
||||
ng-model="extraName"
|
||||
validate-unique="ctrl.checkExtraUnique"
|
||||
placeholder="{$ 'Property Name' | translate $}"/>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-primary"
|
||||
type="button"
|
||||
ng-disabled="!extraName || AddExtraForm.$invalid"
|
||||
ng-click="ctrl.node.extra[extraName] = null;
|
||||
extraName = null">
|
||||
<span class="fa fa-plus"> </span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form class="form-horizontal"
|
||||
id="ExtraForm"
|
||||
name="ExtraForm">
|
||||
<div class="input-group input-group-sm"
|
||||
ng-repeat="(propertyName, propertyValue) in ctrl.node.extra">
|
||||
<span class="input-group-addon"
|
||||
style="width:25%;text-align:right">
|
||||
{$ propertyName $}
|
||||
</span>
|
||||
<input class="form-control"
|
||||
type="text"
|
||||
name="{$ propertyName $}"
|
||||
ng-model="ctrl.node.extra[propertyName]"
|
||||
ng-required="true"/>
|
||||
<div class="input-group-btn">
|
||||
<a class="btn btn-default"
|
||||
ng-click="ctrl.deleteExtra(propertyName)">
|
||||
<span class="fa fa-minus"> </span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer ng-scope">
|
||||
<button class="btn btn-default"
|
||||
ng-click="ctrl.cancel()">
|
||||
<span class="fa fa-close"></span>
|
||||
<span class="ng-scope" translate>Cancel</span>
|
||||
</button>
|
||||
<button type="submit"
|
||||
ng-disabled="!ctrl.driverProperties ||
|
||||
DriverInfoForm.$invalid ||
|
||||
PropertiesForm.$invalid ||
|
||||
ExtraForm.$invalid"
|
||||
ng-click="ctrl.enroll()"
|
||||
class="btn btn-primary"
|
||||
translate>
|
||||
Enroll Node
|
||||
</button>
|
||||
</div>
|
@ -0,0 +1,332 @@
|
||||
/*
|
||||
* 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 REQUIRED = " " + gettext("Required") + ".";
|
||||
var selectOptionsRegexp =
|
||||
new RegExp(
|
||||
gettext('(?:[Oo]ne of )(?!this)((?:(?:"[^"]+"|[^,\. ]+)(?:, |\.))+)'));
|
||||
var defaultValueRegexp = new RegExp(gettext('default is ([^". ]+|"[^"]+")'));
|
||||
var oneOfRegexp =
|
||||
new RegExp(gettext('One of this, (.*) must be specified\.'));
|
||||
var notInsideMatch = -1;
|
||||
|
||||
angular
|
||||
.module('horizon.dashboard.admin.ironic')
|
||||
.factory('horizon.dashboard.admin.ironic.enroll-node.service',
|
||||
enrollNodeService);
|
||||
|
||||
enrollNodeService.$inject = [
|
||||
'$modal',
|
||||
'horizon.dashboard.admin.basePath',
|
||||
'$log'
|
||||
];
|
||||
|
||||
function enrollNodeService($modal, basePath, $log) {
|
||||
var service = {
|
||||
modal: modal,
|
||||
DriverProperty: DriverProperty
|
||||
};
|
||||
|
||||
function modal() {
|
||||
var options = {
|
||||
controller: 'EnrollNodeController as ctrl',
|
||||
templateUrl: basePath + '/ironic/enroll-node/enroll-node.html'
|
||||
};
|
||||
return $modal.open(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new driver property
|
||||
*
|
||||
* @class DriverProperty
|
||||
* @param {string} name - Name of property
|
||||
* @param {string} desc - Description of property
|
||||
* @param {object} propertySet - Set of properties to which this one belongs
|
||||
*
|
||||
* @property {string} defaultValue - Default value of the property
|
||||
* @property {string[]} selectOptions - If the property is limited to a
|
||||
* set of enumerted values then selectOptions will be an array of those
|
||||
* values, otherwise null
|
||||
* @property {boolean} required - Boolean value indicating whether a value
|
||||
* must be supplied for this property if it is active
|
||||
* @property {PostfixExpr} isActiveExpr - Null if this property is always
|
||||
* active; otherwise, a boolean expression that when evaluated will
|
||||
* return whether this variable is active
|
||||
* @propery {string} inputValue User assigned value for this property
|
||||
*/
|
||||
function DriverProperty(name, desc, propertySet) {
|
||||
this.name = name;
|
||||
this.desc = desc;
|
||||
this.propertySet = propertySet;
|
||||
// Determine whether this property should be presented as a selection
|
||||
this.selectOptions = this._analyzeSelectOptions();
|
||||
this.required = null; // Initialize to unknown
|
||||
// Expression to be evaluated to determine whether property is active.
|
||||
// By default the property is considered active.
|
||||
this.isActiveExpr = null;
|
||||
var result = this._analyzeRequiredOnlyDependencies();
|
||||
if (result) {
|
||||
this.required = result[0];
|
||||
this.isActiveExpr = result[1];
|
||||
}
|
||||
if (!this.isActiveExpr) {
|
||||
result = this._analyzeOneOfDependencies();
|
||||
if (result) {
|
||||
this.required = result[0];
|
||||
this.isActiveExpr = result[1];
|
||||
}
|
||||
}
|
||||
if (this.required === null) {
|
||||
this.required = desc.endsWith(REQUIRED);
|
||||
}
|
||||
this.defaultValue = this._getDefaultValue();
|
||||
this.inputValue = null;
|
||||
}
|
||||
|
||||
DriverProperty.prototype.isActive = function() {
|
||||
if (!this.isActiveExpr) {
|
||||
return true;
|
||||
}
|
||||
var active = this.isActiveExpr.evaluate(this.propertySet);
|
||||
return active === null ? true : active;
|
||||
};
|
||||
|
||||
/*
|
||||
* Must a value be provided for this property
|
||||
*
|
||||
* @return {boolean} True if a value must be provided for this property
|
||||
*/
|
||||
DriverProperty.prototype.isRequired = function() {
|
||||
return this.required && this.isActive();
|
||||
};
|
||||
|
||||
DriverProperty.prototype._analyzeSelectOptions = function() {
|
||||
var match = this.desc.match(selectOptionsRegexp);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var matches = match[1].substring(0, match[1].length - 1).split(", ");
|
||||
var options = [];
|
||||
angular.forEach(matches, function(match) {
|
||||
options.push(trimQuotes(match));
|
||||
});
|
||||
return options;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the list of select options for this property
|
||||
*
|
||||
* @return {string[]} null if this property is not selectable; else,
|
||||
* an array of selectable options
|
||||
*/
|
||||
DriverProperty.prototype.getSelectOptions = function() {
|
||||
return this.selectOptions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove leading/trailing double-quotes from a string
|
||||
*
|
||||
* @param {string} str - String to be trimmed
|
||||
* @return {string} trim'd string
|
||||
*/
|
||||
function trimQuotes(str) {
|
||||
return str.charAt(0) === '"'
|
||||
? str.substring(1, str.length - 1) : str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default value of this property
|
||||
*
|
||||
* @return {string} Default value of this property
|
||||
*/
|
||||
DriverProperty.prototype._getDefaultValue = function() {
|
||||
var match = this.desc.match(defaultValueRegexp);
|
||||
return match ? trimQuotes(match[1]) : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the actual value of this property
|
||||
*
|
||||
* @return {string} Get the actual value of this property. If
|
||||
* an input value has not been specified, but a default value exists
|
||||
* that will be returned.
|
||||
*/
|
||||
DriverProperty.prototype.getActualValue = function() {
|
||||
return this.inputValue ? this.inputValue
|
||||
: this.defaultValue ? this.defaultValue : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the description of this property
|
||||
*
|
||||
* @return {string} Description of this property
|
||||
*/
|
||||
DriverProperty.prototype.getDescription = function() {
|
||||
return this.desc;
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the property description to build an expression that will
|
||||
* evaluate to a boolean result indicating whether the property is
|
||||
* active
|
||||
*
|
||||
* @return {array} null if this property is not dependent on any others;
|
||||
* otherwise,
|
||||
* [0] boolean indicating whether if active a value must be
|
||||
* supplied for this property.
|
||||
* [1] an expression that when evaluated will return a boolean
|
||||
* result indicating whether this property is active
|
||||
*/
|
||||
DriverProperty.prototype._analyzeRequiredOnlyDependencies = function() {
|
||||
var re = /(Required|Used) only if ([^ ]+) is set to /g;
|
||||
var match = re.exec(this.desc);
|
||||
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Build logical expression to describe under what conditions this
|
||||
// property is active
|
||||
var expr = new PostfixExpr();
|
||||
var numAdds = 0;
|
||||
|
||||
var i = notInsideMatch;
|
||||
var j = re.lastIndex;
|
||||
while (j < this.desc.length) {
|
||||
if (i === notInsideMatch && this.desc.charAt(j) === ".") {
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.desc.charAt(j) === '"') {
|
||||
if (i === notInsideMatch) {
|
||||
i = j + 1;
|
||||
} else {
|
||||
expr.addProperty(match[2]);
|
||||
expr.addValue(this.desc.substring(i, j));
|
||||
expr.addOperator("==");
|
||||
numAdds++;
|
||||
if (numAdds > 1) {
|
||||
expr.addOperator("or");
|
||||
}
|
||||
i = notInsideMatch;
|
||||
}
|
||||
}
|
||||
j++;
|
||||
}
|
||||
$log.debug("_analyzeRequiredOnlyDependencies | " +
|
||||
this.desc + " | " +
|
||||
match[2] + ", " +
|
||||
JSON.stringify(expr));
|
||||
return [match[1] === "Required", expr];
|
||||
};
|
||||
|
||||
DriverProperty.prototype._analyzeOneOfDependencies = function() {
|
||||
var match = this.desc.match(oneOfRegexp);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Build logical expression to describe under what conditions this
|
||||
// property is active
|
||||
var expr = new PostfixExpr();
|
||||
|
||||
var parts = match[1].split(", or ");
|
||||
expr.addProperty(parts[1]);
|
||||
expr.addValue(null);
|
||||
expr.addOperator("==");
|
||||
|
||||
parts = parts[0].split(", ");
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
expr.addProperty(parts[i]);
|
||||
expr.addValue(null);
|
||||
expr.addOperator("==");
|
||||
expr.addOperator("and");
|
||||
}
|
||||
$log.debug("_analyzeOneOfDependencies | " +
|
||||
this.desc + " | " +
|
||||
JSON.stringify(match) + ", " +
|
||||
JSON.stringify(expr));
|
||||
return [true, expr];
|
||||
};
|
||||
|
||||
/**
|
||||
* PostFixExpr is a class primarily developed to support the
|
||||
* evaluation of boolean expressions that determine whether a
|
||||
* particular property is active.
|
||||
*
|
||||
* The expression is stored as a postfix sequence of operands and
|
||||
* operators. Operands are currently limited to the literal values
|
||||
* and the values of properties in a specified set. Currently
|
||||
* supported operands are ==, or, and.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
function PostfixExpr() {
|
||||
this.elem = [];
|
||||
}
|
||||
|
||||
PostfixExpr.prototype.addProperty = function(propertyName) {
|
||||
this.elem.push({name: propertyName});
|
||||
};
|
||||
|
||||
PostfixExpr.prototype.addValue = function(value) {
|
||||
this.elem.push({value: value});
|
||||
};
|
||||
|
||||
PostfixExpr.prototype.addOperator = function(opId) {
|
||||
this.elem.push({op: opId});
|
||||
};
|
||||
|
||||
/**
|
||||
* Evaluate the experssion using property values from a specified
|
||||
* set
|
||||
*
|
||||
* @param {object} propertySet - Dictionary of DriverProperty instances
|
||||
*
|
||||
* @return {value} Value of the expression. Null if the expression
|
||||
* could not be successfully evaluated.
|
||||
*/
|
||||
PostfixExpr.prototype.evaluate = function(propertySet) {
|
||||
var resultStack = [];
|
||||
for (var i = 0, len = this.elem.length; i < len; i++) {
|
||||
var elem = this.elem[i];
|
||||
if (angular.isDefined(elem.name)) {
|
||||
resultStack.push(propertySet[elem.name].getActualValue());
|
||||
} else if (angular.isDefined(elem.value)) {
|
||||
resultStack.push(elem.value);
|
||||
} else if (angular.isDefined(elem.op)) {
|
||||
if (elem.op === "==") {
|
||||
resultStack.push(resultStack.pop() === resultStack.pop());
|
||||
} else if (elem.op === "or") {
|
||||
resultStack.push(resultStack.pop() || resultStack.pop());
|
||||
} else if (elem.op === "and") {
|
||||
resultStack.push(resultStack.pop() && resultStack.pop());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return resultStack.length === 1 ? resultStack.pop() : null;
|
||||
};
|
||||
|
||||
return service;
|
||||
}
|
||||
})();
|
@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 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.enroll-node.service',
|
||||
function() {
|
||||
var service;
|
||||
|
||||
beforeEach(module('horizon.dashboard.admin'));
|
||||
|
||||
beforeEach(module('horizon.dashboard.admin.ironic'));
|
||||
|
||||
beforeEach(module(function($provide) {
|
||||
$provide.value('$modal', jasmine.createSpy());
|
||||
}));
|
||||
|
||||
beforeEach(inject(function($injector) {
|
||||
service =
|
||||
$injector.get('horizon.dashboard.admin.ironic.enroll-node.service');
|
||||
}));
|
||||
|
||||
it('defines the service', function() {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('DriverProperty', function() {
|
||||
it('Base construction', function() {
|
||||
var propertyName = 'propertyName';
|
||||
var description = '';
|
||||
var propertySet = [];
|
||||
var property = new service.DriverProperty(propertyName,
|
||||
description,
|
||||
propertySet);
|
||||
expect(property.name).toBe(propertyName);
|
||||
expect(property.desc).toBe(description);
|
||||
expect(property.propertySet).toBe(propertySet);
|
||||
expect(property.getSelectOptions()).toBe(null);
|
||||
expect(property.required).toBe(false);
|
||||
expect(property.defaultValue).toBe(null);
|
||||
expect(property.inputValue).toBe(null);
|
||||
expect(property.getActualValue()).toBe(null);
|
||||
expect(property.isActive()).toBe(true);
|
||||
});
|
||||
|
||||
it('Required - ends with', function() {
|
||||
var property = new service.DriverProperty('propertyName',
|
||||
' Required.',
|
||||
[]);
|
||||
expect(property.required).toBe(true);
|
||||
});
|
||||
|
||||
it('Not required - missing space', function() {
|
||||
var property = new service.DriverProperty('propertyName',
|
||||
'Required.',
|
||||
[]);
|
||||
expect(property.required).toBe(false);
|
||||
});
|
||||
|
||||
it('Not required - missing period', function() {
|
||||
var property = new service.DriverProperty('propertyName',
|
||||
' Required',
|
||||
[]);
|
||||
expect(property.required).toBe(false);
|
||||
});
|
||||
|
||||
it('Select options', function() {
|
||||
var property = new service.DriverProperty(
|
||||
'propertyName',
|
||||
'One of "foo", bar.',
|
||||
[]);
|
||||
expect(property.getSelectOptions()).toEqual(['foo', 'bar']);
|
||||
});
|
||||
|
||||
it('Select options - No single quotes', function() {
|
||||
var property = new service.DriverProperty(
|
||||
'propertyName',
|
||||
"One of 'foo', bar.",
|
||||
[]);
|
||||
expect(property.getSelectOptions()).toEqual(["'foo'", 'bar']);
|
||||
});
|
||||
|
||||
it('default - is string', function() {
|
||||
var property = new service.DriverProperty(
|
||||
'propertyName',
|
||||
'default is "5.1".',
|
||||
[]);
|
||||
expect(property._getDefaultValue()).toEqual('5.1');
|
||||
});
|
||||
|
||||
it('default - period processing', function() {
|
||||
var property = new service.DriverProperty(
|
||||
'propertyName',
|
||||
'default is 5.1.',
|
||||
[]);
|
||||
expect(property._getDefaultValue()).toEqual('5');
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
@ -28,159 +28,254 @@
|
||||
];
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name horizon.app.core.openstack-service-api.ironic
|
||||
* @description Provides access to Ironic API
|
||||
* Service that provides access to the Ironic client API
|
||||
*
|
||||
* @param {object} apiService - HTTP service
|
||||
* @param {object} toastService - User message service
|
||||
* @return {object} Ironic API service
|
||||
*/
|
||||
|
||||
function ironicAPI(apiService, toastService) {
|
||||
var service = {
|
||||
getNodes: getNodes,
|
||||
createNode: createNode,
|
||||
deleteNode: deleteNode,
|
||||
getDrivers: getDrivers,
|
||||
getDriverProperties: getDriverProperties,
|
||||
getNode: getNode,
|
||||
getNodes: getNodes,
|
||||
getPortsWithNode: getPortsWithNode,
|
||||
putNodeInMaintenanceMode: putNodeInMaintenanceMode,
|
||||
removeNodeFromMaintenanceMode: removeNodeFromMaintenanceMode,
|
||||
powerOffNode: powerOffNode,
|
||||
powerOnNode: powerOnNode,
|
||||
powerOffNode: powerOffNode
|
||||
putNodeInMaintenanceMode: putNodeInMaintenanceMode,
|
||||
removeNodeFromMaintenanceMode: removeNodeFromMaintenanceMode
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
///////////
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.ironic.getNodes
|
||||
* @description Retrieve a list of nodes
|
||||
* Retrieve a list of nodes
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#get--v1-nodes
|
||||
*
|
||||
* @return Node collection in JSON
|
||||
* @return {promise} Node collection in JSON
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#NodeCollection
|
||||
*/
|
||||
|
||||
function getNodes() {
|
||||
return apiService.get('/api/ironic/nodes/')
|
||||
.error(function() {
|
||||
toastService.add('error', gettext('Unable to retrieve Ironic nodes.'));
|
||||
toastService.add('error',
|
||||
gettext('Unable to retrieve Ironic nodes.'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.ironic.getNode
|
||||
* @description Retrieve information about the given node.
|
||||
* Retrieve information about the given node.
|
||||
*
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#get--v1-nodes-(node_ident)
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#get--v1-
|
||||
* nodes-(node_ident)
|
||||
*
|
||||
* @param {string} uuid – UUID or logical name of a node.
|
||||
* @return {promise} Node
|
||||
*/
|
||||
|
||||
function getNode(uuid) {
|
||||
return apiService.get('/api/ironic/nodes/' + uuid).error(function() {
|
||||
toastService.add('error', gettext('Unable to retrieve the Ironic node.'));
|
||||
});
|
||||
return apiService.get('/api/ironic/nodes/' + uuid)
|
||||
.error(function(reason) {
|
||||
var msg = gettext('Unable to retrieve the Ironic node: %s');
|
||||
toastService.add('error', interpolate(msg, [reason], false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.ironic.getPortsWithNode
|
||||
* @description Retrieve a list of ports associated with a node.
|
||||
* Retrieve a list of ports associated with a node.
|
||||
*
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#get--v1-ports
|
||||
*
|
||||
* @param {string} uuid – UUID or logical name of a node.
|
||||
* @return {promise} List of ports
|
||||
*/
|
||||
|
||||
function getPortsWithNode(uuid) {
|
||||
var config = {
|
||||
params : {
|
||||
node_id: uuid
|
||||
}
|
||||
};
|
||||
return apiService.get('/api/ironic/ports/', config).error(function() {
|
||||
toastService.add('error', gettext('Unable to retrieve the Ironic node ports.'));
|
||||
});
|
||||
return apiService.get('/api/ironic/ports/', config)
|
||||
.error(function(reason) {
|
||||
var msg = gettext(
|
||||
'Unable to retrieve the Ironic node ports: %s');
|
||||
toastService.add('error', interpolate(msg, [reason], false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.ironic.putNodeInMaintenanceMode
|
||||
* @description Put the node in maintenance mode.
|
||||
* Put the node in maintenance mode.
|
||||
*
|
||||
* \href{http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||
* put--v1-nodes-(node_ident)-maintenance}
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||
* put--v1-nodes-(node_ident)-maintenance
|
||||
*
|
||||
* @param {string} uuid – UUID or logical name of a node.
|
||||
* @param {string} reason – Reason for why node is being put into
|
||||
* maintenance mode
|
||||
* @return {promise} Promise
|
||||
*/
|
||||
|
||||
function putNodeInMaintenanceMode(uuid, reason) {
|
||||
var data = {
|
||||
maint_reason: reason ? reason : gettext("No maintenance reason given.")
|
||||
};
|
||||
return apiService.patch('/api/ironic/nodes/' + uuid + '/maintenance', data).error(function() {
|
||||
toastService.add('error',
|
||||
gettext('Unable to put the Ironic node in maintenance mode.'));
|
||||
});
|
||||
return apiService.patch('/api/ironic/nodes/' + uuid + '/maintenance',
|
||||
data)
|
||||
.error(function(reason) {
|
||||
var msg = gettext(
|
||||
'Unable to put the Ironic node in maintenance mode: %s');
|
||||
toastService.add('error', interpolate(msg, [reason], false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.ironic.removeNodeFromMaintenanceMode
|
||||
* @description Remove the node from maintenance mode.
|
||||
* Remove the node from maintenance mode.
|
||||
*
|
||||
* \href{http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||
* delete--v1-nodes-(node_ident)-maintenance}
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||
* delete--v1-nodes-(node_ident)-maintenance
|
||||
*
|
||||
* @param {string} uuid – UUID or logical name of a node.
|
||||
* @return {promise} Promise
|
||||
*/
|
||||
|
||||
function removeNodeFromMaintenanceMode(uuid) {
|
||||
return apiService.delete('/api/ironic/nodes/' + uuid + '/maintenance').error(function() {
|
||||
toastService.add('error',
|
||||
gettext('Unable to remove the Ironic node from maintenance mode.'));
|
||||
});
|
||||
return apiService.delete('/api/ironic/nodes/' + uuid + '/maintenance')
|
||||
.error(function(reason) {
|
||||
var msg = gettext('Unable to remove the Ironic node ' +
|
||||
'from maintenance mode: %s');
|
||||
toastService.add('error', interpolate(msg, [reason], false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.ironic.powerOnNode
|
||||
* @description Set the power state of the node.
|
||||
* Set the power state of the node.
|
||||
*
|
||||
* \href{http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||
* put--v1-nodes-(node_ident)-states-power}
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||
* put--v1-nodes-(node_ident)-states-power
|
||||
*
|
||||
* @param {string} uuid – UUID or logical name of a node.
|
||||
* @return {promise} Promise
|
||||
*/
|
||||
|
||||
function powerOnNode(uuid) {
|
||||
var data = {
|
||||
state: 'on'
|
||||
};
|
||||
return apiService.patch('/api/ironic/nodes/' + uuid + '/states/power', data)
|
||||
.success(function () {
|
||||
toastService.add('success', gettext('Refresh page to see updated power status'));
|
||||
return apiService.patch('/api/ironic/nodes/' + uuid + '/states/power',
|
||||
data)
|
||||
.success(function() {
|
||||
toastService.add('success',
|
||||
gettext('Refresh page to see updated power status'));
|
||||
})
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to power on the node'));
|
||||
.error(function(reason) {
|
||||
var msg = gettext('Unable to power on the node: %s');
|
||||
toastService.add('error', interpolate(msg, [reason], false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.ironic.powerOffNode
|
||||
* @description Set the power state of the node.
|
||||
* Set the power state of the node.
|
||||
*
|
||||
* \href{http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||
* put--v1-nodes-(node_ident)-states-power}
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||
* put--v1-nodes-(node_ident)-states-power
|
||||
*
|
||||
* @param {string} uuid – UUID or logical name of a node.
|
||||
* @return {promise} Promise
|
||||
*/
|
||||
|
||||
function powerOffNode(uuid) {
|
||||
var data = {
|
||||
state: 'off'
|
||||
};
|
||||
return apiService.patch('/api/ironic/nodes/' + uuid + '/states/power', data)
|
||||
.success(function () {
|
||||
toastService.add('success', gettext('Refresh page to see updated power status'));
|
||||
return apiService.patch('/api/ironic/nodes/' + uuid + '/states/power',
|
||||
data)
|
||||
.success(function() {
|
||||
toastService.add('success',
|
||||
gettext('Refresh page to see updated power status'));
|
||||
})
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to power off the node'));
|
||||
.error(function(reason) {
|
||||
var msg = gettext('Unable to power off the node: %s');
|
||||
toastService.add('error', interpolate(msg, [reason], false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Ironic node
|
||||
*
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#post--v1-nodes
|
||||
*
|
||||
* @param {object} params – Object containing parameters that define
|
||||
* the node to be created
|
||||
* @return {promise} Promise
|
||||
*/
|
||||
function createNode(params) {
|
||||
var data = {
|
||||
node: params
|
||||
};
|
||||
return apiService.post('/api/ironic/nodes/', data)
|
||||
.success(function() {
|
||||
})
|
||||
.error(function(reason) {
|
||||
var msg = gettext('Unable to create node: %s');
|
||||
toastService.add('error', interpolate(msg, [reason], false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the specified node from inventory
|
||||
*
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||
* delete--v1-nodes
|
||||
*
|
||||
* @param {string} nodeIdent – UUID or logical name of a node.
|
||||
* @return {promise} Promise
|
||||
*/
|
||||
function deleteNode(nodeIdent) {
|
||||
var data = {
|
||||
node: nodeIdent
|
||||
};
|
||||
return apiService.delete('/api/ironic/nodes/', data)
|
||||
.success(function() {
|
||||
})
|
||||
.error(function(reason) {
|
||||
var msg = gettext('Unable to delete node %s: %s');
|
||||
toastService.add(
|
||||
'error',
|
||||
interpolate(msg, [nodeIdent, reason], false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the list of Ironic drivers
|
||||
*
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#get--v1-drivers
|
||||
*
|
||||
* @return {promise} Driver collection in JSON
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#DriverList
|
||||
*/
|
||||
function getDrivers() {
|
||||
return apiService.get('/api/ironic/drivers/').error(function(reason) {
|
||||
var msg = gettext('Unable to retrieve Ironic drivers: %s');
|
||||
toastService.add('error', interpolate(msg, [reason], false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve properities of a specified driver
|
||||
*
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||
* get--v1-drivers-properties
|
||||
*
|
||||
* @param {string} driverName - Driver name
|
||||
* @returns {promise} Property list
|
||||
*/
|
||||
function getDriverProperties(driverName) {
|
||||
return apiService.get(
|
||||
'/api/ironic/drivers/' + driverName + '/properties').error(
|
||||
function(reason) {
|
||||
var msg = gettext(
|
||||
'Unable to retrieve driver properties: %s');
|
||||
toastService.add('error', interpolate(msg, [reason], false));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}());
|
||||
|
@ -15,7 +15,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default secondary"
|
||||
<button class="btn btn-default secondary"
|
||||
type="button"
|
||||
ng-click="ctrl.cancel()"
|
||||
translate>
|
||||
@ -28,3 +28,4 @@
|
||||
Put Node(s) Into Maintenance Mode
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
@ -20,6 +20,20 @@
|
||||
var POWER_STATE_ON = 'power on';
|
||||
var POWER_STATE_OFF = 'power off';
|
||||
|
||||
var DELETE_NODE_TITLE = gettext("Delete Node");
|
||||
var DELETE_NODE_MSG =
|
||||
gettext('Are you sure you want to delete node "%s"? ' +
|
||||
'This action cannot be undone.');
|
||||
var DELETE_NODE_SUCCESS = gettext('Successfully deleted node "%s"');
|
||||
var DELETE_NODE_ERROR = gettext('Unable to delete node "%s"');
|
||||
|
||||
var DELETE_NODES_TITLE = gettext("Delete Nodes");
|
||||
var DELETE_NODES_MSG =
|
||||
gettext('Are you sure you want to delete nodes "%s"? ' +
|
||||
'This action cannot be undone.');
|
||||
var DELETE_NODES_SUCCESS = gettext('Successfully deleted nodes "%s"');
|
||||
var DELETE_NODES_ERROR = gettext('Error deleting nodes "%s"');
|
||||
|
||||
angular
|
||||
.module('horizon.dashboard.admin.ironic')
|
||||
.factory('horizon.dashboard.admin.ironic.actions', actions);
|
||||
@ -27,11 +41,15 @@
|
||||
actions.$inject = [
|
||||
'horizon.app.core.openstack-service-api.ironic',
|
||||
'horizon.framework.widgets.toast.service',
|
||||
'$q'
|
||||
'horizon.framework.widgets.modal.deleteModalService',
|
||||
'$q',
|
||||
'$rootScope'
|
||||
];
|
||||
|
||||
function actions(ironic, toastService, $q) {
|
||||
function actions(ironic, toastService, deleteModalService, $q, $rootScope) {
|
||||
var service = {
|
||||
deleteNode: deleteNode,
|
||||
deleteNodes: deleteNodes,
|
||||
powerOn: powerOn,
|
||||
powerOff: powerOff,
|
||||
powerOnAll: powerOnNodes,
|
||||
@ -44,33 +62,62 @@
|
||||
|
||||
return service;
|
||||
|
||||
function deleteNode(node) {
|
||||
var labels = {
|
||||
title: DELETE_NODE_TITLE,
|
||||
message: DELETE_NODE_MSG,
|
||||
submit: DELETE_NODE_TITLE,
|
||||
success: DELETE_NODE_SUCCESS,
|
||||
error: DELETE_NODE_ERROR
|
||||
};
|
||||
var context = {
|
||||
labels: labels,
|
||||
deleteEntity: ironic.deleteNode,
|
||||
successEvent: "ironic-ui:delete-node-success"
|
||||
};
|
||||
return deleteModalService.open($rootScope, [node], context);
|
||||
}
|
||||
|
||||
function deleteNodes(nodes) {
|
||||
var labels = {
|
||||
title: DELETE_NODES_TITLE,
|
||||
message: DELETE_NODES_MSG,
|
||||
submit: DELETE_NODES_TITLE,
|
||||
success: DELETE_NODES_SUCCESS,
|
||||
error: DELETE_NODES_ERROR
|
||||
};
|
||||
var context = {
|
||||
labels: labels,
|
||||
deleteEntity: ironic.deleteNode,
|
||||
successEvent: "ironic-ui:delete-node-success"
|
||||
};
|
||||
return deleteModalService.open($rootScope, nodes, context);
|
||||
}
|
||||
|
||||
// power state
|
||||
|
||||
function powerOn(node) {
|
||||
if (node.power_state !== POWER_STATE_OFF) {
|
||||
return $q.reject(gettext("Node is not powered off."));
|
||||
var msg = gettext("Node %s is not powered off.");
|
||||
return $q.reject(interpolate(msg, [node], false));
|
||||
}
|
||||
return ironic.powerOnNode(node.uuid).then(
|
||||
function() {
|
||||
// Set power state to be indeterminate
|
||||
node.power_state = null;
|
||||
},
|
||||
function(reason) {
|
||||
toastService.add('error', reason);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function powerOff(node) {
|
||||
if (node.power_state !== POWER_STATE_ON) {
|
||||
return $q.reject(gettext("Node is not powered on."));
|
||||
var msg = gettext("Node %s is not powered on.");
|
||||
return $q.reject(interpolate(msg, [node], false));
|
||||
}
|
||||
return ironic.powerOffNode(node.uuid).then(
|
||||
function() {
|
||||
// Set power state to be indeterminate
|
||||
node.power_state = null;
|
||||
},
|
||||
function(reason) {
|
||||
toastService.add('error', reason);
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -87,30 +134,26 @@
|
||||
|
||||
function putInMaintenanceMode(node, maintReason) {
|
||||
if (node.maintenance !== false) {
|
||||
return $q.reject(gettext("Node is already in maintenance mode."));
|
||||
var msg = gettext("Node %s is already in maintenance mode.");
|
||||
return $q.reject(interpolate(msg, [node], false));
|
||||
}
|
||||
return ironic.putNodeInMaintenanceMode(node.uuid, maintReason).then(
|
||||
function () {
|
||||
node.maintenance = true;
|
||||
node.maintenance_reason = maintReason;
|
||||
},
|
||||
function(reason) {
|
||||
toastService.add('error', reason);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function removeFromMaintenanceMode(node) {
|
||||
if (node.maintenance !== true) {
|
||||
return $q.reject(gettext("Node is not in maintenance mode."));
|
||||
var msg = gettext("Node %s is not in maintenance mode.");
|
||||
return $q.reject(interpolate(msg, [node], false));
|
||||
}
|
||||
return ironic.removeNodeFromMaintenanceMode(node.uuid).then(
|
||||
function () {
|
||||
node.maintenance = false;
|
||||
node.maintenance_reason = "";
|
||||
},
|
||||
function (reason) {
|
||||
toastService.add('error', reason);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -24,7 +24,7 @@
|
||||
<dt ng-repeat-start="port in ctrl.ports">
|
||||
{$ 'MAC ' + (1 + $index) $}</dt>
|
||||
<dd ng-if="vif_port_id = ctrl.getVifPortId(port)">
|
||||
<a href="/admin/networks/ports/{$ vif_port_id $}/detail">
|
||||
<a href="/dashboard/admin/networks/ports/{$ vif_port_id $}/detail">
|
||||
{$ port.address $}
|
||||
</a>
|
||||
</dd>
|
||||
@ -109,13 +109,13 @@
|
||||
<dd>{$ ctrl.node.instance_info.display_name $}</dd>
|
||||
<dt translate>Ramdisk</dt>
|
||||
<dd>
|
||||
<a href="/admin/images/{$ ctrl.node.instance_info.ramdisk $}/detail">
|
||||
<a href="/dashboard/admin/images/{$ ctrl.node.instance_info.ramdisk $}/detail">
|
||||
{$ ctrl.node.instance_info.ramdisk $}
|
||||
</a>
|
||||
</dd>
|
||||
<dt translate>Kernel</dt>
|
||||
<dd>
|
||||
<a href="/admin/images/{$ ctrl.node.instance_info.kernel $}/detail">
|
||||
<a href="/dashboard/admin/images/{$ ctrl.node.instance_info.kernel $}/detail">
|
||||
{$ ctrl.node.instance_info.kernel $}
|
||||
</a>
|
||||
</dd>
|
||||
|
@ -22,16 +22,20 @@
|
||||
.controller('IronicNodeListController', IronicNodeListController);
|
||||
|
||||
IronicNodeListController.$inject = [
|
||||
'$rootScope',
|
||||
'horizon.app.core.openstack-service-api.ironic',
|
||||
'horizon.dashboard.admin.ironic.actions',
|
||||
'horizon.dashboard.admin.basePath',
|
||||
'horizon.dashboard.admin.ironic.maintenance.service'
|
||||
'horizon.dashboard.admin.ironic.maintenance.service',
|
||||
'horizon.dashboard.admin.ironic.enroll-node.service'
|
||||
];
|
||||
|
||||
function IronicNodeListController(ironic,
|
||||
function IronicNodeListController($rootScope,
|
||||
ironic,
|
||||
actions,
|
||||
basePath,
|
||||
maintenanceService) {
|
||||
maintenanceService,
|
||||
enrollNodeService) {
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.nodes = [];
|
||||
@ -43,6 +47,7 @@
|
||||
ctrl.putNodesInMaintenanceMode = putNodesInMaintenanceMode;
|
||||
ctrl.removeNodeFromMaintenanceMode = removeNodeFromMaintenanceMode;
|
||||
ctrl.removeNodesFromMaintenanceMode = removeNodesFromMaintenanceMode;
|
||||
ctrl.enrollNode = enrollNode;
|
||||
|
||||
/**
|
||||
* Filtering - client-side MagicSearch
|
||||
@ -81,6 +86,15 @@
|
||||
}
|
||||
];
|
||||
|
||||
// Listen for the creation of new nodes, and update the node list
|
||||
$rootScope.$on('ironic-ui:new-node', function() {
|
||||
init();
|
||||
});
|
||||
|
||||
$rootScope.$on('ironic-ui:delete-node-success', function() {
|
||||
init();
|
||||
});
|
||||
|
||||
init();
|
||||
|
||||
// RETRIVE NODES AND PORTS
|
||||
@ -124,6 +138,10 @@
|
||||
function removeNodesFromMaintenanceMode(nodes) {
|
||||
maintenanceService.removeNodesFromMaintenanceMode(nodes);
|
||||
}
|
||||
|
||||
function enrollNode() {
|
||||
enrollNodeService.modal();
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
|
@ -15,7 +15,14 @@
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="100" class="action-col">
|
||||
<th colspan="8">
|
||||
<button class="btn btn-default btn-sm pull-right"
|
||||
ng-click="table.enrollNode()">
|
||||
<span class="fa fa-plus"></span>
|
||||
<span translate>Enroll Node</span>
|
||||
</button>
|
||||
</th>
|
||||
<th class="action-col">
|
||||
<action-list dropdown class="pull-right">
|
||||
<action button-type="split-button"
|
||||
action-classes="'btn btn-default btn-sm'"
|
||||
@ -43,6 +50,13 @@
|
||||
disabled="tCtrl.selected.length === 0">
|
||||
{$ 'Maintenance off' | translate $}
|
||||
</action>
|
||||
<action button-type="menu-item"
|
||||
callback="table.actions.deleteNodes"
|
||||
item="tCtrl.selected"
|
||||
disabled="tCtrl.selected.length === 0">
|
||||
<span class="fa fa-trash"></span>
|
||||
{$ 'Delete nodes' | translate $}
|
||||
</action>
|
||||
</menu>
|
||||
</action-list>
|
||||
</th>
|
||||
@ -140,9 +154,15 @@
|
||||
item="node">
|
||||
{$ 'Maintenance off' | translate $}
|
||||
</action>
|
||||
<action button-type="menu-item"
|
||||
callback="table.actions.deleteNode"
|
||||
disabled="!(node['provision_state']==='available' || node['provision_state']==='nostate' || node['provision_state']==='manageable' || node['provision_state']==='enroll')"
|
||||
item="node">
|
||||
<span class="fa fa-trash"></span>
|
||||
{$ 'Delete node' | translate $}
|
||||
</action>
|
||||
</menu>
|
||||
</action-list>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
Loading…
Reference in New Issue
Block a user