nova.utils.get_ksa_adapter()
Provide a new method: nova.utils.get_ksa_adapter(service_type, ks_auth=None, ks_session=None, min_version=None, max_version=None)) ...to configure a keystoneauth1 Adapter for a service. The Adapter, and its component keystoneauth1 artifacts not passed into the method, are loaded based on options in the conf group corresponding to the specified service_type. The ultimate goal is to replace the various disparate mechanisms used by different services to do endpoint URL and version discovery. In Queens, the original mechanisms will still take precedence, but (other than [glance]api_servers - see the spec) will be deprecated. In Rocky, the deprecated options will be removed. This change incorporates the above utility into endpoint discovery for glance and ironic. Future change sets will do the same for other services (cinder, neutron, placement). Change-Id: If625411f40be0ba642baeb02950f568f43673655 Partial-Implements: bp use-ksa-adapter-for-endpoints Closes-Bug: #1707860
This commit is contained in:
parent
d19feee6dd
commit
0a8f019be0
@ -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))
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
85
nova/conf/utils.py
Normal file
85
nova/conf/utils.py
Normal file
@ -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 {}
|
@ -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.")
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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],
|
||||
|
10
releasenotes/notes/glance-via-ksa-5646eb3d5db51c54.yaml
Normal file
10
releasenotes/notes/glance-via-ksa-5646eb3d5db51c54.yaml
Normal file
@ -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.
|
11
releasenotes/notes/ironic-via-ksa-deffd3dac48ff4eb.yaml
Normal file
11
releasenotes/notes/ironic-via-ksa-deffd3dac48ff4eb.yaml
Normal file
@ -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``.
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user