Make factory for a CloudRegion from CONF objects
This commit enables SDK consumers using oslo.config for keystoneauth1 Adapter settings by introducing a method: openstack.config.cloud_region.from_conf This accepts: - An oslo.config ConfigOpts containing Adapter options in sections named according to project (e.g. [nova], not [compute]). Current behavior is to use defaults if no such section exists, which may not be what we want long term. - A Session. This is currently required - if unspecified, a ConfigException is raised - but in the future we probably want to support creating one (and an auth) from the conf. - Other kwargs to be passed to the CloudRegion constructor. The method returns a CloudRegion that can be used to create a Connection. Needed-By: blueprint openstacksdk-in-nova Co-Authored-By: Eric Fried <openstack@fried.cc> Change-Id: I05fb4da39d2eefc91828ace02db2741b62a2cb0a
This commit is contained in:
parent
9db14f6d78
commit
86ad9debd1
@ -14,7 +14,7 @@ jmespath==0.9.0
|
|||||||
jsonpatch==1.16
|
jsonpatch==1.16
|
||||||
jsonpointer==1.13
|
jsonpointer==1.13
|
||||||
jsonschema==2.6.0
|
jsonschema==2.6.0
|
||||||
keystoneauth1==3.13.0
|
keystoneauth1==3.14.0
|
||||||
linecache2==1.0.0
|
linecache2==1.0.0
|
||||||
mock==2.0.0
|
mock==2.0.0
|
||||||
mox3==0.20.0
|
mox3==0.20.0
|
||||||
@ -22,6 +22,7 @@ munch==2.1.0
|
|||||||
netifaces==0.10.4
|
netifaces==0.10.4
|
||||||
os-client-config==1.28.0
|
os-client-config==1.28.0
|
||||||
os-service-types==1.2.0
|
os-service-types==1.2.0
|
||||||
|
oslo.config==6.1.0
|
||||||
oslotest==3.2.0
|
oslotest==3.2.0
|
||||||
pbr==2.0.0
|
pbr==2.0.0
|
||||||
prometheus-client==0.4.2
|
prometheus-client==0.4.2
|
||||||
|
@ -17,6 +17,7 @@ import warnings
|
|||||||
|
|
||||||
from keystoneauth1 import discover
|
from keystoneauth1 import discover
|
||||||
import keystoneauth1.exceptions.catalog
|
import keystoneauth1.exceptions.catalog
|
||||||
|
from keystoneauth1.loading import adapter as ks_load_adap
|
||||||
from keystoneauth1 import session as ks_session
|
from keystoneauth1 import session as ks_session
|
||||||
import os_service_types
|
import os_service_types
|
||||||
import requestsexceptions
|
import requestsexceptions
|
||||||
@ -45,6 +46,10 @@ SCOPE_KEYS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Sentinel for nonexistence
|
||||||
|
_ENOENT = object()
|
||||||
|
|
||||||
|
|
||||||
def _make_key(key, service_type):
|
def _make_key(key, service_type):
|
||||||
if not service_type:
|
if not service_type:
|
||||||
return key
|
return key
|
||||||
@ -98,6 +103,56 @@ def from_session(session, name=None, region_name=None,
|
|||||||
app_name=app_name, app_version=app_version)
|
app_name=app_name, app_version=app_version)
|
||||||
|
|
||||||
|
|
||||||
|
def from_conf(conf, session=None, **kwargs):
|
||||||
|
"""Create a CloudRegion from oslo.config ConfigOpts.
|
||||||
|
|
||||||
|
:param oslo_config.cfg.ConfigOpts conf:
|
||||||
|
An oslo.config ConfigOpts containing keystoneauth1.Adapter options in
|
||||||
|
sections named according to project (e.g. [nova], not [compute]).
|
||||||
|
TODO: Current behavior is to use defaults if no such section exists,
|
||||||
|
which may not be what we want long term.
|
||||||
|
:param keystoneauth1.session.Session session:
|
||||||
|
An existing authenticated Session to use. This is currently required.
|
||||||
|
TODO: Load this (and auth) from the conf.
|
||||||
|
:param kwargs:
|
||||||
|
Additional keyword arguments to be passed directly to the CloudRegion
|
||||||
|
constructor.
|
||||||
|
:raise openstack.exceptions.ConfigException:
|
||||||
|
If session is not specified.
|
||||||
|
:return:
|
||||||
|
An openstack.config.cloud_region.CloudRegion.
|
||||||
|
"""
|
||||||
|
if not session:
|
||||||
|
# TODO(mordred) Fill this in - not needed for first stab with nova
|
||||||
|
raise exceptions.ConfigException("A Session must be supplied.")
|
||||||
|
config_dict = kwargs.pop('config', config_defaults.get_defaults())
|
||||||
|
stm = os_service_types.ServiceTypes()
|
||||||
|
# TODO(mordred) Think about region_name here
|
||||||
|
region_name = kwargs.pop('region_name', None)
|
||||||
|
for st in stm.all_types_by_service_type:
|
||||||
|
project_name = stm.get_project_name(st)
|
||||||
|
if project_name not in conf:
|
||||||
|
continue
|
||||||
|
opt_dict = {}
|
||||||
|
# Populate opt_dict with (appropriately processed) Adapter conf opts
|
||||||
|
try:
|
||||||
|
ks_load_adap.process_conf_options(conf[project_name], opt_dict)
|
||||||
|
except Exception:
|
||||||
|
# NOTE(efried): This is for oslo_config.cfg.NoSuchOptError, but we
|
||||||
|
# don't want to drag in oslo.config just for that.
|
||||||
|
continue
|
||||||
|
# Load them into config_dict under keys prefixed by ${service_type}_
|
||||||
|
for raw_name, opt_val in opt_dict.items():
|
||||||
|
if raw_name == 'region_name':
|
||||||
|
region_name = opt_val
|
||||||
|
continue
|
||||||
|
config_name = '_'.join([st, raw_name])
|
||||||
|
config_dict[config_name] = opt_val
|
||||||
|
return CloudRegion(
|
||||||
|
session=session, region_name=region_name, config=config_dict,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class CloudRegion(object):
|
class CloudRegion(object):
|
||||||
"""The configuration for a Region of an OpenStack Cloud.
|
"""The configuration for a Region of an OpenStack Cloud.
|
||||||
|
|
||||||
|
@ -112,6 +112,27 @@ is needed:
|
|||||||
compute_api_version='2',
|
compute_api_version='2',
|
||||||
identity_interface='internal')
|
identity_interface='internal')
|
||||||
|
|
||||||
|
From oslo.conf CONF object
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
For applications that have an oslo.config ``CONF`` object that has been
|
||||||
|
populated with ``keystoneauth1.loading.register_adapter_conf_options`` in
|
||||||
|
groups named by the OpenStack service's project name, it is possible to
|
||||||
|
construct a Connection with the ``CONF`` object and an authenticated Session.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This is primarily intended for use by OpenStack services to talk amongst
|
||||||
|
themselves.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from openstack import connection
|
||||||
|
|
||||||
|
conn = connection.Connection(
|
||||||
|
session=session,
|
||||||
|
oslo_config=CONF)
|
||||||
|
|
||||||
From existing CloudRegion
|
From existing CloudRegion
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
@ -249,6 +270,7 @@ class Connection(six.with_metaclass(_meta.ConnectionMeta,
|
|||||||
use_direct_get=False,
|
use_direct_get=False,
|
||||||
task_manager=None,
|
task_manager=None,
|
||||||
rate_limit=None,
|
rate_limit=None,
|
||||||
|
oslo_conf=None,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
"""Create a connection to a cloud.
|
"""Create a connection to a cloud.
|
||||||
|
|
||||||
@ -295,6 +317,11 @@ class Connection(six.with_metaclass(_meta.ConnectionMeta,
|
|||||||
keys as service-type and values as floats expressing the calls
|
keys as service-type and values as floats expressing the calls
|
||||||
per second for that service. Defaults to None, which means no
|
per second for that service. Defaults to None, which means no
|
||||||
rate-limiting is performed.
|
rate-limiting is performed.
|
||||||
|
:param oslo_conf: An oslo.config CONF object.
|
||||||
|
:type oslo_conf: :class:`~oslo_config.cfg.ConfigOpts`
|
||||||
|
An oslo.config ``CONF`` object that has been populated with
|
||||||
|
``keystoneauth1.loading.register_adapter_conf_options`` in
|
||||||
|
groups named by the OpenStack service's project name.
|
||||||
:param kwargs: If a config is not provided, the rest of the parameters
|
:param kwargs: If a config is not provided, the rest of the parameters
|
||||||
provided are assumed to be arguments to be passed to the
|
provided are assumed to be arguments to be passed to the
|
||||||
CloudRegion constructor.
|
CloudRegion constructor.
|
||||||
@ -306,7 +333,11 @@ class Connection(six.with_metaclass(_meta.ConnectionMeta,
|
|||||||
self._extra_services[service.service_type] = service
|
self._extra_services[service.service_type] = service
|
||||||
|
|
||||||
if not self.config:
|
if not self.config:
|
||||||
if session:
|
if oslo_conf:
|
||||||
|
self.config = cloud_region.from_conf(
|
||||||
|
oslo_conf, session=session, app_name=app_name,
|
||||||
|
app_version=app_version)
|
||||||
|
elif session:
|
||||||
self.config = cloud_region.from_session(
|
self.config = cloud_region.from_session(
|
||||||
session=session,
|
session=session,
|
||||||
app_name=app_name, app_version=app_version,
|
app_name=app_name, app_version=app_version,
|
||||||
|
165
openstack/tests/unit/config/test_from_conf.py
Normal file
165
openstack/tests/unit/config/test_from_conf.py
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from keystoneauth1 import exceptions as ks_exc
|
||||||
|
from keystoneauth1 import loading as ks_loading
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from openstack.config import cloud_region
|
||||||
|
from openstack import connection
|
||||||
|
from openstack import exceptions
|
||||||
|
from openstack.tests import fakes
|
||||||
|
from openstack.tests.unit import base
|
||||||
|
|
||||||
|
|
||||||
|
class TestFromConf(base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestFromConf, self).setUp()
|
||||||
|
self.oslo_config_dict = {
|
||||||
|
# All defaults for nova
|
||||||
|
'nova': {},
|
||||||
|
# monasca not in the service catalog
|
||||||
|
'monasca': {},
|
||||||
|
# Overrides for heat
|
||||||
|
'heat': {
|
||||||
|
'region_name': 'SpecialRegion',
|
||||||
|
'interface': 'internal',
|
||||||
|
'endpoint_override': 'https://example.org:8888/heat/v2'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _load_ks_cfg_opts(self):
|
||||||
|
conf = cfg.ConfigOpts()
|
||||||
|
for group, opts in self.oslo_config_dict.items():
|
||||||
|
if opts is not None:
|
||||||
|
ks_loading.register_adapter_conf_options(conf, group)
|
||||||
|
for name, val in opts.items():
|
||||||
|
conf.set_override(name, val, group=group)
|
||||||
|
return conf
|
||||||
|
|
||||||
|
def _get_conn(self):
|
||||||
|
oslocfg = self._load_ks_cfg_opts()
|
||||||
|
# Throw name in here to prove **kwargs is working
|
||||||
|
config = cloud_region.from_conf(
|
||||||
|
oslocfg, session=self.cloud.session, name='from_conf.example.com')
|
||||||
|
self.assertEqual('from_conf.example.com', config.name)
|
||||||
|
|
||||||
|
# TODO(efried): Currently region_name gets set to the last value seen
|
||||||
|
# in the config, which is nondeterministic and surely incorrect.
|
||||||
|
# Sometimes that's SpecialRegion, but some tests use the base fixtures
|
||||||
|
# which have no compute endpoint in SpecialRegion. Force override for
|
||||||
|
# now to make those tests work.
|
||||||
|
config.region_name = None
|
||||||
|
|
||||||
|
return connection.Connection(config=config)
|
||||||
|
|
||||||
|
def test_adapter_opts_set(self):
|
||||||
|
"""Adapter opts specified in the conf."""
|
||||||
|
conn = self._get_conn()
|
||||||
|
|
||||||
|
discovery = {
|
||||||
|
"versions": {
|
||||||
|
"values": [
|
||||||
|
{"status": "stable",
|
||||||
|
"updated": "2019-06-01T00:00:00Z",
|
||||||
|
"media-types": [{
|
||||||
|
"base": "application/json",
|
||||||
|
"type": "application/vnd.openstack.heat-v2+json"}],
|
||||||
|
"id": "v2.0",
|
||||||
|
"links": [{
|
||||||
|
"href": "https://example.org:8888/heat/v2",
|
||||||
|
"rel": "self"}]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.register_uris([
|
||||||
|
dict(method='GET',
|
||||||
|
uri='https://example.org:8888/heat/v2',
|
||||||
|
json=discovery),
|
||||||
|
dict(method='GET',
|
||||||
|
uri='https://example.org:8888/heat/v2/foo',
|
||||||
|
json={'foo': {}}),
|
||||||
|
])
|
||||||
|
|
||||||
|
adap = conn.orchestration
|
||||||
|
# TODO(efried): Fix this when region_name behaves correctly.
|
||||||
|
# self.assertEqual('SpecialRegion', adap.region_name)
|
||||||
|
self.assertEqual('orchestration', adap.service_type)
|
||||||
|
self.assertEqual('internal', adap.interface)
|
||||||
|
self.assertEqual('https://example.org:8888/heat/v2',
|
||||||
|
adap.endpoint_override)
|
||||||
|
|
||||||
|
adap.get('/foo')
|
||||||
|
self.assert_calls()
|
||||||
|
|
||||||
|
def test_default_adapter_opts(self):
|
||||||
|
"""Adapter opts are registered, but all defaulting in conf."""
|
||||||
|
conn = self._get_conn()
|
||||||
|
|
||||||
|
# Nova has empty adapter config, so these default
|
||||||
|
adap = conn.compute
|
||||||
|
self.assertIsNone(adap.region_name)
|
||||||
|
self.assertEqual('compute', adap.service_type)
|
||||||
|
self.assertEqual('public', adap.interface)
|
||||||
|
self.assertIsNone(adap.endpoint_override)
|
||||||
|
|
||||||
|
server_id = str(uuid.uuid4())
|
||||||
|
server_name = self.getUniqueString('name')
|
||||||
|
fake_server = fakes.make_fake_server(server_id, server_name)
|
||||||
|
self.register_uris([
|
||||||
|
self.get_nova_discovery_mock_dict(),
|
||||||
|
dict(method='GET',
|
||||||
|
uri=self.get_mock_url(
|
||||||
|
'compute', 'public', append=['servers', 'detail']),
|
||||||
|
json={'servers': [fake_server]}),
|
||||||
|
])
|
||||||
|
s = next(adap.servers())
|
||||||
|
self.assertEqual(s.id, server_id)
|
||||||
|
self.assertEqual(s.name, server_name)
|
||||||
|
self.assert_calls()
|
||||||
|
|
||||||
|
def test_no_adapter_opts(self):
|
||||||
|
"""Adapter opts for service type not registered."""
|
||||||
|
del self.oslo_config_dict['heat']
|
||||||
|
conn = self._get_conn()
|
||||||
|
|
||||||
|
# TODO(efried): This works, even though adapter opts are not
|
||||||
|
# registered. Should it?
|
||||||
|
adap = conn.orchestration
|
||||||
|
self.assertIsNone(adap.region_name)
|
||||||
|
self.assertEqual('orchestration', adap.service_type)
|
||||||
|
self.assertEqual('public', adap.interface)
|
||||||
|
self.assertIsNone(adap.endpoint_override)
|
||||||
|
|
||||||
|
self.register_uris([
|
||||||
|
dict(method='GET',
|
||||||
|
uri=self.get_mock_url(
|
||||||
|
'orchestration', append=['foo']),
|
||||||
|
json={'foo': {}})
|
||||||
|
])
|
||||||
|
adap.get('/foo')
|
||||||
|
self.assert_calls()
|
||||||
|
|
||||||
|
def test_no_session(self):
|
||||||
|
# TODO(efried): Currently calling without a Session is not implemented.
|
||||||
|
self.assertRaises(exceptions.ConfigException,
|
||||||
|
cloud_region.from_conf, self._load_ks_cfg_opts())
|
||||||
|
|
||||||
|
def test_no_endpoint(self):
|
||||||
|
"""Conf contains adapter opts, but service type not in catalog."""
|
||||||
|
conn = self._get_conn()
|
||||||
|
# Monasca is not in the service catalog
|
||||||
|
self.assertRaises(ks_exc.catalog.EndpointNotFound,
|
||||||
|
getattr, conn, 'monitoring')
|
6
releasenotes/notes/conf-object-ctr-c0e1da0a67dad841.yaml
Normal file
6
releasenotes/notes/conf-object-ctr-c0e1da0a67dad841.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Added the ability to create a ``Connection`` from an ``oslo.config``
|
||||||
|
``CONF`` object. This is primarily intended to be used by OpenStack
|
||||||
|
services using SDK for inter-service communication.
|
@ -8,7 +8,7 @@ requestsexceptions>=1.2.0 # Apache-2.0
|
|||||||
jsonpatch!=1.20,>=1.16 # BSD
|
jsonpatch!=1.20,>=1.16 # BSD
|
||||||
six>=1.10.0 # MIT
|
six>=1.10.0 # MIT
|
||||||
os-service-types>=1.2.0 # Apache-2.0
|
os-service-types>=1.2.0 # Apache-2.0
|
||||||
keystoneauth1>=3.13.0 # Apache-2.0
|
keystoneauth1>=3.14.0 # Apache-2.0
|
||||||
|
|
||||||
munch>=2.1.0 # MIT
|
munch>=2.1.0 # MIT
|
||||||
decorator>=3.4.0 # BSD
|
decorator>=3.4.0 # BSD
|
||||||
|
@ -10,6 +10,7 @@ jsonschema>=2.6.0 # MIT
|
|||||||
mock>=2.0.0 # BSD
|
mock>=2.0.0 # BSD
|
||||||
prometheus-client>=0.4.2 # Apache-2.0
|
prometheus-client>=0.4.2 # Apache-2.0
|
||||||
python-subunit>=1.0.0 # Apache-2.0/BSD
|
python-subunit>=1.0.0 # Apache-2.0/BSD
|
||||||
|
oslo.config>=6.1.0 # Apache-2.0
|
||||||
oslotest>=3.2.0 # Apache-2.0
|
oslotest>=3.2.0 # Apache-2.0
|
||||||
requests-mock>=1.2.0 # Apache-2.0
|
requests-mock>=1.2.0 # Apache-2.0
|
||||||
statsd>=3.3.0
|
statsd>=3.3.0
|
||||||
|
Loading…
Reference in New Issue
Block a user