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:
Eric Fried 2017-04-19 17:03:17 -05:00
parent d19feee6dd
commit 0a8f019be0
13 changed files with 427 additions and 45 deletions

View File

@ -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))
}

View File

@ -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
View 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 {}

View File

@ -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.")

View File

@ -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)

View File

@ -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')

View File

@ -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)

View File

@ -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):

View File

@ -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)

View File

@ -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],

View 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.

View 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``.

View File

@ -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