diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9a50bacd..b1233a72 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -23,6 +23,10 @@ Added ~~~~~ * Support Python 3.7 environment. +* ``https_cert`` and ``https_key`` options of the spec for + ``existing@openstack`` platform to represent client certificate bundle and + key files. Also the support for appropriate system environment variables ( + ``OS_CERT``, ``OS_KEY``) is added. Changed ~~~~~~~ diff --git a/rally_openstack/credential.py b/rally_openstack/credential.py index d7476fe6..5ece771c 100644 --- a/rally_openstack/credential.py +++ b/rally_openstack/credential.py @@ -14,7 +14,6 @@ # under the License. from rally.common import logging -from rally_openstack import osclients LOG = logging.getLogger(__file__) @@ -28,7 +27,7 @@ class OpenStackCredential(dict): region_name=None, endpoint_type=None, domain_name=None, endpoint=None, user_domain_name=None, project_domain_name=None, - https_insecure=False, https_cacert=None, + https_insecure=False, https_cacert=None, https_cert=None, profiler_hmac_key=None, profiler_conn_str=None, **kwargs): if kwargs: raise TypeError("%s" % kwargs) @@ -49,6 +48,7 @@ class OpenStackCredential(dict): ("project_domain_name", project_domain_name), ("https_insecure", https_insecure), ("https_cacert", https_cacert), + ("https_cert", https_cert), ("profiler_hmac_key", profiler_hmac_key), ("profiler_conn_str", profiler_conn_str) ]) @@ -70,5 +70,7 @@ class OpenStackCredential(dict): # this method is mostly used by validation step. let's refactor it and # deprecated this def clients(self, api_info=None): + from rally_openstack import osclients + return osclients.Clients(self, api_info=api_info, cache=self._clients_cache) diff --git a/rally_openstack/exceptions.py b/rally_openstack/exceptions.py new file mode 100644 index 00000000..ace43f4c --- /dev/null +++ b/rally_openstack/exceptions.py @@ -0,0 +1,23 @@ +# All Rights Reserved. +# +# 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 rally import exceptions as rally_exceptions + +RallyException = rally_exceptions.RallyException + + +class AuthenticationFailed(rally_exceptions.InvalidArgumentsException): + error_code = 220 + msg_fmt = ("Failed to authenticate to %(url)s for user '%(username)s'" + " in project '%(project)s': %(etype)s: %(error)s") diff --git a/rally_openstack/osclients.py b/rally_openstack/osclients.py index 787336ed..6c777b05 100644 --- a/rally_openstack/osclients.py +++ b/rally_openstack/osclients.py @@ -20,11 +20,11 @@ from rally.cli import envutils from rally.common import cfg from rally.common import logging from rally.common.plugin import plugin -from rally.common import utils from rally import exceptions from six.moves.urllib import parse from rally_openstack import consts +from rally_openstack import credential as oscred LOG = logging.getLogger(__name__) @@ -105,12 +105,12 @@ def configure(name, default_version=None, default_service_type=None, @plugin.base() class OSClient(plugin.Plugin): - """Base class for openstack clients""" + """Base class for OpenStack clients""" def __init__(self, credential, api_info, cache_obj): self.credential = credential - if isinstance(self.credential, dict): - self.credential = utils.Struct(**self.credential) + if not isinstance(self.credential, oscred.OpenStackCredential): + self.credential = oscred.OpenStackCredential(**self.credential) self.api_info = api_info self.cache = cache_obj @@ -187,13 +187,6 @@ class OSClient(plugin.Plugin): return OSClient.get("keystone")(self.credential, self.api_info, self.cache) - def _get_session(self, auth_url=None, version=None): - LOG.warning( - "Method `rally.osclient.OSClient._get_session` is deprecated since" - " Rally 0.6.0. Use " - "`rally.osclient.OSClient.keystone.get_session` instead.") - return self.keystone.get_session(version) - def _get_endpoint(self, service_type=None): kw = {"service_type": self.choose_service_type(service_type), "region_name": self.credential.region_name} @@ -317,13 +310,13 @@ class Keystone(OSClient): temp_session = session.Session( verify=(self.credential.https_cacert or not self.credential.https_insecure), + cert=self.credential.https_cert, timeout=CONF.openstack_client_http_timeout) version = str(discover.Discover( temp_session, password_args["auth_url"]).version_data()[0]["version"][0]) - if "v2.0" not in password_args["auth_url"] and ( - version != "2"): + if "v2.0" not in password_args["auth_url"] and version != "2": password_args.update({ "user_domain_name": self.credential.user_domain_name, "domain_name": self.credential.domain_name, @@ -334,6 +327,7 @@ class Keystone(OSClient): auth=identity_plugin, verify=(self.credential.https_cacert or not self.credential.https_insecure), + cert=self.credential.https_cert, timeout=CONF.openstack_client_http_timeout) self.cache[key] = (sess, identity_plugin) return self.cache[key] diff --git a/rally_openstack/platforms/existing.py b/rally_openstack/platforms/existing.py index 78f61ee4..e272cb41 100644 --- a/rally_openstack/platforms/existing.py +++ b/rally_openstack/platforms/existing.py @@ -72,6 +72,8 @@ class OpenStack(platform.Platform): "endpoint_type": {"enum": ["public", "internal", "admin", None]}, "https_insecure": {"type": "boolean"}, "https_cacert": {"type": "string"}, + "https_cert": {"type": "string"}, + "https_key": {"type": "string"}, "profiler_hmac_key": {"type": ["string", "null"]}, "profiler_conn_str": {"type": ["string", "null"]}, "admin": {"$ref": "#/definitions/user"}, @@ -114,6 +116,10 @@ class OpenStack(platform.Platform): admin = new_data.pop("admin", None) users = new_data.pop("users", []) + if new_data.get("https_cert") and new_data.get("https_key"): + new_data["https_cert"] = (new_data["https_cert"], + new_data.pop("https_key")) + if admin: if "project_name" in admin: admin["tenant_name"] = admin.pop("project_name") @@ -240,6 +246,8 @@ class OpenStack(platform.Platform): "endpoint_type": endpoint_type, "region_name": sys_environ.get("OS_REGION_NAME", ""), "https_cacert": sys_environ.get("OS_CACERT", ""), + "https_cert": sys_environ.get("OS_CERT", ""), + "https_key": sys_environ.get("OS_KEY", ""), "https_insecure": strutils.bool_from_string( sys_environ.get("OS_INSECURE")), "profiler_hmac_key": sys_environ.get("OSPROFILER_HMAC_KEY"), diff --git a/tests/ci/playbooks/roles/prepare-for-rally-task/tasks/main.yaml b/tests/ci/playbooks/roles/prepare-for-rally-task/tasks/main.yaml index dcbdf99a..620ddeee 100644 --- a/tests/ci/playbooks/roles/prepare-for-rally-task/tasks/main.yaml +++ b/tests/ci/playbooks/roles/prepare-for-rally-task/tasks/main.yaml @@ -153,7 +153,7 @@ - name: Check Environment works become: True become_user: stack - command: "rally env check" + command: "rally --debug env check" - name: Print Environment info become: True diff --git a/tests/unit/contexts/sahara/test_sahara_image.py b/tests/unit/contexts/sahara/test_sahara_image.py index 39264f97..2d4f48a0 100644 --- a/tests/unit/contexts/sahara/test_sahara_image.py +++ b/tests/unit/contexts/sahara/test_sahara_image.py @@ -16,6 +16,7 @@ import mock from rally import exceptions from rally_openstack.contexts.sahara import sahara_image +from tests.unit import fakes from tests.unit import test @@ -42,7 +43,7 @@ class SaharaImageTestCase(test.ScenarioTestCase): for j in range(self.users_per_tenant): self.users_key.append({"id": "%s_%s" % (str(i), str(j)), "tenant_id": str(i), - "credential": mock.MagicMock()}) + "credential": fakes.FakeCredential()}) @property def url_image_context(self): @@ -59,7 +60,7 @@ class SaharaImageTestCase(test.ScenarioTestCase): "username": "test_user" } }, - "admin": {"credential": mock.MagicMock()}, + "admin": {"credential": fakes.FakeCredential()}, "users": self.users_key, "tenants": self.tenants }) @@ -77,7 +78,7 @@ class SaharaImageTestCase(test.ScenarioTestCase): "image_uuid": "some_id" } }, - "admin": {"credential": mock.MagicMock()}, + "admin": {"credential": fakes.FakeCredential()}, "users": self.users_key, "tenants": self.tenants, }) diff --git a/tests/unit/fakes.py b/tests/unit/fakes.py index fe88adf8..70858537 100644 --- a/tests/unit/fakes.py +++ b/tests/unit/fakes.py @@ -33,6 +33,7 @@ import six from swiftclient import exceptions as swift_exceptions from rally_openstack import consts +from rally_openstack import credential def generate_uuid(): @@ -83,12 +84,13 @@ def setup_dict(data, required=None, defaults=None): return defaults -def fake_credential(**config): - m = mock.Mock() - m.to_dict.return_value = config - for key, value in config.items(): - setattr(m, key, value) - return m +class FakeCredential(credential.OpenStackCredential): + def __init__(self, **creds): + creds.setdefault("auth_url", "https://example.com") + creds.setdefault("username", "admin") + creds.setdefault("password", "pass") + super(FakeCredential, self).__init__(**creds) + self.clients = mock.Mock() class FakeResource(object): @@ -1610,7 +1612,7 @@ class FakeClients(object): self._ec2 = None self._senlin = None self._watcher = None - self._credential = credential_ or fake_credential( + self._credential = credential_ or FakeCredential( auth_url="http://fake.example.org:5000/v2.0/", username="fake_username", password="fake_password", @@ -1827,7 +1829,7 @@ class FakeUserContext(FakeContext): admin = { "id": "adminuuid", - "credential": fake_credential( + "credential": FakeCredential( auth_url="aurl", username="aname", password="apwd", @@ -1835,7 +1837,7 @@ class FakeUserContext(FakeContext): } user = { "id": "uuid", - "credential": fake_credential( + "credential": FakeCredential( auth_url="url", username="name", password="pwd", diff --git a/tests/unit/platforms/test_existing.py b/tests/unit/platforms/test_existing.py index c71773cf..04522832 100644 --- a/tests/unit/platforms/test_existing.py +++ b/tests/unit/platforms/test_existing.py @@ -155,8 +155,10 @@ class ExistingPlatformTestCase(PlatformBaseTestCase): "OS_INTERFACE": "publicURL", "OS_REGION_NAME": "Region1", "OS_CACERT": "Cacert", + "OS_CERT": "cert", + "OS_KEY": "key", "OS_INSECURE": True, - "OSPROFILER_HMAC_KEY": "key", + "OSPROFILER_HMAC_KEY": "hmackey", "OSPROFILER_CONN_STR": "https://example2.com", } @@ -173,8 +175,10 @@ class ExistingPlatformTestCase(PlatformBaseTestCase): "endpoint_type": "public", "region_name": "Region1", "https_cacert": "Cacert", + "https_cert": "cert", + "https_key": "key", "https_insecure": True, - "profiler_hmac_key": "key", + "profiler_hmac_key": "hmackey", "profiler_conn_str": "https://example2.com" }, result["spec"]) @@ -182,7 +186,7 @@ class ExistingPlatformTestCase(PlatformBaseTestCase): sys_env["OS_IDENTITY_API_VERSION"] = "3" result = existing.OpenStack.create_spec_from_sys_environ(sys_env) - print(json.dumps(result["spec"], indent=4)) + self.assertEqual( { "admin": { @@ -196,8 +200,10 @@ class ExistingPlatformTestCase(PlatformBaseTestCase): "auth_url": "https://example.com", "region_name": "Region1", "https_cacert": "Cacert", + "https_cert": "cert", + "https_key": "key", "https_insecure": True, - "profiler_hmac_key": "key", + "profiler_hmac_key": "hmackey", "profiler_conn_str": "https://example2.com" }, result["spec"]) diff --git a/tests/unit/scenarios/cinder/test_utils.py b/tests/unit/scenarios/cinder/test_utils.py index c5650f21..22d9816f 100644 --- a/tests/unit/scenarios/cinder/test_utils.py +++ b/tests/unit/scenarios/cinder/test_utils.py @@ -15,6 +15,7 @@ import mock +from rally_openstack import credential from rally_openstack.scenarios.cinder import utils from tests.unit import test @@ -23,13 +24,17 @@ class CinderBasicTestCase(test.ScenarioTestCase): def _get_context(self): context = test.get_test_context() + + cred = credential.OpenStackCredential(auth_url="url", + username="user", + password="pass") context.update({ "admin": { "id": "fake_user_id", - "credential": mock.MagicMock() + "credential": cred }, "user": {"id": "fake_user_id", - "credential": mock.MagicMock()}, + "credential": cred}, "tenant": {"id": "fake", "name": "fake", "volumes": [{"id": "uuid", "size": 1}], "servers": [1]}}) diff --git a/tests/unit/test_credential.py b/tests/unit/test_credential.py index bdc0e284..6ff716a4 100644 --- a/tests/unit/test_credential.py +++ b/tests/unit/test_credential.py @@ -39,6 +39,7 @@ class OpenStackCredentialTestCase(test.TestCase): "endpoint_type": None, "https_insecure": False, "https_cacert": None, + "https_cert": None, "project_domain_name": None, "user_domain_name": None, "profiler_hmac_key": None, diff --git a/tests/unit/test_osclients.py b/tests/unit/test_osclients.py index 50ae645c..1cfc1344 100644 --- a/tests/unit/test_osclients.py +++ b/tests/unit/test_osclients.py @@ -85,12 +85,13 @@ class OSClientTestCase(test.TestCase, OSClientTestCaseUtils): def test_choose_service_type(self): default_service_type = "default_service_type" - @osclients.configure("test_choose_service_type", + @osclients.configure(self.id(), default_service_type=default_service_type) class FakeClient(osclients.OSClient): create_client = mock.MagicMock() - fake_client = FakeClient(mock.MagicMock(), {}, {}) + fake_client = FakeClient({"auth_url": "url", "username": "user", + "password": "pass"}, {}, {}) self.assertEqual(default_service_type, fake_client.choose_service_type()) self.assertEqual("foo", @@ -122,40 +123,33 @@ class OSClientTestCase(test.TestCase, OSClientTestCaseUtils): mock_url_for.assert_called_once_with(**call_args) mock_choose_service_type.assert_called_once_with(service_type) - @mock.patch("%s.Keystone.get_session" % PATH) - def test__get_session(self, mock_keystone_get_session): - osclient = osclients.OSClient(None, None, None) - auth_url = "auth_url" - version = "version" - import warnings - with mock.patch.object(warnings, "warn") as mock_warn: - self.assertEqual(mock_keystone_get_session.return_value, - osclient._get_session(auth_url, version)) - self.assertFalse(mock_warn.called) - mock_keystone_get_session.assert_called_once_with(version) - class CachedTestCase(test.TestCase): def test_cached(self): - clients = osclients.Clients(mock.MagicMock()) - client_name = "CachedTestCase.test_cached" - fake_client = osclients.configure(client_name)(osclients.OSClient)( - clients.credential, clients.api_info, clients.cache) + clients = osclients.Clients({"auth_url": "url", "username": "user", + "password": "pass"}) + + @osclients.configure(self.id()) + class SomeClient(osclients.OSClient): + pass + + fake_client = SomeClient(clients.credential, clients.api_info, + clients.cache) fake_client.create_client = mock.MagicMock() self.assertEqual({}, clients.cache) fake_client() self.assertEqual( - {client_name: fake_client.create_client.return_value}, + {self.id(): fake_client.create_client.return_value}, clients.cache) fake_client.create_client.assert_called_once_with() fake_client() fake_client.create_client.assert_called_once_with() fake_client("2") self.assertEqual( - {client_name: fake_client.create_client.return_value, - "%s('2',)" % client_name: fake_client.create_client.return_value}, + {self.id(): fake_client.create_client.return_value, + "%s('2',)" % self.id(): fake_client.create_client.return_value}, clients.cache) clients.clear() self.assertEqual({}, clients.cache) @@ -258,12 +252,12 @@ class TestCreateKeystoneClient(test.TestCase, OSClientTestCaseUtils): domain_name=None, project_domain_name=None, user_domain_name=None) self.ksa_session.Session.assert_has_calls( - [mock.call(timeout=180.0, verify=True), + [mock.call(timeout=180.0, verify=True, cert=None), mock.call(auth=self.ksa_identity_plugin, timeout=180.0, - verify=True)]) + verify=True, cert=None)]) def test_keystone_property(self): - keystone = osclients.Keystone(None, None, None) + keystone = osclients.Keystone(self.credential, None, None) self.assertRaises(exceptions.RallyException, lambda: keystone.keystone) @mock.patch("%s.Keystone.get_session" % PATH) @@ -272,7 +266,7 @@ class TestCreateKeystoneClient(test.TestCase, OSClientTestCaseUtils): auth_plugin = mock.MagicMock() mock_keystone_get_session.return_value = (session, auth_plugin) cache = {} - keystone = osclients.Keystone(None, None, cache) + keystone = osclients.Keystone(self.credential, None, cache) self.assertEqual(auth_plugin.get_access.return_value, keystone.auth_ref) diff --git a/tests/unit/verification/tempest/test_config.py b/tests/unit/verification/tempest/test_config.py index b5e7e51d..dffd238b 100644 --- a/tests/unit/verification/tempest/test_config.py +++ b/tests/unit/verification/tempest/test_config.py @@ -48,7 +48,7 @@ class TempestConfigfileManagerTestCase(test.TestCase): def setUp(self): super(TempestConfigfileManagerTestCase, self).setUp() deployment = fakes.FakeDeployment(uuid="fake_deployment", - admin=fakes.fake_credential(**CRED)) + admin=fakes.FakeCredential(**CRED)) self.tempest = config.TempestConfigfileManager(deployment) def test__configure_auth(self): diff --git a/tests/unit/verification/tempest/test_context.py b/tests/unit/verification/tempest/test_context.py index 04e9ed32..fb04ed15 100644 --- a/tests/unit/verification/tempest/test_context.py +++ b/tests/unit/verification/tempest/test_context.py @@ -55,7 +55,7 @@ class TempestContextTestCase(test.TestCase): self.mock_isfile = mock.patch("os.path.isfile", return_value=True).start() - self.cred = fakes.fake_credential(**CRED) + self.cred = fakes.FakeCredential(**CRED) self.deployment = fakes.FakeDeployment( uuid="fake_deployment", admin=self.cred) cfg = {"verifier": mock.Mock(deployment=self.deployment),