def post(self, node_ident, callback_url): """Process a heartbeat from the deploy ramdisk. :param node_ident: the UUID or logical name of a node. :param callback_url: the URL to reach back to the ramdisk. :raises: NodeNotFound if node with provided UUID or name was not found. :raises: InvalidUuidOrName if node_ident is not valid name or UUID. :raises: NoValidHost if RPC topic for node could not be retrieved. :raises: NotFound if requested API version does not allow this endpoint. """ if not api_utils.allow_ramdisk_endpoints(): raise exception.NotFound() cdict = pecan.request.context.to_policy_values() policy.authorize('baremetal:node:ipa_heartbeat', cdict, cdict) rpc_node = api_utils.get_rpc_node(node_ident) try: topic = pecan.request.rpcapi.get_topic_for(rpc_node) except exception.NoValidHost as e: e.code = http_client.BAD_REQUEST raise pecan.request.rpcapi.heartbeat(pecan.request.context, rpc_node.uuid, callback_url, topic=topic)
def test_get_rpc_node_expect_uuid(self, mock_gbn, mock_gbu, mock_anln, mock_pr): mock_anln.return_value = True self.node["uuid"] = self.valid_uuid mock_gbu.return_value = self.node self.assertEqual(self.node, utils.get_rpc_node(self.valid_uuid)) self.assertEqual(1, mock_gbu.call_count) self.assertEqual(0, mock_gbn.call_count)
def power(self, node_ident, target): """Set the power state of the node. :param node_ident: the UUID or logical name of a node. :param target: The desired power state of the node. :raises: ClientSideError (HTTP 409) if a power operation is already in progress. :raises: InvalidStateRequested (HTTP 400) if the requested target state is not valid or if the node is in CLEANING state. """ # TODO(lucasagomes): Test if it's able to transition to the # target state from the current one rpc_node = api_utils.get_rpc_node(node_ident) topic = pecan.request.rpcapi.get_topic_for(rpc_node) if target not in [ir_states.POWER_ON, ir_states.POWER_OFF, ir_states.REBOOT]: raise exception.InvalidStateRequested( action=target, node=node_ident, state=rpc_node.power_state) # Don't change power state for nodes in cleaning elif rpc_node.provision_state == ir_states.CLEANING: raise exception.InvalidStateRequested( action=target, node=node_ident, state=rpc_node.provision_state) pecan.request.rpcapi.change_node_power_state(pecan.request.context, rpc_node.uuid, target, topic) # Set the HTTP Location Header url_args = '/'.join([node_ident, 'states']) pecan.response.location = link.build_url('nodes', url_args)
def _get_ports_collection( self, node_ident, address, marker, limit, sort_key, sort_dir, resource_url=None, fields=None ): if self.from_nodes and not node_ident: raise exception.MissingParameterValue(_("Node identifier not specified.")) limit = api_utils.validate_limit(limit) sort_dir = api_utils.validate_sort_dir(sort_dir) marker_obj = None if marker: marker_obj = objects.Port.get_by_uuid(pecan.request.context, marker) if sort_key in self.invalid_sort_key_list: raise exception.InvalidParameterValue( _("The sort_key value %(key)s is an invalid field for " "sorting") % {"key": sort_key} ) if node_ident: # FIXME(comstud): Since all we need is the node ID, we can # make this more efficient by only querying # for that column. This will get cleaned up # as we move to the object interface. node = api_utils.get_rpc_node(node_ident) ports = objects.Port.list_by_node_id( pecan.request.context, node.id, limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir ) elif address: ports = self._get_ports_by_address(address) else: ports = objects.Port.list(pecan.request.context, limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir) return PortCollection.convert_with_links( ports, limit, url=resource_url, fields=fields, sort_key=sort_key, sort_dir=sort_dir )
def _get_allocations_collection(self, node_ident=None, resource_class=None, state=None, marker=None, limit=None, sort_key='id', sort_dir='asc', resource_url=None, fields=None): """Return allocations collection. :param node_ident: UUID or name of a node. :param marker: Pagination marker for large data sets. :param limit: Maximum number of resources to return in a single result. :param sort_key: Column to sort results by. Default: id. :param sort_dir: Direction to sort. "asc" or "desc". Default: asc. :param resource_url: Optional, URL to the allocation resource. :param fields: Optional, a list with a specified set of fields of the resource to be returned. """ limit = api_utils.validate_limit(limit) sort_dir = api_utils.validate_sort_dir(sort_dir) if sort_key in self.invalid_sort_key_list: raise exception.InvalidParameterValue( _("The sort_key value %(key)s is an invalid field for " "sorting") % {'key': sort_key}) marker_obj = None if marker: marker_obj = objects.Allocation.get_by_uuid(pecan.request.context, marker) if node_ident: try: node_uuid = api_utils.get_rpc_node(node_ident).uuid except exception.NodeNotFound as exc: exc.code = http_client.BAD_REQUEST raise else: node_uuid = None possible_filters = { 'node_uuid': node_uuid, 'resource_class': resource_class, 'state': state } filters = {} for key, value in possible_filters.items(): if value is not None: filters[key] = value allocations = objects.Allocation.list(pecan.request.context, limit=limit, marker=marker_obj, sort_key=sort_key, sort_dir=sort_dir, filters=filters) return AllocationCollection.convert_with_links(allocations, limit, url=resource_url, fields=fields, sort_key=sort_key, sort_dir=sort_dir)
def test_get_rpc_node_expect_name(self, mock_gbn, mock_gbu, mock_anln, mock_pr): mock_pr.version.minor = 10 mock_anln.return_value = True self.node["name"] = self.valid_name mock_gbn.return_value = self.node self.assertEqual(self.node, utils.get_rpc_node(self.valid_name)) self.assertEqual(0, mock_gbu.call_count) self.assertEqual(1, mock_gbn.call_count)
def test_get_rpc_node_by_uuid_no_logical_name(self, mock_gbn, mock_gbu, mock_anln, mock_pr): # allow_node_logical_name() should have no effect mock_anln.return_value = False self.node["uuid"] = self.valid_uuid mock_gbu.return_value = self.node self.assertEqual(self.node, utils.get_rpc_node(self.valid_uuid)) self.assertEqual(1, mock_gbu.call_count) self.assertEqual(0, mock_gbn.call_count)
def test_get_rpc_node_by_uuid_no_logical_name(self, mock_gbn, mock_gbu, mock_anln, mock_pr): # allow_node_logical_name() should have no effect mock_anln.return_value = False self.node['uuid'] = self.valid_uuid mock_gbu.return_value = self.node self.assertEqual(self.node, utils.get_rpc_node(self.valid_uuid)) self.assertEqual(1, mock_gbu.call_count) self.assertEqual(0, mock_gbn.call_count)
def detach(self, volume_id, node_id=None): cdict = pecan.request.context.to_policy_values() policy.authorize('baremetal:volume:detach_volume', cdict, cdict) rpc_node = api_utils.get_rpc_node(node_ident) topic = pecan.request.rpcapi.get_topic_for(rpc_node) return pecan.request.rpcapi.detach_volume(pecan.request.context, volume_id, node_id)
def test_get_rpc_node_expect_name(self, mock_gbn, mock_gbu, mock_anln, mock_pr): mock_pr.version.minor = 10 mock_anln.return_value = True self.node['name'] = self.valid_name mock_gbn.return_value = self.node self.assertEqual(self.node, utils.get_rpc_node(self.valid_name)) self.assertEqual(0, mock_gbu.call_count) self.assertEqual(1, mock_gbn.call_count)
def _get_portgroups_collection(self, node_ident, address, marker, limit, sort_key, sort_dir, resource_url=None, fields=None, detail=None): """Return portgroups collection. :param node_ident: UUID or name of a node. :param address: MAC address of a portgroup. :param marker: Pagination marker for large data sets. :param limit: Maximum number of resources to return in a single result. :param sort_key: Column to sort results by. Default: id. :param sort_dir: Direction to sort. "asc" or "desc". Default: asc. :param resource_url: Optional, URL to the portgroup resource. :param fields: Optional, a list with a specified set of fields of the resource to be returned. """ limit = api_utils.validate_limit(limit) sort_dir = api_utils.validate_sort_dir(sort_dir) marker_obj = None if marker: marker_obj = objects.Portgroup.get_by_uuid(api.request.context, marker) if sort_key in self.invalid_sort_key_list: raise exception.InvalidParameterValue( _("The sort_key value %(key)s is an invalid field for " "sorting") % {'key': sort_key}) node_ident = self.parent_node_ident or node_ident if node_ident: # FIXME: Since all we need is the node ID, we can # make this more efficient by only querying # for that column. This will get cleaned up # as we move to the object interface. node = api_utils.get_rpc_node(node_ident) portgroups = objects.Portgroup.list_by_node_id( api.request.context, node.id, limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir) elif address: portgroups = self._get_portgroups_by_address(address) else: portgroups = objects.Portgroup.list(api.request.context, limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir) parameters = {} if detail is not None: parameters['detail'] = detail return PortgroupCollection.convert_with_links(portgroups, limit, url=resource_url, fields=fields, sort_key=sort_key, sort_dir=sort_dir, **parameters)
def _get_ports_collection(self, node_ident, address, portgroup_ident, marker, limit, sort_key, sort_dir, resource_url=None, fields=None): limit = api_utils.validate_limit(limit) sort_dir = api_utils.validate_sort_dir(sort_dir) marker_obj = None if marker: marker_obj = objects.Port.get_by_uuid(pecan.request.context, marker) if sort_key in self.invalid_sort_key_list: raise exception.InvalidParameterValue( _("The sort_key value %(key)s is an invalid field for " "sorting") % {'key': sort_key}) node_ident = self.parent_node_ident or node_ident portgroup_ident = self.parent_portgroup_ident or portgroup_ident if node_ident and portgroup_ident: raise exception.OperationNotPermitted() if portgroup_ident: # FIXME: Since all we need is the portgroup ID, we can # make this more efficient by only querying # for that column. This will get cleaned up # as we move to the object interface. portgroup = api_utils.get_rpc_portgroup(portgroup_ident) ports = objects.Port.list_by_portgroup_id(pecan.request.context, portgroup.id, limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir) elif node_ident: # FIXME(comstud): Since all we need is the node ID, we can # make this more efficient by only querying # for that column. This will get cleaned up # as we move to the object interface. node = api_utils.get_rpc_node(node_ident) ports = objects.Port.list_by_node_id(pecan.request.context, node.id, limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir) elif address: ports = self._get_ports_by_address(address) else: ports = objects.Port.list(pecan.request.context, limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir) return PortCollection.convert_with_links(ports, limit, url=resource_url, fields=fields, sort_key=sort_key, sort_dir=sort_dir)
def get_one(self, node_ident): """Retrieve information about the given node. :param node_ident: UUID or logical name of a node. """ if self.from_chassis: raise exception.OperationNotPermitted rpc_node = api_utils.get_rpc_node(node_ident) return Node.convert_with_links(rpc_node)
def get(self, node_ident): """List the states of the node. :param node_ident: the UUID or logical_name of a node. """ # NOTE(lucasagomes): All these state values come from the # DB. Ironic counts with a periodic task that verify the current # power states of the nodes and update the DB accordingly. rpc_node = api_utils.get_rpc_node(node_ident) return NodeStates.convert(rpc_node)
def get_all(self): """List node bios settings.""" cdict = api.request.context.to_policy_values() policy.authorize('baremetal:node:bios:get', cdict, cdict) node = api_utils.get_rpc_node(self.node_ident) settings = objects.BIOSSettingList.get_by_node_id( api.request.context, node.id) return BIOSSettingsCollection.collection_from_list( self.node_ident, settings)
def attach(self, volume_id, node_id, connector_info): cdict = pecan.request.context.to_policy_values() policy.authorize('baremetal:volume:attach_volume', cdict, cdict) rpc_node = api_utils.get_rpc_node(node_id) topic = pecan.request.rpcapi.get_topic_for(rpc_node) return pecan.request.rpcapi.attach_volume(pecan.request.context, volume_id, node_id, connector_info, topic)
def _default(self, node_ident, method, data=None): """Call a vendor extension. :param node_ident: UUID or logical name of a node. :param method: name of the method in vendor driver. :param data: body of data to supply to the specified method. """ # Raise an exception if node is not found rpc_node = api_utils.get_rpc_node(node_ident) topic = pecan.request.rpcapi.get_topic_for(rpc_node) return api_utils.vendor_passthru(rpc_node.uuid, method, topic, data=data)
def _set_maintenance(self, node_ident, maintenance_mode, reason=None): rpc_node = api_utils.get_rpc_node(node_ident) rpc_node.maintenance = maintenance_mode rpc_node.maintenance_reason = reason try: topic = pecan.request.rpcapi.get_topic_for(rpc_node) except exception.NoValidHost as e: e.code = 400 raise e pecan.request.rpcapi.update_node(pecan.request.context, rpc_node, topic=topic)
def put(self, node_ident, enabled): """Start and stop the node console. :param node_ident: UUID or logical name of a node. :param enabled: Boolean value; whether to enable or disable the console. """ rpc_node = api_utils.get_rpc_node(node_ident) topic = pecan.request.rpcapi.get_topic_for(rpc_node) pecan.request.rpcapi.set_console_mode(pecan.request.context, rpc_node.uuid, enabled, topic) # Set the HTTP Location Header url_args = "/".join([node_ident, "states", "console"]) pecan.response.location = link.build_url("nodes", url_args)
def _get_portgroups_collection(self, node_ident, address, marker, limit, sort_key, sort_dir, resource_url=None, fields=None): """Return portgroups collection. :param node_ident: UUID or name of a node. :param address: MAC address of a portgroup. :param marker: Pagination marker for large data sets. :param limit: Maximum number of resources to return in a single result. :param sort_key: Column to sort results by. Default: id. :param sort_dir: Direction to sort. "asc" or "desc". Default: asc. :param resource_url: Optional, URL to the portgroup resource. :param fields: Optional, a list with a specified set of fields of the resource to be returned. """ limit = api_utils.validate_limit(limit) sort_dir = api_utils.validate_sort_dir(sort_dir) marker_obj = None if marker: marker_obj = objects.Portgroup.get_by_uuid(pecan.request.context, marker) if sort_key in self.invalid_sort_key_list: raise exception.InvalidParameterValue( _("The sort_key value %(key)s is an invalid field for " "sorting") % {'key': sort_key}) node_ident = self.parent_node_ident or node_ident if node_ident: # FIXME: Since all we need is the node ID, we can # make this more efficient by only querying # for that column. This will get cleaned up # as we move to the object interface. node = api_utils.get_rpc_node(node_ident) portgroups = objects.Portgroup.list_by_node_id( pecan.request.context, node.id, limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir) elif address: portgroups = self._get_portgroups_by_address(address) else: portgroups = objects.Portgroup.list(pecan.request.context, limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir) return PortgroupCollection.convert_with_links(portgroups, limit, url=resource_url, fields=fields, sort_key=sort_key, sort_dir=sort_dir)
def get_one(self, node_ident, fields=None): """Retrieve information about the given node. :param node_ident: UUID or logical name of a node. :param fields: Optional, a list with a specified set of fields of the resource to be returned. """ if self.from_chassis: raise exception.OperationNotPermitted api_utils.check_allow_specify_fields(fields) rpc_node = api_utils.get_rpc_node(node_ident) return Node.convert_with_links(rpc_node, fields=fields)
def _get_volume_connectors_collection(self, node_ident, marker, limit, sort_key, sort_dir, resource_url=None, fields=None, detail=None): limit = api_utils.validate_limit(limit) sort_dir = api_utils.validate_sort_dir(sort_dir) marker_obj = None if marker: marker_obj = objects.VolumeConnector.get_by_uuid( pecan.request.context, marker) if sort_key in self.invalid_sort_key_list: raise exception.InvalidParameterValue( _("The sort_key value %(key)s is an invalid field for " "sorting") % {'key': sort_key}) node_ident = self.parent_node_ident or node_ident if node_ident: # FIXME(comstud): Since all we need is the node ID, we can # make this more efficient by only querying # for that column. This will get cleaned up # as we move to the object interface. node = api_utils.get_rpc_node(node_ident) connectors = objects.VolumeConnector.list_by_node_id( pecan.request.context, node.id, limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir) else: connectors = objects.VolumeConnector.list(pecan.request.context, limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir) return VolumeConnectorCollection.convert_with_links(connectors, limit, url=resource_url, fields=fields, sort_key=sort_key, sort_dir=sort_dir, detail=detail)
def put(self, node_ident, enabled): """Start and stop the node console. :param node_ident: UUID or logical name of a node. :param enabled: Boolean value; whether to enable or disable the console. """ rpc_node = api_utils.get_rpc_node(node_ident) topic = pecan.request.rpcapi.get_topic_for(rpc_node) pecan.request.rpcapi.set_console_mode(pecan.request.context, rpc_node.uuid, enabled, topic) # Set the HTTP Location Header url_args = '/'.join([node_ident, 'states', 'console']) pecan.response.location = link.build_url('nodes', url_args)
def get(self, node_ident): """Get connection information about the console. :param node_ident: UUID or logical name of a node. """ rpc_node = api_utils.get_rpc_node(node_ident) topic = pecan.request.rpcapi.get_topic_for(rpc_node) try: console = pecan.request.rpcapi.get_console_information(pecan.request.context, rpc_node.uuid, topic) console_state = True except exception.NodeConsoleNotEnabled: console = None console_state = False return ConsoleInfo(console_enabled=console_state, console_info=console)
def post(self, node_ident, data): #cdict = pecan.request.context.to_dict() #policy.authorize('baremetal:node:set_raid_state', cdict, cdict) LOG.warning("[dbg]Enter pxeauto api...") LOG.warning("[dbg]node_ident: %s", node_ident) LOG.warning("[dbg]data: %s", data) rpc_node = api_utils.get_rpc_node(node_ident) LOG.warning("[dbg]rpc_node: %s", rpc_node) LOG.warning("[dbg]rpc_node.uuid: %s", rpc_node.uuid) topic = pecan.request.rpcapi.get_topic_for(rpc_node) LOG.warning("[dbg]topic: %s", topic) pecan.request.rpcapi.pxeauto(pecan.request.context, rpc_node.uuid, data, topic=topic)
def _get_ports_collection(self, node_ident, address, marker, limit, sort_key, sort_dir, expand=False, resource_url=None): if self.from_nodes and not node_ident: raise exception.MissingParameterValue( _("Node identifier not specified.")) limit = api_utils.validate_limit(limit) sort_dir = api_utils.validate_sort_dir(sort_dir) marker_obj = None if marker: marker_obj = objects.Port.get_by_uuid(pecan.request.context, marker) if node_ident: # FIXME(comstud): Since all we need is the node ID, we can # make this more efficient by only querying # for that column. This will get cleaned up # as we move to the object interface. node = api_utils.get_rpc_node(node_ident) ports = objects.Port.list_by_node_id(pecan.request.context, node.id, limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir) elif address: ports = self._get_ports_by_address(address) else: ports = objects.Port.list(pecan.request.context, limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir) return PortCollection.convert_with_links(ports, limit, url=resource_url, expand=expand, sort_key=sort_key, sort_dir=sort_dir)
def get(self, node_ident): """Get connection information about the console. :param node_ident: UUID or logical name of a node. """ rpc_node = api_utils.get_rpc_node(node_ident) topic = pecan.request.rpcapi.get_topic_for(rpc_node) try: console = pecan.request.rpcapi.get_console_information( pecan.request.context, rpc_node.uuid, topic) console_state = True except exception.NodeConsoleNotEnabled: console = None console_state = False return ConsoleInfo(console_enabled=console_state, console_info=console)
def get_one(self, setting_name): """Retrieve information about the given bios setting. :param setting_name: Logical name of the setting to retrieve. """ api_utils.check_policy('baremetal:node:bios:get') node = api_utils.get_rpc_node(self.node_ident) try: setting = objects.BIOSSetting.get(api.request.context, node.id, setting_name) except exception.BIOSSettingNotFound: raise exception.BIOSSettingNotFound(node=node.uuid, name=setting_name) return {setting_name: convert_with_links(setting, node.uuid)}
def _get_boot_device(self, node_ident, supported=False): """Get the current boot device or a list of supported devices. :param node_ident: the UUID or logical name of a node. :param supported: Boolean value. If true return a list of supported boot devices, if false return the current boot device. Default: False. :returns: The current boot device or a list of the supported boot devices. """ rpc_node = api_utils.get_rpc_node(node_ident) topic = pecan.request.rpcapi.get_topic_for(rpc_node) if supported: return pecan.request.rpcapi.get_supported_boot_devices(pecan.request.context, rpc_node.uuid, topic) else: return pecan.request.rpcapi.get_boot_device(pecan.request.context, rpc_node.uuid, topic)
def delete(self, node_ident): """Delete a node. :param node_ident: UUID or logical name of a node. """ if self.from_chassis: raise exception.OperationNotPermitted rpc_node = api_utils.get_rpc_node(node_ident) try: topic = pecan.request.rpcapi.get_topic_for(rpc_node) except exception.NoValidHost as e: e.code = 400 raise e pecan.request.rpcapi.destroy_node(pecan.request.context, rpc_node.uuid, topic)
def _get_volume_targets_collection(self, node_ident, marker, limit, sort_key, sort_dir, resource_url=None, fields=None, detail=None, project=None): limit = api_utils.validate_limit(limit) sort_dir = api_utils.validate_sort_dir(sort_dir) marker_obj = None if marker: marker_obj = objects.VolumeTarget.get_by_uuid( api.request.context, marker) if sort_key in self.invalid_sort_key_list: raise exception.InvalidParameterValue( _("The sort_key value %(key)s is an invalid field for " "sorting") % {'key': sort_key}) node_ident = self.parent_node_ident or node_ident if node_ident: # FIXME(comstud): Since all we need is the node ID, we can # make this more efficient by only querying # for that column. This will get cleaned up # as we move to the object interface. node = api_utils.get_rpc_node(node_ident) targets = objects.VolumeTarget.list_by_node_id( api.request.context, node.id, limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir, project=project) else: targets = objects.VolumeTarget.list(api.request.context, limit, marker_obj, sort_key=sort_key, sort_dir=sort_dir, project=project) cdict = api.request.context.to_policy_values() if not policy.check_policy('baremetal:volume:view_target_properties', cdict, cdict): for target in targets: self._redact_target_properties(target) return list_convert_with_links(targets, limit, url=resource_url, fields=fields, sort_key=sort_key, sort_dir=sort_dir, detail=detail)
def put(self, node_ident, boot_device, persistent=False): """Set the boot device for a node. Set the boot device to use on next reboot of the node. :param node_ident: the UUID or logical name of a node. :param boot_device: the boot device, one of :mod:`ironic.common.boot_devices`. :param persistent: Boolean value. True if the boot device will persist to all future boots, False if not. Default: False. """ rpc_node = api_utils.get_rpc_node(node_ident) topic = pecan.request.rpcapi.get_topic_for(rpc_node) pecan.request.rpcapi.set_boot_device( pecan.request.context, rpc_node.uuid, boot_device, persistent=persistent, topic=topic )
def methods(self, node_ident): """Retrieve information about vendor methods of the given node. :param node_ident: UUID or logical name of a node. :returns: dictionary with <vendor method name>:<method metadata> entries. :raises: NodeNotFound if the node is not found. """ # Raise an exception if node is not found rpc_node = api_utils.get_rpc_node(node_ident) if rpc_node.driver not in _VENDOR_METHODS: topic = pecan.request.rpcapi.get_topic_for(rpc_node) ret = pecan.request.rpcapi.get_node_vendor_passthru_methods( pecan.request.context, rpc_node.uuid, topic=topic) _VENDOR_METHODS[rpc_node.driver] = ret return _VENDOR_METHODS[rpc_node.driver]
def _get_boot_device(self, node_ident, supported=False): """Get the current boot device or a list of supported devices. :param node_ident: the UUID or logical name of a node. :param supported: Boolean value. If true return a list of supported boot devices, if false return the current boot device. Default: False. :returns: The current boot device or a list of the supported boot devices. """ rpc_node = api_utils.get_rpc_node(node_ident) topic = pecan.request.rpcapi.get_topic_for(rpc_node) if supported: return pecan.request.rpcapi.get_supported_boot_devices( pecan.request.context, rpc_node.uuid, topic) else: return pecan.request.rpcapi.get_boot_device(pecan.request.context, rpc_node.uuid, topic)
def get_one(self, setting_name): """Retrieve information about the given bios setting. :param setting_name: Logical name of the setting to retrieve. """ cdict = pecan.request.context.to_policy_values() policy.authorize('baremetal:node:bios:get', cdict, cdict) node = api_utils.get_rpc_node(self.node_ident) try: setting = objects.BIOSSetting.get(pecan.request.context, node.id, setting_name) except exception.BIOSSettingNotFound: raise exception.BIOSSettingNotFound(node=node.uuid, name=setting_name) return { setting_name: BIOSSetting.convert_with_links(setting, node.uuid) }
def validate(self, node=None, node_uuid=None): """Validate the driver interfaces, using the node's UUID or name. Note that the 'node_uuid' interface is deprecated in favour of the 'node' interface :param node: UUID or name of a node. :param node_uuid: UUID of a node. """ if node: # We're invoking this interface using positional notation, or # explicitly using 'node'. Try and determine which one. if not api_utils.allow_node_logical_names() and not uuidutils.is_uuid_like(node): raise exception.NotAcceptable() rpc_node = api_utils.get_rpc_node(node_uuid or node) topic = pecan.request.rpcapi.get_topic_for(rpc_node) return pecan.request.rpcapi.validate_driver_interfaces(pecan.request.context, rpc_node.uuid, topic)
def put(self, node_ident, boot_device, persistent=False): """Set the boot device for a node. Set the boot device to use on next reboot of the node. :param node_ident: the UUID or logical name of a node. :param boot_device: the boot device, one of :mod:`ironic.common.boot_devices`. :param persistent: Boolean value. True if the boot device will persist to all future boots, False if not. Default: False. """ rpc_node = api_utils.get_rpc_node(node_ident) topic = pecan.request.rpcapi.get_topic_for(rpc_node) pecan.request.rpcapi.set_boot_device(pecan.request.context, rpc_node.uuid, boot_device, persistent=persistent, topic=topic)
def validate(self, node=None, node_uuid=None): """Validate the driver interfaces, using the node's UUID or name. Note that the 'node_uuid' interface is deprecated in favour of the 'node' interface :param node: UUID or name of a node. :param node_uuid: UUID of a node. """ if node: # We're invoking this interface using positional notation, or # explicitly using 'node'. Try and determine which one. if (not api_utils.allow_node_logical_names() and not uuidutils.is_uuid_like(node)): raise exception.NotAcceptable() rpc_node = api_utils.get_rpc_node(node_uuid or node) topic = pecan.request.rpcapi.get_topic_for(rpc_node) return pecan.request.rpcapi.validate_driver_interfaces( pecan.request.context, rpc_node.uuid, topic)
def post(self, node_ident, callback_url): """Process a heartbeat from the deploy ramdisk. :param node_ident: the UUID or logical name of a node. :param callback_url: the URL to reach back to the ramdisk. """ if not api_utils.allow_ramdisk_endpoints(): raise exception.NotFound() cdict = pecan.request.context.to_dict() policy.authorize('baremetal:node:ipa_heartbeat', cdict, cdict) rpc_node = api_utils.get_rpc_node(node_ident) try: topic = pecan.request.rpcapi.get_topic_for(rpc_node) except exception.NoValidHost as e: e.code = http_client.BAD_REQUEST raise pecan.request.rpcapi.heartbeat(pecan.request.context, rpc_node.uuid, callback_url, topic=topic)
def post(self, node_ident, callback_url, agent_version=None): """Process a heartbeat from the deploy ramdisk. :param node_ident: the UUID or logical name of a node. :param callback_url: the URL to reach back to the ramdisk. :param agent_version: The version of the agent that is heartbeating. ``None`` indicates that the agent that is heartbeating is a version before sending agent_version was introduced so agent v3.0.0 (the last release before sending agent_version was introduced) will be assumed. :raises: NodeNotFound if node with provided UUID or name was not found. :raises: InvalidUuidOrName if node_ident is not valid name or UUID. :raises: NoValidHost if RPC topic for node could not be retrieved. :raises: NotFound if requested API version does not allow this endpoint. """ if not api_utils.allow_ramdisk_endpoints(): raise exception.NotFound() if agent_version and not api_utils.allow_agent_version_in_heartbeat(): raise exception.InvalidParameterValue( _('Field "agent_version" not recognised')) cdict = pecan.request.context.to_policy_values() policy.authorize('baremetal:node:ipa_heartbeat', cdict, cdict) rpc_node = api_utils.get_rpc_node(node_ident) try: topic = pecan.request.rpcapi.get_topic_for(rpc_node) except exception.NoValidHost as e: e.code = http_client.BAD_REQUEST raise pecan.request.rpcapi.heartbeat( pecan.request.context, rpc_node.uuid, callback_url, agent_version, topic=topic)
def power(self, node_ident, target): """Set the power state of the node. :param node_ident: the UUID or logical name of a node. :param target: The desired power state of the node. :raises: ClientSideError (HTTP 409) if a power operation is already in progress. :raises: InvalidStateRequested (HTTP 400) if the requested target state is not valid or if the node is in CLEANING state. """ # TODO(lucasagomes): Test if it's able to transition to the # target state from the current one rpc_node = api_utils.get_rpc_node(node_ident) topic = pecan.request.rpcapi.get_topic_for(rpc_node) if target not in [ir_states.POWER_ON, ir_states.POWER_OFF, ir_states.REBOOT]: raise exception.InvalidStateRequested( action=target, node=node_ident, state=rpc_node.power_state) # Don't change power state for nodes being cleaned elif rpc_node.provision_state in (ir_states.CLEANWAIT, ir_states.CLEANING): raise exception.InvalidStateRequested( action=target, node=node_ident, state=rpc_node.provision_state) pecan.request.rpcapi.change_node_power_state(pecan.request.context, rpc_node.uuid, target, topic) # Set the HTTP Location Header url_args = '/'.join([node_ident, 'states']) pecan.response.location = link.build_url('nodes', url_args)
def _default(self, node_ident, method, data=None): """Call a vendor extension. :param node_ident: UUID or logical name of a node. :param method: name of the method in vendor driver. :param data: body of data to supply to the specified method. """ # Raise an exception if node is not found rpc_node = api_utils.get_rpc_node(node_ident) topic = pecan.request.rpcapi.get_topic_for(rpc_node) # Raise an exception if method is not specified if not method: raise wsme.exc.ClientSideError(_("Method not specified")) if data is None: data = {} http_method = pecan.request.method.upper() ret, is_async = pecan.request.rpcapi.vendor_passthru( pecan.request.context, rpc_node.uuid, method, http_method, data, topic) status_code = 202 if is_async else 200 return wsme.api.Response(ret, status_code=status_code)
def provision(self, node_ident, target, configdrive=None): """Asynchronous trigger the provisioning of the node. This will set the target provision state of the node, and a background task will begin which actually applies the state change. This call will return a 202 (Accepted) indicating the request was accepted and is in progress; the client should continue to GET the status of this node to observe the status of the requested action. :param node_ident: UUID or logical name of a node. :param target: The desired provision state of the node. :param configdrive: Optional. A gzipped and base64 encoded configdrive. Only valid when setting provision state to "active". :raises: NodeLocked (HTTP 409) if the node is currently locked. :raises: ClientSideError (HTTP 409) if the node is already being provisioned. :raises: InvalidStateRequested (HTTP 400) if the requested transition is not possible from the current state. :raises: NotAcceptable (HTTP 406) if the API version specified does not allow the requested state transition. """ check_allow_management_verbs(target) rpc_node = api_utils.get_rpc_node(node_ident) topic = pecan.request.rpcapi.get_topic_for(rpc_node) # Normally, we let the task manager recognize and deal with # NodeLocked exceptions. However, that isn't done until the RPC calls # below. In order to main backward compatibility with our API HTTP # response codes, we have this check here to deal with cases where # a node is already being operated on (DEPLOYING or such) and we # want to continue returning 409. Without it, we'd return 400. if rpc_node.reservation: raise exception.NodeLocked(node=rpc_node.uuid, host=rpc_node.reservation) if (target in (ir_states.ACTIVE, ir_states.REBUILD) and rpc_node.maintenance): raise exception.NodeInMaintenance(op=_('provisioning'), node=rpc_node.uuid) m = ir_states.machine.copy() m.initialize(rpc_node.provision_state) if not m.is_valid_event(ir_states.VERBS.get(target, target)): raise exception.InvalidStateRequested( action=target, node=rpc_node.uuid, state=rpc_node.provision_state) if configdrive and target != ir_states.ACTIVE: msg = (_('Adding a config drive is only supported when setting ' 'provision state to %s') % ir_states.ACTIVE) raise wsme.exc.ClientSideError(msg, status_code=400) # Note that there is a race condition. The node state(s) could change # by the time the RPC call is made and the TaskManager manager gets a # lock. if target == ir_states.ACTIVE: pecan.request.rpcapi.do_node_deploy(pecan.request.context, rpc_node.uuid, False, configdrive, topic) elif target == ir_states.REBUILD: pecan.request.rpcapi.do_node_deploy(pecan.request.context, rpc_node.uuid, True, None, topic) elif target == ir_states.DELETED: pecan.request.rpcapi.do_node_tear_down( pecan.request.context, rpc_node.uuid, topic) elif target == ir_states.VERBS['inspect']: pecan.request.rpcapi.inspect_hardware( pecan.request.context, rpc_node.uuid, topic=topic) elif target in ( ir_states.VERBS['manage'], ir_states.VERBS['provide']): pecan.request.rpcapi.do_provisioning_action( pecan.request.context, rpc_node.uuid, target, topic) else: msg = (_('The requested action "%(action)s" could not be ' 'understood.') % {'action': target}) raise exception.InvalidStateRequested(message=msg) # Set the HTTP Location Header url_args = '/'.join([node_ident, 'states']) pecan.response.location = link.build_url('nodes', url_args)
def provision(self, node_ident, target, configdrive=None): """Asynchronous trigger the provisioning of the node. This will set the target provision state of the node, and a background task will begin which actually applies the state change. This call will return a 202 (Accepted) indicating the request was accepted and is in progress; the client should continue to GET the status of this node to observe the status of the requested action. :param node_ident: UUID or logical name of a node. :param target: The desired provision state of the node. :param configdrive: Optional. A gzipped and base64 encoded configdrive. Only valid when setting provision state to "active". :raises: NodeLocked (HTTP 409) if the node is currently locked. :raises: ClientSideError (HTTP 409) if the node is already being provisioned. :raises: InvalidStateRequested (HTTP 400) if the requested transition is not possible from the current state. :raises: NotAcceptable (HTTP 406) if the API version specified does not allow the requested state transition. """ check_allow_management_verbs(target) rpc_node = api_utils.get_rpc_node(node_ident) topic = pecan.request.rpcapi.get_topic_for(rpc_node) if (target in (ir_states.ACTIVE, ir_states.REBUILD) and rpc_node.maintenance): raise exception.NodeInMaintenance(op=_('provisioning'), node=rpc_node.uuid) m = ir_states.machine.copy() m.initialize(rpc_node.provision_state) if not m.is_valid_event(ir_states.VERBS.get(target, target)): # Normally, we let the task manager recognize and deal with # NodeLocked exceptions. However, that isn't done until the RPC # calls below. # In order to main backward compatibility with our API HTTP # response codes, we have this check here to deal with cases where # a node is already being operated on (DEPLOYING or such) and we # want to continue returning 409. Without it, we'd return 400. if rpc_node.reservation: raise exception.NodeLocked(node=rpc_node.uuid, host=rpc_node.reservation) raise exception.InvalidStateRequested( action=target, node=rpc_node.uuid, state=rpc_node.provision_state) if configdrive and target != ir_states.ACTIVE: msg = (_('Adding a config drive is only supported when setting ' 'provision state to %s') % ir_states.ACTIVE) raise wsme.exc.ClientSideError(msg, status_code=400) # Note that there is a race condition. The node state(s) could change # by the time the RPC call is made and the TaskManager manager gets a # lock. if target == ir_states.ACTIVE: pecan.request.rpcapi.do_node_deploy(pecan.request.context, rpc_node.uuid, False, configdrive, topic) elif target == ir_states.REBUILD: pecan.request.rpcapi.do_node_deploy(pecan.request.context, rpc_node.uuid, True, None, topic) elif target == ir_states.DELETED: pecan.request.rpcapi.do_node_tear_down( pecan.request.context, rpc_node.uuid, topic) elif target == ir_states.VERBS['inspect']: pecan.request.rpcapi.inspect_hardware( pecan.request.context, rpc_node.uuid, topic=topic) elif target in ( ir_states.VERBS['manage'], ir_states.VERBS['provide']): pecan.request.rpcapi.do_provisioning_action( pecan.request.context, rpc_node.uuid, target, topic) else: msg = (_('The requested action "%(action)s" could not be ' 'understood.') % {'action': target}) raise exception.InvalidStateRequested(message=msg) # Set the HTTP Location Header url_args = '/'.join([node_ident, 'states']) pecan.response.location = link.build_url('nodes', url_args)
def patch(self, node_ident, patch): """Update an existing node. :param node_ident: UUID or logical name of a node. :param patch: a json PATCH document to apply to this node. """ if self.from_chassis: raise exception.OperationNotPermitted rpc_node = api_utils.get_rpc_node(node_ident) # Check if node is transitioning state, although nodes in some states # can be updated. if ((rpc_node.maintenance or rpc_node.provision_state == ir_states.CLEANING) and patch == [{'op': 'remove', 'path': '/instance_uuid'}]): # Allow node.instance_uuid removal during cleaning, but not other # operations. Also allow it during maintenance, to break # association with Nova in case of serious problems. # TODO(JoshNang) remove node.instance_uuid when removing # instance_info and stop removing node.instance_uuid in the Nova # Ironic driver. Bug: 1436568 LOG.debug('Removing instance uuid %(instance)s from node %(node)s', {'instance': rpc_node.instance_uuid, 'node': rpc_node.uuid}) elif ((rpc_node.target_power_state or rpc_node.target_provision_state) and rpc_node.provision_state not in ir_states.UPDATE_ALLOWED_STATES): msg = _("Node %s can not be updated while a state transition " "is in progress.") raise wsme.exc.ClientSideError(msg % node_ident, status_code=409) # Verify that if we're patching 'name' that it is a valid name = api_utils.get_patch_value(patch, '/name') if name: if not api_utils.allow_node_logical_names(): raise exception.NotAcceptable() if not api_utils.is_valid_node_name(name): msg = _("Node %(node)s: Cannot change name to invalid " "name '%(name)s'") raise wsme.exc.ClientSideError(msg % {'node': node_ident, 'name': name}, status_code=400) try: node_dict = rpc_node.as_dict() # NOTE(lucasagomes): # 1) Remove chassis_id because it's an internal value and # not present in the API object # 2) Add chassis_uuid node_dict['chassis_uuid'] = node_dict.pop('chassis_id', None) node = Node(**api_utils.apply_jsonpatch(node_dict, patch)) except api_utils.JSONPATCH_EXCEPTIONS as e: raise exception.PatchError(patch=patch, reason=e) # Update only the fields that have changed for field in objects.Node.fields: try: patch_val = getattr(node, field) except AttributeError: # Ignore fields that aren't exposed in the API continue if patch_val == wtypes.Unset: patch_val = None if rpc_node[field] != patch_val: rpc_node[field] = patch_val # NOTE(deva): we calculate the rpc topic here in case node.driver # has changed, so that update is sent to the # new conductor, not the old one which may fail to # load the new driver. try: topic = pecan.request.rpcapi.get_topic_for(rpc_node) except exception.NoValidHost as e: # NOTE(deva): convert from 404 to 400 because client can see # list of available drivers and shouldn't request # one that doesn't exist. e.code = 400 raise e # NOTE(lucasagomes): If it's changing the driver and the console # is enabled we prevent updating it because the new driver will # not be able to stop a console started by the previous one. delta = rpc_node.obj_what_changed() if 'driver' in delta and rpc_node.console_enabled: raise wsme.exc.ClientSideError( _("Node %s can not update the driver while the console is " "enabled. Please stop the console first.") % node_ident, status_code=409) new_node = pecan.request.rpcapi.update_node( pecan.request.context, rpc_node, topic) return Node.convert_with_links(new_node)
def patch(self, node_ident, patch): """Update an existing node. :param node_ident: UUID or logical name of a node. :param patch: a json PATCH document to apply to this node. """ if self.from_chassis: raise exception.OperationNotPermitted rpc_node = api_utils.get_rpc_node(node_ident) # TODO(lucasagomes): This code is here for backward compatibility # with old nova Ironic drivers that will attempt to remove the # instance even if it's already deleted in Ironic. This conditional # should be removed in the next cycle (Mitaka). remove_inst_uuid_patch = [{'op': 'remove', 'path': '/instance_uuid'}] if (rpc_node.provision_state in (ir_states.CLEANING, ir_states.CLEANWAIT) and patch == remove_inst_uuid_patch): # The instance_uuid is already removed as part of the node's # tear down, skip this update. return Node.convert_with_links(rpc_node) elif rpc_node.maintenance and patch == remove_inst_uuid_patch: LOG.debug('Removing instance uuid %(instance)s from node %(node)s', {'instance': rpc_node.instance_uuid, 'node': rpc_node.uuid}) # Check if node is transitioning state, although nodes in some states # can be updated. elif (rpc_node.target_provision_state and rpc_node.provision_state not in ir_states.UPDATE_ALLOWED_STATES): msg = _("Node %s can not be updated while a state transition " "is in progress.") raise wsme.exc.ClientSideError(msg % node_ident, status_code=409) name = api_utils.get_patch_value(patch, '/name') error_msg = _("Node %(node)s: Cannot change name to invalid " "name '%(name)s'") % {'node': node_ident, 'name': name} self._check_name_acceptable(name, error_msg) try: node_dict = rpc_node.as_dict() # NOTE(lucasagomes): # 1) Remove chassis_id because it's an internal value and # not present in the API object # 2) Add chassis_uuid node_dict['chassis_uuid'] = node_dict.pop('chassis_id', None) node = Node(**api_utils.apply_jsonpatch(node_dict, patch)) except api_utils.JSONPATCH_EXCEPTIONS as e: raise exception.PatchError(patch=patch, reason=e) self._update_changed_fields(node, rpc_node) # NOTE(deva): we calculate the rpc topic here in case node.driver # has changed, so that update is sent to the # new conductor, not the old one which may fail to # load the new driver. try: topic = pecan.request.rpcapi.get_topic_for(rpc_node) except exception.NoValidHost as e: # NOTE(deva): convert from 404 to 400 because client can see # list of available drivers and shouldn't request # one that doesn't exist. e.code = 400 raise e self._check_driver_changed_and_console_enabled(rpc_node, node_ident) new_node = pecan.request.rpcapi.update_node( pecan.request.context, rpc_node, topic) return Node.convert_with_links(new_node)