Use ksa adapter for placement conf & requests

Switch usage of the placement API over to using the new
nova.utils.get_ksa_adapter method.  Now REST calls with the placement
API go through the resulting keystoneauth1 Adapter - which already
incorporates endpoint filtering - rather than a keystoneauth1 Session.

To make this fit, switch the placement conf over to using
nova.conf.utils.register_ksa_opts and get_ksa_adapter_opts.  In so
doing, deprecate os_interface and os_region_name in favor of the
imported Adapter opts valid_interfaces and region_name, respectively.

Change-Id: I69e9b30d96390a70198b12d74e7efa9bd61db217
Partial-Implements: bp use-ksa-adapter-for-endpoints
This commit is contained in:
Eric Fried 2017-08-09 15:09:14 -05:00 committed by Matt Riedemann
parent 8352d555a5
commit 987d451f4d
8 changed files with 203 additions and 234 deletions

View File

@ -26,7 +26,6 @@ import textwrap
import traceback
from keystoneauth1 import exceptions as ks_exc
from keystoneauth1 import loading as keystone
from oslo_config import cfg
import pkg_resources
import prettytable
@ -41,6 +40,7 @@ from nova.db.sqlalchemy import api as db_session
from nova.i18n import _
from nova.objects import cell_mapping as cell_mapping_obj
from nova.objects import fields
from nova import utils
from nova import version
CONF = nova.conf.CONF
@ -174,22 +174,16 @@ class UpgradeCommands(object):
return UpgradeCheckResult(UpgradeCheckCode.SUCCESS)
def _placement_get(self, path):
@staticmethod
def _placement_get(path):
"""Do an HTTP get call against placement engine.
This is in a dedicated method to make it easier for unit
testing purposes.
"""
ks_filter = {'service_type': 'placement',
'region_name': CONF.placement.os_region_name,
'interface': CONF.placement.os_interface}
auth = keystone.load_auth_from_conf_options(
CONF, 'placement')
client = keystone.load_session_from_conf_options(
CONF, 'placement', auth=auth)
return client.get(path, endpoint_filter=ks_filter).json()
client = utils.get_ksa_adapter('placement')
return client.get(path).json()
def _check_placement(self):
"""Checks to see if the placement API is ready for scheduling.
@ -198,6 +192,7 @@ class UpgradeCommands(object):
service catalog and that we can make requests against it.
"""
try:
# TODO(efried): Use ksa's version filtering in _placement_get
versions = self._placement_get("/")
max_version = pkg_resources.parse_version(
versions["versions"][0]["max_version"])

View File

@ -13,14 +13,25 @@
from keystoneauth1 import loading as ks_loading
from oslo_config import cfg
from nova.conf import utils as confutils
DEFAULT_SERVICE_TYPE = 'placement'
placement_group = cfg.OptGroup(
'placement',
title='Placement Service Options',
help="Configuration options for connecting to the placement API service")
placement_opts = [
cfg.StrOpt('os_region_name',
help="""
cfg.StrOpt(
'os_region_name',
deprecated_for_removal=True,
deprecated_since='17.0.0',
deprecated_reason='Endpoint lookup uses the service catalog via '
'common keystoneauth1 Adapter configuration '
'options. Use the region_name option instead.',
help="""
Region name of this node. This is used when picking the URL in the service
catalog.
@ -28,19 +39,32 @@ Possible values:
* Any string representing region name
"""),
cfg.StrOpt('os_interface',
help="""
cfg.StrOpt(
'os_interface',
deprecated_for_removal=True,
deprecated_since='17.0.0',
deprecated_reason='Endpoint lookup uses the service catalog via '
'common keystoneauth1 Adapter configuration '
'options. Use the valid_interfaces option instead.',
help="""
Endpoint interface for this node. This is used when picking the URL in the
service catalog.
""")
]
deprecated_opts = {
'region_name': [cfg.DeprecatedOpt('os_region_name',
group=placement_group.name)],
'valid_interfaces': [cfg.DeprecatedOpt('os_interface',
group=placement_group.name)]
}
def register_opts(conf):
conf.register_group(placement_group)
conf.register_opts(placement_opts, group=placement_group)
ks_loading.register_session_conf_options(conf, placement_group.name)
ks_loading.register_auth_conf_options(conf, placement_group.name)
confutils.register_ksa_opts(conf, placement_group, DEFAULT_SERVICE_TYPE,
deprecated_opts=deprecated_opts)
def list_opts():
@ -51,5 +75,7 @@ def list_opts():
ks_loading.get_auth_common_conf_options() +
ks_loading.get_auth_plugin_conf_options('password') +
ks_loading.get_auth_plugin_conf_options('v2password') +
ks_loading.get_auth_plugin_conf_options('v3password'))
ks_loading.get_auth_plugin_conf_options('v3password') +
confutils.get_ksa_adapter_opts(DEFAULT_SERVICE_TYPE,
deprecated_opts=deprecated_opts))
}

View File

