From ac114ecc630b9ad9e4fa67d42d07f850bec6e5ea Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Mon, 14 May 2018 07:57:27 -0700 Subject: [PATCH] Make scheduler client allow multiple member_of query parameters This implements support for placement version 1.24, which allows multiple member_of query parameters to allocation_candidates. Related to blueprint alloc-candidates-member-of Change-Id: Id7eecbfe53f3a973d828122cf0149b2e10b8833f --- nova/scheduler/client/report.py | 43 ++++++++----------- .../unit/scheduler/client/test_report.py | 8 ++-- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/nova/scheduler/client/report.py b/nova/scheduler/client/report.py index 83c96ab4ea..2b4469eb9d 100644 --- a/nova/scheduler/client/report.py +++ b/nova/scheduler/client/report.py @@ -345,33 +345,26 @@ class SchedulerReportClient(object): resource_query = ",".join( sorted("%s:%s" % (rc, amount) for (rc, amount) in res.items())) - qs_params = { - 'resources': resource_query, - 'limit': CONF.scheduler.max_placement_results, - } + qs_params = [ + ('resources', resource_query), + ('limit', CONF.scheduler.max_placement_results), + ] + required_traits_params = [] if required_traits: - qs_params['required'] = ",".join(required_traits) + required_traits_params.extend(required_traits) if forbidden_traits: # Sorted to make testing easier to manage and for # predictability. - forbiddens = ',!'.join(sorted(forbidden_traits)) - if qs_params['required']: - qs_params['required'] += ',!' + forbiddens - else: - qs_params['required'] = '!' + forbiddens - if aggregates: - # NOTE(danms): In 1.21, placement cannot take an AND'd - # set of aggregates, only an OR'd set. Thus, if we have - # required and optional sets, we must do the naive thing - # and AND ours together. That will not achieve the same - # result, but we can't do it from the client side. When - # placement supports AND'ing multiple sets, we can fix this. - # TODO(danms): Update this when placement can take multiple - # member_of query parameters. - required_agg = set.intersection(*[set(x) for x in aggregates]) - qs_params['member_of'] = 'in:' + ','.join(sorted(required_agg)) + required_traits_params.extend( + ['!%s' % trait for trait in sorted(forbidden_traits)]) + if required_traits_params: + qs_params.append(('required', ','.join(required_traits_params))) - version = '1.21' + if aggregates: + for agg_list in aggregates: + qs_params.append(('member_of', 'in:%s' % ','.join(agg_list))) + + version = '1.24' url = "/allocation_candidates?%s" % parse.urlencode(qs_params) resp = self.get(url, version=version, global_request_id=context.global_id) @@ -389,8 +382,10 @@ class SchedulerReportClient(object): "API for filters %(resources)s, traits %(traits)s, " "aggregates %(aggregates)s. Got " "%(status_code)d: %(err_text)s.") - args['traits'] = qs_params.get('required', '(none)') - args['aggregates'] = qs_params.get('aggregates', '(none)') + query_traits = [v for k, v in qs_params if k == 'required'] + query_aggs = [v for k, v in qs_params if k == 'member_of'] + args['traits'] = query_traits and query_traits[0] or '(none)' + args['aggregates'] = ','.join(query_aggs) or '(none)' LOG.error(msg, args) return None, None, None diff --git a/nova/tests/unit/scheduler/client/test_report.py b/nova/tests/unit/scheduler/client/test_report.py index 481772eb5d..c19b10ed0e 100644 --- a/nova/tests/unit/scheduler/client/test_report.py +++ b/nova/tests/unit/scheduler/client/test_report.py @@ -1463,7 +1463,7 @@ class TestProviderOperations(SchedulerReportClientTestCase): expected_query = { 'resources': ['MEMORY_MB:1024,VCPU:1'], 'required': ['CUSTOM_TRAIT1,!CUSTOM_TRAIT3,!CUSTOM_TRAIT4'], - 'member_of': ['in:agg1,agg2'], + 'member_of': ['in:agg1,agg2,agg3', 'in:agg1,agg2'], 'limit': ['1000'] } @@ -1474,7 +1474,7 @@ class TestProviderOperations(SchedulerReportClientTestCase): self.client.get_allocation_candidates(self.context, resources) self.ks_adap_mock.get.assert_called_once_with( - mock.ANY, raise_exc=False, microversion='1.21', + mock.ANY, raise_exc=False, microversion='1.24', headers={'X-Openstack-Request-Id': self.context.global_id}) url = self.ks_adap_mock.get.call_args[0][0] split_url = parse.urlsplit(url) @@ -1506,7 +1506,7 @@ class TestProviderOperations(SchedulerReportClientTestCase): self.client.get_allocation_candidates(self.context, resources) self.ks_adap_mock.get.assert_called_once_with( - mock.ANY, raise_exc=False, microversion='1.21', + mock.ANY, raise_exc=False, microversion='1.24', headers={'X-Openstack-Request-Id': self.context.global_id}) url = self.ks_adap_mock.get.call_args[0][0] split_url = parse.urlsplit(url) @@ -1533,7 +1533,7 @@ class TestProviderOperations(SchedulerReportClientTestCase): res = self.client.get_allocation_candidates(self.context, resources) self.ks_adap_mock.get.assert_called_once_with( - mock.ANY, raise_exc=False, microversion='1.21', + mock.ANY, raise_exc=False, microversion='1.24', headers={'X-Openstack-Request-Id': self.context.global_id}) url = self.ks_adap_mock.get.call_args[0][0] split_url = parse.urlsplit(url)