diff options
-rw-r--r-- | README.rst | 15 | ||||
-rw-r--r-- | os_client_config/cloud_config.py | 2 | ||||
-rw-r--r-- | os_client_config/config.py | 31 | ||||
-rw-r--r-- | os_client_config/tests/base.py | 80 | ||||
-rw-r--r-- | os_client_config/tests/test_config.py | 67 | ||||
-rw-r--r-- | os_client_config/tests/test_environ.py | 67 | ||||
-rw-r--r-- | os_client_config/tests/test_os_client_config.py | 28 |
7 files changed, 181 insertions, 109 deletions
@@ -16,14 +16,13 @@ os-client-config honors all of the normal `OS_*` variables. It does not provide backwards compatibility to service-specific variables such as `NOVA_USERNAME`. -If you have environment variables and no config files, os-client-config -will produce a cloud config object named "openstack" containing your -values from the environment. +If you have OpenStack environment variables seet and no config files, +os-client-config will produce a cloud config object named "envvars" containing +your values from the environment. Service specific settings, like the nova service type, are set with the default service type as a prefix. For instance, to set a special service_type -for trove (because you're using Rackspace) set: -:: +for trove set:: export OS_DATABASE_SERVICE_TYPE=rax:database @@ -40,7 +39,7 @@ locations: The first file found wins. The keys are all of the keys you'd expect from `OS_*` - except lower case -and without the OS prefix. So, username is set with `username`. +and without the OS prefix. So, region name is set with `region_name`. Service specific settings, like the nova service type, are set with the default service type as a prefix. For instance, to set a special service_type @@ -119,6 +118,10 @@ behaviors: * `cache.max_age` and nothing else gets you memory cache. * Otherwise, `cache.class` and `cache.arguments` are passed in +`os-client-config` does not actually cache anything itself, but it collects +and presents the cache information so that your various applications that +are connecting to OpenStack can share a cache should you desire. + :: cache: diff --git a/os_client_config/cloud_config.py b/os_client_config/cloud_config.py index 6f501c6..c17bfc2 100644 --- a/os_client_config/cloud_config.py +++ b/os_client_config/cloud_config.py @@ -15,7 +15,7 @@ class CloudConfig(object): def __init__(self, name, region, config): - self.name = name or 'openstack' + self.name = name self.region = region self.config = config diff --git a/os_client_config/config.py b/os_client_config/config.py index 8494043..70326cf 100644 --- a/os_client_config/config.py +++ b/os_client_config/config.py @@ -54,10 +54,12 @@ def get_boolean(value): def _get_os_environ(): ret = dict(defaults._defaults) - for (k, v) in os.environ.items(): - if k.startswith('OS_'): - newkey = k[3:].lower() - ret[newkey] = v + environkeys = [k for k in os.environ.keys() if k.startswith('OS_')] + if not environkeys: + return None + for k in environkeys: + newkey = k[3:].lower() + ret[newkey] = os.environ[k] return ret @@ -80,7 +82,7 @@ class OpenStackConfig(object): self._config_files = config_files or CONFIG_FILES self._vendor_files = vendor_files or VENDOR_FILES - self.defaults = _get_os_environ() + self.defaults = dict(defaults._defaults) # use a config file if it exists where expected self.cloud_config = self._load_config_file() @@ -88,6 +90,14 @@ class OpenStackConfig(object): self.cloud_config = dict( clouds=dict(openstack=dict(self.defaults))) + envvars = _get_os_environ() + if envvars: + if 'envvars' in self.cloud_config['clouds']: + raise exceptions.OpenStackConfigException( + 'clouds.yaml defines a cloud named envvars, and OS_' + ' env vars are set') + self.cloud_config['clouds']['envvars'] = envvars + self._cache_max_age = None self._cache_path = CACHE_PATH self._cache_class = 'dogpile.cache.null' @@ -109,6 +119,7 @@ class OpenStackConfig(object): if os.path.exists(path): with open(path, 'r') as f: return yaml.safe_load(f) + return dict(clouds=dict()) def _load_vendor_file(self): for path in self._vendor_files: @@ -135,7 +146,7 @@ class OpenStackConfig(object): # No region configured return '' - def _get_region(self, cloud): + def _get_region(self, cloud=None): return self._get_regions(cloud).split(',')[0] def _get_cloud_sections(self): @@ -152,7 +163,7 @@ class OpenStackConfig(object): our_cloud = self.cloud_config['clouds'].get(name, dict()) - # Get the defaults (including env vars) first + # Get the defaults cloud.update(self.defaults) # yes, I know the next line looks silly @@ -197,7 +208,8 @@ class OpenStackConfig(object): if key in cloud['auth']: target = cloud['auth'][key] del cloud['auth'][key] - cloud['auth'][target_key] = target + if target: + cloud['auth'][target_key] = target return cloud def _fix_backwards_auth_plugin(self, cloud): @@ -321,6 +333,9 @@ class OpenStackConfig(object): :param kwargs: Additional configuration options """ + if cloud is None and 'envvars' in self._get_cloud_sections(): + cloud = 'envvars' + args = self._fix_args(kwargs, argparse=argparse) if 'region_name' not in args or args['region_name'] is None: diff --git a/os_client_config/tests/base.py b/os_client_config/tests/base.py index 1c30cdb..f6e815d 100644 --- a/os_client_config/tests/base.py +++ b/os_client_config/tests/base.py @@ -15,9 +15,87 @@ # License for the specific language governing permissions and limitations # under the License. + +import os +import tempfile + +from os_client_config import cloud_config + +import extras +import fixtures from oslotest import base +import yaml -class TestCase(base.BaseTestCase): +VENDOR_CONF = { + 'public-clouds': { + '_test_cloud_in_our_cloud': { + 'auth': { + 'username': 'testotheruser', + 'project_name': 'testproject', + }, + }, + } +} +USER_CONF = { + 'clouds': { + '_test_cloud_': { + 'cloud': '_test_cloud_in_our_cloud', + 'auth': { + 'username': 'testuser', + 'password': 'testpass', + }, + 'region_name': 'test-region', + }, + '_test_cloud_no_vendor': { + 'cloud': '_test_non_existant_cloud', + 'auth': { + 'username': 'testuser', + 'password': 'testpass', + 'project_name': 'testproject', + }, + 'region_name': 'test-region', + }, + }, + 'cache': {'max_age': 1}, +} + + +def _write_yaml(obj): + # Assume NestedTempfile so we don't have to cleanup + with tempfile.NamedTemporaryFile(delete=False) as obj_yaml: + obj_yaml.write(yaml.safe_dump(obj).encode('utf-8')) + return obj_yaml.name + +class TestCase(base.BaseTestCase): """Test case base class for all unit tests.""" + + def setUp(self): + super(TestCase, self).setUp() + + self.useFixture(fixtures.NestedTempfile()) + conf = dict(USER_CONF) + tdir = self.useFixture(fixtures.TempDir()) + conf['cache']['path'] = tdir.path + self.cloud_yaml = _write_yaml(conf) + self.vendor_yaml = _write_yaml(VENDOR_CONF) + + # Isolate the test runs from the environment + # Do this as two loops because you can't modify the dict in a loop + # over the dict in 3.4 + keys_to_isolate = [] + for env in os.environ.keys(): + if env.startswith('OS_'): + keys_to_isolate.append(env) + for env in keys_to_isolate: + self.useFixture(fixtures.EnvironmentVariable(env)) + + def _assert_cloud_details(self, cc): + self.assertIsInstance(cc, cloud_config.CloudConfig) + self.assertTrue(extras.safe_hasattr(cc, 'auth')) + self.assertIsInstance(cc.auth, dict) + self.assertIsNone(cc.cloud) + self.assertIn('username', cc.auth) + self.assertEqual('testuser', cc.auth['username']) + self.assertEqual('testproject', cc.auth['project_name']) diff --git a/os_client_config/tests/test_config.py b/os_client_config/tests/test_config.py index 5ef3048..9decf3e 100644 --- a/os_client_config/tests/test_config.py +++ b/os_client_config/tests/test_config.py @@ -12,66 +12,12 @@ # License for the specific language governing permissions and limitations # under the License. -import tempfile - -import extras -import fixtures -import testtools -import yaml - from os_client_config import cloud_config from os_client_config import config - -VENDOR_CONF = { - 'public-clouds': { - '_test_cloud_in_our_cloud': { - 'auth': { - 'username': 'testotheruser', - 'project_name': 'testproject', - }, - }, - } -} -USER_CONF = { - 'clouds': { - '_test_cloud_': { - 'cloud': '_test_cloud_in_our_cloud', - 'auth': { - 'username': 'testuser', - 'password': 'testpass', - }, - 'region_name': 'test-region', - }, - '_test_cloud_no_vendor': { - 'cloud': '_test_non_existant_cloud', - 'auth': { - 'username': 'testuser', - 'password': 'testpass', - 'project_name': 'testproject', - }, - 'region_name': 'test-region', - }, - }, - 'cache': {'max_age': 1}, -} +from os_client_config.tests import base -def _write_yaml(obj): - # Assume NestedTempfile so we don't have to cleanup - with tempfile.NamedTemporaryFile(delete=False) as obj_yaml: - obj_yaml.write(yaml.safe_dump(obj).encode('utf-8')) - return obj_yaml.name - - -class TestConfig(testtools.TestCase): - def setUp(self): - super(TestConfig, self).setUp() - self.useFixture(fixtures.NestedTempfile()) - conf = dict(USER_CONF) - tdir = self.useFixture(fixtures.TempDir()) - conf['cache']['path'] = tdir.path - self.cloud_yaml = _write_yaml(conf) - self.vendor_yaml = _write_yaml(VENDOR_CONF) +class TestConfig(base.TestCase): def test_get_one_cloud(self): c = config.OpenStackConfig(config_files=[self.cloud_yaml], @@ -90,12 +36,3 @@ class TestConfig(testtools.TestCase): self._assert_cloud_details(cc) cc = c.get_one_cloud('_test_cloud_no_vendor') self._assert_cloud_details(cc) - - def _assert_cloud_details(self, cc): - self.assertIsInstance(cc, cloud_config.CloudConfig) - self.assertTrue(extras.safe_hasattr(cc, 'auth')) - self.assertIsInstance(cc.auth, dict) - self.assertIsNone(cc.cloud) - self.assertIn('username', cc.auth) - self.assertEqual('testuser', cc.auth['username']) - self.assertEqual('testproject', cc.auth['project_name']) diff --git a/os_client_config/tests/test_environ.py b/os_client_config/tests/test_environ.py new file mode 100644 index 0000000..473c417 --- /dev/null +++ b/os_client_config/tests/test_environ.py @@ -0,0 +1,67 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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. + + +from os_client_config import cloud_config +from os_client_config import config +from os_client_config import exceptions +from os_client_config.tests import base + +import fixtures + + +class TestConfig(base.TestCase): + + def test_get_one_cloud(self): + c = config.OpenStackConfig(config_files=[self.cloud_yaml], + vendor_files=[self.vendor_yaml]) + self.assertIsInstance(c.get_one_cloud(), cloud_config.CloudConfig) + + def test_no_environ(self): + c = config.OpenStackConfig(config_files=[self.cloud_yaml], + vendor_files=[self.vendor_yaml]) + self.assertRaises( + exceptions.OpenStackConfigException, c.get_one_cloud, 'envvars') + + def test_environ_exists(self): + self.useFixture( + fixtures.EnvironmentVariable('OS_AUTH_URL', 'https://example.com')) + self.useFixture( + fixtures.EnvironmentVariable('OS_USERNAME', 'testuser')) + self.useFixture( + fixtures.EnvironmentVariable('OS_PROJECT_NAME', 'testproject')) + c = config.OpenStackConfig(config_files=[self.cloud_yaml], + vendor_files=[self.vendor_yaml]) + cc = c.get_one_cloud('envvars') + self._assert_cloud_details(cc) + self.assertNotIn('auth_url', cc.config) + self.assertIn('auth_url', cc.config['auth']) + self.assertNotIn('auth_url', cc.config) + cc = c.get_one_cloud('_test_cloud_') + self._assert_cloud_details(cc) + cc = c.get_one_cloud('_test_cloud_no_vendor') + self._assert_cloud_details(cc) + + def test_get_one_cloud_with_config_files(self): + c = config.OpenStackConfig(config_files=[self.cloud_yaml], + vendor_files=[self.vendor_yaml]) + self.assertIsInstance(c.cloud_config, dict) + self.assertIn('cache', c.cloud_config) + self.assertIsInstance(c.cloud_config['cache'], dict) + self.assertIn('max_age', c.cloud_config['cache']) + self.assertIn('path', c.cloud_config['cache']) + cc = c.get_one_cloud('_test_cloud_') + self._assert_cloud_details(cc) + cc = c.get_one_cloud('_test_cloud_no_vendor') + self._assert_cloud_details(cc) diff --git a/os_client_config/tests/test_os_client_config.py b/os_client_config/tests/test_os_client_config.py deleted file mode 100644 index 7421b6f..0000000 --- a/os_client_config/tests/test_os_client_config.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- - -# 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. - -""" -test_os_client_config ----------------------------------- - -Tests for `os_client_config` module. -""" - -from os_client_config.tests import base - - -class TestOs_client_config(base.TestCase): - - def test_something(self): - pass |