@ -19,7 +19,6 @@ import re
import time
from keystoneauth1 import exceptions as ks_exc
from keystoneauth1 import loading as keystone
from oslo_log import log as logging
from six.moves.urllib import parse
@ -246,9 +245,6 @@ class SchedulerReportClient(object):
self._client = self._create_client()
# NOTE(danms): Keep track of how naggy we've been
self._warn_count = 0
self.ks_filter = {'service_type': 'placement',
'region_name': CONF.placement.os_region_name,
'interface': CONF.placement.os_interface}
@utils.synchronized(PLACEMENT_CLIENT_SEMAPHORE)
def _create_client(self):
@ -257,73 +253,36 @@ class SchedulerReportClient(object):
self._provider_tree = provider_tree.ProviderTree()
self._provider_aggregate_map = {}
self.aggregate_refresh_time = {}
auth_plugin = keystone.load_auth_from_conf_options(
CONF, 'placement')
return keystone.load_session_from_conf_options(
CONF, 'placement', auth=auth_plugin,
additional_headers={'accept': 'application/json'})
# TODO(mriedem): Perform some version discovery at some point.
client = utils.get_ksa_adapter('placement')
# Set accept header on every request to ensure we notify placement
# service of our response body media type preferences.
client.additional_headers = {'accept': 'application/json'}
return client
def get(self, url, version=None):
kwargs = {}
if version is not None:
# TODO(mriedem): Perform some version discovery at some point.
kwargs = {
'headers': {
'OpenStack-API-Version': 'placement %s' % version
},
}
return self._client.get(
url,
endpoint_filter=self.ks_filter, raise_exc=False, **kwargs)
return self._client.get(url, raise_exc=False, microversion=version)
def post(self, url, data, version=None):
# NOTE(sdague): using json= instead of data= sets the
# media type to application/json for us. Placement API is
# more sensitive to this than other APIs in the OpenStack
# ecosystem.
kwargs = {}
if version is not None:
# TODO(mriedem): Perform some version discovery at some point.
kwargs = {
'headers': {
'OpenStack-API-Version': 'placement %s' % version
},
}
return self._client.post(
url, json=data,
endpoint_filter=self.ks_filter, raise_exc=False, **kwargs)
return self._client.post(url, json=data, raise_exc=False,
microversion=version)
def put(self, url, data, version=None):
# NOTE(sdague): using json= instead of data= sets the
# media type to application/json for us. Placement API is
# more sensitive to this than other APIs in the OpenStack
# ecosystem.
kwargs = {}
if version is not None:
# TODO(mriedem): Perform some version discovery at some point.
kwargs = {
'headers': {
'OpenStack-API-Version': 'placement %s' % version
},
}
kwargs = {'microversion': version}
if data:
kwargs['json'] = data
return self._client.put(
url, endpoint_filter=self.ks_filter, raise_exc=False,
**kwargs)
return self._client.put(url, raise_exc=False, **kwargs)
def delete(self, url, version=None):
kwargs = {}
if version is not None:
# TODO(mriedem): Perform some version discovery at some point.
kwargs = {
'headers': {
'OpenStack-API-Version': 'placement %s' % version
},
}
return self._client.delete(
url,
endpoint_filter=self.ks_filter, raise_exc=False, **kwargs)
return self._client.delete(url, raise_exc=False, microversion=version)
@safe_connect
def get_allocation_candidates(self, resources):

View File

@ -11,6 +11,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from keystoneauth1 import adapter
from keystoneauth1 import session
import mock
import requests
@ -41,11 +42,10 @@ class NoAuthReportClient(report.SchedulerReportClient):
'x-auth-token': 'admin',
'OpenStack-API-Version': 'placement latest',
}
self._client = session.Session(
auth=None,
session=request_session,
additional_headers=headers,
)
self._client = adapter.Adapter(
session.Session(auth=None, session=request_session,
additional_headers=headers),
service_type='placement')
class SchedulerReportClientTests(test.TestCase):

View File

@ -123,34 +123,33 @@ class TestPlacementCheck(test.NoDBTestCase):
self.assertIn('No credentials specified', res.details)
@mock.patch.object(keystone, "load_auth_from_conf_options")
@mock.patch.object(session.Session, 'get')
@mock.patch.object(session.Session, 'request')
def _test_placement_get_interface(
self, expected_interface, mock_get, mock_auth):
def fake_get(path, *a, **kw):
def fake_request(path, method, *a, **kw):
self.assertEqual(mock.sentinel.path, path)
self.assertEqual('GET', method)
self.assertIn('endpoint_filter', kw)
self.assertEqual(expected_interface,
kw['endpoint_filter']['interface'])
return mock.Mock(autospec='requests.models.Response')
mock_get.side_effect = fake_get
mock_get.side_effect = fake_request
self.cmd._placement_get(mock.sentinel.path)
mock_auth.assert_called_once_with(status.CONF, 'placement')
self.assertTrue(mock_get.called)
@mock.patch.object(keystone, "load_auth_from_conf_options")
@mock.patch.object(session.Session, 'get')
def test_placement_get_interface_default(self, mock_get, mock_auth):
"""Tests that None is specified for interface by default."""
self._test_placement_get_interface(None)
def test_placement_get_interface_default(self):
"""Tests that we try internal, then public interface by default."""
self._test_placement_get_interface(['internal', 'public'])
@mock.patch.object(keystone, "load_auth_from_conf_options")
@mock.patch.object(session.Session, 'get')
def test_placement_get_interface_internal(self, mock_get, mock_auth):
def test_placement_get_interface_internal(self):
"""Tests that "internal" is specified for interface when configured."""
self.flags(os_interface='internal', group='placement')
self._test_placement_get_interface('internal')
# TODO(efried): Test that the deprecated opts (e.g. os_interface) still
# work once bug #1709728 is resolved.
self.flags(valid_interfaces='internal', group='placement')
self._test_placement_get_interface(['internal'])
@mock.patch.object(status.UpgradeCommands, "_placement_get")
def test_invalid_auth(self, get):

