diff --git a/ironic_ui/api/ironic.py b/ironic_ui/api/ironic.py index e34a1a51..f99c0cc3 100755 --- a/ironic_ui/api/ironic.py +++ b/ironic_ui/api/ironic.py @@ -27,7 +27,6 @@ from openstack_dashboard.api import base DEFAULT_IRONIC_API_VERSION = '1.20' DEFAULT_INSECURE = False DEFAULT_CACERT = None - IRONIC_CLIENT_CLASS_NAME = 'baremetal' @@ -117,6 +116,31 @@ def node_set_provision_state(request, node_id, state, cleansteps=None): cleansteps=cleansteps) +def node_set_console_mode(request, node_id, enabled): + """Start or stop the serial console for a given node. + + :param request: HTTP request. + :param node_id: The UUID or name of the node. + :param enabled: True to start the console, False to stop it + :return: node. + + http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.set_console_mode + """ + return ironicclient(request).node.set_console_mode(node_id, enabled) + + +def node_get_console(request, node_id): + """Get connection information for a node's console. + + :param request: HTTP request. + :param node_id: The UUID or name of the node. + :return: Console connection information + + http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.get_console + """ + return ironicclient(request).node.get_console(node_id) + + def node_set_maintenance(request, node_id, state, maint_reason=None): """Set the maintenance mode on a given node. @@ -194,7 +218,7 @@ def node_get_boot_device(request, node_id): """Get the boot device for a specified node. :param request: HTTP request. - :param node_id: The id of the node. + :param node_id: The UUID or name of the node. :return: Dictionary with keys "boot_device" and "persistent" http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.get_boot_device @@ -250,16 +274,16 @@ def port_delete(request, port_uuid): return ironicclient(request).port.delete(port_uuid) -def port_update(request, port_id, patch): +def port_update(request, port_uuid, patch): """Update a specified port. :param request: HTTP request. - :param node_id: The uuid of the port. + :param port_id: The UUID of the port. :param patch: Sequence of update operations - :return: port. + :return: Port. http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.port.html#ironicclient.v1.port.PortManager.update """ - port = ironicclient(request).port.update(port_id, patch) + port = ironicclient(request).port.update(port_uuid, patch) return dict([(f, getattr(port, f, '')) for f in res_fields.PORT_DETAILED_RESOURCE.fields]) diff --git a/ironic_ui/api/ironic_rest_api.py b/ironic_ui/api/ironic_rest_api.py index 5fac1f7f..36bde840 100755 --- a/ironic_ui/api/ironic_rest_api.py +++ b/ironic_ui/api/ironic_rest_api.py @@ -177,6 +177,34 @@ class StatesProvision(generic.View): clean_steps) +@urls.register +class StatesConsole(generic.View): + + url_regex = r'ironic/nodes/(?P[0-9a-f-]+)/states/console$' + + @rest_utils.ajax() + def get(self, request, node_uuid): + """Get connection information for the node's console + + :param request: HTTP request. + :param node_id: Node uuid + :return: Connection information + """ + return ironic.node_get_console(request, node_uuid) + + @rest_utils.ajax(data_required=True) + def put(self, request, node_uuid): + """Start or stop the serial console. + + :param request: HTTP request. + :param node_id: Node uuid + :return: Return code + """ + return ironic.node_set_console_mode(request, + node_uuid, + request.DATA.get('enabled')) + + @urls.register class Maintenance(generic.View): diff --git a/ironic_ui/static/dashboard/admin/ironic/ironic.service.js b/ironic_ui/static/dashboard/admin/ironic/ironic.service.js index 454cc37a..80b3ae7d 100755 --- a/ironic_ui/static/dashboard/admin/ironic/ironic.service.js +++ b/ironic_ui/static/dashboard/admin/ironic/ironic.service.js @@ -50,6 +50,8 @@ getNodes: getNodes, getPortsWithNode: getPortsWithNode, getBootDevice: getBootDevice, + nodeGetConsole: nodeGetConsole, + nodeSetConsoleMode: nodeSetConsoleMode, powerOffNode: powerOffNode, powerOnNode: powerOnNode, putNodeInMaintenanceMode: putNodeInMaintenanceMode, @@ -514,6 +516,44 @@ return $q.reject(msg); }); } + + /** + * @description Set the console mode of the node. + * + * http://developer.openstack.org/api-ref/baremetal/? + * expanded=start-stop-console-detail#start-stop-console + * + * @param {string} uuid – UUID of a node. + * @param {boolean} enabled – true to start the console, false to stop it + * @return {promise} Promise + */ + function nodeSetConsoleMode(uuid, enabled) { + return apiService.put('/api/ironic/nodes/' + uuid + '/states/console', + {enabled: enabled}) + .then(function(response) { + var msg = gettext('Refresh page to see updated console details'); + toastService.add('success', interpolate(msg, [uuid], false)); + return response.data; + }) + .catch(function(response) { + var msg = gettext('Unable to set console mode: %s'); + toastService.add('error', interpolate(msg, [response.data], false)); + return $q.reject(msg); + }); + } + + function nodeGetConsole(uuid) { + return apiService.get('/api/ironic/nodes/' + uuid + '/states/console') + .then(function(response) { + return response.data; // Object containing console information + }) + .catch(function(response) { + var msg = gettext('Unable to get console for node %s: %s'); + toastService.add('error', + interpolate(msg, [uuid, response.data], false)); + return $q.reject(msg); + }); + } } }()); diff --git a/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.controller.js b/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.controller.js index b9f018ec..6950e690 100755 --- a/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.controller.js +++ b/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.controller.js @@ -72,6 +72,7 @@ ctrl.node = null; ctrl.nodeValidation = []; + ctrl.nodeValidationMap = {}; // Indexed by interface ctrl.nodeStateTransitions = []; ctrl.ports = []; ctrl.portsSrc = []; @@ -84,11 +85,14 @@ ctrl.deletePort = deletePort; ctrl.editPort = editPort; ctrl.refresh = refresh; + ctrl.toggleConsoleMode = toggleConsoleMode; $scope.emptyObject = function(obj) { return angular.isUndefined(obj) || Object.keys(obj).length === 0; }; + $scope.isDefined = angular.isDefined; + var editNodeHandler = $rootScope.$on(ironicEvents.EDIT_NODE_SUCCESS, function() { @@ -149,6 +153,10 @@ return ironic.getNode(uuid).then(function (node) { ctrl.node = node; ctrl.node.id = ctrl.node.uuid; + ironic.nodeGetConsole(uuid).then(function(consoleData) { + ctrl.node.console_enabled = consoleData.console_enabled; + ctrl.node.console_info = consoleData.console_info; + }); }); } @@ -188,9 +196,11 @@ function validateNode() { ironic.validateNode(ctrl.node.uuid).then(function(response) { var nodeValidation = []; + ctrl.nodeValidationMap = {}; angular.forEach(response.data, function(status) { status.id = status.interface; nodeValidation.push(status); + ctrl.nodeValidationMap[status.interface] = status; }); ctrl.nodeValidation = nodeValidation; }); @@ -274,5 +284,15 @@ function refresh() { init(); } + + /** + * @name horizon.dashboard.admin.ironic.NodeDetailsController.toggleConsoleMode + * @description Toggle the state of the console for the current node + * + * @return {void} + */ + function toggleConsoleMode() { + ironic.nodeSetConsoleMode(ctrl.node.uuid, !ctrl.node.console_enabled); + } } })(); diff --git a/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.html b/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.html index 1f808fc8..0d9cfdc9 100644 --- a/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.html +++ b/ironic_ui/static/dashboard/admin/ironic/node-details/node-details.html @@ -49,6 +49,12 @@ callback="ctrl.editNode"> {$ ::'Edit' | translate $} + + {$ ctrl.node.console_enabled ? 'Disable console' : 'Enable console' | translate $} + diff --git a/ironic_ui/static/dashboard/admin/ironic/node-details/sections/overview.html b/ironic_ui/static/dashboard/admin/ironic/node-details/sections/overview.html index 0e8cd23a..47476997 100644 --- a/ironic_ui/static/dashboard/admin/ironic/node-details/sections/overview.html +++ b/ironic_ui/static/dashboard/admin/ironic/node-details/sections/overview.html @@ -21,6 +21,16 @@
{$ ctrl.node.reservation | noValue $}
Console Enabled
{$ ctrl.node.console_enabled | yesno $}
+
Console Info
+
+ + shellinabox + +
+
+ {$ ctrl.node.console_info | noValue $} +
diff --git a/releasenotes/notes/add-console-support-ccffcedc845ca214.yaml b/releasenotes/notes/add-console-support-ccffcedc845ca214.yaml new file mode 100644 index 00000000..8786e4d2 --- /dev/null +++ b/releasenotes/notes/add-console-support-ccffcedc845ca214.yaml @@ -0,0 +1,15 @@ +--- +features: + - | + Support has been added for starting, stopping, and accessing the + console associated with a node. + - | + The action dropdown menu in the node-details panel has a new item + ``Enable|Disable console``. + - | + The node-details/overview panel has a new ``Console info`` item in the + ``General`` section. The value of this field is dependent on the console + type. For ``shellinabox`` the value is an anchor with the URL required + to access the web console and title ``shellinabox``. For others, the + value is a string representation of the console_info object returned + by the get_console api call.