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 test_owner_unsupported_set_no_remove_default(self): # Physical network set, no change required. allocation = objects.Allocation(self.context, **self.fake_allocation) allocation.owner = None allocation.obj_reset_changes() allocation._convert_to_version("1.0", False) self.assertIsNone(allocation.owner) self.assertEqual({}, allocation.obj_get_changes())
def test_owner_unsupported_set_no_remove_non_default(self): # Physical network set, should be set to default. allocation = objects.Allocation(self.context, **self.fake_allocation) allocation.owner = 'owner1' allocation.obj_reset_changes() allocation._convert_to_version("1.0", False) self.assertIsNone(allocation.owner) self.assertEqual({'owner': None}, allocation.obj_get_changes())
def test_owner_unsupported_set_remove(self): # Physical network set, should be removed. allocation = objects.Allocation(self.context, **self.fake_allocation) allocation.owner = 'owner1' allocation.obj_reset_changes() allocation._convert_to_version("1.0") self.assertNotIn('owner', allocation) self.assertEqual({}, allocation.obj_get_changes())
def test_owner_unsupported_missing(self): # Physical network not set, no change required. allocation = objects.Allocation(self.context, **self.fake_allocation) delattr(allocation, 'owner') allocation.obj_reset_changes() allocation._convert_to_version("1.0") self.assertNotIn('owner', allocation) self.assertEqual({}, allocation.obj_get_changes())
def test_owner_supported_set(self): # Physical network set, no change required. allocation = objects.Allocation(self.context, **self.fake_allocation) allocation.owner = 'owner1' allocation.obj_reset_changes() allocation._convert_to_version("1.1") self.assertEqual('owner1', allocation.owner) self.assertEqual({}, allocation.obj_get_changes())
def test_owner_supported_missing(self): # Physical network not set, should be set to default. allocation = objects.Allocation(self.context, **self.fake_allocation) delattr(allocation, 'owner') allocation.obj_reset_changes() allocation._convert_to_version("1.1") self.assertIsNone(allocation.owner) self.assertEqual({'owner': None}, allocation.obj_get_changes())
def test_create(self): allocation = objects.Allocation(self.context, **self.fake_allocation) with mock.patch.object(self.dbapi, 'create_allocation', autospec=True) as mock_create_allocation: mock_create_allocation.return_value = ( db_utils.get_test_allocation()) allocation.create() args, _kwargs = mock_create_allocation.call_args self.assertEqual(objects.Allocation.VERSION, args[0]['version'])
def get_test_allocation(ctxt, **kw): """Return an Allocation object with appropriate attributes. NOTE: The object leaves the attributes marked as changed, such that a create() could be used to commit it to the DB. """ kw['object_type'] = 'allocation' get_db_allocation_checked = check_keyword_arguments( db_utils.get_test_allocation) db_allocation = get_db_allocation_checked(**kw) # Let DB generate ID if it isn't specified explicitly if 'id' not in kw: del db_allocation['id'] allocation = objects.Allocation(ctxt) for key in db_allocation: setattr(allocation, key, db_allocation[key]) return allocation
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, allocation): """Create a new allocation. :param allocation: an allocation within the request body. """ context = api.request.context cdict = context.to_policy_values() allocation = self._authorize_create_allocation(allocation) if (allocation.get('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) # TODO(TheJulia): We need to likely look at refactoring post # processing for allocations as pep8 says it is a complexity of 19, # although it is not actually that horrible since it is phased out # just modifying/assembling the allocation. Given that, it seems # not great to try for a full method rewrite at the same time as # RBAC work, so the complexity limit is being raised. :( if (CONF.oslo_policy.enforce_new_defaults and cdict.get('system_scope') != 'all'): # if not a system scope originated request, we need to check/apply # an owner - But we can only do this with when new defaults are # enabled. project_id = cdict.get('project_id') req_alloc_owner = allocation.get('owner') if req_alloc_owner: if not api_utils.check_policy_true( 'baremetal:allocation:create_restricted'): if req_alloc_owner != project_id: msg = _("Cannot create allocation with an owner " "Project ID value %(req_owner)s not matching " "the requestor Project ID %(project)s. " "Policy baremetal:allocation:create_restricted" " is required for this capability.") % { 'req_owner': req_alloc_owner, 'project': project_id } raise exception.NotAuthorized(msg) # NOTE(TheJulia): IF not restricted, i.e. else above, # their supplied allocation owner is okay, they are allowed # to provide an override by policy. else: # An allocation owner was not supplied, we need to save one. allocation['owner'] = project_id node = None if allocation.get('node'): if api_utils.allow_allocation_backfill(): try: node = api_utils.get_rpc_node(allocation['node']) api_utils.check_owner_policy( 'node', 'baremetal:node:get', node.owner, node.lessee, conceal_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.get('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.get('candidate_nodes'): # Convert nodes from names to UUIDs and check their validity try: owner = None if not api_utils.check_policy_true( 'baremetal:allocation:create_restricted'): owner = cdict.get('project_id') # Filter the candidate search by the requestor project ID # if any. The result is processes authenticating with system # scope will not be impacted, where as project scoped requests # will need additional authorization. converted = api.request.dbapi.check_node_list( allocation['candidate_nodes'], project=owner) 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'] ] # NOTE(yuriyz): UUID is mandatory for notifications payload if not allocation.get('uuid'): if node and node.instance_uuid: # When backfilling without UUID requested, assume that the # target instance_uuid is the desired UUID allocation['uuid'] = node.instance_uuid else: allocation['uuid'] = uuidutils.generate_uuid() new_allocation = objects.Allocation(context, **allocation) 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 convert_with_links(new_allocation)