diff --git a/nova/conf/glance.py b/nova/conf/glance.py index 61693b5a5b7e..43a31d60811d 100644 --- a/nova/conf/glance.py +++ b/nova/conf/glance.py @@ -15,22 +15,29 @@ from keystoneauth1 import loading as ks_loading from oslo_config import cfg +from nova.conf import utils as confutils + + +DEFAULT_SERVICE_TYPE = 'image' + glance_group = cfg.OptGroup( 'glance', title='Glance Options', help='Configuration options for the Image service') glance_opts = [ - # NOTE(sdague): there is intentionally no default here. This - # requires configuration. Eventually this will come from the - # service catalog, however we don't have a good path there atm. - # TODO(raj_singh): Add "required=True" flag to this option. + # NOTE(sdague/efried): there is intentionally no default here. This + # requires configuration if ksa adapter config is not used. cfg.ListOpt('api_servers', help=""" List of glance api servers endpoints available to nova. https is used for ssl-based glance api servers. +NOTE: The preferred mechanism for endpoint discovery is via keystoneauth1 +loading options. Only use api_servers if you need multiple endpoints and are +unable to use a load balancer for some reason. + Possible values: * A list of any fully qualified url of the form "scheme://hostname:port[/path]" @@ -130,28 +137,29 @@ Related options: help='Enable or disable debug logging with glanceclient.') ] +deprecated_ksa_opts = { + 'insecure': [cfg.DeprecatedOpt('api_insecure', group=glance_group.name)], + 'cafile': [cfg.DeprecatedOpt('ca_file', group="ssl")], + 'certfile': [cfg.DeprecatedOpt('cert_file', group="ssl")], + 'keyfile': [cfg.DeprecatedOpt('key_file', group="ssl")], +} + def register_opts(conf): conf.register_group(glance_group) conf.register_opts(glance_opts, group=glance_group) - deprecated = { - 'insecure': [cfg.DeprecatedOpt('api_insecure', - group=glance_group.name)], - 'cafile': [cfg.DeprecatedOpt('ca_file', - group="ssl")], - 'certfile': [cfg.DeprecatedOpt('cert_file', - group="ssl")], - 'keyfile': [cfg.DeprecatedOpt('key_file', - group="ssl")], - } - ks_loading.register_session_conf_options(conf, glance_group.name, - deprecated) + confutils.register_ksa_opts(conf, glance_group, DEFAULT_SERVICE_TYPE, + deprecated_opts=deprecated_ksa_opts) def list_opts(): - return { - glance_group: ( - glance_opts + - ks_loading.get_session_conf_options()) + return {glance_group: ( + glance_opts + + ks_loading.get_session_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') + + confutils.get_ksa_adapter_opts(DEFAULT_SERVICE_TYPE, + deprecated_opts=deprecated_ksa_opts)) } diff --git a/nova/conf/ironic.py b/nova/conf/ironic.py index 756e544ea2a7..f2ac59aa626f 100644 --- a/nova/conf/ironic.py +++ b/nova/conf/ironic.py @@ -16,6 +16,11 @@ from keystoneauth1 import loading as ks_loading from oslo_config import cfg +from nova.conf import utils as confutils + + +DEFAULT_SERVICE_TYPE = 'baremetal' + ironic_group = cfg.OptGroup( 'ironic', title='Ironic Options', @@ -35,6 +40,13 @@ ironic_options = [ cfg.URIOpt( 'api_endpoint', schemes=['http', 'https'], + deprecated_for_removal=True, + deprecated_reason='Endpoint lookup uses the service catalog via ' + 'common keystoneauth1 Adapter configuration ' + 'options. In the current release, api_endpoint will ' + 'override this behavior, but will be ignored and/or ' + 'removed in a future release. To achieve the same ' + 'result, use the endpoint_override option instead.', sample_default='http://ironic.example.org:6385/', help='URL override for the Ironic API endpoint.'), cfg.IntOpt( @@ -69,17 +81,24 @@ Related options: 'changed. Set to 0 to disable timeout.'), ] +deprecated_opts = { + 'endpoint_override': [cfg.DeprecatedOpt('api_endpoint', + group=ironic_group.name)]} + def register_opts(conf): conf.register_group(ironic_group) conf.register_opts(ironic_options, group=ironic_group) - ks_loading.register_auth_conf_options(conf, group=ironic_group.name) - ks_loading.register_session_conf_options(conf, group=ironic_group.name) + confutils.register_ksa_opts(conf, ironic_group, DEFAULT_SERVICE_TYPE, + deprecated_opts=deprecated_opts) def list_opts(): - return {ironic_group: ironic_options + - ks_loading.get_session_conf_options() + - ks_loading.get_auth_common_conf_options() + - ks_loading.get_auth_plugin_conf_options('v3password') - } + return {ironic_group: ( + ironic_options + + ks_loading.get_session_conf_options() + + ks_loading.get_auth_common_conf_options() + + ks_loading.get_auth_plugin_conf_options('v3password') + + confutils.get_ksa_adapter_opts(DEFAULT_SERVICE_TYPE, + deprecated_opts=deprecated_opts)) + } diff --git a/nova/conf/utils.py b/nova/conf/utils.py new file mode 100644 index 000000000000..f24ef44baa75 --- /dev/null +++ b/nova/conf/utils.py @@ -0,0 +1,85 @@ +# Copyright 2017 OpenStack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +"""Common utilities for conf providers. + +This module does not provide any actual conf options. +""" +from keystoneauth1 import loading as ks_loading +from oslo_config import cfg + + +_ADAPTER_VERSION_OPTS = ('version', 'min_version', 'max_version') + + +def get_ksa_adapter_opts(default_service_type, deprecated_opts=None): + """Get auth, Session, and Adapter conf options from keystonauth1.loading. + + :param default_service_type: Default for the service_type conf option on + the Adapter. + :param deprecated_opts: dict of deprecated opts to register with the ksa + Adapter opts. Works the same as the + deprecated_opts kwarg to: + keystoneauth1.loading.session.Session.register_conf_options + :return: List of cfg.Opts. + """ + opts = ks_loading.get_adapter_conf_options(include_deprecated=False, + deprecated_opts=deprecated_opts) + + for opt in opts[:]: + # Remove version-related opts. Required/supported versions are + # something the code knows about, not the operator. + if opt.dest in _ADAPTER_VERSION_OPTS: + opts.remove(opt) + + # Override defaults that make sense for nova + cfg.set_defaults(opts, + valid_interfaces=['internal', 'public'], + service_type=default_service_type) + return opts + + +def _dummy_opt(name): + # A config option that can't be set by the user, so it behaves as if it's + # ignored; but consuming code may expect it to be present in a conf group. + return cfg.Opt(name, type=lambda x: None) + + +def register_ksa_opts(conf, group, default_service_type, deprecated_opts=None): + """Register keystoneauth auth, Session, and Adapter opts. + + :param conf: oslo_config.cfg.CONF in which to register the options + :param group: Conf group, or string name thereof, in which to register the + options. + :param default_service_type: Default for the service_type conf option on + the Adapter. + :param deprecated_opts: dict of deprecated opts to register with the ksa + Session or Adapter opts. See docstring for + the deprecated_opts param of: + keystoneauth1.loading.session.Session.register_conf_options + """ + # ksa register methods need the group name as a string. oslo doesn't care. + group = getattr(group, 'name', group) + ks_loading.register_session_conf_options( + conf, group, deprecated_opts=deprecated_opts) + ks_loading.register_auth_conf_options(conf, group) + conf.register_opts(get_ksa_adapter_opts( + default_service_type, deprecated_opts=deprecated_opts), group=group) + # Have to register dummies for the version-related opts we removed + for name in _ADAPTER_VERSION_OPTS: + conf.register_opt(_dummy_opt(name), group=group) + + +# NOTE(efried): Required for docs build. +def list_opts(): + return {} diff --git a/nova/exception.py b/nova/exception.py index 972c148590f6..68e5e5f4590d 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -968,6 +968,11 @@ class ServiceNotFound(NotFound): msg_fmt = _("Service %(service_id)s could not be found.") +class ConfGroupForServiceTypeNotFound(ServiceNotFound): + msg_fmt = _("No conf group name could be found for service type " + "%(stype)s. Please report this bug.") + + class ServiceBinaryExists(NovaException): msg_fmt = _("Service with host %(host)s binary %(binary)s exists.") diff --git a/nova/image/glance.py b/nova/image/glance.py index 456a8f8591ce..eb4e96772da4 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -47,6 +47,7 @@ import nova.image.download as image_xfers from nova import objects from nova.objects import fields from nova import service_auth +from nova import utils LOG = logging.getLogger(__name__) @@ -106,22 +107,33 @@ def generate_identity_headers(context, status='Confirmed'): def get_api_servers(): - """Shuffle a list of CONF.glance.api_servers and return an iterator - that will cycle through the list, looping around to the beginning - if necessary. + """Shuffle a list of service endpoints and return an iterator that will + cycle through the list, looping around to the beginning if necessary. """ + # NOTE(efried): utils.get_ksa_adapter().get_endpoint() is the preferred + # mechanism for endpoint discovery. Only use `api_servers` if you really + # need to shuffle multiple endpoints. api_servers = [] + if CONF.glance.api_servers: + for api_server in CONF.glance.api_servers: + if '//' not in api_server: + api_server = 'http://' + api_server + # NOTE(sdague): remove in O. + LOG.warning("No protocol specified in for api_server '%s', " + "please update [glance] api_servers with fully " + "qualified url including scheme (http / https)", + api_server) + api_servers.append(api_server) + random.shuffle(api_servers) + else: + # TODO(efried): Plumb in a reasonable auth from callers' contexts + ksa_adap = utils.get_ksa_adapter( + nova.conf.glance.DEFAULT_SERVICE_TYPE, + min_version='2.0', max_version='2.latest') + # TODO(efried): Use ksa_adap.get_endpoint() when bug #1707995 is fixed. + api_servers.append(ksa_adap.endpoint_override or + ksa_adap.get_endpoint_data().catalog_url) - for api_server in CONF.glance.api_servers: - if '//' not in api_server: - api_server = 'http://' + api_server - # NOTE(sdague): remove in O. - LOG.warning("No protocol specified in for api_server '%s', " - "please update [glance] api_servers with fully " - "qualified url including scheme (http / https)", - api_server) - api_servers.append(api_server) - random.shuffle(api_servers) return itertools.cycle(api_servers) diff --git a/nova/tests/unit/image/test_glance.py b/nova/tests/unit/image/test_glance.py index 879ce27ee057..347314a7028d 100644 --- a/nova/tests/unit/image/test_glance.py +++ b/nova/tests/unit/image/test_glance.py @@ -1573,7 +1573,8 @@ class TestDelete(test.NoDBTestCase): class TestGlanceApiServers(test.NoDBTestCase): - def test_get_api_servers(self): + def test_get_api_servers_multiple(self): + """Test get_api_servers via `api_servers` conf option.""" glance_servers = ['10.0.1.1:9292', 'https://10.0.0.1:9293', 'http://10.0.2.2:9294'] @@ -1589,6 +1590,24 @@ class TestGlanceApiServers(test.NoDBTestCase): if i > 2: break + @mock.patch('keystoneauth1.adapter.Adapter.get_endpoint_data') + def test_get_api_servers_get_ksa_adapter(self, mock_epd): + """Test get_api_servers via nova.utils.get_ksa_adapter().""" + self.flags(api_servers=None, group='glance') + api_servers = glance.get_api_servers() + self.assertEqual(mock_epd.return_value.catalog_url, next(api_servers)) + # Still get itertools.cycle behavior + self.assertEqual(mock_epd.return_value.catalog_url, next(api_servers)) + mock_epd.assert_called_once_with() + + # Now test with endpoint_override - get_endpoint_data is not called. + mock_epd.reset_mock() + self.flags(endpoint_override='foo', group='glance') + api_servers = glance.get_api_servers() + self.assertEqual('foo', next(api_servers)) + self.assertEqual('foo', next(api_servers)) + mock_epd.assert_not_called() + class TestUpdateGlanceImage(test.NoDBTestCase): @mock.patch('nova.image.glance.GlanceImageServiceV2') diff --git a/nova/tests/unit/test_utils.py b/nova/tests/unit/test_utils.py index a1815ba41b65..f2a7d0f626a0 100644 --- a/nova/tests/unit/test_utils.py +++ b/nova/tests/unit/test_utils.py @@ -20,6 +20,8 @@ import os.path import tempfile import eventlet +from keystoneauth1.identity import base as ks_identity +from keystoneauth1 import session as ks_session import mock import netaddr from oslo_concurrency import processutils @@ -1307,3 +1309,79 @@ class TestObjectCallHelpers(test.NoDBTestCase): test_utils.obj_called_once_with( tester.foo, 3, test_objects.MyObj(foo=3, bar='baz'))) + + +class GetKSAAdapterTestCase(test.NoDBTestCase): + """Tests for nova.utils.get_endpoint_data().""" + def setUp(self): + super(GetKSAAdapterTestCase, self).setUp() + self.sess = mock.create_autospec(ks_session.Session, instance=True) + self.auth = mock.create_autospec(ks_identity.BaseIdentityPlugin, + instance=True) + + load_sess_p = mock.patch( + 'keystoneauth1.loading.load_session_from_conf_options') + self.addCleanup(load_sess_p.stop) + self.load_sess = load_sess_p.start() + self.load_sess.return_value = self.sess + + load_adap_p = mock.patch( + 'keystoneauth1.loading.load_adapter_from_conf_options') + self.addCleanup(load_adap_p.stop) + self.load_adap = load_adap_p.start() + + load_auth_p = mock.patch( + 'keystoneauth1.loading.load_auth_from_conf_options') + self.addCleanup(load_auth_p.stop) + self.load_auth = load_auth_p.start() + self.load_auth.return_value = self.auth + + def test_bogus_service_type(self): + self.assertRaises(exception.ConfGroupForServiceTypeNotFound, + utils.get_ksa_adapter, 'bogus') + self.load_auth.assert_not_called() + self.load_sess.assert_not_called() + self.load_adap.assert_not_called() + + def test_all_params(self): + ret = utils.get_ksa_adapter( + 'image', ksa_auth='auth', ksa_session='sess', + min_version='min', max_version='max') + # Returned the result of load_adapter_from_conf_options + self.assertEqual(self.load_adap.return_value, ret) + # Because we supplied ksa_auth, load_auth* not called + self.load_auth.assert_not_called() + # Ditto ksa_session/load_session* + self.load_sess.assert_not_called() + # load_adapter* called with what we passed in (and the right group) + self.load_adap.assert_called_once_with( + utils.CONF, 'glance', session='sess', auth='auth', + min_version='min', max_version='max') + + def test_auth_from_session(self): + self.sess.auth = 'auth' + ret = utils.get_ksa_adapter('baremetal', ksa_session=self.sess) + # Returned the result of load_adapter_from_conf_options + self.assertEqual(self.load_adap.return_value, ret) + # Because ksa_auth found in ksa_session, load_auth* not called + self.load_auth.assert_not_called() + # Because we supplied ksa_session, load_session* not called + self.load_sess.assert_not_called() + # load_adapter* called with the auth from the session + self.load_adap.assert_called_once_with( + utils.CONF, 'ironic', session=self.sess, auth='auth', + min_version=None, max_version=None) + + def test_load_auth_and_session(self): + ret = utils.get_ksa_adapter('volumev2') + # Returned the result of load_adapter_from_conf_options + self.assertEqual(self.load_adap.return_value, ret) + # Had to load the auth + self.load_auth.assert_called_once_with(utils.CONF, 'cinder') + # Had to load the session, passed in the loaded auth + self.load_sess.assert_called_once_with(utils.CONF, 'cinder', + auth=self.auth) + # load_adapter* called with the loaded auth & session + self.load_adap.assert_called_once_with( + utils.CONF, 'cinder', session=self.sess, auth=self.auth, + min_version=None, max_version=None) diff --git a/nova/tests/unit/virt/ironic/test_client_wrapper.py b/nova/tests/unit/virt/ironic/test_client_wrapper.py index c320b134e588..a4ec9d6d01ff 100644 --- a/nova/tests/unit/virt/ironic/test_client_wrapper.py +++ b/nova/tests/unit/virt/ironic/test_client_wrapper.py @@ -15,6 +15,7 @@ from ironicclient import client as ironic_client from ironicclient import exc as ironic_exception +from keystoneauth1 import discover as ksa_disc import keystoneauth1.session import mock from oslo_config import cfg @@ -41,6 +42,13 @@ class IronicClientWrapperTestCase(test.NoDBTestCase): self.ironicclient = client_wrapper.IronicClientWrapper() # Do not waste time sleeping cfg.CONF.set_override('api_retry_interval', 0, 'ironic') + get_ksa_adapter_p = mock.patch('nova.utils.get_ksa_adapter') + self.addCleanup(get_ksa_adapter_p.stop) + self.get_ksa_adapter = get_ksa_adapter_p.start() + get_auth_plugin_p = mock.patch('nova.virt.ironic.client_wrapper.' + 'IronicClientWrapper._get_auth_plugin') + self.addCleanup(get_auth_plugin_p.stop) + self.get_auth_plugin = get_auth_plugin_p.start() @mock.patch.object(client_wrapper.IronicClientWrapper, '_multi_getattr') @mock.patch.object(client_wrapper.IronicClientWrapper, '_get_client') @@ -69,6 +77,38 @@ class IronicClientWrapperTestCase(test.NoDBTestCase): ironicclient = client_wrapper.IronicClientWrapper() # dummy call to have _get_client() called ironicclient.call("node.list") + # With no api_endpoint in the conf, ironic_url is retrieved from + # nova.utils.get_ksa_adapter().get_endpoint() + self.get_ksa_adapter.assert_called_once_with( + 'baremetal', ksa_auth=self.get_auth_plugin.return_value, + ksa_session='session', min_version=(1, 32), + max_version=(1, ksa_disc.LATEST)) + expected = {'session': 'session', + 'max_retries': CONF.ironic.api_max_retries, + 'retry_interval': CONF.ironic.api_retry_interval, + 'os_ironic_api_version': '1.32', + 'ironic_url': + self.get_ksa_adapter.return_value.get_endpoint.return_value} + mock_ir_cli.assert_called_once_with(1, **expected) + + @mock.patch.object(keystoneauth1.session, 'Session') + @mock.patch.object(ironic_client, 'get_client') + def test__get_client_session_service_not_found(self, mock_ir_cli, + mock_session): + """Validate behavior when get_endpoint_data raises.""" + mock_session.return_value = 'session' + self.get_ksa_adapter.side_effect = ( + exception.ConfGroupForServiceTypeNotFound(stype='baremetal')) + ironicclient = client_wrapper.IronicClientWrapper() + # dummy call to have _get_client() called + ironicclient.call("node.list") + # With no api_endpoint in the conf, ironic_url is retrieved from + # nova.utils.get_endpoint_data + self.get_ksa_adapter.assert_called_once_with( + 'baremetal', ksa_auth=self.get_auth_plugin.return_value, + ksa_session='session', min_version=(1, 32), + max_version=(1, ksa_disc.LATEST)) + # When get_endpoint_data raises any ServiceNotFound, None is returned. expected = {'session': 'session', 'max_retries': CONF.ironic.api_max_retries, 'retry_interval': CONF.ironic.api_retry_interval, @@ -76,6 +116,24 @@ class IronicClientWrapperTestCase(test.NoDBTestCase): 'ironic_url': None} mock_ir_cli.assert_called_once_with(1, **expected) + @mock.patch.object(keystoneauth1.session, 'Session') + @mock.patch.object(ironic_client, 'get_client') + def test__get_client_session_legacy(self, mock_ir_cli, mock_session): + """Endpoint discovery via legacy api_endpoint conf option.""" + mock_session.return_value = 'session' + endpoint = 'https://baremetal.example.com/endpoint' + self.flags(api_endpoint=endpoint, group='ironic') + ironicclient = client_wrapper.IronicClientWrapper() + # dummy call to have _get_client() called + ironicclient.call("node.list") + self.get_ksa_adapter.assert_not_called() + expected = {'session': 'session', + 'max_retries': CONF.ironic.api_max_retries, + 'retry_interval': CONF.ironic.api_retry_interval, + 'os_ironic_api_version': '1.32', + 'ironic_url': endpoint} + mock_ir_cli.assert_called_once_with(1, **expected) + @mock.patch.object(client_wrapper.IronicClientWrapper, '_multi_getattr') @mock.patch.object(client_wrapper.IronicClientWrapper, '_get_client') def test_call_fail_exception(self, mock_get_client, mock_multi_getattr): diff --git a/nova/utils.py b/nova/utils.py index 7a3209b19355..7653a5fc79c5 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -33,7 +33,9 @@ import tempfile import time import eventlet +from keystoneauth1 import loading as ks_loading import netaddr +from os_service_types import service_types from oslo_concurrency import lockutils from oslo_concurrency import processutils from oslo_context import context as common_context @@ -87,6 +89,8 @@ VIM_IMAGE_ATTRIBUTES = ( _FILE_CACHE = {} +_SERVICE_TYPES = service_types.ServiceTypes() + def get_root_helper(): if CONF.workarounds.disable_rootwrap: @@ -1262,3 +1266,57 @@ def isotime(at=None): def strtime(at): return at.strftime("%Y-%m-%dT%H:%M:%S.%f") + + +def get_ksa_adapter(service_type, ksa_auth=None, ksa_session=None, + min_version=None, max_version=None): + """Construct a keystoneauth1 Adapter for a given service type. + + We expect to find a conf group whose name corresponds to the service_type's + project according to the service-types-authority. That conf group must + provide at least ksa adapter options. Depending how the result is to be + used, ksa auth and/or session options may also be required, or the relevant + parameter supplied. + + :param service_type: String name of the service type for which the Adapter + is to be constructed. + :param ksa_auth: A keystoneauth1 auth plugin. If not specified, we attempt + to find one in ksa_session. Failing that, we attempt to + load one from the conf. + :param ksa_session: A keystoneauth1 Session. If not specified, we attempt + to load one from the conf. + :param min_version: The minimum major version of the adapter's endpoint, + intended to be used as the lower bound of a range with + max_version. + If min_version is given with no max_version it is as + if max version is 'latest'. + :param max_version: The maximum major version of the adapter's endpoint, + intended to be used as the upper bound of a range with + min_version. + :return: A keystoneauth1 Adapter object for the specified service_type. + :raise: ConfGroupForServiceTypeNotFound If no conf group name could be + found for the specified service_type. This should be considered a + bug. + """ + # 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) + + # Ensure we have an auth. + # NOTE(efried): This could be None, and that could be okay - e.g. if the + # result is being used for get_endpoint() and the conf only contains + # endpoint_override. + if not ksa_auth: + if ksa_session and ksa_session.auth: + ksa_auth = ksa_session.auth + else: + ksa_auth = ks_loading.load_auth_from_conf_options(CONF, confgrp) + + if not ksa_session: + ksa_session = ks_loading.load_session_from_conf_options( + CONF, confgrp, auth=ksa_auth) + + return ks_loading.load_adapter_from_conf_options( + CONF, confgrp, session=ksa_session, auth=ksa_auth, + min_version=min_version, max_version=max_version) diff --git a/nova/virt/ironic/client_wrapper.py b/nova/virt/ironic/client_wrapper.py index 9453adfcf27d..184ca84d732b 100644 --- a/nova/virt/ironic/client_wrapper.py +++ b/nova/virt/ironic/client_wrapper.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +from keystoneauth1 import discover as ks_disc from keystoneauth1 import loading as ks_loading from oslo_log import log as logging from oslo_utils import importutils @@ -20,6 +21,7 @@ from oslo_utils import importutils import nova.conf from nova import exception from nova.i18n import _ +from nova import utils LOG = logging.getLogger(__name__) @@ -89,10 +91,26 @@ class IronicClientWrapper(object): kwargs['retry_interval'] = retry_interval kwargs['os_ironic_api_version'] = '%d.%d' % IRONIC_API_VERSION - # NOTE(clenimar): by default, the endpoint is taken from the service - # catalog. Use `api_endpoint` if you want to override it. - ironic_url = (CONF.ironic.api_endpoint - if CONF.ironic.api_endpoint else None) + # NOTE(clenimar/efried): by default, the endpoint is taken from the + # service catalog. Use `endpoint_override` if you want to override it. + if CONF.ironic.api_endpoint: + # NOTE(efried): `api_endpoint` still overrides service catalog and + # `endpoint_override` conf options. This will be removed in a + # future release. + ironic_url = CONF.ironic.api_endpoint + else: + try: + ksa_adap = utils.get_ksa_adapter( + nova.conf.ironic.DEFAULT_SERVICE_TYPE, + ksa_auth=auth_plugin, ksa_session=sess, + min_version=IRONIC_API_VERSION, + max_version=(IRONIC_API_VERSION[0], ks_disc.LATEST)) + ironic_url = ksa_adap.get_endpoint() + except exception.ServiceNotFound: + # NOTE(efried): No reason to believe service catalog lookup + # won't also fail in ironic client init, but this way will + # yield the expected exception/behavior. + ironic_url = None try: cli = ironic.client.get_client(IRONIC_API_VERSION[0], diff --git a/releasenotes/notes/glance-via-ksa-5646eb3d5db51c54.yaml b/releasenotes/notes/glance-via-ksa-5646eb3d5db51c54.yaml new file mode 100644 index 000000000000..1039b6d7199b --- /dev/null +++ b/releasenotes/notes/glance-via-ksa-5646eb3d5db51c54.yaml @@ -0,0 +1,10 @@ +--- +upgrade: + - | + Nova now uses keystoneauth1 configuration to set up communication with the + image service. Use keystoneauth1 loading parameters for auth, Session, and + Adapter setup in the ``[glance]`` conf section. This includes using + ``endpoint_override`` in favor of ``api_servers``. The + ``[glance]api_servers`` conf option is still supported, but should only be + used if you need multiple endpoints and are unable to use a load balancer + for some reason. diff --git a/releasenotes/notes/ironic-via-ksa-deffd3dac48ff4eb.yaml b/releasenotes/notes/ironic-via-ksa-deffd3dac48ff4eb.yaml new file mode 100644 index 000000000000..35138fff9258 --- /dev/null +++ b/releasenotes/notes/ironic-via-ksa-deffd3dac48ff4eb.yaml @@ -0,0 +1,11 @@ +--- +upgrade: + - | + Nova now uses keystoneauth1 configuration to set up communication with the + baremetal service. Use keystoneauth1 loading parameters for auth, Session, + and Adapter setup in the ``[ironic]`` conf section. This includes using + ``endpoint_override`` in favor of ``api_endpoint``. +deprecations: + - | + Configuration option ``[ironic]api_endpoint`` is deprecated in favor of + ``[ironic]endpoint_override``. diff --git a/requirements.txt b/requirements.txt index 69beefa1b769..699d078e9715 100644 --- a/requirements.txt +++ b/requirements.txt @@ -62,3 +62,4 @@ os-xenapi>=0.3.1 # Apache-2.0 tooz>=1.58.0 # Apache-2.0 cursive>=0.1.2 # Apache-2.0 pypowervm>=1.1.7 # Apache-2.0 +os-service-types>=1.1.0 # Apache-2.0