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 post(self, node_ident, callback_url, agent_version=None, agent_token=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. :param agent_token: randomly generated validation token. :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 = api.request.context.to_policy_values() policy.authorize('baremetal:node:ipa_heartbeat', cdict, cdict) rpc_node = api_utils.get_rpc_node_with_suffix(node_ident) dii = rpc_node['driver_internal_info'] agent_url = dii.get('agent_url') # If we have an agent_url on file, and we get something different # we should fail because this is unexpected behavior of the agent. if agent_url is not None and agent_url != callback_url: LOG.error('Received heartbeat for node %(node)s with ' 'callback URL %(url)s. This is not expected, ' 'and the heartbeat will not be processed.', {'node': rpc_node.uuid, 'url': callback_url}) raise exception.Invalid( _('Detected change in ramdisk provided ' '"callback_url"')) # NOTE(TheJulia): If tokens are required, lets go ahead and fail the # heartbeat very early on. token_required = CONF.require_agent_token if token_required and agent_token is None: LOG.error('Agent heartbeat received for node %(node)s ' 'without an agent token.', {'node': node_ident}) raise exception.InvalidParameterValue( _('Agent token is required for heartbeat processing.')) try: topic = api.request.rpcapi.get_topic_for(rpc_node) except exception.NoValidHost as e: e.code = http_client.BAD_REQUEST raise api.request.rpcapi.heartbeat( api.request.context, rpc_node.uuid, callback_url, agent_version, agent_token, 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_with_suffix(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 get_all(self, addresses=None, node_uuid=None): """Look up a node by its MAC addresses and optionally UUID. If the "restrict_lookup" option is set to True (the default), limit the search to nodes in certain transient states (e.g. deploy wait). :param addresses: list of MAC addresses for a node. :param node_uuid: UUID of a node. :raises: NotFound if requested API version does not allow this endpoint. :raises: NotFound if suitable node was not found. """ if not api_utils.allow_ramdisk_endpoints(): raise exception.NotFound() cdict = pecan.request.context.to_dict() policy.authorize('baremetal:driver:ipa_lookup', cdict, cdict) if not addresses and not node_uuid: raise exception.IncompleteLookup() try: if node_uuid: node = objects.Node.get_by_uuid( pecan.request.context, node_uuid) else: node = objects.Node.get_by_port_addresses( pecan.request.context, addresses) except exception.NotFound: # NOTE(dtantsur): we are reraising the same exception to make sure # we don't disclose the difference between nodes that are not found # at all and nodes in a wrong state by different error messages. raise exception.NotFound() if (CONF.api.restrict_lookup and node.provision_state not in _LOOKUP_ALLOWED_STATES): raise exception.NotFound() return LookupResult.convert_with_links(node)
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 get_all(self, addresses=None, node_uuid=None): """Look up a node by its MAC addresses and optionally UUID. If the "restrict_lookup" option is set to True (the default), limit the search to nodes in certain transient states (e.g. deploy wait). :param addresses: list of MAC addresses for a node. :param node_uuid: UUID of a node. :raises: NotFound if requested API version does not allow this endpoint. :raises: NotFound if suitable node was not found or node's provision state is not allowed for the lookup. :raises: IncompleteLookup if neither node UUID nor any valid MAC address was provided. """ if not api_utils.allow_ramdisk_endpoints(): raise exception.NotFound() cdict = pecan.request.context.to_policy_values() policy.authorize('baremetal:driver:ipa_lookup', cdict, cdict) # Validate the list of MAC addresses if addresses is None: addresses = [] valid_addresses = [] invalid_addresses = [] for addr in addresses: try: mac = utils.validate_and_normalize_mac(addr) valid_addresses.append(mac) except exception.InvalidMAC: invalid_addresses.append(addr) if invalid_addresses: node_log = ('' if not node_uuid else '(Node UUID: %s)' % node_uuid) LOG.warning( 'The following MAC addresses "%(addrs)s" are ' 'invalid and will be ignored by the lookup ' 'request %(node)s', { 'addrs': ', '.join(invalid_addresses), 'node': node_log }) if not valid_addresses and not node_uuid: raise exception.IncompleteLookup() try: if node_uuid: node = objects.Node.get_by_uuid(pecan.request.context, node_uuid) else: node = objects.Node.get_by_port_addresses( pecan.request.context, valid_addresses) except exception.NotFound: # NOTE(dtantsur): we are reraising the same exception to make sure # we don't disclose the difference between nodes that are not found # at all and nodes in a wrong state by different error messages. raise exception.NotFound() if (CONF.api.restrict_lookup and node.provision_state not in _LOOKUP_ALLOWED_STATES): raise exception.NotFound() return LookupResult.convert_with_links(node)
def test_allow_ramdisk_endpoints(self, mock_request): mock_request.version.minor = 22 self.assertTrue(utils.allow_ramdisk_endpoints()) mock_request.version.minor = 21 self.assertFalse(utils.allow_ramdisk_endpoints())
def convert(): v1 = V1() v1.id = "v1" v1.links = [ link.Link.make_link('self', pecan.request.public_url, 'v1', '', bookmark=True), link.Link.make_link('describedby', 'http://docs.openstack.org', 'developer/ironic/dev', 'api-spec-v1.html', bookmark=True, type='text/html') ] v1.media_types = [ MediaType('application/json', 'application/vnd.openstack.ironic.v1+json') ] v1.chassis = [ link.Link.make_link('self', pecan.request.public_url, 'chassis', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'chassis', '', bookmark=True) ] v1.nodes = [ link.Link.make_link('self', pecan.request.public_url, 'nodes', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'nodes', '', bookmark=True) ] v1.ports = [ link.Link.make_link('self', pecan.request.public_url, 'ports', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'ports', '', bookmark=True) ] if utils.allow_portgroups(): v1.portgroups = [ link.Link.make_link('self', pecan.request.public_url, 'portgroups', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'portgroups', '', bookmark=True) ] v1.drivers = [ link.Link.make_link('self', pecan.request.public_url, 'drivers', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'drivers', '', bookmark=True) ] if utils.allow_volume(): v1.volume = [ link.Link.make_link('self', pecan.request.public_url, 'volume', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'volume', '', bookmark=True) ] v1.books = [ link.Link.make_link('self', pecan.request.public_url, 'books', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'books', '', bookmark=True) ] if utils.allow_ramdisk_endpoints(): v1.lookup = [ link.Link.make_link('self', pecan.request.public_url, 'lookup', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'lookup', '', bookmark=True) ] v1.heartbeat = [ link.Link.make_link('self', pecan.request.public_url, 'heartbeat', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'heartbeat', '', bookmark=True) ] return v1
def convert(): v1 = V1() v1.id = "v1" v1.links = [link.Link.make_link('self', pecan.request.public_url, 'v1', '', bookmark=True), link.Link.make_link('describedby', 'https://docs.openstack.org', '/ironic/latest/contributor/', 'webapi.html', bookmark=True, type='text/html') ] v1.media_types = [MediaType('application/json', 'application/vnd.openstack.ironic.v1+json')] v1.chassis = [link.Link.make_link('self', pecan.request.public_url, 'chassis', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'chassis', '', bookmark=True) ] v1.nodes = [link.Link.make_link('self', pecan.request.public_url, 'nodes', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'nodes', '', bookmark=True) ] v1.ports = [link.Link.make_link('self', pecan.request.public_url, 'ports', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'ports', '', bookmark=True) ] if utils.allow_portgroups(): v1.portgroups = [ link.Link.make_link('self', pecan.request.public_url, 'portgroups', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'portgroups', '', bookmark=True) ] v1.drivers = [link.Link.make_link('self', pecan.request.public_url, 'drivers', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'drivers', '', bookmark=True) ] if utils.allow_volume(): v1.volume = [ link.Link.make_link('self', pecan.request.public_url, 'volume', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'volume', '', bookmark=True) ] if utils.allow_ramdisk_endpoints(): v1.lookup = [link.Link.make_link('self', pecan.request.public_url, 'lookup', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'lookup', '', bookmark=True) ] v1.heartbeat = [link.Link.make_link('self', pecan.request.public_url, 'heartbeat', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'heartbeat', '', bookmark=True) ] if utils.allow_expose_conductors(): v1.conductors = [link.Link.make_link('self', pecan.request.public_url, 'conductors', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'conductors', '', bookmark=True) ] if utils.allow_allocations(): v1.allocations = [link.Link.make_link('self', pecan.request.public_url, 'allocations', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'allocations', '', bookmark=True) ] if utils.allow_expose_events(): v1.events = [link.Link.make_link('self', pecan.request.public_url, 'events', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'events', '', bookmark=True) ] if utils.allow_deploy_templates(): v1.deploy_templates = [ link.Link.make_link('self', pecan.request.public_url, 'deploy_templates', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'deploy_templates', '', bookmark=True) ] v1.version = version.default_version() return v1
def post(self, node_ident, callback_url, agent_version=None, agent_token=None, agent_verify_ca=None, agent_status=None, agent_status_message=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. :param agent_token: randomly generated validation token. :param agent_verify_ca: TLS certificate to use to connect to the agent. :param agent_status: Current status of the heartbeating agent. Used by anaconda ramdisk to send status back to Ironic. The valid states are 'start', 'end', 'error' :param agent_status_message: Optional status message describing current agent_status :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')) if ((agent_status or agent_status_message) and not api_utils.allow_status_in_heartbeat()): raise exception.InvalidParameterValue( _('Fields "agent_status" and "agent_status_message" ' 'not recognised.')) api_utils.check_policy('baremetal:node:ipa_heartbeat') if (agent_verify_ca is not None and not api_utils.allow_verify_ca_in_heartbeat()): raise exception.InvalidParameterValue( _('Field "agent_verify_ca" not recognised in this version')) rpc_node = api_utils.get_rpc_node_with_suffix(node_ident) dii = rpc_node['driver_internal_info'] agent_url = dii.get('agent_url') # If we have an agent_url on file, and we get something different # we should fail because this is unexpected behavior of the agent. if agent_url is not None and agent_url != callback_url: LOG.error( 'Received heartbeat for node %(node)s with ' 'callback URL %(url)s. This is not expected, ' 'and the heartbeat will not be processed.', { 'node': rpc_node.uuid, 'url': callback_url }) raise exception.Invalid( _('Detected change in ramdisk provided ' '"callback_url"')) # NOTE(TheJulia): If tokens are required, lets go ahead and fail the # heartbeat very early on. if agent_token is None: LOG.error( 'Agent heartbeat received for node %(node)s ' 'without an agent token.', {'node': node_ident}) raise exception.InvalidParameterValue( _('Agent token is required for heartbeat processing.')) if agent_status is not None and agent_status not in AGENT_VALID_STATES: valid_states = ','.join(AGENT_VALID_STATES) LOG.error( 'Agent heartbeat received for node %(node)s ' 'has an invalid agent status: %(agent_status)s. ' 'Valid states are %(valid_states)s ', { 'node': node_ident, 'agent_status': agent_status, 'valid_states': valid_states }) msg = (_('Agent status is invalid. Valid states are %s.') % valid_states) raise exception.InvalidParameterValue(msg) try: topic = api.request.rpcapi.get_topic_for(rpc_node) except exception.NoValidHost as e: e.code = http_client.BAD_REQUEST raise api.request.rpcapi.heartbeat(api.request.context, rpc_node.uuid, callback_url, agent_version, agent_token, agent_verify_ca, agent_status, agent_status_message, topic=topic)
def get_all(self, addresses=None, node_uuid=None): """Look up a node by its MAC addresses and optionally UUID. If the "restrict_lookup" option is set to True (the default), limit the search to nodes in certain transient states (e.g. deploy wait). :param addresses: list of MAC addresses for a node. :param node_uuid: UUID of a node. :raises: NotFound if requested API version does not allow this endpoint. :raises: NotFound if suitable node was not found. """ if not api_utils.allow_ramdisk_endpoints(): raise exception.NotFound() cdict = pecan.request.context.to_dict() policy.authorize('baremetal:driver:ipa_lookup', cdict, cdict) # Validate the list of MAC addresses if addresses is None: addresses = [] valid_addresses = [] invalid_addresses = [] for addr in addresses: try: mac = utils.validate_and_normalize_mac(addr) valid_addresses.append(mac) except exception.InvalidMAC: invalid_addresses.append(addr) if invalid_addresses: node_log = ('' if not node_uuid else _LW('(Node UUID: %s)') % node_uuid) LOG.warning(_LW('The following MAC addresses "%(addrs)s" are ' 'invalid and will be ignored by the lookup ' 'request %(node)s'), {'addrs': ', '.join(invalid_addresses), 'node': node_log}) if not valid_addresses and not node_uuid: raise exception.IncompleteLookup() try: if node_uuid: node = objects.Node.get_by_uuid( pecan.request.context, node_uuid) else: node = objects.Node.get_by_port_addresses( pecan.request.context, valid_addresses) except exception.NotFound: # NOTE(dtantsur): we are reraising the same exception to make sure # we don't disclose the difference between nodes that are not found # at all and nodes in a wrong state by different error messages. raise exception.NotFound() if (CONF.api.restrict_lookup and node.provision_state not in _LOOKUP_ALLOWED_STATES): raise exception.NotFound() return LookupResult.convert_with_links(node)
def v1(): v1 = { 'id': "v1", 'links': [ link.make_link('self', api.request.public_url, 'v1', '', bookmark=True), link.make_link('describedby', 'https://docs.openstack.org', '/ironic/latest/contributor/', 'webapi.html', bookmark=True, type='text/html') ], 'media_types': { 'base': 'application/json', 'type': 'application/vnd.openstack.ironic.v1+json' }, 'chassis': [ link.make_link('self', api.request.public_url, 'chassis', ''), link.make_link('bookmark', api.request.public_url, 'chassis', '', bookmark=True) ], 'nodes': [ link.make_link('self', api.request.public_url, 'nodes', ''), link.make_link('bookmark', api.request.public_url, 'nodes', '', bookmark=True) ], 'ports': [ link.make_link('self', api.request.public_url, 'ports', ''), link.make_link('bookmark', api.request.public_url, 'ports', '', bookmark=True) ], 'drivers': [ link.make_link('self', api.request.public_url, 'drivers', ''), link.make_link('bookmark', api.request.public_url, 'drivers', '', bookmark=True) ], 'version': version.default_version() } if utils.allow_portgroups(): v1['portgroups'] = [ link.make_link('self', api.request.public_url, 'portgroups', ''), link.make_link('bookmark', api.request.public_url, 'portgroups', '', bookmark=True) ] if utils.allow_volume(): v1['volume'] = [ link.make_link('self', api.request.public_url, 'volume', ''), link.make_link('bookmark', api.request.public_url, 'volume', '', bookmark=True) ] if utils.allow_ramdisk_endpoints(): v1['lookup'] = [ link.make_link('self', api.request.public_url, 'lookup', ''), link.make_link('bookmark', api.request.public_url, 'lookup', '', bookmark=True) ] v1['heartbeat'] = [ link.make_link('self', api.request.public_url, 'heartbeat', ''), link.make_link('bookmark', api.request.public_url, 'heartbeat', '', bookmark=True) ] if utils.allow_expose_conductors(): v1['conductors'] = [ link.make_link('self', api.request.public_url, 'conductors', ''), link.make_link('bookmark', api.request.public_url, 'conductors', '', bookmark=True) ] if utils.allow_allocations(): v1['allocations'] = [ link.make_link('self', api.request.public_url, 'allocations', ''), link.make_link('bookmark', api.request.public_url, 'allocations', '', bookmark=True) ] if utils.allow_expose_events(): v1['events'] = [ link.make_link('self', api.request.public_url, 'events', ''), link.make_link('bookmark', api.request.public_url, 'events', '', bookmark=True) ] if utils.allow_deploy_templates(): v1['deploy_templates'] = [ link.make_link('self', api.request.public_url, 'deploy_templates', ''), link.make_link('bookmark', api.request.public_url, 'deploy_templates', '', bookmark=True) ] return v1
def convert(): v1 = V1() v1.id = "v1" v1.links = [link.Link.make_link('self', pecan.request.public_url, 'v1', '', bookmark=True), link.Link.make_link('describedby', 'http://docs.openstack.org', 'developer/ironic/dev', 'api-spec-v1.html', bookmark=True, type='text/html') ] v1.media_types = [MediaType('application/json', 'application/vnd.openstack.ironic.v1+json')] v1.chassis = [link.Link.make_link('self', pecan.request.public_url, 'chassis', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'chassis', '', bookmark=True) ] v1.nodes = [link.Link.make_link('self', pecan.request.public_url, 'nodes', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'nodes', '', bookmark=True) ] v1.ports = [link.Link.make_link('self', pecan.request.public_url, 'ports', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'ports', '', bookmark=True) ] if utils.allow_portgroups(): v1.portgroups = [ link.Link.make_link('self', pecan.request.public_url, 'portgroups', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'portgroups', '', bookmark=True) ] v1.drivers = [link.Link.make_link('self', pecan.request.public_url, 'drivers', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'drivers', '', bookmark=True) ] if utils.allow_ramdisk_endpoints(): v1.lookup = [link.Link.make_link('self', pecan.request.public_url, 'lookup', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'lookup', '', bookmark=True) ] v1.heartbeat = [link.Link.make_link('self', pecan.request.public_url, 'heartbeat', ''), link.Link.make_link('bookmark', pecan.request.public_url, 'heartbeat', '', bookmark=True) ] return v1