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.
|
# limitations under the License.
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
import six
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class BaseAuth(object):
|
class BaseAuth(object):
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def validate(self, token):
|
def validate(self, token):
|
||||||
return True
|
return True
|
||||||
|
@ -12,40 +12,30 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from keystoneclient.v2_0 import client as keystone_client
|
from keystoneauth1.identity import v3
|
||||||
from keystoneclient.v2_0 import tokens
|
from keystoneauth1 import session
|
||||||
|
from keystoneclient.v3 import client as keystone_client
|
||||||
|
|
||||||
from almanach.api.auth import base_auth
|
from almanach.api.auth import base_auth
|
||||||
from almanach.core import exception
|
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):
|
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):
|
def validate(self, token):
|
||||||
if token is None:
|
if token is None:
|
||||||
raise exception.AuthenticationFailureException("No token provided")
|
raise exception.AuthenticationFailureException('No token provided')
|
||||||
|
|
||||||
try:
|
result = self._client.tokens.validate(token)
|
||||||
self.token_manager_factory.get_manager().validate(token)
|
|
||||||
except Exception as e:
|
if not result:
|
||||||
raise exception.AuthenticationFailureException(e)
|
raise exception.AuthenticationFailureException('Invalid token')
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -39,5 +39,4 @@ class AuthenticationAdapter(object):
|
|||||||
|
|
||||||
def _get_keystone_auth(self):
|
def _get_keystone_auth(self):
|
||||||
LOG.info('Loading Keystone authentication backend')
|
LOG.info('Loading Keystone authentication backend')
|
||||||
token_manager = keystone_auth.KeystoneTokenManagerFactory(self.config)
|
return keystone_auth.KeystoneAuthentication(self.config)
|
||||||
return keystone_auth.KeystoneAuthentication(token_manager)
|
|
||||||
|
@ -75,7 +75,7 @@ def authenticated(api_call):
|
|||||||
auth_adapter.validate(flask.request.headers.get('X-Auth-Token'))
|
auth_adapter.validate(flask.request.headers.get('X-Auth-Token'))
|
||||||
return api_call(*args, **kwargs)
|
return api_call(*args, **kwargs)
|
||||||
except exception.AuthenticationFailureException as e:
|
except exception.AuthenticationFailureException as e:
|
||||||
LOG.error("Authentication failure: %s", e)
|
LOG.error("Authentication failure: %s", e.message)
|
||||||
return flask.Response('Unauthorized', 401)
|
return flask.Response('Unauthorized', 401)
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
@ -67,10 +67,8 @@ auth_opts = [
|
|||||||
cfg.StrOpt('keystone_password',
|
cfg.StrOpt('keystone_password',
|
||||||
secret=True,
|
secret=True,
|
||||||
help='Keystone service password'),
|
help='Keystone service password'),
|
||||||
cfg.StrOpt('keystone_tenant',
|
|
||||||
help='Keystone service tenant'),
|
|
||||||
cfg.StrOpt('keystone_url',
|
cfg.StrOpt('keystone_url',
|
||||||
default='http://keystone_url:5000/v2.0',
|
default='http://keystone_url:5000/v3',
|
||||||
help='Keystone URL'),
|
help='Keystone URL'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ Example of config file for devstack:
|
|||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
|
|
||||||
[identity]
|
[identity]
|
||||||
auth_version = v3
|
auth_version = v2
|
||||||
uri = http://192.168.50.50:5000/v2.0
|
uri = http://192.168.50.50:5000/v2.0
|
||||||
uri_v3 = http://192.168.50.50:5000/v3
|
uri_v3 = http://192.168.50.50:5000/v3
|
||||||
|
|
||||||
|
@ -30,3 +30,7 @@ class AlmanachClient(rest_client.RestClient):
|
|||||||
def get_version(self):
|
def get_version(self):
|
||||||
resp, response_body = self.get('info')
|
resp, response_body = self.get('info')
|
||||||
return resp, response_body
|
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
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from flexmock import flexmock
|
import mock
|
||||||
from hamcrest import assert_that
|
|
||||||
from hamcrest import calling
|
|
||||||
from hamcrest import equal_to
|
|
||||||
from hamcrest import raises
|
|
||||||
|
|
||||||
from almanach.api.auth import keystone_auth
|
from almanach.api.auth import keystone_auth
|
||||||
from almanach.core import exception
|
from almanach.core import exception
|
||||||
@ -27,23 +23,26 @@ class KeystoneAuthenticationTest(base.BaseTestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(KeystoneAuthenticationTest, self).setUp()
|
super(KeystoneAuthenticationTest, self).setUp()
|
||||||
self.token_manager_factory = flexmock()
|
self.session_mock = mock.patch('keystoneauth1.session.Session').start()
|
||||||
self.keystone_token_manager = flexmock()
|
self.keystone_mock = mock.patch('keystoneclient.v3.client.Client').start()
|
||||||
self.auth_backend = keystone_auth.KeystoneAuthentication(self.token_manager_factory)
|
|
||||||
|
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):
|
def test_with_correct_token(self):
|
||||||
token = "my token"
|
token = 'some keystone token'
|
||||||
self.token_manager_factory.should_receive("get_manager").and_return(self.keystone_token_manager)
|
self.validation_mock.return_value = True
|
||||||
self.keystone_token_manager.should_receive("validate").with_args(token)
|
self.driver.validate(token)
|
||||||
assert_that(self.auth_backend.validate(token), equal_to(True))
|
self.validation_mock.assert_called_once_with(token)
|
||||||
|
|
||||||
def test_with_invalid_token(self):
|
def test_with_invalid_token(self):
|
||||||
token = "bad token"
|
token = 'some keystone token'
|
||||||
self.token_manager_factory.should_receive("get_manager").and_return(self.keystone_token_manager)
|
self.validation_mock.return_value = False
|
||||||
self.keystone_token_manager.should_receive("validate").with_args(token).and_raise(Exception)
|
self.assertRaises(exception.AuthenticationFailureException, self.driver.validate, token)
|
||||||
assert_that(calling(self.auth_backend.validate)
|
self.validation_mock.assert_called_once_with(token)
|
||||||
.with_args(token), raises(exception.AuthenticationFailureException))
|
|
||||||
|
|
||||||
def test_with_empty_token(self):
|
def test_with_empty_token(self):
|
||||||
assert_that(calling(self.auth_backend.validate)
|
token = None
|
||||||
.with_args(None), raises(exception.AuthenticationFailureException))
|
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_ip $ALMANACH_SERVICE_HOST
|
||||||
iniset $ALMANACH_CONF api bind_port $ALMANACH_SERVICE_PORT
|
iniset $ALMANACH_CONF api bind_port $ALMANACH_SERVICE_PORT
|
||||||
|
|
||||||
iniset $ALMANACH_CONF auth auth_token secret
|
iniset $ALMANACH_CONF auth strategy keystone
|
||||||
iniset $ALMANACH_CONF auth auth_strategy private_key
|
|
||||||
|
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
|
iniset $ALMANACH_CONF collector transport_url rabbit://stackrabbit:secret@localhost:5672
|
||||||
|
|
||||||
@ -47,7 +50,8 @@ function almanach_configure_external_services {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if is_service_enabled cinder; then
|
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
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ enable_service almanach-api
|
|||||||
ALMANACH_DIR=$DEST/almanach
|
ALMANACH_DIR=$DEST/almanach
|
||||||
|
|
||||||
ALMANACH_CONF_DIR=/etc/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_PROTOCOL=http
|
||||||
ALMANACH_SERVICE_HOST=${ALMANACH_SERVICE_HOST:-${SERVICE_HOST}}
|
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
|
strategy = keystone
|
||||||
keystone_username = my_service_username
|
keystone_username = my_service_username
|
||||||
keystone_password = my_service_password
|
keystone_password = my_service_password
|
||||||
keystone_tenant = my_service_tenant_name
|
keystone_url = http://keystone_url:5000/v3
|
||||||
keystone_url = http://keystone_url:5000/v2.0
|
|
||||||
|
|
||||||
|
|
||||||
RabbitMQ configuration
|
RabbitMQ configuration
|
||||||
|
@ -34,9 +34,6 @@ bind_port = 8000
|
|||||||
# Keystone service password (string value)
|
# Keystone service password (string value)
|
||||||
#keystone_password = <None>
|
#keystone_password = <None>
|
||||||
|
|
||||||
# Keystone service tenant (string value)
|
|
||||||
#keystone_tenant = <None>
|
|
||||||
|
|
||||||
# Keystone URL (string value)
|
# Keystone URL (string value)
|
||||||
#keystone_url = http://keystone_url:5000/v2.0
|
#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
|
pymongo>=3.0.2,!=3.1 # Apache-2.0
|
||||||
pytz>=2013.6 # MIT
|
pytz>=2013.6 # MIT
|
||||||
voluptuous>=0.8.9 # BSD License
|
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
|
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.serialization>=1.10.0 # Apache-2.0
|
||||||
oslo.config>=3.14.0 # Apache-2.0
|
oslo.config>=3.14.0 # Apache-2.0
|
||||||
oslo.log>=3.11.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
|
oslo.service>=1.10.0 # Apache-2.0
|
Loading…
Reference in New Issue
Block a user