View File

@ -36,11 +36,8 @@ class SafeConnectedTestCase(test.NoDBTestCase):
def setUp(self):
super(SafeConnectedTestCase, self).setUp()
self.context = context.get_admin_context()
self.ks_sess_mock = mock.Mock()
with test.nested(
mock.patch('keystoneauth1.loading.load_auth_from_conf_options')
) as _auth_mock: # noqa
with mock.patch('keystoneauth1.loading.load_auth_from_conf_options'):
self.client = report.SchedulerReportClient()
@mock.patch('keystoneauth1.session.Session.request')
@ -157,21 +154,23 @@ class TestConstructor(test.NoDBTestCase):
load_auth_mock.assert_called_once_with(CONF, 'placement')
load_sess_mock.assert_called_once_with(CONF, 'placement',
additional_headers={'accept': 'application/json'},
auth=load_auth_mock.return_value)
self.assertIsNone(client.ks_filter['interface'])
auth=load_auth_mock.return_value)
self.assertEqual(['internal', 'public'], client._client.interface)
self.assertEqual({'accept': 'application/json'},
client._client.additional_headers)
@mock.patch('keystoneauth1.loading.load_session_from_conf_options')
@mock.patch('keystoneauth1.loading.load_auth_from_conf_options')
def test_constructor_admin_interface(self, load_auth_mock, load_sess_mock):
self.flags(os_interface='admin', group='placement')
self.flags(valid_interfaces='admin', group='placement')
client = report.SchedulerReportClient()
load_auth_mock.assert_called_once_with(CONF, 'placement')
load_sess_mock.assert_called_once_with(CONF, 'placement',
additional_headers={'accept': 'application/json'},
auth=load_auth_mock.return_value)
self.assertEqual('admin', client.ks_filter['interface'])
auth=load_auth_mock.return_value)
self.assertEqual(['admin'], client._client.interface)
self.assertEqual({'accept': 'application/json'},
client._client.additional_headers)
class SchedulerReportClientTestCase(test.NoDBTestCase):
@ -179,7 +178,7 @@ class SchedulerReportClientTestCase(test.NoDBTestCase):
def setUp(self):
super(SchedulerReportClientTestCase, self).setUp()
self.context = context.get_admin_context()
self.ks_sess_mock = mock.Mock()
self.ks_adap_mock = mock.Mock()
self.compute_node = objects.ComputeNode(
uuid=uuids.compute_node,
hypervisor_hostname='foo',
@ -192,10 +191,10 @@ class SchedulerReportClientTestCase(test.NoDBTestCase):
)
with test.nested(
mock.patch('keystoneauth1.session.Session',
return_value=self.ks_sess_mock),
mock.patch('keystoneauth1.adapter.Adapter',
return_value=self.ks_adap_mock),
mock.patch('keystoneauth1.loading.load_auth_from_conf_options')
) as (_auth_mock, _sess_mock):
):
self.client = report.SchedulerReportClient()
def _init_provider_tree(self, generation_override=None,
@ -298,9 +297,9 @@ class TestPutAllocations(SchedulerReportClientTestCase):
get_resp_mock.json.return_value = {
'allocations': {}, # build instance, not move
}
self.ks_sess_mock.get.return_value = get_resp_mock
self.ks_adap_mock.get.return_value = get_resp_mock
resp_mock = mock.Mock(status_code=204)
self.ks_sess_mock.put.return_value = resp_mock
self.ks_adap_mock.put.return_value = resp_mock
consumer_uuid = uuids.consumer_uuid
alloc_req = {
'allocations': {
@ -323,10 +322,9 @@ class TestPutAllocations(SchedulerReportClientTestCase):
expected_payload = copy.deepcopy(alloc_req)
expected_payload['project_id'] = project_id
expected_payload['user_id'] = user_id
self.ks_sess_mock.put.assert_called_once_with(
expected_url, endpoint_filter=mock.ANY,
headers={'OpenStack-API-Version': 'placement 1.10'},
json=expected_payload, raise_exc=False)
self.ks_adap_mock.put.assert_called_once_with(
expected_url, microversion='1.10', json=expected_payload,
raise_exc=False)
self.assertTrue(res)
@ -349,9 +347,9 @@ class TestPutAllocations(SchedulerReportClientTestCase):
},
}
self.ks_sess_mock.get.return_value = get_resp_mock
self.ks_adap_mock.get.return_value = get_resp_mock
resp_mock = mock.Mock(status_code=204)
self.ks_sess_mock.put.return_value = resp_mock
self.ks_adap_mock.put.return_value = resp_mock
consumer_uuid = uuids.consumer_uuid
alloc_req = {
'allocations': [
@ -399,13 +397,12 @@ class TestPutAllocations(SchedulerReportClientTestCase):
}
expected_payload['project_id'] = project_id
expected_payload['user_id'] = user_id
self.ks_sess_mock.put.assert_called_once_with(
expected_url, endpoint_filter=mock.ANY,
headers={'OpenStack-API-Version': 'placement 1.10'},
json=mock.ANY, raise_exc=False)
self.ks_adap_mock.put.assert_called_once_with(
expected_url, microversion='1.10', json=mock.ANY,
raise_exc=False)
# We have to pull the json body from the mock call_args to validate
# it separately otherwise hash seed issues get in the way.
actual_payload = self.ks_sess_mock.put.call_args[1]['json']
actual_payload = self.ks_adap_mock.put.call_args[1]['json']
sort_by_uuid = lambda x: x['resource_provider']['uuid']
expected_allocations = sorted(expected_payload['allocations'],
key=sort_by_uuid)
@ -443,9 +440,9 @@ class TestPutAllocations(SchedulerReportClientTestCase):
},
}
self.ks_sess_mock.get.return_value = get_resp_mock
self.ks_adap_mock.get.return_value = get_resp_mock
resp_mock = mock.Mock(status_code=204)
self.ks_sess_mock.put.return_value = resp_mock
self.ks_adap_mock.put.return_value = resp_mock
consumer_uuid = uuids.consumer_uuid
alloc_req = {
'allocations': [
@ -510,14 +507,13 @@ class TestPutAllocations(SchedulerReportClientTestCase):
}
expected_payload['project_id'] = project_id
expected_payload['user_id'] = user_id
self.ks_sess_mock.put.assert_called_once_with(
expected_url, endpoint_filter=mock.ANY,
headers={'OpenStack-API-Version': 'placement 1.10'},
json=mock.ANY, raise_exc=False)
self.ks_adap_mock.put.assert_called_once_with(
expected_url, microversion='1.10', json=mock.ANY,
raise_exc=False)
# We have to pull the allocations from the json body from the
# mock call_args to validate it separately otherwise hash seed
# issues get in the way.
actual_payload = self.ks_sess_mock.put.call_args[1]['json']
actual_payload = self.ks_adap_mock.put.call_args[1]['json']
sort_by_uuid = lambda x: x['resource_provider']['uuid']
expected_allocations = sorted(expected_payload['allocations'],
key=sort_by_uuid)
@ -548,9 +544,9 @@ class TestPutAllocations(SchedulerReportClientTestCase):
},
}
self.ks_sess_mock.get.return_value = get_current_allocations_resp_mock
self.ks_adap_mock.get.return_value = get_current_allocations_resp_mock
put_allocations_resp_mock = mock.Mock(status_code=204)
self.ks_sess_mock.put.return_value = put_allocations_resp_mock
self.ks_adap_mock.put.return_value = put_allocations_resp_mock
consumer_uuid = uuids.consumer_uuid
# This is the resize-up allocation where VCPU, MEMORY_MB and DISK_GB
# are all being increased but on the same host. We also throw a custom
@ -597,13 +593,11 @@ class TestPutAllocations(SchedulerReportClientTestCase):
}
expected_payload['project_id'] = project_id
expected_payload['user_id'] = user_id
self.ks_sess_mock.put.assert_called_once_with(
expected_url, endpoint_filter=mock.ANY,
headers={'OpenStack-API-Version': 'placement 1.10'},
json=mock.ANY, raise_exc=False)
self.ks_adap_mock.put.assert_called_once_with(
expected_url, microversion='1.10', json=mock.ANY, raise_exc=False)
# We have to pull the json body from the mock call_args to validate
# it separately otherwise hash seed issues get in the way.
actual_payload = self.ks_sess_mock.put.call_args[1]['json']
actual_payload = self.ks_adap_mock.put.call_args[1]['json']
sort_by_uuid = lambda x: x['resource_provider']['uuid']
expected_allocations = sorted(expected_payload['allocations'],
key=sort_by_uuid)
@ -641,9 +635,9 @@ class TestPutAllocations(SchedulerReportClientTestCase):
},
}
self.ks_sess_mock.get.return_value = get_current_allocations_resp_mock
self.ks_adap_mock.get.return_value = get_current_allocations_resp_mock
put_allocations_resp_mock = mock.Mock(status_code=204)
self.ks_sess_mock.put.return_value = put_allocations_resp_mock
self.ks_adap_mock.put.return_value = put_allocations_resp_mock
consumer_uuid = uuids.consumer_uuid
# This is the resize-up allocation where VCPU, MEMORY_MB and DISK_GB
# are all being increased but DISK_GB is on a shared storage provider.
@ -700,13 +694,11 @@ class TestPutAllocations(SchedulerReportClientTestCase):
}
expected_payload['project_id'] = project_id
expected_payload['user_id'] = user_id
self.ks_sess_mock.put.assert_called_once_with(
expected_url, endpoint_filter=mock.ANY,
headers={'OpenStack-API-Version': 'placement 1.10'},
json=mock.ANY, raise_exc=False)
self.ks_adap_mock.put.assert_called_once_with(
expected_url, microversion='1.10', json=mock.ANY, raise_exc=False)
# We have to pull the json body from the mock call_args to validate
# it separately otherwise hash seed issues get in the way.
actual_payload = self.ks_sess_mock.put.call_args[1]['json']
actual_payload = self.ks_adap_mock.put.call_args[1]['json']
sort_by_uuid = lambda x: x['resource_provider']['uuid']
expected_allocations = sorted(expected_payload['allocations'],
key=sort_by_uuid)
@ -721,7 +713,7 @@ class TestPutAllocations(SchedulerReportClientTestCase):
get_resp_mock.json.return_value = {
'allocations': {}, # build instance, not move
}
self.ks_sess_mock.get.return_value = get_resp_mock
self.ks_adap_mock.get.return_value = get_resp_mock
resp_mocks = [
mock.Mock(
status_code=409,
@ -730,7 +722,7 @@ class TestPutAllocations(SchedulerReportClientTestCase):
'Please retry your update'),
mock.Mock(status_code=204),
]
self.ks_sess_mock.put.side_effect = resp_mocks
self.ks_adap_mock.put.side_effect = resp_mocks
consumer_uuid = uuids.consumer_uuid
alloc_req = {
'allocations': [
@ -758,16 +750,11 @@ class TestPutAllocations(SchedulerReportClientTestCase):
# We should have exactly two calls to the placement API that look
# identical since we're retrying the same HTTP request
expected_calls = [
mock.call(expected_url, endpoint_filter=mock.ANY,
headers={'OpenStack-API-Version': 'placement 1.10'},
json=expected_payload, raise_exc=False),
mock.call(expected_url, endpoint_filter=mock.ANY,
headers={'OpenStack-API-Version': 'placement 1.10'},
json=expected_payload, raise_exc=False),
]
mock.call(expected_url, microversion='1.10', json=expected_payload,
raise_exc=False)] * 2
self.assertEqual(len(expected_calls),
self.ks_sess_mock.put.call_count)
self.ks_sess_mock.put.assert_has_calls(expected_calls)
self.ks_adap_mock.put.call_count)
self.ks_adap_mock.put.assert_has_calls(expected_calls)
self.assertTrue(res)
@ -777,9 +764,9 @@ class TestPutAllocations(SchedulerReportClientTestCase):
get_resp_mock.json.return_value = {
'allocations': {}, # build instance, not move
}
self.ks_sess_mock.get.return_value = get_resp_mock
self.ks_adap_mock.get.return_value = get_resp_mock
resp_mock = mock.Mock(status_code=409)
self.ks_sess_mock.put.return_value = resp_mock
self.ks_adap_mock.put.return_value = resp_mock
consumer_uuid = uuids.consumer_uuid
alloc_req = {
'allocations': [
@ -804,10 +791,9 @@ class TestPutAllocations(SchedulerReportClientTestCase):
expected_payload = copy.deepcopy(alloc_req)
expected_payload['project_id'] = project_id
expected_payload['user_id'] = user_id
self.ks_sess_mock.put.assert_called_once_with(
expected_url, endpoint_filter=mock.ANY,
headers={'OpenStack-API-Version': 'placement 1.10'},
json=expected_payload, raise_exc=False)
self.ks_adap_mock.put.assert_called_once_with(
expected_url, microversion='1.10', json=expected_payload,
raise_exc=False)
self.assertFalse(res)
self.assertTrue(mock_log.called)
@ -837,9 +823,9 @@ class TestPutAllocations(SchedulerReportClientTestCase):
},
},
}
self.ks_sess_mock.get.return_value = get_resp_mock
self.ks_adap_mock.get.return_value = get_resp_mock
resp_mock = mock.Mock(status_code=204)
self.ks_sess_mock.put.return_value = resp_mock
self.ks_adap_mock.put.return_value = resp_mock
consumer_uuid = uuids.consumer_uuid
project_id = uuids.project_id
user_id = uuids.user_id
@ -865,17 +851,15 @@ class TestPutAllocations(SchedulerReportClientTestCase):
expected_payload['user_id'] = user_id
# We have to pull the json body from the mock call_args to validate
# it separately otherwise hash seed issues get in the way.
actual_payload = self.ks_sess_mock.put.call_args[1]['json']
actual_payload = self.ks_adap_mock.put.call_args[1]['json']
sort_by_uuid = lambda x: x['resource_provider']['uuid']
expected_allocations = sorted(expected_payload['allocations'],
key=sort_by_uuid)
actual_allocations = sorted(actual_payload['allocations'],
key=sort_by_uuid)
self.assertEqual(expected_allocations, actual_allocations)
self.ks_sess_mock.put.assert_called_once_with(
expected_url, endpoint_filter=mock.ANY,
headers={'OpenStack-API-Version': 'placement 1.10'},
json=mock.ANY, raise_exc=False)
self.ks_adap_mock.put.assert_called_once_with(
expected_url, microversion='1.10', json=mock.ANY, raise_exc=False)
self.assertTrue(res)
@ -911,9 +895,9 @@ class TestPutAllocations(SchedulerReportClientTestCase):
},
},
}
self.ks_sess_mock.get.return_value = get_resp_mock
self.ks_adap_mock.get.return_value = get_resp_mock
resp_mock = mock.Mock(status_code=204)
self.ks_sess_mock.put.return_value = resp_mock
self.ks_adap_mock.put.return_value = resp_mock
consumer_uuid = uuids.consumer_uuid
project_id = uuids.project_id
user_id = uuids.user_id
@ -947,17 +931,15 @@ class TestPutAllocations(SchedulerReportClientTestCase):
expected_payload['user_id'] = user_id
# We have to pull the json body from the mock call_args to validate
# it separately otherwise hash seed issues get in the way.
actual_payload = self.ks_sess_mock.put.call_args[1]['json']
actual_payload = self.ks_adap_mock.put.call_args[1]['json']
sort_by_uuid = lambda x: x['resource_provider']['uuid']
expected_allocations = sorted(expected_payload['allocations'],
key=sort_by_uuid)
actual_allocations = sorted(actual_payload['allocations'],
key=sort_by_uuid)
self.assertEqual(expected_allocations, actual_allocations)
self.ks_sess_mock.put.assert_called_once_with(
expected_url, endpoint_filter=mock.ANY,
headers={'OpenStack-API-Version': 'placement 1.10'},
json=mock.ANY, raise_exc=False)
self.ks_adap_mock.put.assert_called_once_with(
expected_url, microversion='1.10', json=mock.ANY, raise_exc=False)
self.assertTrue(res)
@ -986,15 +968,15 @@ class TestPutAllocations(SchedulerReportClientTestCase):
},
},
}
self.ks_sess_mock.get.return_value = get_resp_mock
self.ks_adap_mock.get.return_value = get_resp_mock
consumer_uuid = uuids.consumer_uuid
project_id = uuids.project_id
user_id = uuids.user_id
res = self.client.remove_provider_from_instance_allocation(
consumer_uuid, uuids.source, user_id, project_id, mock.Mock())
self.ks_sess_mock.get.assert_called()
self.ks_sess_mock.put.assert_not_called()
self.ks_adap_mock.get.assert_called()
self.ks_adap_mock.put.assert_not_called()
self.assertTrue(res)
@ -1004,15 +986,15 @@ class TestPutAllocations(SchedulerReportClientTestCase):
existing allocations fails for some reason
"""
get_resp_mock = mock.Mock(status_code=500)
self.ks_sess_mock.get.return_value = get_resp_mock
self.ks_adap_mock.get.return_value = get_resp_mock
consumer_uuid = uuids.consumer_uuid
project_id = uuids.project_id
user_id = uuids.user_id
res = self.client.remove_provider_from_instance_allocation(
consumer_uuid, uuids.source, user_id, project_id, mock.Mock())
self.ks_sess_mock.get.assert_called()
self.ks_sess_mock.put.assert_not_called()
self.ks_adap_mock.get.assert_called()
self.ks_adap_mock.put.assert_not_called()
self.assertFalse(res)
@ -1148,15 +1130,14 @@ class TestProviderOperations(SchedulerReportClientTestCase):
}
resources = {'VCPU': 1, 'MEMORY_MB': 1024}
resp_mock.json.return_value = json_data
self.ks_sess_mock.get.return_value = resp_mock
self.ks_adap_mock.get.return_value = resp_mock
alloc_reqs, p_sums = self.client.get_allocation_candidates(resources)
expected_url = '/allocation_candidates?%s' % parse.urlencode(
{'resources': 'MEMORY_MB:1024,VCPU:1'})
self.ks_sess_mock.get.assert_called_once_with(
expected_url, endpoint_filter=mock.ANY, raise_exc=False,
headers={'OpenStack-API-Version': 'placement 1.10'})
self.ks_adap_mock.get.assert_called_once_with(
expected_url, raise_exc=False, microversion='1.10')
self.assertEqual(mock.sentinel.alloc_reqs, alloc_reqs)
self.assertEqual(mock.sentinel.p_sums, p_sums)
@ -1164,14 +1145,13 @@ class TestProviderOperations(SchedulerReportClientTestCase):
# Ensure _get_resource_provider() just returns None when the placement
# API doesn't find a resource provider matching a UUID
resp_mock = mock.Mock(status_code=404)
self.ks_sess_mock.get.return_value = resp_mock
self.ks_adap_mock.get.return_value = resp_mock
res = self.client.get_allocation_candidates({'foo': 'bar'})
expected_url = '/allocation_candidates?resources=foo%3Abar'
self.ks_sess_mock.get.assert_called_once_with(
expected_url, endpoint_filter=mock.ANY, raise_exc=False,
headers={'OpenStack-API-Version': 'placement 1.10'})
self.ks_adap_mock.get.assert_called_once_with(
expected_url, raise_exc=False, microversion='1.10')
self.assertIsNone(res[0])
self.assertIsNone(res[0])
@ -1186,7 +1166,7 @@ class TestProviderOperations(SchedulerReportClientTestCase):
'generation': 42,
}
resp_mock.json.return_value = json_data
self.ks_sess_mock.get.return_value = resp_mock
self.ks_adap_mock.get.return_value = resp_mock
result = self.client._get_resource_provider(uuid)
@ -1196,24 +1176,22 @@ class TestProviderOperations(SchedulerReportClientTestCase):
generation=42,
)
expected_url = '/resource_providers/' + uuid
self.ks_sess_mock.get.assert_called_once_with(expected_url,
endpoint_filter=mock.ANY,
raise_exc=False)
self.ks_adap_mock.get.assert_called_once_with(
expected_url, raise_exc=False, microversion=None)
self.assertEqual(expected_provider_dict, result)
def test_get_resource_provider_not_found(self):
# Ensure _get_resource_provider() just returns None when the placement
# API doesn't find a resource provider matching a UUID
resp_mock = mock.Mock(status_code=404)
self.ks_sess_mock.get.return_value = resp_mock
self.ks_adap_mock.get.return_value = resp_mock
uuid = uuids.compute_node
result = self.client._get_resource_provider(uuid)
expected_url = '/resource_providers/' + uuid
self.ks_sess_mock.get.assert_called_once_with(expected_url,
endpoint_filter=mock.ANY,
raise_exc=False)
self.ks_adap_mock.get.assert_called_once_with(
expected_url, raise_exc=False, microversion=None)
self.assertIsNone(result)
@mock.patch.object(report.LOG, 'error')
@ -1222,19 +1200,18 @@ class TestProviderOperations(SchedulerReportClientTestCase):
# communicate with the placement API and not getting an error we can
# deal with
resp_mock = mock.Mock(status_code=503)
self.ks_sess_mock.get.return_value = resp_mock
self.ks_sess_mock.get.return_value.headers = {
self.ks_adap_mock.get.return_value = resp_mock
self.ks_adap_mock.get.return_value.headers = {
'openstack-request-id': uuids.request_id}
uuid = uuids.compute_node
result = self.client._get_resource_provider(uuid)
expected_url = '/resource_providers/' + uuid
self.ks_sess_mock.get.assert_called_once_with(expected_url,
endpoint_filter=mock.ANY,
raise_exc=False)
# A 503 Service Unavailable should trigger an error log that
# includes the placement request id and return None
self.ks_adap_mock.get.assert_called_once_with(
expected_url, raise_exc=False, microversion=None)
# A 503 Service Unavailable should trigger an error log
# that includes the placement request id and return None
# from _get_resource_provider()
self.assertTrue(logging_mock.called)
self.assertIsNone(result)
@ -1248,7 +1225,7 @@ class TestProviderOperations(SchedulerReportClientTestCase):
uuid = uuids.compute_node
name = 'computehost'
resp_mock = mock.Mock(status_code=201)
self.ks_sess_mock.post.return_value = resp_mock
self.ks_adap_mock.post.return_value = resp_mock
result = self.client._create_resource_provider(uuid, name)
@ -1262,11 +1239,9 @@ class TestProviderOperations(SchedulerReportClientTestCase):
generation=0,
)
expected_url = '/resource_providers'
self.ks_sess_mock.post.assert_called_once_with(
expected_url,
endpoint_filter=mock.ANY,
json=expected_payload,
raise_exc=False)
self.ks_adap_mock.post.assert_called_once_with(
expected_url, json=expected_payload, raise_exc=False,
microversion=None)
self.assertEqual(expected_provider_dict, result)
@mock.patch.object(report.LOG, 'info')
@ -1282,8 +1257,8 @@ class TestProviderOperations(SchedulerReportClientTestCase):
uuid = uuids.compute_node
name = 'computehost'
resp_mock = mock.Mock(status_code=409)
self.ks_sess_mock.post.return_value = resp_mock
self.ks_sess_mock.post.return_value.headers = {
self.ks_adap_mock.post.return_value = resp_mock
self.ks_adap_mock.post.return_value.headers = {
'openstack-request-id': uuids.request_id}
get_rp_mock.return_value = mock.sentinel.get_rp
@ -1295,11 +1270,9 @@ class TestProviderOperations(SchedulerReportClientTestCase):
'name': name,
}
expected_url = '/resource_providers'
self.ks_sess_mock.post.assert_called_once_with(
expected_url,
endpoint_filter=mock.ANY,
json=expected_payload,
raise_exc=False)
self.ks_adap_mock.post.assert_called_once_with(
expected_url, json=expected_payload, raise_exc=False,
microversion=None)
self.assertEqual(mock.sentinel.get_rp, result)
# The 409 response will produce a message to the info log.
self.assertTrue(logging_mock.called)
@ -1314,8 +1287,8 @@ class TestProviderOperations(SchedulerReportClientTestCase):
uuid = uuids.compute_node
name = 'computehost'
resp_mock = mock.Mock(status_code=503)
self.ks_sess_mock.post.return_value = resp_mock
self.ks_sess_mock.post.return_value.headers = {
self.ks_adap_mock.post.return_value = resp_mock
self.ks_adap_mock.post.return_value.headers = {
'x-openstack-request-id': uuids.request_id}
result = self.client._create_resource_provider(uuid, name)
@ -1325,11 +1298,9 @@ class TestProviderOperations(SchedulerReportClientTestCase):
'name': name,
}
expected_url = '/resource_providers'
self.ks_sess_mock.post.assert_called_once_with(
expected_url,
endpoint_filter=mock.ANY,
json=expected_payload,
raise_exc=False)
self.ks_adap_mock.post.assert_called_once_with(
expected_url, json=expected_payload, raise_exc=False,
microversion=None)
# A 503 Service Unavailable should log an error that
# includes the placement request id and
# _create_resource_provider() should return None
@ -1353,7 +1324,7 @@ class TestAggregates(SchedulerReportClientTestCase):
],
}
resp_mock.json.return_value = json_data
self.ks_sess_mock.get.return_value = resp_mock
self.ks_adap_mock.get.return_value = resp_mock
result = self.client._get_provider_aggregates(uuid)
@ -1362,9 +1333,8 @@ class TestAggregates(SchedulerReportClientTestCase):
uuids.agg2,
])
expected_url = '/resource_providers/' + uuid + '/aggregates'
self.ks_sess_mock.get.assert_called_once_with(
expected_url, endpoint_filter=mock.ANY, raise_exc=False,
headers={'OpenStack-API-Version': 'placement 1.1'})
self.ks_adap_mock.get.assert_called_once_with(
expected_url, raise_exc=False, microversion='1.1')
self.assertEqual(expected, result)
@mock.patch.object(report.LOG, 'warning')
@ -1376,16 +1346,15 @@ class TestAggregates(SchedulerReportClientTestCase):
"""
uuid = uuids.compute_node
resp_mock = mock.Mock(status_code=404)
self.ks_sess_mock.get.return_value = resp_mock
self.ks_sess_mock.get.return_value.headers = {
self.ks_adap_mock.get.return_value = resp_mock
self.ks_adap_mock.get.return_value.headers = {
'x-openstack-request-id': uuids.request_id}
result = self.client._get_provider_aggregates(uuid)
expected_url = '/resource_providers/' + uuid + '/aggregates'
self.ks_sess_mock.get.assert_called_once_with(
expected_url, endpoint_filter=mock.ANY, raise_exc=False,
headers={'OpenStack-API-Version': 'placement 1.1'})
self.ks_adap_mock.get.assert_called_once_with(
expected_url, raise_exc=False, microversion='1.1')
self.assertTrue(log_mock.called)
self.assertEqual(uuids.request_id,
log_mock.call_args[0][1]['placement_req_id'])
@ -1398,16 +1367,15 @@ class TestAggregates(SchedulerReportClientTestCase):
"""
uuid = uuids.compute_node
resp_mock = mock.Mock(status_code=400)
self.ks_sess_mock.get.return_value = resp_mock
self.ks_sess_mock.get.return_value.headers = {
self.ks_adap_mock.get.return_value = resp_mock
self.ks_adap_mock.get.return_value.headers = {
'x-openstack-request-id': uuids.request_id}
result = self.client._get_provider_aggregates(uuid)
expected_url = '/resource_providers/' + uuid + '/aggregates'
self.ks_sess_mock.get.assert_called_once_with(
expected_url, endpoint_filter=mock.ANY, raise_exc=False,
headers={'OpenStack-API-Version': 'placement 1.1'})
self.ks_adap_mock.get.assert_called_once_with(
expected_url, raise_exc=False, microversion='1.1')
self.assertTrue(log_mock.called)
self.assertEqual(uuids.request_id,
log_mock.call_args[0][1]['placement_req_id'])

View File

@ -1299,8 +1299,14 @@ def get_ksa_adapter(service_type, ksa_auth=None, ksa_session=None,
"""
# Get the conf group corresponding to the service type.
confgrp = _SERVICE_TYPES.get_project_name(service_type)
if not confgrp:
raise exception.ConfGroupForServiceTypeNotFound(stype=service_type)
if not confgrp or not hasattr(CONF, confgrp):
# Try the service type as the conf group. This is necessary for e.g.
# placement, while it's still part of the nova project.
# Note that this might become the first thing we try if/as we move to
# using service types for conf group names in general.
confgrp = service_type
if not confgrp or not hasattr(CONF, confgrp):
raise exception.ConfGroupForServiceTypeNotFound(stype=service_type)
# Ensure we have an auth.
# NOTE(efried): This could be None, and that could be okay - e.g. if the

View File

@ -0,0 +1,16 @@
---
upgrade:
- |
Nova now uses keystoneauth1 configuration to set up communication with the
placement service. Use keystoneauth1 loading parameters for auth, Session,
and Adapter setup in the ``[placement]`` conf section. Note that, by
default, the 'internal' interface will be tried first, followed by the
'public' interface. Use the conf option ``[placement].valid_interfaces``
to override this behavior.
deprecations:
- |
Configuration options in the ``[placement]`` section are deprecated as
follows:
* ``os_region_name`` is deprecated in favor of ``region_name``
* ``os_interface`` is deprecated in favor of ``valid_interfaces``