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
This commit is contained in:
Frédéric Guillot 2016-12-07 16:46:50 -05:00
parent 23d672ed12
commit 38d243f445
16 changed files with 147 additions and 62 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -34,9 +34,6 @@ bind_port = 8000
# Keystone service password (string value)
#keystone_password = <None>
# Keystone service tenant (string value)
#keystone_tenant = <None>
# Keystone URL (string value)
#keystone_url = http://keystone_url:5000/v2.0

View File

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

View File

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