def test_trait_create_duplicated_trait(self): trait = trait_obj.Trait(self.ctx) trait.name = 'CUSTOM_TRAIT_A' trait.create() tmp_trait = trait_obj.Trait.get_by_name(self.ctx, 'CUSTOM_TRAIT_A') self.assertEqual('CUSTOM_TRAIT_A', tmp_trait.name) duplicated_trait = trait_obj.Trait(self.ctx) duplicated_trait.name = 'CUSTOM_TRAIT_A' self.assertRaises(exception.TraitExists, duplicated_trait.create)
def put_trait(req): context = req.environ['placement.context'] context.can(policies.TRAITS_UPDATE) want_version = req.environ[microversion.MICROVERSION_ENVIRON] name = util.wsgi_path_item(req.environ, 'name') try: jsonschema.validate(name, schema.CUSTOM_TRAIT) except jsonschema.ValidationError: raise webob.exc.HTTPBadRequest( 'The trait is invalid. A valid trait must be no longer than ' '255 characters, start with the prefix "CUSTOM_" and use ' 'following characters: "A"-"Z", "0"-"9" and "_"') trait = trait_obj.Trait(context) trait.name = name try: trait.create() req.response.status = 201 except exception.TraitExists: # Get the trait that already exists to get last-modified time. if want_version.matches((1, 15)): trait = trait_obj.Trait.get_by_name(context, name) req.response.status = 204 req.response.content_type = None req.response.location = util.trait_url(req.environ, trait) if want_version.matches((1, 15)): req.response.last_modified = trait.created_at req.response.cache_control = 'no-cache' return req.response
def test_trait_destroy(self): t = trait_obj.Trait(self.ctx) t.name = 'CUSTOM_TRAIT_A' t.create() t = trait_obj.Trait.get_by_name(self.ctx, 'CUSTOM_TRAIT_A') self.assertEqual(t.name, 'CUSTOM_TRAIT_A') t.destroy() self.assertRaises(exception.TraitNotFound, trait_obj.Trait.get_by_name, self.ctx, 'CUSTOM_TRAIT_A')
def test_traits_get_all(self): trait_names = ['CUSTOM_TRAIT_A', 'CUSTOM_TRAIT_B', 'CUSTOM_TRAIT_C'] for name in trait_names: t = trait_obj.Trait(self.ctx) t.name = name t.create() self._assert_traits_in(trait_names, trait_obj.get_all(self.ctx))
def test_traits_get_all_with_prefix_filter(self): trait_names = ['CUSTOM_TRAIT_A', 'CUSTOM_TRAIT_B', 'CUSTOM_TRAIT_C'] for name in trait_names: t = trait_obj.Trait(self.ctx) t.name = name t.create() traits = trait_obj.get_all(self.ctx, filters={'prefix': 'CUSTOM'}) self._assert_traits( ['CUSTOM_TRAIT_A', 'CUSTOM_TRAIT_B', 'CUSTOM_TRAIT_C'], traits)
def set_traits(rp, *traits): tlist = [] for tname in traits: try: trait = trait_obj.Trait.get_by_name(rp._context, tname) except exception.TraitNotFound: trait = trait_obj.Trait(rp._context, name=tname) trait.create() tlist.append(trait) rp.set_traits(tlist) return tlist
def test_traits_get_all_with_associated_false(self): rp1 = self._create_provider('fake_resource_provider1') rp2 = self._create_provider('fake_resource_provider2') trait_names = ['CUSTOM_TRAIT_A', 'CUSTOM_TRAIT_B', 'CUSTOM_TRAIT_C'] for name in trait_names: t = trait_obj.Trait(self.ctx) t.name = name t.create() associated_traits = trait_obj.get_all( self.ctx, filters={'name_in': ['CUSTOM_TRAIT_A', 'CUSTOM_TRAIT_B']}) rp1.set_traits(associated_traits) rp2.set_traits(associated_traits) self._assert_traits_in( ['CUSTOM_TRAIT_C'], trait_obj.get_all(self.ctx, filters={'associated': False}))
def put_trait(req): context = req.environ['placement.context'] context.can(policies.TRAITS_UPDATE) want_version = req.environ[microversion.MICROVERSION_ENVIRON] name = util.wsgi_path_item(req.environ, 'name') try: jsonschema.validate(name, schema.CUSTOM_TRAIT) except jsonschema.ValidationError: raise webob.exc.HTTPBadRequest( 'The trait is invalid. A valid trait must be no longer than ' '255 characters, start with the prefix "CUSTOM_" and use ' 'following characters: "A"-"Z", "0"-"9" and "_"') status = 204 try: trait = trait_obj.Trait.get_by_name(context, name) except exception.TraitNotFound: try: trait = trait_obj.Trait(context, name=name) trait.create() status = 201 except exception.TraitExists: # Something just created the trait pass req.response.status = status req.response.content_type = None req.response.location = util.trait_url(req.environ, trait) if want_version.matches((1, 15)): # If the TraitExists exception was hit above, created_at is None # so fall back to now for the last modified header. last_modified = (trait.created_at or timeutils.utcnow(with_timezone=True)) req.response.last_modified = last_modified req.response.cache_control = 'no-cache' return req.response
def _build_provider_summaries(context, usages, prov_traits): """Given a list of dicts of usage information and a map of providers to their associated string traits, returns a dict, keyed by resource provider ID, of ProviderSummary objects. :param context: placement.context.RequestContext object :param usages: A list of dicts with the following format: { 'resource_provider_id': <internal resource provider ID>, 'resource_provider_uuid': <UUID>, 'resource_class_id': <internal resource class ID>, 'total': integer, 'reserved': integer, 'allocation_ratio': float, } :param prov_traits: A dict, keyed by internal resource provider ID, of string trait names associated with that provider """ # Before we go creating provider summary objects, first grab all the # provider information (including root, parent and UUID information) for # all providers involved in our operation rp_ids = set(usage['resource_provider_id'] for usage in usages) provider_ids = rp_obj.provider_ids_from_rp_ids(context, rp_ids) # Build up a dict, keyed by internal resource provider ID, of # ProviderSummary objects containing one or more ProviderSummaryResource # objects representing the resources the provider has inventory for. summaries = {} for usage in usages: rp_id = usage['resource_provider_id'] summary = summaries.get(rp_id) if not summary: pids = provider_ids[rp_id] summary = ProviderSummary( resource_provider=rp_obj.ResourceProvider( context, id=pids.id, uuid=pids.uuid, root_provider_uuid=pids.root_uuid, parent_provider_uuid=pids.parent_uuid), resources=[], ) summaries[rp_id] = summary traits = prov_traits[rp_id] summary.traits = [trait_obj.Trait(context, name=tname) for tname in traits] rc_id = usage['resource_class_id'] if rc_id is None: # NOTE(tetsuro): This provider doesn't have any inventory itself. # But we include this provider in summaries since another # provider in the same tree will be in the "allocation_request". # Let's skip the following and leave "ProviderSummary.resources" # field empty. continue # NOTE(jaypipes): usage['used'] may be None due to the LEFT JOIN of # the usages subquery, so we coerce NULL values to 0 here. It may # also be a Decimal, as that's the type that mysql tends to return # when func.sum is used in a query. We need an int, otherwise later # JSON serialization will not work. used = int(usage['used'] or 0) allocation_ratio = usage['allocation_ratio'] cap = int((usage['total'] - usage['reserved']) * allocation_ratio) rc_name = rc_cache.RC_CACHE.string_from_id(rc_id) rpsr = ProviderSummaryResource( resource_class=rc_name, capacity=cap, used=used, max_unit=usage['max_unit'], ) summary.resources.append(rpsr) return summaries
def test_trait_destroy_with_standard_trait(self): t = trait_obj.Trait(self.ctx) t.id = 1 t.name = 'HW_CPU_X86_AVX' self.assertRaises(exception.TraitCannotDeleteStandard, t.destroy)
def test_trait_get(self): t = trait_obj.Trait(self.ctx) t.name = 'CUSTOM_TRAIT_A' t.create() t = trait_obj.Trait.get_by_name(self.ctx, 'CUSTOM_TRAIT_A') self.assertEqual(t.name, 'CUSTOM_TRAIT_A')
def test_trait_create_without_name_set(self): t = trait_obj.Trait(self.ctx) self.assertRaises(exception.ObjectActionError, t.create)
def test_trait_create_with_id_set(self): t = trait_obj.Trait(self.ctx) t.name = 'CUSTOM_TRAIT_A' t.id = 1 self.assertRaises(exception.ObjectActionError, t.create)
def test_trait_create(self): t = trait_obj.Trait(self.ctx) t.name = 'CUSTOM_TRAIT_A' t.create() self.assertIsNotNone(t.id) self.assertEqual(t.name, 'CUSTOM_TRAIT_A')
def _build_provider_summaries(context, usages, prov_traits): """Given a list of dicts of usage information and a map of providers to their associated string traits, returns a dict, keyed by resource provider UUID, of ProviderSummary objects. :param context: placement.context.RequestContext object :param usages: A list of dicts with the following format: { "resource_provider_uuid": <UUID>, "resource_class_name": <resource class name>, "total": integer, "reserved": integer, "allocation_ratio": float, "used": integer, } :param prov_traits: A dict, keyed by resource provider UUID, of string trait names associated with that provider """ # Before we go creating provider summary objects, first grab all the # provider information (including root, parent and UUID information) for # all providers involved in our operation rp_uuids = list(set(usage["resource_provider_uuid"] for usage in usages)) provider_dict = res_ctx.provider_uuids_from_rp_uuids(context, rp_uuids) provider_uuids = list(provider_dict.keys()) # Build up a dict, keyed by resource provider UUID, of ProviderSummary # objects containing one or more ProviderSummaryResource objects # representing the resources the provider has inventory for. summaries = {} for usage in usages: rp_uuid = usage["resource_provider_uuid"] summary = summaries.get(rp_uuid) if not summary: puuids = provider_dict[rp_uuid] summary = ProviderSummary( resource_provider=rp_obj.ResourceProvider( context, uuid=puuids.uuid, root_provider_uuid=puuids.root_uuid, parent_provider_uuid=puuids.parent_uuid), resources=[], ) summaries[rp_uuid] = summary traits = prov_traits[rp_uuid] summary.traits = [trait_obj.Trait(context, name=tname) for tname in traits] rc_name = usage['resource_class_name'] if rc_name is None: # NOTE(tetsuro): This provider doesn't have any inventory itself. # But we include this provider in summaries since another # provider in the same tree will be in the "allocation_request". # Let's skip the following and leave "ProviderSummary.resources" # field empty. continue used = int(usage["used"] or 0) allocation_ratio = usage["allocation_ratio"] cap = int((usage["total"] - usage["reserved"]) * allocation_ratio) rpsr = ProviderSummaryResource( resource_class=rc_name, capacity=cap, used=used, max_unit=usage["max_unit"], ) summary.resources.append(rpsr) return summaries