From 38d243f44535c4c0cc49f39bb9e92926a75cd241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Guillot?= Date: Wed, 7 Dec 2016 16:46:50 -0500 Subject: [PATCH] Add tempest scenario for volume types - This scenario test the creation of a new volume type. - Cinder was not sending notifications because the config file have to be changed. - Keystone authentication was broken, we are now using the v3 API. - Kombu released a breaking change, the global constraints have been updated: https://review.openstack.org/#/c/408643/ Change-Id: Ie6a50ff427b6402f0d023fe3468f3efac77a8fce --- almanach/api/auth/base_auth.py | 3 ++ almanach/api/auth/keystone_auth.py | 40 ++++++-------- almanach/api/auth_adapter.py | 3 +- almanach/api/v1/routes.py | 2 +- almanach/core/opts.py | 4 +- almanach/tests/tempest/README.rst | 2 +- .../tests/tempest/services/almanach_client.py | 4 ++ almanach/tests/tempest/tests/scenario/base.py | 52 +++++++++++++++++++ .../tests/scenario/test_volume_type.py | 37 +++++++++++++ .../tests/unit/api/auth/test_keystone_auth.py | 37 +++++++------ devstack/plugin.sh | 10 ++-- devstack/settings | 2 +- doc/source/index.rst | 3 +- etc/almanach/almanach.docker.conf | 3 -- requirements.txt | 6 ++- tox.ini | 1 + 16 files changed, 147 insertions(+), 62 deletions(-) create mode 100644 almanach/tests/tempest/tests/scenario/base.py create mode 100644 almanach/tests/tempest/tests/scenario/test_volume_type.py diff --git a/almanach/api/auth/base_auth.py b/almanach/api/auth/base_auth.py index 8a85341..4374f7d 100644 --- a/almanach/api/auth/base_auth.py +++ b/almanach/api/auth/base_auth.py @@ -13,9 +13,12 @@ # limitations under the License. import abc +import six +@six.add_metaclass(abc.ABCMeta) class BaseAuth(object): + @abc.abstractmethod def validate(self, token): return True diff --git a/almanach/api/auth/keystone_auth.py b/almanach/api/auth/keystone_auth.py index 279f598..82dc210 100644 --- a/almanach/api/auth/keystone_auth.py +++ b/almanach/api/auth/keystone_auth.py @@ -12,40 +12,30 @@ # See the License for the specific language governing permissions and # limitations under the License. -from keystoneclient.v2_0 import client as keystone_client -from keystoneclient.v2_0 import tokens +from keystoneauth1.identity import v3 +from keystoneauth1 import session +from keystoneclient.v3 import client as keystone_client from almanach.api.auth import base_auth from almanach.core import exception -class KeystoneTokenManagerFactory(object): - def __init__(self, config): - self.auth_url = config.auth.keystone_url - self.tenant_name = config.auth.keystone_tenant - self.username = config.auth.keystone_username - self.password = config.auth.keystone_password - - def get_manager(self): - return tokens.TokenManager(keystone_client.Client( - username=self.username, - password=self.password, - auth_url=self.auth_url, - tenant_name=self.tenant_name) - ) - - class KeystoneAuthentication(base_auth.BaseAuth): - def __init__(self, token_manager_factory): - self.token_manager_factory = token_manager_factory + + def __init__(self, config): + auth = v3.Password(username=config.auth.keystone_username, + password=config.auth.keystone_password, + auth_url=config.auth.keystone_url) + sess = session.Session(auth=auth) + self._client = keystone_client.Client(session=sess) def validate(self, token): if token is None: - raise exception.AuthenticationFailureException("No token provided") + raise exception.AuthenticationFailureException('No token provided') - try: - self.token_manager_factory.get_manager().validate(token) - except Exception as e: - raise exception.AuthenticationFailureException(e) + result = self._client.tokens.validate(token) + + if not result: + raise exception.AuthenticationFailureException('Invalid token') return True diff --git a/almanach/api/auth_adapter.py b/almanach/api/auth_adapter.py index a3fadf2..515aa92 100644 --- a/almanach/api/auth_adapter.py +++ b/almanach/api/auth_adapter.py @@ -39,5 +39,4 @@ class AuthenticationAdapter(object): def _get_keystone_auth(self): LOG.info('Loading Keystone authentication backend') - token_manager = keystone_auth.KeystoneTokenManagerFactory(self.config) - return keystone_auth.KeystoneAuthentication(token_manager) + return keystone_auth.KeystoneAuthentication(self.config) diff --git a/almanach/api/v1/routes.py b/almanach/api/v1/routes.py index 2bdab66..7a11d85 100644 --- a/almanach/api/v1/routes.py +++ b/almanach/api/v1/routes.py @@ -75,7 +75,7 @@ def authenticated(api_call): auth_adapter.validate(flask.request.headers.get('X-Auth-Token')) return api_call(*args, **kwargs) except exception.AuthenticationFailureException as e: - LOG.error("Authentication failure: %s", e) + LOG.error("Authentication failure: %s", e.message) return flask.Response('Unauthorized', 401) return decorator diff --git a/almanach/core/opts.py b/almanach/core/opts.py index dbff423..66e5d90 100644 --- a/almanach/core/opts.py +++ b/almanach/core/opts.py @@ -67,10 +67,8 @@ auth_opts = [ cfg.StrOpt('keystone_password', secret=True, help='Keystone service password'), - cfg.StrOpt('keystone_tenant', - help='Keystone service tenant'), cfg.StrOpt('keystone_url', - default='http://keystone_url:5000/v2.0', + default='http://keystone_url:5000/v3', help='Keystone URL'), ] diff --git a/almanach/tests/tempest/README.rst b/almanach/tests/tempest/README.rst index 9df9009..3f940cb 100644 --- a/almanach/tests/tempest/README.rst +++ b/almanach/tests/tempest/README.rst @@ -13,7 +13,7 @@ Example of config file for devstack: [DEFAULT] [identity] - auth_version = v3 + auth_version = v2 uri = http://192.168.50.50:5000/v2.0 uri_v3 = http://192.168.50.50:5000/v3 diff --git a/almanach/tests/tempest/services/almanach_client.py b/almanach/tests/tempest/services/almanach_client.py index 393f211..4a8226d 100644 --- a/almanach/tests/tempest/services/almanach_client.py +++ b/almanach/tests/tempest/services/almanach_client.py @@ -30,3 +30,7 @@ class AlmanachClient(rest_client.RestClient): def get_version(self): resp, response_body = self.get('info') return resp, response_body + + def get_volume_type(self, volume_type_id): + resp, response_body = self.get('volume_type/{}'.format(volume_type_id)) + return resp, response_body diff --git a/almanach/tests/tempest/tests/scenario/base.py b/almanach/tests/tempest/tests/scenario/base.py new file mode 100644 index 0000000..d9a1a4e --- /dev/null +++ b/almanach/tests/tempest/tests/scenario/base.py @@ -0,0 +1,52 @@ +# Copyright 2016 Internap. +# +# 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 oslo_log import log +from tempest.common.utils import data_utils +from tempest import config +from tempest.scenario import manager + +from almanach.tests.tempest import clients + +CONF = config.CONF +LOG = log.getLogger(__name__) + + +class BaseAlmanachScenarioTest(manager.ScenarioTest): + + credentials = ['primary', 'admin'] + + @classmethod + def setup_clients(cls): + super(BaseAlmanachScenarioTest, cls).setup_clients() + cred_provider = cls._get_credentials_provider() + credentials = cred_provider.get_creds_by_roles(['admin']).credentials + cls.os = clients.Manager(credentials=credentials) + cls.almanach_client = cls.os.almanach_client + + def create_volume_type(self, name=None): + client = self.os_adm.volume_types_v2_client + + if not name: + name = 'generic' + + randomized_name = data_utils.rand_name('scenario-type-' + name) + LOG.info("Creating a volume type with name: %s", randomized_name) + + body = client.create_volume_type(name=randomized_name)['volume_type'] + self.assertIn('id', body) + self.addCleanup(client.delete_volume_type, body['id']) + + LOG.info("Created volume type with ID: %s", body['id']) + return body diff --git a/almanach/tests/tempest/tests/scenario/test_volume_type.py b/almanach/tests/tempest/tests/scenario/test_volume_type.py new file mode 100644 index 0000000..807a804 --- /dev/null +++ b/almanach/tests/tempest/tests/scenario/test_volume_type.py @@ -0,0 +1,37 @@ +# Copyright 2016 Internap. +# +# 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 oslo_log import log +from oslo_serialization import jsonutils as json +from tempest import config + +from almanach.tests.tempest.tests.scenario import base + +CONF = config.CONF +LOG = log.getLogger(__name__) + + +class TestVolumeTypeScenario(base.BaseAlmanachScenarioTest): + + def test_create_volume_type(self): + volume_type = self.create_volume_type(name='my_custom_volume_type') + LOG.info(volume_type) + + resp, response_body = self.almanach_client.get_volume_type(volume_type['id']) + self.assertEqual(resp.status, 200) + + response_body = json.loads(response_body) + self.assertIsInstance(response_body, dict) + self.assertEqual(volume_type['id'], response_body['volume_type_id']) + self.assertEqual(volume_type['name'], response_body['volume_type_name']) diff --git a/almanach/tests/unit/api/auth/test_keystone_auth.py b/almanach/tests/unit/api/auth/test_keystone_auth.py index 1f6ad05..58b925d 100644 --- a/almanach/tests/unit/api/auth/test_keystone_auth.py +++ b/almanach/tests/unit/api/auth/test_keystone_auth.py @@ -12,11 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from flexmock import flexmock -from hamcrest import assert_that -from hamcrest import calling -from hamcrest import equal_to -from hamcrest import raises +import mock from almanach.api.auth import keystone_auth from almanach.core import exception @@ -27,23 +23,26 @@ class KeystoneAuthenticationTest(base.BaseTestCase): def setUp(self): super(KeystoneAuthenticationTest, self).setUp() - self.token_manager_factory = flexmock() - self.keystone_token_manager = flexmock() - self.auth_backend = keystone_auth.KeystoneAuthentication(self.token_manager_factory) + self.session_mock = mock.patch('keystoneauth1.session.Session').start() + self.keystone_mock = mock.patch('keystoneclient.v3.client.Client').start() + + self.validation_mock = mock.Mock() + self.keystone_mock.return_value.tokens.validate = self.validation_mock + + self.driver = keystone_auth.KeystoneAuthentication(self.config) def test_with_correct_token(self): - token = "my token" - self.token_manager_factory.should_receive("get_manager").and_return(self.keystone_token_manager) - self.keystone_token_manager.should_receive("validate").with_args(token) - assert_that(self.auth_backend.validate(token), equal_to(True)) + token = 'some keystone token' + self.validation_mock.return_value = True + self.driver.validate(token) + self.validation_mock.assert_called_once_with(token) def test_with_invalid_token(self): - token = "bad token" - self.token_manager_factory.should_receive("get_manager").and_return(self.keystone_token_manager) - self.keystone_token_manager.should_receive("validate").with_args(token).and_raise(Exception) - assert_that(calling(self.auth_backend.validate) - .with_args(token), raises(exception.AuthenticationFailureException)) + token = 'some keystone token' + self.validation_mock.return_value = False + self.assertRaises(exception.AuthenticationFailureException, self.driver.validate, token) + self.validation_mock.assert_called_once_with(token) def test_with_empty_token(self): - assert_that(calling(self.auth_backend.validate) - .with_args(None), raises(exception.AuthenticationFailureException)) + token = None + self.assertRaises(exception.AuthenticationFailureException, self.driver.validate, token) diff --git a/devstack/plugin.sh b/devstack/plugin.sh index deb8e6e..bd23949 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -33,8 +33,11 @@ function almanach_configure { iniset $ALMANACH_CONF api bind_ip $ALMANACH_SERVICE_HOST iniset $ALMANACH_CONF api bind_port $ALMANACH_SERVICE_PORT - iniset $ALMANACH_CONF auth auth_token secret - iniset $ALMANACH_CONF auth auth_strategy private_key + iniset $ALMANACH_CONF auth strategy keystone + + iniset $ALMANACH_CONF auth keystone_username almanach + iniset $ALMANACH_CONF auth keystone_password $SERVICE_PASSWORD + iniset $ALMANACH_CONF auth keystone_url $KEYSTONE_SERVICE_URI/v2.0 iniset $ALMANACH_CONF collector transport_url rabbit://stackrabbit:secret@localhost:5672 @@ -47,7 +50,8 @@ function almanach_configure_external_services { fi if is_service_enabled cinder; then - iniset $CINDER_CONF DEFAULT notification_topics "almanach,notifications" + iniset $CINDER_CONF oslo_messaging_notifications topics "almanach,notifications" + iniset $CINDER_CONF oslo_messaging_notifications driver "messagingv2" fi } diff --git a/devstack/settings b/devstack/settings index cbe6dab..7a70d13 100644 --- a/devstack/settings +++ b/devstack/settings @@ -4,7 +4,7 @@ enable_service almanach-api ALMANACH_DIR=$DEST/almanach ALMANACH_CONF_DIR=/etc/almanach -ALMANACH_CONF=$ALMANACH_CONF_DIR/almanach.cfg +ALMANACH_CONF=$ALMANACH_CONF_DIR/almanach.conf ALMANACH_SERVICE_PROTOCOL=http ALMANACH_SERVICE_HOST=${ALMANACH_SERVICE_HOST:-${SERVICE_HOST}} diff --git a/doc/source/index.rst b/doc/source/index.rst index 3516d13..e0601b4 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -112,8 +112,7 @@ To use this authentication backend you have to define the authentication strateg strategy = keystone keystone_username = my_service_username keystone_password = my_service_password - keystone_tenant = my_service_tenant_name - keystone_url = http://keystone_url:5000/v2.0 + keystone_url = http://keystone_url:5000/v3 RabbitMQ configuration diff --git a/etc/almanach/almanach.docker.conf b/etc/almanach/almanach.docker.conf index e06c005..22fcce8 100644 --- a/etc/almanach/almanach.docker.conf +++ b/etc/almanach/almanach.docker.conf @@ -34,9 +34,6 @@ bind_port = 8000 # Keystone service password (string value) #keystone_password = -# Keystone service tenant (string value) -#keystone_tenant = - # Keystone URL (string value) #keystone_url = http://keystone_url:5000/v2.0 diff --git a/requirements.txt b/requirements.txt index 1e4251f..1ca6abc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,10 +4,12 @@ jsonpickle==0.7.1 pymongo>=3.0.2,!=3.1 # Apache-2.0 pytz>=2013.6 # MIT voluptuous>=0.8.9 # BSD License -python-keystoneclient>=3.6.0 # Apache-2.0 +keystoneauth1>=2.14.0 # Apache-2.0 +python-keystoneclient>=3.8.0 # Apache-2.0 six>=1.9.0 # MIT +kombu>=3.0.25,!=4.0.0,!=4.0.1 # BSD oslo.serialization>=1.10.0 # Apache-2.0 oslo.config>=3.14.0 # Apache-2.0 oslo.log>=3.11.0 # Apache-2.0 -oslo.messaging>=5.2.0 # Apache-2.0 +oslo.messaging>=5.14.0 # Apache-2.0 oslo.service>=1.10.0 # Apache-2.0 \ No newline at end of file diff --git a/tox.ini b/tox.ini index b91b738..fc84da3 100644 --- a/tox.ini +++ b/tox.ini @@ -25,3 +25,4 @@ commands = python setup.py build_sphinx --fresh-env [flake8] show-source = True max-line-length = 120 +exclude = .venv,.git,.tox,dist,doc,*egg,build