summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2017-06-19 21:57:08 +0000
committerGerrit Code Review <review@openstack.org>2017-06-19 21:57:08 +0000
commit0249e07ac6b2262d4b97e774d7d085b9efb67d88 (patch)
treef15cc7775bfec1b14093d42b9ee4be8384e1db9a
parentac41395f11002c09e7cd1c4a26632e73bc1bc952 (diff)
parent604f17d60cf37ba34aa8a02b6113474faf7eafcc (diff)
Merge "placement: produce set of allocation candidates"
-rw-r--r--nova/objects/resource_provider.py371
-rw-r--r--nova/tests/functional/db/test_resource_provider.py382
2 files changed, 753 insertions, 0 deletions
diff --git a/nova/objects/resource_provider.py b/nova/objects/resource_provider.py
index 51b24af..3b04410 100644
--- a/nova/objects/resource_provider.py
+++ b/nova/objects/resource_provider.py
@@ -10,6 +10,7 @@
10# License for the specific language governing permissions and limitations 10# License for the specific language governing permissions and limitations
11# under the License. 11# under the License.
12 12
13import collections
13import copy 14import copy
14# NOTE(cdent): The resource provider objects are designed to never be 15# NOTE(cdent): The resource provider objects are designed to never be
15# used over RPC. Remote manipulation is done with the placement HTTP 16# used over RPC. Remote manipulation is done with the placement HTTP
@@ -2170,3 +2171,373 @@ class TraitList(base.ObjectListBase, base.NovaObject):
2170 def get_all(cls, context, filters=None): 2171 def get_all(cls, context, filters=None):
2171 db_traits = cls._get_all_from_db(context, filters) 2172 db_traits = cls._get_all_from_db(context, filters)
2172 return base.obj_make_list(context, cls(context), Trait, db_traits) 2173 return base.obj_make_list(context, cls(context), Trait, db_traits)
2174
2175
2176@base.NovaObjectRegistry.register_if(False)
2177class AllocationRequestResource(base.NovaObject):
2178 # 1.0: Initial version
2179 VERSION = '1.0'
2180
2181 fields = {
2182 'resource_provider': fields.ObjectField('ResourceProvider'),
2183 'resource_class': fields.ResourceClassField(read_only=True),
2184 'amount': fields.NonNegativeIntegerField(),
2185 }
2186
2187
2188@base.NovaObjectRegistry.register_if(False)
2189class AllocationRequest(base.NovaObject):
2190 # 1.0: Initial version
2191 VERSION = '1.0'
2192
2193 fields = {
2194 'resource_requests': fields.ListOfObjectsField(
2195 'AllocationRequestResource'
2196 ),
2197 }
2198
2199
2200@base.NovaObjectRegistry.register_if(False)
2201class ProviderSummaryResource(base.NovaObject):
2202 # 1.0: Initial version
2203 VERSION = '1.0'
2204
2205 fields = {
2206 'resource_class': fields.ResourceClassField(read_only=True),
2207 'capacity': fields.NonNegativeIntegerField(),
2208 'used': fields.NonNegativeIntegerField(),
2209 }
2210
2211
2212@base.NovaObjectRegistry.register_if(False)
2213class ProviderSummary(base.NovaObject):
2214 # 1.0: Initial version
2215 VERSION = '1.0'
2216
2217 fields = {
2218 'resource_provider': fields.ObjectField('ResourceProvider'),
2219 'resources': fields.ListOfObjectsField('ProviderSummaryResource'),
2220 'traits': fields.ListOfObjectsField('Trait'),
2221 }
2222
2223
2224@db_api.api_context_manager.reader
2225def _get_usages_by_provider_and_rc(ctx, rp_ids, rc_ids):
2226 """Returns a row iterator of usage records grouped by resource provider ID
2227 and resource class ID for all resource providers and resource classes
2228 involved in our request
2229 """
2230 # We build up a SQL expression that looks like this:
2231 # SELECT
2232 # rp.id as resource_provider_id
2233 # , rp.uuid as resource_provider_uuid
2234 # , inv.resource_class_id
2235 # , inv.total
2236 # , inv.reserved
2237 # , inv.allocation_ratio
2238 # , usage.used
2239 # FROM resource_providers AS rp
2240 # JOIN inventories AS inv
2241 # ON rp.id = inv.resource_provider_id
2242 # LEFT JOIN (
2243 # SELECT resource_provider_id, resource_class_id, SUM(used) as used
2244 # FROM allocations
2245 # WHERE resource_provider_id IN ($rp_ids)
2246 # AND resource_class_id IN ($rc_ids)
2247 # GROUP BY resource_provider_id, resource_class_id
2248 # )
2249 # AS usages
2250 # ON inv.resource_provider_id = usage.resource_provider_id
2251 # AND inv.resource_class_id = usage.resource_class_id
2252 # WHERE rp.id IN ($rp_ids)
2253 # AND inv.resource_class_id IN ($rc_ids)
2254 rpt = sa.alias(_RP_TBL, name="rp")
2255 inv = sa.alias(_INV_TBL, name="inv")
2256 # Build our derived table (subquery in the FROM clause) that sums used
2257 # amounts for resource provider and resource class
2258 usage = sa.alias(
2259 sa.select([
2260 _ALLOC_TBL.c.resource_provider_id,
2261 _ALLOC_TBL.c.resource_class_id,
2262 sql.func.sum(_ALLOC_TBL.c.used).label('used'),
2263 ]).where(
2264 sa.and_(
2265 _ALLOC_TBL.c.resource_provider_id.in_(rp_ids),
2266 _ALLOC_TBL.c.resource_class_id.in_(rc_ids),
2267 ),
2268 ).group_by(
2269 _ALLOC_TBL.c.resource_provider_id,
2270 _ALLOC_TBL.c.resource_class_id
2271 ),
2272 name='usage',
2273 )
2274 # Build a join between the resource providers and inventories table
2275 rpt_inv_join = sa.join(rpt, inv, rpt.c.id == inv.c.resource_provider_id)
2276 # And then join to the derived table of usages
2277 usage_join = sa.outerjoin(
2278 rpt_inv_join,
2279 usage,
2280 sa.and_(
2281 usage.c.resource_provider_id == inv.c.resource_provider_id,
2282 usage.c.resource_class_id == inv.c.resource_class_id,
2283 ),
2284 )
2285 query = sa.select([
2286 rpt.c.id.label("resource_provider_id"),
2287 rpt.c.uuid.label("resource_provider_uuid"),
2288 inv.c.resource_class_id,
2289 inv.c.total,
2290 inv.c.reserved,
2291 inv.c.allocation_ratio,
2292 usage.c.used,
2293 ]).select_from(usage_join)
2294 return ctx.session.execute(query).fetchall()
2295
2296
2297@base.NovaObjectRegistry.register_if(False)
2298class AllocationCandidates(base.NovaObject):
2299 """The AllocationCandidates object is a collection of possible allocations
2300 that match some request for resources, along with some summary information
2301 about the resource providers involved in these allocation candidates.
2302 """
2303 # 1.0: Initial version
2304 VERSION = '1.0'
2305
2306 fields = {
2307 # A collection of allocation possibilities that can be attempted by the
2308 # caller that would, at the time of calling, meet the requested
2309 # resource constraints
2310 'allocation_requests': fields.ListOfObjectsField('AllocationRequest'),
2311 # Information about usage and inventory that relate to any provider
2312 # contained in any of the AllocationRequest objects in the
2313 # allocation_requests field
2314 'provider_summaries': fields.ListOfObjectsField('ProviderSummary'),
2315 }
2316
2317 @classmethod
2318 def get_by_filters(cls, context, filters):
2319 """Returns an AllocationCandidates object containing all resource
2320 providers matching a set of supplied resource constraints, with a set
2321 of allocation requests constructed from that list of resource
2322 providers.
2323
2324 :param filters: A dict of filters containing one or more of the
2325 following keys:
2326
2327 'resources': A dict, keyed by resource class name, of amounts of
2328 that resource being requested. The resource provider
2329 must either have capacity for the amount being
2330 requested or be associated via aggregate to a provider
2331 that shares this resource and has capacity for the
2332 requested amount.
2333 """
2334 _ensure_rc_cache(context)
2335 alloc_reqs, provider_summaries = cls._get_by_filters(context, filters)
2336 return cls(
2337 context,
2338 allocation_requests=alloc_reqs,
2339 provider_summaries=provider_summaries,
2340 )
2341
2342 # TODO(jaypipes): See what we can pull out of here into helper functions to
2343 # minimize the complexity of this method.
2344 @staticmethod
2345 @db_api.api_context_manager.reader
2346 def _get_by_filters(context, filters):
2347 # We first get the list of "root providers" that either have the
2348 # requested resources or are associated with the providers that
2349 # share one or more of the requested resource(s)
2350 resources = filters.get('resources')
2351 if not resources:
2352 raise ValueError(_("Supply a resources collection in filters."))
2353
2354 # Transform resource string names to internal integer IDs
2355 resources = {
2356 _RC_CACHE.id_from_string(key): value
2357 for key, value in resources.items()
2358 }
2359
2360 roots = [r[0] for r in _get_all_with_shared(context, resources)]
2361
2362 if not roots:
2363 return [], []
2364
2365 # Contains a set of resource provider IDs for each resource class
2366 # requested
2367 sharing_providers = {
2368 rc_id: _get_providers_with_shared_capacity(context, rc_id, amount)
2369 for rc_id, amount in resources.items()
2370 }
2371 # We need to grab usage information for all the providers identified as
2372 # potentially fulfilling part of the resource request. This includes
2373 # "root providers" returned from _get_all_with_shared() as well as all
2374 # the providers of shared resources. Here, we simply grab a unique set
2375 # of all those resource provider internal IDs by set union'ing them
2376 # together
2377 all_rp_ids = set(roots)
2378 for rps in sharing_providers.values():
2379 all_rp_ids |= set(rps)
2380
2381 # Grab usage summaries for each provider (local or sharing) and
2382 # resource class requested
2383 usages = _get_usages_by_provider_and_rc(
2384 context,
2385 all_rp_ids,
2386 list(resources.keys()),
2387 )
2388
2389 # Build up a dict, keyed by internal resource provider ID, of usage
2390 # information from which we will then build both allocation request and
2391 # provider summary information
2392 summaries = {}
2393 for usage in usages:
2394 u_rp_id = usage['resource_provider_id']
2395 u_rp_uuid = usage['resource_provider_uuid']
2396 u_rc_id = usage['resource_class_id']
2397 # NOTE(jaypipes): usage['used'] may be None due to the LEFT JOIN of
2398 # the usages subquery, so we coerce NULL values to 0 here.
2399 used = usage['used'] or 0
2400 allocation_ratio = usage['allocation_ratio']
2401 cap = int((usage['total'] - usage['reserved']) * allocation_ratio)
2402
2403 summary = summaries.get(u_rp_id)
2404 if not summary:
2405 summary = {
2406 'uuid': u_rp_uuid,
2407 'resources': {},
2408 # TODO(jaypipes): Fill in the provider's traits...
2409 'traits': [],
2410 }
2411 summaries[u_rp_id] = summary
2412 summary['resources'][u_rc_id] = {
2413 'capacity': cap,
2414 'used': used,
2415 }
2416
2417 # Next, build up a list of allocation requests. These allocation
2418 # requests are AllocationRequest objects, containing resource provider
2419 # UUIDs, resource class names and amounts to consume from that resource
2420 # provider
2421 alloc_request_objs = []
2422
2423 # Build a dict, keyed by resource class ID, of
2424 # AllocationRequestResource objects that represent each resource
2425 # provider for a shared resource
2426 sharing_resource_requests = collections.defaultdict(list)
2427 for shared_rc_id in sharing_providers.keys():
2428 sharing = sharing_providers[shared_rc_id]
2429 for sharing_rp_id in sharing:
2430 sharing_summary = summaries[sharing_rp_id]
2431 sharing_rp_uuid = sharing_summary['uuid']
2432 sharing_res_req = AllocationRequestResource(
2433 context,
2434 resource_provider=ResourceProvider(
2435 context,
2436 uuid=sharing_rp_uuid,
2437 ),
2438 resource_class=_RC_CACHE.string_from_id(shared_rc_id),
2439 amount=resources[shared_rc_id],
2440 )
2441 sharing_resource_requests[shared_rc_id].append(sharing_res_req)
2442
2443 for root_rp_id in roots:
2444 root_summary = summaries[root_rp_id]
2445 root_rp_uuid = root_summary['uuid']
2446 local_resources = set(
2447 rc_id for rc_id in resources.keys()
2448 if rc_id in root_summary['resources']
2449 )
2450 shared_resources = set(
2451 rc_id for rc_id in resources.keys()
2452 if rc_id not in root_summary['resources']
2453 )
2454 # Determine if the root provider actually has all the resources
2455 # requested. If not, we need to add an AllocationRequest
2456 # alternative containing this resource for each sharing provider
2457 has_all = len(shared_resources) == 0
2458 if has_all:
2459 resource_requests = [
2460 AllocationRequestResource(
2461 context,
2462 resource_provider=ResourceProvider(
2463 context,
2464 uuid=root_rp_uuid,
2465 ),
2466 resource_class=_RC_CACHE.string_from_id(rc_id),
2467 amount=amount,
2468 ) for rc_id, amount in resources.items()
2469 ]
2470 req_obj = AllocationRequest(
2471 context,
2472 resource_requests=resource_requests,
2473 )
2474 alloc_request_objs.append(req_obj)
2475
2476 # If there are no resource providers sharing resources involved in
2477 # this request, there's no point building a set of allocation
2478 # requests that involve resource providers other than the "root
2479 # providers" that have all the local resources on them
2480 if not sharing_resource_requests:
2481 continue
2482
2483 # add an AllocationRequest that includes local resources from the
2484 # root provider and shared resources from each sharing provider of
2485 # that resource class
2486 non_shared_resources = local_resources - shared_resources
2487 non_shared_requests = [
2488 AllocationRequestResource(
2489 context,
2490 resource_provider=ResourceProvider(
2491 context,
2492 uuid=root_rp_uuid,
2493 ),
2494 resource_class=_RC_CACHE.string_from_id(rc_id),
2495 amount=amount,
2496 ) for rc_id, amount in resources.items()
2497 if rc_id in non_shared_resources
2498 ]
2499 sharing_request_tuples = zip(
2500 sharing_resource_requests[shared_rc_id]
2501 for shared_rc_id in shared_resources
2502 )
2503 # sharing_request_tuples will now contain a list of tuples with the
2504 # tuples being AllocationRequestResource objects for each provider
2505 # of a shared resource
2506 for shared_request_tuple in sharing_request_tuples:
2507 shared_requests = list(*shared_request_tuple)
2508 resource_requests = non_shared_requests + shared_requests
2509 req_obj = AllocationRequest(
2510 context,
2511 resource_requests=resource_requests,
2512 )
2513 alloc_request_objs.append(req_obj)
2514
2515 # Finally, construct the object representations for the provider
2516 # summaries we built above. These summaries may be used by the
2517 # scheduler (or any other caller) to sort and weigh for its eventual
2518 # placement and claim decisions
2519 summary_objs = []
2520 for rp_id, summary in summaries.items():
2521 rp_uuid = summary['uuid']
2522 rps_resources = []
2523 for rc_id, usage in summary['resources'].items():
2524 rc_name = _RC_CACHE.string_from_id(rc_id)
2525 rpsr_obj = ProviderSummaryResource(
2526 context,
2527 resource_class=rc_name,
2528 capacity=usage['capacity'],
2529 used=usage['used'],
2530 )
2531 rps_resources.append(rpsr_obj)
2532
2533 summary_obj = ProviderSummary(
2534 context,
2535 resource_provider=ResourceProvider(
2536 context,
2537 uuid=rp_uuid,
2538 ),
2539 resources=rps_resources,
2540 )
2541 summary_objs.append(summary_obj)
2542
2543 return alloc_request_objs, summary_objs
diff --git a/nova/tests/functional/db/test_resource_provider.py b/nova/tests/functional/db/test_resource_provider.py
index c95bc88..a9db02f 100644
--- a/nova/tests/functional/db/test_resource_provider.py
+++ b/nova/tests/functional/db/test_resource_provider.py
@@ -2207,3 +2207,385 @@ class SharedProviderTestCase(ResourceProviderBaseCase):
2207 ) 2207 )
2208 got_ids = [rp.id for rp in got_rps] 2208 got_ids = [rp.id for rp in got_rps]
2209 self.assertEqual([cn1.id], got_ids) 2209 self.assertEqual([cn1.id], got_ids)
2210
2211
2212class AllocationCandidatesTestCase(ResourceProviderBaseCase):
2213 """Tests a variety of scenarios with both shared and non-shared resource
2214 providers that the AllocationCandidates.get_by_filters() method returns a
2215 set of alternative allocation requests and provider summaries that may be
2216 used by the scheduler to sort/weigh the options it has for claiming
2217 resources against providers.
2218 """
2219
2220 def _requested_resources(self):
2221 # The resources we will request
2222 resources = {
2223 fields.ResourceClass.VCPU: 1,
2224 fields.ResourceClass.MEMORY_MB: 64,
2225 fields.ResourceClass.DISK_GB: 100,
2226 }
2227 return resources
2228
2229 def _find_summary_for_provider(self, p_sums, rp_uuid):
2230 for summary in p_sums:
2231 if summary.resource_provider.uuid == rp_uuid:
2232 return summary
2233
2234 def _find_summary_for_resource(self, p_sum, rc_name):
2235 for resource in p_sum.resources:
2236 if resource.resource_class == rc_name:
2237 return resource
2238
2239 def _find_requests_for_provider(self, reqs, rp_uuid):
2240 res = []
2241 for ar in reqs:
2242 for rr in ar.resource_requests:
2243 if rr.resource_provider.uuid == rp_uuid:
2244 res.append(rr)
2245 return res
2246
2247 def _find_request_for_resource(self, res_reqs, rc_name):
2248 for rr in res_reqs:
2249 if rr.resource_class == rc_name:
2250 return rr
2251
2252 def test_all_local(self):
2253 """Create some resource providers that can satisfy the request for
2254 resources with local (non-shared) resources and verify that the
2255 allocation requests returned by AllocationCandidates correspond with
2256 each of these resource providers.
2257 """
2258 # Create two compute node providers with VCPU, RAM and local disk
2259 cn1_uuid = uuidsentinel.cn1
2260 cn1 = objects.ResourceProvider(
2261 self.context,
2262 name='cn1',
2263 uuid=cn1_uuid,
2264 )
2265 cn1.create()
2266
2267 cn2_uuid = uuidsentinel.cn2
2268 cn2 = objects.ResourceProvider(
2269 self.context,
2270 name='cn2',
2271 uuid=cn2_uuid,
2272 )
2273 cn2.create()
2274
2275 for cn in (cn1, cn2):
2276 vcpu = objects.Inventory(
2277 resource_provider=cn,
2278 resource_class=fields.ResourceClass.VCPU,
2279 total=24,
2280 reserved=0,
2281 min_unit=1,
2282 max_unit=24,
2283 step_size=1,
2284 allocation_ratio=16.0,
2285 )
2286 memory_mb = objects.Inventory(
2287 resource_provider=cn,
2288 resource_class=fields.ResourceClass.MEMORY_MB,
2289 total=32768,
2290 reserved=0,
2291 min_unit=64,
2292 max_unit=32768,
2293 step_size=64,
2294 allocation_ratio=1.5,
2295 )
2296 disk_gb = objects.Inventory(
2297 resource_provider=cn,
2298 resource_class=fields.ResourceClass.DISK_GB,
2299 total=2000,
2300 reserved=100,
2301 min_unit=10,
2302 max_unit=100,
2303 step_size=10,
2304 allocation_ratio=1.0,
2305 )
2306 disk_gb.obj_set_defaults()
2307 inv_list = objects.InventoryList(objects=[
2308 vcpu,
2309 memory_mb,
2310 disk_gb,
2311 ])
2312 cn.set_inventory(inv_list)
2313
2314 # Ask for the alternative placement possibilities and verify each
2315 # provider is returned
2316 requested_resources = self._requested_resources()
2317 p_alts = rp_obj.AllocationCandidates.get_by_filters(
2318 self.context,
2319 filters={
2320 'resources': requested_resources,
2321 },
2322 )
2323
2324 # Verify the provider summary information indicates 0 usage and
2325 # capacity calculated from above inventory numbers for both compute
2326 # nodes
2327 p_sums = p_alts.provider_summaries
2328 self.assertEqual(2, len(p_sums))
2329
2330 p_sum_rps = set([ps.resource_provider.uuid for ps in p_sums])
2331
2332 self.assertEqual(set([cn1_uuid, cn2_uuid]), p_sum_rps)
2333
2334 cn1_p_sum = self._find_summary_for_provider(p_sums, cn1_uuid)
2335 self.assertIsNotNone(cn1_p_sum)
2336 self.assertEqual(3, len(cn1_p_sum.resources))
2337
2338 cn1_p_sum_vcpu = self._find_summary_for_resource(cn1_p_sum, 'VCPU')
2339 self.assertIsNotNone(cn1_p_sum_vcpu)
2340
2341 expected_capacity = (24 * 16.0)
2342 self.assertEqual(expected_capacity, cn1_p_sum_vcpu.capacity)
2343 self.assertEqual(0, cn1_p_sum_vcpu.used)
2344
2345 # Let's verify the disk for the second compute node
2346 cn2_p_sum = self._find_summary_for_provider(p_sums, cn2_uuid)
2347 self.assertIsNotNone(cn2_p_sum)
2348 self.assertEqual(3, len(cn2_p_sum.resources))
2349
2350 cn2_p_sum_disk = self._find_summary_for_resource(cn2_p_sum, 'DISK_GB')
2351 self.assertIsNotNone(cn2_p_sum_disk)
2352
2353 expected_capacity = ((2000 - 100) * 1.0)
2354 self.assertEqual(expected_capacity, cn2_p_sum_disk.capacity)
2355 self.assertEqual(0, cn2_p_sum_disk.used)
2356
2357 # Verify the allocation requests that are returned. There should be 2
2358 # allocation requests, one for each compute node, containing 3
2359 # resources in each allocation request, one each for VCPU, RAM, and
2360 # disk. The amounts of the requests should correspond to the requested
2361 # resource amounts in the filter:resources dict passed to
2362 # AllocationCandidates.get_by_filters().
2363 a_reqs = p_alts.allocation_requests
2364 self.assertEqual(2, len(a_reqs))
2365
2366 a_req_rps = set()
2367 for ar in a_reqs:
2368 for rr in ar.resource_requests:
2369 a_req_rps.add(rr.resource_provider.uuid)
2370
2371 self.assertEqual(set([cn1_uuid, cn2_uuid]), a_req_rps)
2372
2373 cn1_reqs = self._find_requests_for_provider(a_reqs, cn1_uuid)
2374 # There should be a req object for each resource we have requested
2375 self.assertEqual(3, len(cn1_reqs))
2376
2377 cn1_req_vcpu = self._find_request_for_resource(cn1_reqs, 'VCPU')
2378 self.assertIsNotNone(cn1_req_vcpu)
2379 self.assertEqual(requested_resources['VCPU'], cn1_req_vcpu.amount)
2380
2381 cn2_req_disk = self._find_request_for_resource(cn1_reqs, 'DISK_GB')
2382 self.assertIsNotNone(cn2_req_disk)
2383 self.assertEqual(requested_resources['DISK_GB'], cn2_req_disk.amount)
2384
2385 def test_local_with_shared_disk(self):
2386 """Create some resource providers that can satisfy the request for
2387 resources with local VCPU and MEMORY_MB but rely on a shared storage
2388 pool to satisfy DISK_GB and verify that the allocation requests
2389 returned by AllocationCandidates have DISK_GB served up by the shared
2390 storage pool resource provider and VCPU/MEMORY_MB by the compute node
2391 providers
2392 """
2393 # The aggregate that will be associated to everything...
2394 agg_uuid = uuidsentinel.agg
2395
2396 # Create two compute node providers with VCPU, RAM and NO local disk
2397 cn1_uuid = uuidsentinel.cn1
2398 cn1 = objects.ResourceProvider(
2399 self.context,
2400 name='cn1',
2401 uuid=cn1_uuid,
2402 )
2403 cn1.create()
2404
2405 cn2_uuid = uuidsentinel.cn2
2406 cn2 = objects.ResourceProvider(
2407 self.context,
2408 name='cn2',
2409 uuid=cn2_uuid,
2410 )
2411 cn2.create()
2412
2413 # Populate the two compute node providers with inventory, sans DISK_GB
2414 for cn in (cn1, cn2):
2415 vcpu = objects.Inventory(
2416 resource_provider=cn,
2417 resource_class=fields.ResourceClass.VCPU,
2418 total=24,
2419 reserved=0,
2420 min_unit=1,
2421 max_unit=24,
2422 step_size=1,
2423 allocation_ratio=16.0,
2424 )
2425 memory_mb = objects.Inventory(
2426 resource_provider=cn,
2427 resource_class=fields.ResourceClass.MEMORY_MB,
2428 total=1024,
2429 reserved=0,
2430 min_unit=64,
2431 max_unit=1024,
2432 step_size=1,
2433 allocation_ratio=1.5,
2434 )
2435 inv_list = objects.InventoryList(objects=[vcpu, memory_mb])
2436 cn.set_inventory(inv_list)
2437
2438 # Create the shared storage pool
2439 ss_uuid = uuidsentinel.ss
2440 ss = objects.ResourceProvider(
2441 self.context,
2442 name='shared storage',
2443 uuid=ss_uuid,
2444 )
2445 ss.create()
2446
2447 # Give the shared storage pool some inventory of DISK_GB
2448 disk_gb = objects.Inventory(
2449 resource_provider=ss,
2450 resource_class=fields.ResourceClass.DISK_GB,
2451 total=2000,
2452 reserved=100,
2453 min_unit=10,
2454 max_unit=100,
2455 step_size=1,
2456 allocation_ratio=1.0,
2457 )
2458 inv_list = objects.InventoryList(objects=[disk_gb])
2459 ss.set_inventory(inv_list)
2460
2461 # Mark the shared storage pool as having inventory shared among any
2462 # provider associated via aggregate
2463 t = objects.Trait(
2464 self.context,
2465 name="MISC_SHARES_VIA_AGGREGATE",
2466 )
2467 # TODO(jaypipes): Once MISC_SHARES_VIA_AGGREGATE is a standard
2468 # os-traits trait, we won't need to create() here. Instead, we will
2469 # just do:
2470 # t = objects.Trait.get_by_name(
2471 # self.context,
2472 # "MISC_SHARES_VIA_AGGREGATE",
2473 # )
2474 t.create()
2475 ss.set_traits(objects.TraitList(objects=[t]))
2476
2477 # Now associate the shared storage pool and both compute nodes with the
2478 # same aggregate
2479 cn1.set_aggregates([agg_uuid])
2480 cn2.set_aggregates([agg_uuid])
2481 ss.set_aggregates([agg_uuid])
2482
2483 # Ask for the alternative placement possibilities and verify each
2484 # compute node provider is listed in the allocation requests as well as
2485 # the shared storage pool provider
2486 requested_resources = self._requested_resources()
2487 p_alts = rp_obj.AllocationCandidates.get_by_filters(
2488 self.context,
2489 filters={
2490 'resources': requested_resources,
2491 },
2492 )
2493
2494 # Verify the provider summary information indicates 0 usage and
2495 # capacity calculated from above inventory numbers for both compute
2496 # nodes
2497 p_sums = p_alts.provider_summaries
2498 self.assertEqual(3, len(p_sums))
2499
2500 p_sum_rps = set([ps.resource_provider.uuid for ps in p_sums])
2501
2502 self.assertEqual(set([cn1_uuid, cn2_uuid, ss_uuid]), p_sum_rps)
2503
2504 cn1_p_sum = self._find_summary_for_provider(p_sums, cn1_uuid)
2505 self.assertIsNotNone(cn1_p_sum)
2506 self.assertEqual(2, len(cn1_p_sum.resources))
2507
2508 cn1_p_sum_vcpu = self._find_summary_for_resource(cn1_p_sum, 'VCPU')
2509 self.assertIsNotNone(cn1_p_sum_vcpu)
2510
2511 expected_capacity = (24 * 16.0)
2512 self.assertEqual(expected_capacity, cn1_p_sum_vcpu.capacity)
2513 self.assertEqual(0, cn1_p_sum_vcpu.used)
2514
2515 # Let's verify memory for the second compute node
2516 cn2_p_sum = self._find_summary_for_provider(p_sums, cn2_uuid)
2517 self.assertIsNotNone(cn2_p_sum)
2518 self.assertEqual(2, len(cn2_p_sum.resources))
2519
2520 cn2_p_sum_ram = self._find_summary_for_resource(cn2_p_sum, 'MEMORY_MB')
2521 self.assertIsNotNone(cn2_p_sum_ram)
2522
2523 expected_capacity = (1024 * 1.5)
2524 self.assertEqual(expected_capacity, cn2_p_sum_ram.capacity)
2525 self.assertEqual(0, cn2_p_sum_ram.used)
2526
2527 # Let's verify only diks for the shared storage pool
2528 ss_p_sum = self._find_summary_for_provider(p_sums, ss_uuid)
2529 self.assertIsNotNone(ss_p_sum)
2530 self.assertEqual(1, len(ss_p_sum.resources))
2531
2532 ss_p_sum_disk = self._find_summary_for_resource(ss_p_sum, 'DISK_GB')
2533 self.assertIsNotNone(ss_p_sum_disk)
2534
2535 expected_capacity = ((2000 - 100) * 1.0)
2536 self.assertEqual(expected_capacity, ss_p_sum_disk.capacity)
2537 self.assertEqual(0, ss_p_sum_disk.used)
2538
2539 # Verify the allocation requests that are returned. There should be 2
2540 # allocation requests, one for each compute node, containing 3
2541 # resources in each allocation request, one each for VCPU, RAM, and
2542 # disk. The amounts of the requests should correspond to the requested
2543 # resource amounts in the filter:resources dict passed to
2544 # AllocationCandidates.get_by_filters(). The providers for VCPU and
2545 # MEMORY_MB should be the compute nodes while the provider for the
2546 # DISK_GB should be the shared storage pool
2547 a_reqs = p_alts.allocation_requests
2548 self.assertEqual(2, len(a_reqs))
2549
2550 a_req_rps = set()
2551 for ar in a_reqs:
2552 for rr in ar.resource_requests:
2553 a_req_rps.add(rr.resource_provider.uuid)
2554
2555 self.assertEqual(set([cn1_uuid, cn2_uuid, ss_uuid]), a_req_rps)
2556
2557 cn1_reqs = self._find_requests_for_provider(a_reqs, cn1_uuid)
2558 # There should be a req object for only VCPU and MEMORY_MB
2559 self.assertEqual(2, len(cn1_reqs))
2560
2561 cn1_req_vcpu = self._find_request_for_resource(cn1_reqs, 'VCPU')
2562 self.assertIsNotNone(cn1_req_vcpu)
2563 self.assertEqual(requested_resources['VCPU'], cn1_req_vcpu.amount)
2564
2565 cn2_reqs = self._find_requests_for_provider(a_reqs, cn2_uuid)
2566
2567 # There should NOT be an allocation resource request that lists a
2568 # compute node provider UUID for DISK_GB, since the shared storage pool
2569 # is the thing that is providing the disk
2570 cn1_req_disk = self._find_request_for_resource(cn1_reqs, 'DISK_GB')
2571 self.assertIsNone(cn1_req_disk)
2572 cn2_req_disk = self._find_request_for_resource(cn2_reqs, 'DISK_GB')
2573 self.assertIsNone(cn2_req_disk)
2574
2575 # Let's check the second compute node for MEMORY_MB
2576 cn2_req_ram = self._find_request_for_resource(cn2_reqs, 'MEMORY_MB')
2577 self.assertIsNotNone(cn2_req_ram)
2578 self.assertEqual(requested_resources['MEMORY_MB'], cn2_req_ram.amount)
2579
2580 # We should find the shared storage pool providing the DISK_GB for each
2581 # of the allocation requests
2582 ss_reqs = self._find_requests_for_provider(a_reqs, ss_uuid)
2583 self.assertEqual(2, len(ss_reqs))
2584
2585 # Shared storage shouldn't be listed as providing anything but disk...
2586 ss_req_ram = self._find_request_for_resource(ss_reqs, 'MEMORY_MB')
2587 self.assertIsNone(ss_req_ram)
2588
2589 ss_req_disk = self._find_request_for_resource(ss_reqs, 'DISK_GB')
2590 self.assertIsNotNone(ss_req_disk)
2591 self.assertEqual(requested_resources['DISK_GB'], ss_req_disk.amount)