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 post(self, node): """Create a new node. :param node: a node within the request body. """ if self.from_chassis: raise exception.OperationNotPermitted # NOTE(deva): get_topic_for checks if node.driver is in the hash ring # and raises NoValidHost if it is not. # We need to ensure that node has a UUID before it can # be mapped onto the hash ring. if not node.uuid: node.uuid = utils.generate_uuid() try: pecan.request.rpcapi.get_topic_for(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 new_node = objects.Node(context=pecan.request.context, **node.as_dict()) new_node.create() # Set the HTTP Location Header pecan.response.location = link.build_url('nodes', new_node.uuid) return Node.convert_with_links(new_node)
def post(self, port): """Create a new port. :param port: a port within the request body. :raises: NotAcceptable, HTTPNotFound, Conflict """ context = pecan.request.context cdict = context.to_policy_values() policy.authorize('baremetal:port:create', cdict, cdict) if self.parent_node_ident or self.parent_portgroup_ident: raise exception.OperationNotPermitted() pdict = port.as_dict() self._check_allowed_port_fields(pdict) extra = pdict.get('extra') vif = extra.get('vif_port_id') if extra else None if vif: common_utils.warn_about_deprecated_extra_vif_port_id() if (pdict.get('portgroup_uuid') and (pdict.get('pxe_enabled') or vif)): rpc_pg = objects.Portgroup.get_by_uuid(context, pdict['portgroup_uuid']) if not rpc_pg.standalone_ports_supported: msg = _("Port group %s doesn't support standalone ports. " "This port cannot be created as a member of that " "port group because either 'extra/vif_port_id' " "was specified or 'pxe_enabled' was set to True.") raise exception.Conflict(msg % pdict['portgroup_uuid']) # NOTE(yuriyz): UUID is mandatory for notifications payload if not pdict.get('uuid'): pdict['uuid'] = uuidutils.generate_uuid() rpc_port = objects.Port(context, **pdict) rpc_node = objects.Node.get_by_id(context, rpc_port.node_id) notify_extra = { 'node_uuid': port.node_uuid, 'portgroup_uuid': port.portgroup_uuid } notify.emit_start_notification(context, rpc_port, 'create', **notify_extra) with notify.handle_error_notification(context, rpc_port, 'create', **notify_extra): # TODO(mgoddard): In RPC API v1.41, port creation was moved to the # conductor service to facilitate validation of the physical # network field of ports in portgroups. Further consideration is # required determine how best to support rolling upgrades from a # release in which ports are created by the API service to one in # which they are created by the conductor service, while ensuring # that all required validation is performed. topic = pecan.request.rpcapi.get_topic_for(rpc_node) new_port = pecan.request.rpcapi.create_port( context, rpc_port, topic) notify.emit_end_notification(context, new_port, 'create', **notify_extra) # Set the HTTP Location Header pecan.response.location = link.build_url('ports', new_port.uuid) return Port.convert_with_links(new_port)
def post(self, port): """Create a new port. :param port: a port within the request body. :raises: NotAcceptable, HTTPNotFound, Conflict """ context = pecan.request.context cdict = context.to_policy_values() policy.authorize('baremetal:port:create', cdict, cdict) if self.parent_node_ident or self.parent_portgroup_ident: raise exception.OperationNotPermitted() pdict = port.as_dict() self._check_allowed_port_fields(pdict) extra = pdict.get('extra') vif = extra.get('vif_port_id') if extra else None if vif: common_utils.warn_about_deprecated_extra_vif_port_id() if (pdict.get('portgroup_uuid') and (pdict.get('pxe_enabled') or vif)): rpc_pg = objects.Portgroup.get_by_uuid(context, pdict['portgroup_uuid']) if not rpc_pg.standalone_ports_supported: msg = _("Port group %s doesn't support standalone ports. " "This port cannot be created as a member of that " "port group because either 'extra/vif_port_id' " "was specified or 'pxe_enabled' was set to True.") raise exception.Conflict( msg % pdict['portgroup_uuid']) # NOTE(yuriyz): UUID is mandatory for notifications payload if not pdict.get('uuid'): pdict['uuid'] = uuidutils.generate_uuid() rpc_port = objects.Port(context, **pdict) rpc_node = objects.Node.get_by_id(context, rpc_port.node_id) notify_extra = {'node_uuid': port.node_uuid, 'portgroup_uuid': port.portgroup_uuid} notify.emit_start_notification(context, rpc_port, 'create', **notify_extra) with notify.handle_error_notification(context, rpc_port, 'create', **notify_extra): # TODO(mgoddard): In RPC API v1.41, port creation was moved to the # conductor service to facilitate validation of the physical # network field of ports in portgroups. Further consideration is # required determine how best to support rolling upgrades from a # release in which ports are created by the API service to one in # which they are created by the conductor service, while ensuring # that all required validation is performed. topic = pecan.request.rpcapi.get_topic_for(rpc_node) new_port = pecan.request.rpcapi.create_port(context, rpc_port, topic) notify.emit_end_notification(context, new_port, 'create', **notify_extra) # Set the HTTP Location Header pecan.response.location = link.build_url('ports', new_port.uuid) return Port.convert_with_links(new_port)
def post(self, node): """Create a new node. :param node: a node within the request body. """ if self.from_chassis: raise exception.OperationNotPermitted # NOTE(deva): get_topic_for checks if node.driver is in the hash ring # and raises NoValidHost if it is not. # We need to ensure that node has a UUID before it can # be mapped onto the hash ring. if not node.uuid: node.uuid = uuidutils.generate_uuid() try: pecan.request.rpcapi.get_topic_for(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 error_msg = _("Cannot create node with invalid name " "%(name)s") % {'name': node.name} self._check_name_acceptable(node.name, error_msg) node.provision_state = api_utils.initial_node_provision_state() new_node = objects.Node(pecan.request.context, **node.as_dict()) new_node.create() # Set the HTTP Location Header pecan.response.location = link.build_url('nodes', new_node.uuid) return Node.convert_with_links(new_node)
def post(self, portgroup): """Create a new portgroup. :param portgroup: a portgroup within the request body. """ if not api_utils.allow_portgroups(): raise exception.NotFound() cdict = pecan.request.context.to_dict() policy.authorize('baremetal:portgroup:create', cdict, cdict) if self.parent_node_ident: raise exception.OperationNotPermitted() if (portgroup.name and not api_utils.is_valid_logical_name(portgroup.name)): error_msg = _("Cannot create portgroup with invalid name " "'%(name)s'") % { 'name': portgroup.name } raise wsme.exc.ClientSideError(error_msg, status_code=http_client.BAD_REQUEST) new_portgroup = objects.Portgroup(pecan.request.context, **portgroup.as_dict()) new_portgroup.create() # Set the HTTP Location Header pecan.response.location = link.build_url('portgroups', new_portgroup.uuid) return Portgroup.convert_with_links(new_portgroup)
def post(self, node): """Create a new node. :param node: a node within the request body. """ if self.from_chassis: raise exception.OperationNotPermitted # NOTE(deva): get_topic_for checks if node.driver is in the hash ring # and raises NoValidHost if it is not. # We need to ensure that node has a UUID before it can # be mapped onto the hash ring. if not node.uuid: node.uuid = uuidutils.generate_uuid() try: pecan.request.rpcapi.get_topic_for(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 = http_client.BAD_REQUEST raise e error_msg = _("Cannot create node with invalid name " "%(name)s") % {'name': node.name} self._check_name_acceptable(node.name, error_msg) node.provision_state = api_utils.initial_node_provision_state() new_node = objects.Node(pecan.request.context, **node.as_dict()) new_node.create() # Set the HTTP Location Header pecan.response.location = link.build_url('nodes', new_node.uuid) return Node.convert_with_links(new_node)
def post(self, portgroup): """Create a new portgroup. :param portgroup: a portgroup within the request body. """ if not api_utils.allow_portgroups(): raise exception.NotFound() cdict = pecan.request.context.to_dict() policy.authorize('baremetal:portgroup:create', cdict, cdict) if self.parent_node_ident: raise exception.OperationNotPermitted() if (portgroup.name and not api_utils.is_valid_logical_name(portgroup.name)): error_msg = _("Cannot create portgroup with invalid name " "'%(name)s'") % {'name': portgroup.name} raise wsme.exc.ClientSideError( error_msg, status_code=http_client.BAD_REQUEST) new_portgroup = objects.Portgroup(pecan.request.context, **portgroup.as_dict()) new_portgroup.create() # Set the HTTP Location Header pecan.response.location = link.build_url('portgroups', new_portgroup.uuid) return Portgroup.convert_with_links(new_portgroup)
def post(self, port): """Create a new port. :param port: a port within the request body. :raises: NotAcceptable, HTTPNotFound """ cdict = pecan.request.context.to_dict() policy.authorize('baremetal:port:create', cdict, cdict) if self.parent_node_ident or self.parent_portgroup_ident: raise exception.OperationNotPermitted() pdict = port.as_dict() if (not api_utils.allow_port_advanced_net_fields() and set(pdict).intersection(self.advanced_net_fields)): raise exception.NotAcceptable() if (not api_utils.allow_portgroups_subcontrollers() and 'portgroup_uuid' in pdict): raise exception.NotAcceptable() new_port = objects.Port(pecan.request.context, **pdict) new_port.create() # Set the HTTP Location Header pecan.response.location = link.build_url('ports', new_port.uuid) return Port.convert_with_links(new_port)
def power(self, node_uuid, target): """Set the power state of the node. :param node_uuid: UUID 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. """ # TODO(lucasagomes): Test if it's able to transition to the # target state from the current one rpc_node = objects.Node.get_by_uuid(pecan.request.context, node_uuid) 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(state=target, node=node_uuid) pecan.request.rpcapi.change_node_power_state(pecan.request.context, node_uuid, target, topic) # Set the HTTP Location Header url_args = '/'.join([node_uuid, 'states']) pecan.response.location = link.build_url('nodes', url_args)
def power(self, node_uuid, target): """Set the power state of the node. :param node_uuid: UUID 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. """ # TODO(lucasagomes): Test if it's able to transition to the # target state from the current one rpc_node = objects.Node.get_by_uuid(pecan.request.context, node_uuid) 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(state=target, node=node_uuid) pecan.request.rpcapi.change_node_power_state(pecan.request.context, node_uuid, target, topic) # Set the HTTP Location Header url_args = '/'.join([node_uuid, 'states']) pecan.response.location = link.build_url('nodes', url_args)
def post(self, allocation): """Create a new allocation. :param allocation: an allocation within the request body. """ context = pecan.request.context cdict = context.to_policy_values() policy.authorize('baremetal:allocation:create', cdict, cdict) if allocation.node_uuid is not wtypes.Unset: msg = _("Cannot set node_uuid when creating an allocation") raise exception.Invalid(msg) if (allocation.name and not api_utils.is_valid_logical_name(allocation.name)): msg = _("Cannot create allocation with invalid name " "'%(name)s'") % { 'name': allocation.name } raise exception.Invalid(msg) if allocation.traits: for trait in allocation.traits: api_utils.validate_trait(trait) if allocation.candidate_nodes: # Convert nodes from names to UUIDs and check their validity try: converted = pecan.request.dbapi.check_node_list( allocation.candidate_nodes) except exception.NodeNotFound as exc: exc.code = http_client.BAD_REQUEST raise else: # Make sure we keep the ordering of candidate nodes. allocation.candidate_nodes = [ converted[ident] for ident in allocation.candidate_nodes ] all_dict = allocation.as_dict() # NOTE(yuriyz): UUID is mandatory for notifications payload if not all_dict.get('uuid'): all_dict['uuid'] = uuidutils.generate_uuid() new_allocation = objects.Allocation(context, **all_dict) topic = pecan.request.rpcapi.get_random_topic() notify.emit_start_notification(context, new_allocation, 'create') with notify.handle_error_notification(context, new_allocation, 'create'): new_allocation = pecan.request.rpcapi.create_allocation( context, new_allocation, topic) notify.emit_end_notification(context, new_allocation, 'create') # Set the HTTP Location Header pecan.response.location = link.build_url('allocations', new_allocation.uuid) return Allocation.convert_with_links(new_allocation)
def provision(self, node_uuid, target): """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_uuid: UUID of a node. :param target: The desired provision state of the node. :raises: ClientSideError (HTTP 409) if the node is already being provisioned. :raises: ClientSideError (HTTP 400) if the node is already in the requested state. :raises: InvalidStateRequested (HTTP 400) if the requested target state is not valid. """ rpc_node = objects.Node.get_by_uuid(pecan.request.context, node_uuid) topic = pecan.request.rpcapi.get_topic_for(rpc_node) if target == rpc_node.provision_state: msg = (_("Node %(node)s is already in the '%(state)s' state.") % { 'node': rpc_node['uuid'], 'state': target }) raise wsme.exc.ClientSideError(msg, status_code=400) if target not in (ir_states.ACTIVE, ir_states.DELETED, ir_states.REBUILD): raise exception.InvalidStateRequested(state=target, node=node_uuid) valid_states_if_processing = [ir_states.DEPLOYFAIL] if target == ir_states.DELETED: valid_states_if_processing.append(ir_states.DEPLOYWAIT) if (rpc_node.target_provision_state is not None and rpc_node.provision_state not in valid_states_if_processing): msg = ( _('Node %s is already being provisioned or decommissioned.') % rpc_node.uuid) raise wsme.exc.ClientSideError(msg, status_code=409) # Conflict # 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 in (ir_states.ACTIVE, ir_states.REBUILD): rebuild = (target == ir_states.REBUILD) pecan.request.rpcapi.do_node_deploy(pecan.request.context, node_uuid, rebuild, topic) elif target == ir_states.DELETED: pecan.request.rpcapi.do_node_tear_down(pecan.request.context, node_uuid, topic) # Set the HTTP Location Header url_args = '/'.join([node_uuid, 'states']) pecan.response.location = link.build_url('nodes', url_args)
def post(self, portgroup): """Create a new portgroup. :param portgroup: a portgroup within the request body. """ if not api_utils.allow_portgroups(): raise exception.NotFound() context = pecan.request.context cdict = context.to_policy_values() policy.authorize('baremetal:portgroup:create', cdict, cdict) if self.parent_node_ident: raise exception.OperationNotPermitted() if (not api_utils.allow_portgroup_mode_properties() and (portgroup.mode is not wtypes.Unset or portgroup.properties is not wtypes.Unset)): raise exception.NotAcceptable() if (portgroup.name and not api_utils.is_valid_logical_name(portgroup.name)): error_msg = _("Cannot create portgroup with invalid name " "'%(name)s'") % { 'name': portgroup.name } raise wsme.exc.ClientSideError(error_msg, status_code=http_client.BAD_REQUEST) pg_dict = portgroup.as_dict() vif = pg_dict.get('extra', {}).get('vif_port_id') if vif: common_utils.warn_about_deprecated_extra_vif_port_id() # NOTE(yuriyz): UUID is mandatory for notifications payload if not pg_dict.get('uuid'): pg_dict['uuid'] = uuidutils.generate_uuid() new_portgroup = objects.Portgroup(context, **pg_dict) notify.emit_start_notification(context, new_portgroup, 'create', node_uuid=portgroup.node_uuid) with notify.handle_error_notification(context, new_portgroup, 'create', node_uuid=portgroup.node_uuid): new_portgroup.create() notify.emit_end_notification(context, new_portgroup, 'create', node_uuid=portgroup.node_uuid) # Set the HTTP Location Header pecan.response.location = link.build_url('portgroups', new_portgroup.uuid) return Portgroup.convert_with_links(new_portgroup)
def post(self, chassis): """Create a new chassis. :param chassis: a chassis within the request body. """ new_chassis = pecan.request.dbapi.create_chassis(chassis.as_dict()) # Set the HTTP Location Header pecan.response.location = link.build_url('chassis', new_chassis.uuid) return Chassis.convert_with_links(new_chassis)
def post(self, port): """Create a new port. :param port: a port within the request body. :raises: NotAcceptable, HTTPNotFound, Conflict """ context = pecan.request.context cdict = context.to_policy_values() policy.authorize('baremetal:port:create', cdict, cdict) if self.parent_node_ident or self.parent_portgroup_ident: raise exception.OperationNotPermitted() pdict = port.as_dict() if (not api_utils.allow_port_advanced_net_fields() and set(pdict).intersection(self.advanced_net_fields)): raise exception.NotAcceptable() if (not api_utils.allow_portgroups_subcontrollers() and 'portgroup_uuid' in pdict): raise exception.NotAcceptable() extra = pdict.get('extra') vif = extra.get('vif_port_id') if extra else None if vif: common_utils.warn_about_deprecated_extra_vif_port_id() if (pdict.get('portgroup_uuid') and (pdict.get('pxe_enabled') or vif)): rpc_pg = objects.Portgroup.get_by_uuid(context, pdict['portgroup_uuid']) if not rpc_pg.standalone_ports_supported: msg = _("Port group %s doesn't support standalone ports. " "This port cannot be created as a member of that " "port group because either 'extra/vif_port_id' " "was specified or 'pxe_enabled' was set to True.") raise exception.Conflict(msg % pdict['portgroup_uuid']) # NOTE(yuriyz): UUID is mandatory for notifications payload if not pdict.get('uuid'): pdict['uuid'] = uuidutils.generate_uuid() new_port = objects.Port(context, **pdict) notify.emit_start_notification(context, new_port, 'create', node_uuid=port.node_uuid) with notify.handle_error_notification(context, new_port, 'create', node_uuid=port.node_uuid): new_port.create() notify.emit_end_notification(context, new_port, 'create', node_uuid=port.node_uuid) # Set the HTTP Location Header pecan.response.location = link.build_url('ports', new_port.uuid) return Port.convert_with_links(new_port)
def post(self, allocation): """Create a new allocation. :param allocation: an allocation within the request body. """ context = pecan.request.context cdict = context.to_policy_values() policy.authorize('baremetal:allocation:create', cdict, cdict) if allocation.node_uuid is not wtypes.Unset: msg = _("Cannot set node_uuid when creating an allocation") raise exception.Invalid(msg) if (allocation.name and not api_utils.is_valid_logical_name(allocation.name)): msg = _("Cannot create allocation with invalid name " "'%(name)s'") % {'name': allocation.name} raise exception.Invalid(msg) if allocation.traits: for trait in allocation.traits: api_utils.validate_trait(trait) if allocation.candidate_nodes: # Convert nodes from names to UUIDs and check their validity try: converted = pecan.request.dbapi.check_node_list( allocation.candidate_nodes) except exception.NodeNotFound as exc: exc.code = http_client.BAD_REQUEST raise else: # Make sure we keep the ordering of candidate nodes. allocation.candidate_nodes = [ converted[ident] for ident in allocation.candidate_nodes] all_dict = allocation.as_dict() # NOTE(yuriyz): UUID is mandatory for notifications payload if not all_dict.get('uuid'): all_dict['uuid'] = uuidutils.generate_uuid() new_allocation = objects.Allocation(context, **all_dict) topic = pecan.request.rpcapi.get_random_topic() notify.emit_start_notification(context, new_allocation, 'create') with notify.handle_error_notification(context, new_allocation, 'create'): new_allocation = pecan.request.rpcapi.create_allocation( context, new_allocation, topic) notify.emit_end_notification(context, new_allocation, 'create') # Set the HTTP Location Header pecan.response.location = link.build_url('allocations', new_allocation.uuid) return Allocation.convert_with_links(new_allocation)
def post(self, portgroup): """Create a new portgroup. :param portgroup: a portgroup within the request body. """ if not api_utils.allow_portgroups(): raise exception.NotFound() context = api.request.context api_utils.check_policy('baremetal:portgroup:create') if self.parent_node_ident: raise exception.OperationNotPermitted() if (not api_utils.allow_portgroup_mode_properties() and (portgroup.get('mode') or portgroup.get('properties'))): raise exception.NotAcceptable() if (portgroup.get('name') and not api_utils.is_valid_logical_name(portgroup['name'])): error_msg = _("Cannot create portgroup with invalid name " "'%(name)s'") % { 'name': portgroup['name'] } raise exception.ClientSideError( error_msg, status_code=http_client.BAD_REQUEST) api_utils.handle_post_port_like_extra_vif(portgroup) # NOTE(yuriyz): UUID is mandatory for notifications payload if not portgroup.get('uuid'): portgroup['uuid'] = uuidutils.generate_uuid() node = api_utils.replace_node_uuid_with_id(portgroup) new_portgroup = objects.Portgroup(context, **portgroup) notify.emit_start_notification(context, new_portgroup, 'create', node_uuid=node.uuid) with notify.handle_error_notification(context, new_portgroup, 'create', node_uuid=node.uuid): new_portgroup.create() notify.emit_end_notification(context, new_portgroup, 'create', node_uuid=node.uuid) # Set the HTTP Location Header api.response.location = link.build_url('portgroups', new_portgroup.uuid) return convert_with_links(new_portgroup)
def provision(self, node_uuid, target): """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_uuid: UUID of a node. :param target: The desired provision state of the node. :raises: ClientSideError (HTTP 409) if the node is already being provisioned. :raises: ClientSideError (HTTP 400) if the node is already in the requested state. :raises: InvalidStateRequested (HTTP 400) if the requested target state is not valid. """ rpc_node = objects.Node.get_by_uuid(pecan.request.context, node_uuid) topic = pecan.request.rpcapi.get_topic_for(rpc_node) if target == rpc_node.provision_state: msg = (_("Node %(node)s is already in the '%(state)s' state.") % {'node': rpc_node['uuid'], 'state': target}) raise wsme.exc.ClientSideError(msg, status_code=400) if target in (ir_states.ACTIVE, ir_states.REBUILD): processing = rpc_node.target_provision_state is not None elif target == ir_states.DELETED: processing = (rpc_node.target_provision_state is not None and rpc_node.provision_state != ir_states.DEPLOYWAIT) else: raise exception.InvalidStateRequested(state=target, node=node_uuid) if processing: msg = (_('Node %s is already being provisioned or decommissioned.') % rpc_node.uuid) raise wsme.exc.ClientSideError(msg, status_code=409) # Conflict # 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 in (ir_states.ACTIVE, ir_states.REBUILD): rebuild = (target == ir_states.REBUILD) pecan.request.rpcapi.do_node_deploy( pecan.request.context, node_uuid, rebuild, topic) elif target == ir_states.DELETED: pecan.request.rpcapi.do_node_tear_down( pecan.request.context, node_uuid, topic) # Set the HTTP Location Header url_args = '/'.join([node_uuid, 'states']) pecan.response.location = link.build_url('nodes', url_args)
def post(self, connector): """Create a new volume connector. :param connector: a volume connector within the request body. :returns: API-serializable volume connector object. :raises: OperationNotPermitted if accessed with specifying a parent node. :raises: VolumeConnectorTypeAndIdAlreadyExists if a volume connector already exists with the same type and connector_id :raises: VolumeConnectorAlreadyExists if a volume connector with the same UUID already exists """ context = api.request.context owner = None lessee = None raise_node_not_found = False node_uuid = connector.get('node_uuid') try: node = api_utils.replace_node_uuid_with_id(connector) owner = node.owner lessee = node.lessee except exception.NotFound: raise_node_not_found = True api_utils.check_owner_policy('node', 'baremetal:volume:create', owner, lessee=lessee, conceal_node=False) if raise_node_not_found: raise exception.InvalidInput(fieldname='node_uuid', value=node_uuid) if self.parent_node_ident: raise exception.OperationNotPermitted() # NOTE(hshiina): UUID is mandatory for notification payload if not connector.get('uuid'): connector['uuid'] = uuidutils.generate_uuid() new_connector = objects.VolumeConnector(context, **connector) notify.emit_start_notification(context, new_connector, 'create', node_uuid=node.uuid) with notify.handle_error_notification(context, new_connector, 'create', node_uuid=node.uuid): new_connector.create() notify.emit_end_notification(context, new_connector, 'create', node_uuid=node.uuid) # Set the HTTP Location Header api.response.location = link.build_url('volume/connectors', new_connector.uuid) return convert_with_links(new_connector)
def post(self, port): """Create a new port. :param port: a port within the request body. """ if self.from_nodes: raise exception.OperationNotPermitted new_port = pecan.request.dbapi.create_port(port.as_dict()) # Set the HTTP Location Header pecan.response.location = link.build_url('ports', new_port.uuid) return Port.convert_with_links(new_port)
def put(self, node_uuid, enabled): """Start and stop the node console. :param node_uuid: UUID of a node. :param enabled: Boolean value; whether to enable or disable the console. """ rpc_node = objects.Node.get_by_uuid(pecan.request.context, node_uuid) topic = pecan.request.rpcapi.get_topic_for(rpc_node) pecan.request.rpcapi.set_console_mode(pecan.request.context, node_uuid, enabled, topic) # Set the HTTP Location Header url_args = "/".join([node_uuid, "states", "console"]) pecan.response.location = link.build_url("nodes", url_args)
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 post(self, portgroup): """Create a new portgroup. :param portgroup: a portgroup within the request body. """ if not api_utils.allow_portgroups(): raise exception.NotFound() context = pecan.request.context cdict = context.to_policy_values() policy.authorize('baremetal:portgroup:create', cdict, cdict) if self.parent_node_ident: raise exception.OperationNotPermitted() if (not api_utils.allow_portgroup_mode_properties() and (portgroup.mode is not wtypes.Unset or portgroup.properties is not wtypes.Unset)): raise exception.NotAcceptable() if (portgroup.name and not api_utils.is_valid_logical_name(portgroup.name)): error_msg = _("Cannot create portgroup with invalid name " "'%(name)s'") % {'name': portgroup.name} raise wsme.exc.ClientSideError( error_msg, status_code=http_client.BAD_REQUEST) pg_dict = portgroup.as_dict() vif = pg_dict.get('extra', {}).get('vif_port_id') if vif: common_utils.warn_about_deprecated_extra_vif_port_id() # NOTE(yuriyz): UUID is mandatory for notifications payload if not pg_dict.get('uuid'): pg_dict['uuid'] = uuidutils.generate_uuid() new_portgroup = objects.Portgroup(context, **pg_dict) notify.emit_start_notification(context, new_portgroup, 'create', node_uuid=portgroup.node_uuid) with notify.handle_error_notification(context, new_portgroup, 'create', node_uuid=portgroup.node_uuid): new_portgroup.create() notify.emit_end_notification(context, new_portgroup, 'create', node_uuid=portgroup.node_uuid) # Set the HTTP Location Header pecan.response.location = link.build_url('portgroups', new_portgroup.uuid) return Portgroup.convert_with_links(new_portgroup)
def post(self, chassis): """Create a new chassis. :param chassis: a chassis within the request body. """ cdict = pecan.request.context.to_dict() policy.authorize('baremetal:chassis:create', cdict, cdict) new_chassis = objects.Chassis(pecan.request.context, **chassis.as_dict()) new_chassis.create() # Set the HTTP Location Header pecan.response.location = link.build_url('chassis', new_chassis.uuid) return Chassis.convert_with_links(new_chassis)
def put(self, node_uuid, enabled): """Start and stop the node console. :param node_uuid: UUID of a node. :param enabled: Boolean value; whether to enable or disable the console. """ rpc_node = objects.Node.get_by_uuid(pecan.request.context, node_uuid) topic = pecan.request.rpcapi.get_topic_for(rpc_node) pecan.request.rpcapi.set_console_mode(pecan.request.context, node_uuid, enabled, topic) # Set the HTTP Location Header url_args = '/'.join([node_uuid, 'states', 'console']) pecan.response.location = link.build_url('nodes', url_args)
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 post(self, target): """Create a new volume target. :param target: a volume target within the request body. :returns: API-serializable volume target object. :raises: OperationNotPermitted if accessed with specifying a parent node. :raises: VolumeTargetBootIndexAlreadyExists if a volume target already exists with the same node ID and boot index :raises: VolumeTargetAlreadyExists if a volume target with the same UUID exists """ context = api.request.context api_utils.check_policy('baremetal:volume:create') if self.parent_node_ident: raise exception.OperationNotPermitted() # NOTE(hshiina): UUID is mandatory for notification payload if not target.get('uuid'): target['uuid'] = uuidutils.generate_uuid() node = api_utils.replace_node_uuid_with_id(target) new_target = objects.VolumeTarget(context, **target) notify.emit_start_notification(context, new_target, 'create', node_uuid=node.uuid) with notify.handle_error_notification(context, new_target, 'create', node_uuid=node.uuid): new_target.create() notify.emit_end_notification(context, new_target, 'create', node_uuid=node.uuid) # Set the HTTP Location Header api.response.location = link.build_url('volume/targets', new_target.uuid) return convert_with_links(new_target)
def post(self, connector): """Create a new volume connector. :param connector: a volume connector within the request body. :returns: API-serializable volume connector object. :raises: OperationNotPermitted if accessed with specifying a parent node. :raises: VolumeConnectorTypeAndIdAlreadyExists if a volume connector already exists with the same type and connector_id :raises: VolumeConnectorAlreadyExists if a volume connector with the same UUID already exists """ context = pecan.request.context cdict = context.to_policy_values() policy.authorize('baremetal:volume:create', cdict, cdict) if self.parent_node_ident: raise exception.OperationNotPermitted() connector_dict = connector.as_dict() # NOTE(hshiina): UUID is mandatory for notification payload if not connector_dict.get('uuid'): connector_dict['uuid'] = uuidutils.generate_uuid() new_connector = objects.VolumeConnector(context, **connector_dict) notify.emit_start_notification(context, new_connector, 'create', node_uuid=connector.node_uuid) with notify.handle_error_notification(context, new_connector, 'create', node_uuid=connector.node_uuid): new_connector.create() notify.emit_end_notification(context, new_connector, 'create', node_uuid=connector.node_uuid) # Set the HTTP Location Header pecan.response.location = link.build_url('volume/connectors', new_connector.uuid) return VolumeConnector.convert_with_links(new_connector)
def post(self, node): """Create a new node. :param node: a node within the request body. """ if self.from_chassis: raise exception.OperationNotPermitted # NOTE(deva): get_topic_for checks if node.driver is in the hash ring # and raises NoValidHost if it is not. # We need to ensure that node has a UUID before it can # be mapped onto the hash ring. if not node.uuid: node.uuid = uuidutils.generate_uuid() try: pecan.request.rpcapi.get_topic_for(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 # Verify that if we're creating a new node with a 'name' set # that it is a valid name if node.name: if not api_utils.allow_node_logical_names(): raise exception.NotAcceptable() if not api_utils.is_valid_node_name(node.name): msg = _("Cannot create node with invalid name %(name)s") raise wsme.exc.ClientSideError(msg % {'name': node.name}, status_code=400) node.provision_state = api_utils.initial_node_provision_state() new_node = objects.Node(pecan.request.context, **node.as_dict()) new_node.create() # Set the HTTP Location Header pecan.response.location = link.build_url('nodes', new_node.uuid) return Node.convert_with_links(new_node)
def post(self, port): """Create a new port. :param port: a port within the request body. """ if self.from_nodes: raise exception.OperationNotPermitted if not (api_utils.allow_node_name()): if port.node_uuid == wtypes.Unset: e = exception.BadRequest() e.code = 400 raise e new_port = objects.Port(pecan.request.context, **port.as_dict()) new_port.create() # Set the HTTP Location Header pecan.response.location = link.build_url("ports", new_port.uuid) return Port.convert_with_links(new_port)
def post(self, chassis): """Create a new chassis. :param chassis: a chassis within the request body. """ context = api.request.context api_utils.check_policy('baremetal:chassis:create') # NOTE(yuriyz): UUID is mandatory for notifications payload if not chassis.get('uuid'): chassis['uuid'] = uuidutils.generate_uuid() new_chassis = objects.Chassis(context, **chassis) notify.emit_start_notification(context, new_chassis, 'create') with notify.handle_error_notification(context, new_chassis, 'create'): new_chassis.create() notify.emit_end_notification(context, new_chassis, 'create') # Set the HTTP Location Header api.response.location = link.build_url('chassis', new_chassis.uuid) return convert_with_links(new_chassis)
def post(self, port): """Create a new port. :param port: a port within the request body. :raises: NotAcceptable, HTTPNotFound, Conflict """ cdict = pecan.request.context.to_dict() policy.authorize('baremetal:port:create', cdict, cdict) if self.parent_node_ident or self.parent_portgroup_ident: raise exception.OperationNotPermitted() pdict = port.as_dict() if (not api_utils.allow_port_advanced_net_fields() and set(pdict).intersection(self.advanced_net_fields)): raise exception.NotAcceptable() if (not api_utils.allow_portgroups_subcontrollers() and 'portgroup_uuid' in pdict): raise exception.NotAcceptable() extra = pdict.get('extra') vif = extra.get('vif_port_id') if extra else None if (pdict.get('portgroup_uuid') and (pdict.get('pxe_enabled') or vif)): rpc_pg = objects.Portgroup.get_by_uuid(pecan.request.context, pdict['portgroup_uuid']) if not rpc_pg.standalone_ports_supported: msg = _("Port group %s doesn't support standalone ports. " "This port cannot be created as a member of that " "port group because either 'extra/vif_port_id' " "was specified or 'pxe_enabled' was set to True.") raise exception.Conflict( msg % pdict['portgroup_uuid']) new_port = objects.Port(pecan.request.context, **pdict) new_port.create() # Set the HTTP Location Header pecan.response.location = link.build_url('ports', new_port.uuid) return Port.convert_with_links(new_port)
def post(self, chassis): """Create a new chassis. :param chassis: a chassis within the request body. """ context = pecan.request.context cdict = context.to_policy_values() policy.authorize('baremetal:chassis:create', cdict, cdict) # NOTE(yuriyz): UUID is mandatory for notifications payload if not chassis.uuid: chassis.uuid = uuidutils.generate_uuid() new_chassis = objects.Chassis(context, **chassis.as_dict()) notify.emit_start_notification(context, new_chassis, 'create') with notify.handle_error_notification(context, new_chassis, 'create'): new_chassis.create() notify.emit_end_notification(context, new_chassis, 'create') # Set the HTTP Location Header pecan.response.location = link.build_url('chassis', new_chassis.uuid) return Chassis.convert_with_links(new_chassis)
def post(self, chassis): """Create a new chassis. :param chassis: a chassis within the request body. """ context = api.request.context cdict = context.to_policy_values() policy.authorize('baremetal:chassis:create', cdict, cdict) # NOTE(yuriyz): UUID is mandatory for notifications payload if not chassis.uuid: chassis.uuid = uuidutils.generate_uuid() new_chassis = objects.Chassis(context, **chassis.as_dict()) notify.emit_start_notification(context, new_chassis, 'create') with notify.handle_error_notification(context, new_chassis, 'create'): new_chassis.create() notify.emit_end_notification(context, new_chassis, 'create') # Set the HTTP Location Header api.response.location = link.build_url('chassis', new_chassis.uuid) return Chassis.convert_with_links(new_chassis)
def post(self, port): """Create a new port. :param port: a port within the request body. :raises: NotAcceptable """ if self.from_nodes: raise exception.OperationNotPermitted() pdict = port.as_dict() if not api_utils.allow_port_advanced_net_fields(): if set(pdict).intersection(self.advanced_net_fields): raise exception.NotAcceptable() new_port = objects.Port(pecan.request.context, **pdict) new_port.create() # Set the HTTP Location Header pecan.response.location = link.build_url('ports', new_port.uuid) return Port.convert_with_links(new_port)
def post(self, node): """Create a new node. :param node: a node within the request body. """ if self.from_chassis: raise exception.OperationNotPermitted # NOTE(deva): get_topic_for checks if node.driver is in the hash ring # and raises NoValidHost if it is not. # We need to ensure that node has a UUID before it can # be mapped onto the hash ring. if not node.uuid: node.uuid = uuidutils.generate_uuid() try: pecan.request.rpcapi.get_topic_for(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 # Verify that if we're creating a new node with a 'name' set # that it is a valid name if node.name: if not api_utils.allow_node_logical_names(): raise exception.NotAcceptable() if not api_utils.is_valid_node_name(node.name): msg = _("Cannot create node with invalid name %(name)s") raise wsme.exc.ClientSideError(msg % {'name': node.name}, status_code=400) new_node = objects.Node(pecan.request.context, **node.as_dict()) new_node.create() # Set the HTTP Location Header pecan.response.location = link.build_url('nodes', new_node.uuid) return Node.convert_with_links(new_node)
def post(self, target): """Create a new volume target. :param target: a volume target within the request body. :returns: API-serializable volume target object. :raises: OperationNotPermitted if accessed with specifying a parent node. :raises: VolumeTargetBootIndexAlreadyExists if a volume target already exists with the same node ID and boot index :raises: VolumeTargetAlreadyExists if a volume target with the same UUID exists """ context = pecan.request.context cdict = context.to_policy_values() policy.authorize('baremetal:volume:create', cdict, cdict) if self.parent_node_ident: raise exception.OperationNotPermitted() target_dict = target.as_dict() # NOTE(hshiina): UUID is mandatory for notification payload if not target_dict.get('uuid'): target_dict['uuid'] = uuidutils.generate_uuid() new_target = objects.VolumeTarget(context, **target_dict) notify.emit_start_notification(context, new_target, 'create', node_uuid=target.node_uuid) with notify.handle_error_notification(context, new_target, 'create', node_uuid=target.node_uuid): new_target.create() notify.emit_end_notification(context, new_target, 'create', node_uuid=target.node_uuid) # Set the HTTP Location Header pecan.response.location = link.build_url('volume/targets', new_target.uuid) return VolumeTarget.convert_with_links(new_target)
def post(self, template): """Create a new deploy template. :param template: a deploy template within the request body. """ api_utils.check_policy('baremetal:deploy_template:create') context = api.request.context tdict = template.as_dict() # NOTE(mgoddard): UUID is mandatory for notifications payload if not tdict.get('uuid'): tdict['uuid'] = uuidutils.generate_uuid() new_template = objects.DeployTemplate(context, **tdict) notify.emit_start_notification(context, new_template, 'create') with notify.handle_error_notification(context, new_template, 'create'): new_template.create() # Set the HTTP Location Header api.response.location = link.build_url('deploy_templates', new_template.uuid) api_template = DeployTemplate.convert_with_links(new_template) notify.emit_end_notification(context, new_template, 'create') return api_template
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 post(self, port): """Create a new port. :param port: a port within the request body. :raises: NotAcceptable, HTTPNotFound, Conflict """ context = pecan.request.context cdict = context.to_policy_values() policy.authorize('baremetal:port:create', cdict, cdict) if self.parent_node_ident or self.parent_portgroup_ident: raise exception.OperationNotPermitted() pdict = port.as_dict() self._check_allowed_port_fields(pdict) if (port.is_smartnic and not types.locallinkconnectiontype .validate_for_smart_nic(port.local_link_connection)): raise exception.Invalid( "Smart NIC port must have port_id " "and hostname in local_link_connection") create_remotely = pecan.request.rpcapi.can_send_create_port() if (not create_remotely and pdict.get('portgroup_uuid')): # NOTE(mgoddard): In RPC API v1.41, port creation was moved to the # conductor service to facilitate validation of the physical # network field of ports in portgroups. During a rolling upgrade, # the RPCAPI will reject the create_port method, so we need to # create the port locally. If the port is a member of a portgroup, # we are unable to perform the validation and must reject the # request. raise exception.NotAcceptable() vif = api_utils.handle_post_port_like_extra_vif(pdict) if (pdict.get('portgroup_uuid') and (pdict.get('pxe_enabled') or vif)): rpc_pg = objects.Portgroup.get_by_uuid(context, pdict['portgroup_uuid']) if not rpc_pg.standalone_ports_supported: msg = _("Port group %s doesn't support standalone ports. " "This port cannot be created as a member of that " "port group because either 'extra/vif_port_id' " "was specified or 'pxe_enabled' was set to True.") raise exception.Conflict( msg % pdict['portgroup_uuid']) # NOTE(yuriyz): UUID is mandatory for notifications payload if not pdict.get('uuid'): pdict['uuid'] = uuidutils.generate_uuid() rpc_port = objects.Port(context, **pdict) rpc_node = objects.Node.get_by_id(context, rpc_port.node_id) notify_extra = {'node_uuid': port.node_uuid, 'portgroup_uuid': port.portgroup_uuid} notify.emit_start_notification(context, rpc_port, 'create', **notify_extra) with notify.handle_error_notification(context, rpc_port, 'create', **notify_extra): # NOTE(mgoddard): In RPC API v1.41, port creation was moved to the # conductor service to facilitate validation of the physical # network field of ports in portgroups. During a rolling upgrade, # the RPCAPI will reject the create_port method, so we need to # create the port locally. if create_remotely: topic = pecan.request.rpcapi.get_topic_for(rpc_node) new_port = pecan.request.rpcapi.create_port(context, rpc_port, topic) else: rpc_port.create() new_port = rpc_port notify.emit_end_notification(context, new_port, 'create', **notify_extra) # Set the HTTP Location Header pecan.response.location = link.build_url('ports', new_port.uuid) return Port.convert_with_links(new_port)
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 post(self, port): """Create a new port. :param port: a port within the request body. :raises: NotAcceptable, HTTPNotFound, Conflict """ if self.parent_node_ident or self.parent_portgroup_ident: raise exception.OperationNotPermitted() context = api.request.context cdict = context.to_policy_values() policy.authorize('baremetal:port:create', cdict, cdict) pdict = port.as_dict() self._check_allowed_port_fields(pdict) if (port.is_smartnic and not types.locallinkconnectiontype.validate_for_smart_nic( port.local_link_connection)): raise exception.Invalid("Smart NIC port must have port_id " "and hostname in local_link_connection") create_remotely = api.request.rpcapi.can_send_create_port() if (not create_remotely and pdict.get('portgroup_uuid')): # NOTE(mgoddard): In RPC API v1.41, port creation was moved to the # conductor service to facilitate validation of the physical # network field of ports in portgroups. During a rolling upgrade, # the RPCAPI will reject the create_port method, so we need to # create the port locally. If the port is a member of a portgroup, # we are unable to perform the validation and must reject the # request. raise exception.NotAcceptable() vif = api_utils.handle_post_port_like_extra_vif(pdict) if (pdict.get('portgroup_uuid') and (pdict.get('pxe_enabled') or vif)): rpc_pg = objects.Portgroup.get_by_uuid(context, pdict['portgroup_uuid']) if not rpc_pg.standalone_ports_supported: msg = _("Port group %s doesn't support standalone ports. " "This port cannot be created as a member of that " "port group because either 'extra/vif_port_id' " "was specified or 'pxe_enabled' was set to True.") raise exception.Conflict(msg % pdict['portgroup_uuid']) # NOTE(yuriyz): UUID is mandatory for notifications payload if not pdict.get('uuid'): pdict['uuid'] = uuidutils.generate_uuid() rpc_port = objects.Port(context, **pdict) rpc_node = objects.Node.get_by_id(context, rpc_port.node_id) notify_extra = { 'node_uuid': port.node_uuid, 'portgroup_uuid': port.portgroup_uuid } notify.emit_start_notification(context, rpc_port, 'create', **notify_extra) with notify.handle_error_notification(context, rpc_port, 'create', **notify_extra): # NOTE(mgoddard): In RPC API v1.41, port creation was moved to the # conductor service to facilitate validation of the physical # network field of ports in portgroups. During a rolling upgrade, # the RPCAPI will reject the create_port method, so we need to # create the port locally. if create_remotely: topic = api.request.rpcapi.get_topic_for(rpc_node) new_port = api.request.rpcapi.create_port( context, rpc_port, topic) else: rpc_port.create() new_port = rpc_port notify.emit_end_notification(context, new_port, 'create', **notify_extra) # Set the HTTP Location Header api.response.location = link.build_url('ports', new_port.uuid) return Port.convert_with_links(new_port)
def post(self, allocation): """Create a new allocation. :param allocation: an allocation within the request body. """ context = api.request.context allocation = self._authorize_create_allocation(allocation) if (allocation.name and not api_utils.is_valid_logical_name(allocation.name)): msg = _("Cannot create allocation with invalid name " "'%(name)s'") % { 'name': allocation.name } raise exception.Invalid(msg) if allocation.traits: for trait in allocation.traits: api_utils.validate_trait(trait) node = None if allocation.node is not atypes.Unset: if api_utils.allow_allocation_backfill(): try: node = api_utils.get_rpc_node(allocation.node) except exception.NodeNotFound as exc: exc.code = http_client.BAD_REQUEST raise else: msg = _("Cannot set node when creating an allocation " "in this API version") raise exception.Invalid(msg) if not allocation.resource_class: if node: allocation.resource_class = node.resource_class else: msg = _("The resource_class field is mandatory when not " "backfilling") raise exception.Invalid(msg) if allocation.candidate_nodes: # Convert nodes from names to UUIDs and check their validity try: converted = api.request.dbapi.check_node_list( allocation.candidate_nodes) except exception.NodeNotFound as exc: exc.code = http_client.BAD_REQUEST raise else: # Make sure we keep the ordering of candidate nodes. allocation.candidate_nodes = [ converted[ident] for ident in allocation.candidate_nodes ] all_dict = allocation.as_dict() # NOTE(yuriyz): UUID is mandatory for notifications payload if not all_dict.get('uuid'): if node and node.instance_uuid: # When backfilling without UUID requested, assume that the # target instance_uuid is the desired UUID all_dict['uuid'] = node.instance_uuid else: all_dict['uuid'] = uuidutils.generate_uuid() new_allocation = objects.Allocation(context, **all_dict) if node: new_allocation.node_id = node.id topic = api.request.rpcapi.get_topic_for(node) else: topic = api.request.rpcapi.get_random_topic() notify.emit_start_notification(context, new_allocation, 'create') with notify.handle_error_notification(context, new_allocation, 'create'): new_allocation = api.request.rpcapi.create_allocation( context, new_allocation, topic) notify.emit_end_notification(context, new_allocation, 'create') # Set the HTTP Location Header api.response.location = link.build_url('allocations', new_allocation.uuid) return Allocation.convert_with_links(new_allocation)
def post(self, port): """Create a new port. :param port: a port within the request body. :raises: NotAcceptable, HTTPNotFound, Conflict """ if self.parent_node_ident or self.parent_portgroup_ident: raise exception.OperationNotPermitted() context = api.request.context api_utils.check_policy('baremetal:port:create') # NOTE(lucasagomes): Create the node_id attribute on-the-fly # to satisfy the api -> rpc object # conversion. node = api_utils.replace_node_uuid_with_id(port) self._check_allowed_port_fields(port) portgroup = None if port.get('portgroup_uuid'): try: portgroup = objects.Portgroup.get(api.request.context, port.pop('portgroup_uuid')) if portgroup.node_id != node.id: raise exception.BadRequest(_('Port can not be added to a ' 'portgroup belonging to a ' 'different node.')) # NOTE(lucasagomes): Create the portgroup_id attribute # on-the-fly to satisfy the api -> # rpc object conversion. port['portgroup_id'] = portgroup.id except exception.PortgroupNotFound as e: # Change error code because 404 (NotFound) is inappropriate # response for a POST request to create a Port e.code = http_client.BAD_REQUEST # BadRequest raise e if port.get('is_smartnic'): try: api_utils.LOCAL_LINK_SMART_NIC_VALIDATOR( 'local_link_connection', port.get('local_link_connection')) except exception.Invalid: raise exception.Invalid( "Smart NIC port must have port_id " "and hostname in local_link_connection") physical_network = port.get('physical_network') if physical_network is not None and not physical_network: raise exception.Invalid('A non-empty value is required when ' 'setting physical_network') vif = api_utils.handle_post_port_like_extra_vif(port) if (portgroup and (port.get('pxe_enabled') or vif)): if not portgroup.standalone_ports_supported: msg = _("Port group %s doesn't support standalone ports. " "This port cannot be created as a member of that " "port group because either 'extra/vif_port_id' " "was specified or 'pxe_enabled' was set to True.") raise exception.Conflict( msg % portgroup.uuid) # NOTE(yuriyz): UUID is mandatory for notifications payload if not port.get('uuid'): port['uuid'] = uuidutils.generate_uuid() rpc_port = objects.Port(context, **port) notify_extra = { 'node_uuid': node.uuid, 'portgroup_uuid': portgroup and portgroup.uuid or None } notify.emit_start_notification(context, rpc_port, 'create', **notify_extra) with notify.handle_error_notification(context, rpc_port, 'create', **notify_extra): topic = api.request.rpcapi.get_topic_for(node) new_port = api.request.rpcapi.create_port(context, rpc_port, topic) notify.emit_end_notification(context, new_port, 'create', **notify_extra) # Set the HTTP Location Header api.response.location = link.build_url('ports', new_port.uuid) return convert_with_links(new_port)
def post(self, portgroup): """Create a new portgroup. :param portgroup: a portgroup within the request body. """ if not api_utils.allow_portgroups(): raise exception.NotFound() raise_node_not_found = False node = None owner = None lessee = None node_uuid = portgroup.get('node_uuid') try: # The replace_node_uuid_with_id also checks access to the node # and will raise an exception if access is not permitted. node = api_utils.replace_node_uuid_with_id(portgroup) owner = node.owner lessee = node.lessee except exception.NotFound: raise_node_not_found = True # While the rule is for the port, the base object that controls access # is the node. api_utils.check_owner_policy('node', 'baremetal:portgroup:create', owner, lessee=lessee, conceal_node=False) if raise_node_not_found: # Delayed raise of NodeNotFound because we want to check # the access policy first. raise exception.NodeNotFound(node=node_uuid, code=http_client.BAD_REQUEST) context = api.request.context if self.parent_node_ident: raise exception.OperationNotPermitted() if (not api_utils.allow_portgroup_mode_properties() and (portgroup.get('mode') or portgroup.get('properties'))): raise exception.NotAcceptable() if (portgroup.get('name') and not api_utils.is_valid_logical_name(portgroup['name'])): error_msg = _("Cannot create portgroup with invalid name " "'%(name)s'") % { 'name': portgroup['name'] } raise exception.ClientSideError( error_msg, status_code=http_client.BAD_REQUEST) # NOTE(yuriyz): UUID is mandatory for notifications payload if not portgroup.get('uuid'): portgroup['uuid'] = uuidutils.generate_uuid() new_portgroup = objects.Portgroup(context, **portgroup) notify.emit_start_notification(context, new_portgroup, 'create', node_uuid=node.uuid) with notify.handle_error_notification(context, new_portgroup, 'create', node_uuid=node.uuid): new_portgroup.create() notify.emit_end_notification(context, new_portgroup, 'create', node_uuid=node.uuid) # Set the HTTP Location Header api.response.location = link.build_url('portgroups', new_portgroup.uuid) return convert_with_links(new_portgroup)