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:
parent
23d672ed12
commit
38d243f445
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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'),
|
||||
]
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
52
almanach/tests/tempest/tests/scenario/base.py
Normal file
52
almanach/tests/tempest/tests/scenario/base.py
Normal 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
|
37
almanach/tests/tempest/tests/scenario/test_volume_type.py
Normal file
37
almanach/tests/tempest/tests/scenario/test_volume_type.py
Normal 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'])
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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}}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
Loading…
Reference in New Issue
Block a user