Refactor trove-tempest-plugin
Depends-On: https://review.opendev.org/#/c/697870/ Story: 2006554 Task: 36639 Change-Id: I6251f070f330ee886e6436d92c20d78e0401d59e
This commit is contained in:
parent
eda92dae2d
commit
605a0d3a51
52
.zuul.yaml
52
.zuul.yaml
|
@ -5,32 +5,66 @@
|
|||
- tempest-plugin-jobs
|
||||
check:
|
||||
jobs:
|
||||
- python-troveclient-tempest-neutron-src
|
||||
- trove-tempest-plugin
|
||||
- trove-tempest-ipv6-only
|
||||
- trove-tempest-plugin:
|
||||
voting: false
|
||||
- trove-tempest-ipv6-only:
|
||||
voting: false
|
||||
gate:
|
||||
queue: trove
|
||||
jobs:
|
||||
- python-troveclient-tempest-neutron-src
|
||||
- trove-tempest-plugin
|
||||
- trove-tempest-ipv6-only
|
||||
- trove-tempest-plugin:
|
||||
voting: false
|
||||
- trove-tempest-ipv6-only:
|
||||
voting: false
|
||||
|
||||
- job:
|
||||
name: trove-tempest-plugin
|
||||
parent: devstack-tempest
|
||||
timeout: 7800
|
||||
required-projects: &base_required_projects
|
||||
- openstack/neutron
|
||||
- openstack/python-troveclient
|
||||
- openstack/trove
|
||||
- openstack/trove-tempest-plugin
|
||||
- openstack/tempest
|
||||
irrelevant-files:
|
||||
- ^.*\.rst$
|
||||
- ^doc/.*$
|
||||
- ^etc/.*$
|
||||
- ^releasenotes/.*$
|
||||
vars: &base_vars
|
||||
tox_envlist: all
|
||||
tempest_concurrency: 2
|
||||
devstack_localrc:
|
||||
TEMPEST_PLUGINS: /opt/stack/trove-tempest-plugin
|
||||
USE_PYTHON3: true
|
||||
devstack_local_conf:
|
||||
post-config:
|
||||
$TROVE_CONF:
|
||||
DEFAULT:
|
||||
usage_timeout: 1800
|
||||
devstack_plugins:
|
||||
trove: https://opendev.org/openstack/trove
|
||||
trove: https://opendev.org/openstack/trove.git
|
||||
devstack_services:
|
||||
tempest: true
|
||||
etcd3: false
|
||||
tls-proxy: false
|
||||
ceilometer-acentral: false
|
||||
ceilometer-acompute: false
|
||||
ceilometer-alarm-evaluator: false
|
||||
ceilometer-alarm-notifier: false
|
||||
ceilometer-anotification: false
|
||||
ceilometer-api: false
|
||||
ceilometer-collector: false
|
||||
cinder: true
|
||||
c-sch: true
|
||||
c-api: true
|
||||
c-vol: true
|
||||
c-bak: false
|
||||
swift: true
|
||||
s-account: true
|
||||
s-container: true
|
||||
s-object: true
|
||||
s-proxy: true
|
||||
tempest: true
|
||||
tempest_test_regex: ^trove_tempest_plugin\.tests
|
||||
|
||||
- job:
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
|
||||
sphinxcontrib-apidoc>=0.2.0 # BSD
|
||||
sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7' # BSD
|
||||
sphinx!=1.6.6,!=1.6.7,>=1.6.2,!=2.1.0;python_version>='3.4' # BSD
|
||||
openstackdocstheme>=1.18.1 # Apache-2.0
|
||||
|
||||
# releasenotes
|
||||
reno>=2.5.0 # Apache-2.0
|
||||
|
|
|
@ -3,8 +3,12 @@
|
|||
# process, which may cause wedges in the gate later.
|
||||
|
||||
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
||||
six>=1.10.0 # MIT
|
||||
oslo.config>=5.2.0 # Apache-2.0
|
||||
oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
|
||||
testtools>=2.2.0 # MIT
|
||||
oslo.log>=3.44.1 # Apache-2.0
|
||||
oslo.serialization>=2.29.1 # Apache-2.0
|
||||
oslo.service>=1.40.1 # Apache-2.0
|
||||
oslotest>=3.2.0 # Apache-2.0
|
||||
requests>=2.14.2 # Apache-2.0
|
||||
six>=1.10.0 # MIT
|
||||
tempest>=17.1.0 # Apache-2.0
|
||||
tenacity>=5.1.1 # Apache-2.0
|
||||
|
|
|
@ -2,5 +2,11 @@
|
|||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
hacking<0.13,>=0.12.0 # Apache-2.0
|
||||
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
|
||||
|
||||
coverage!=4.4,>=4.0 # Apache-2.0
|
||||
python-subunit>=1.0.0 # Apache-2.0/BSD
|
||||
oslotest>=3.2.0 # Apache-2.0
|
||||
stestr>=2.0.0 # Apache-2.0
|
||||
testscenarios>=0.4 # Apache-2.0/BSD
|
||||
testtools>=2.2.0 # MIT
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
# Copyright 2018 Samsung Electronics
|
||||
# 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 tempest import config
|
||||
from tempest.lib.services import clients
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class Manager(clients.ServiceClients):
|
||||
"""Service clients proxy.
|
||||
|
||||
Enhances tests with a convenient way to access available service clients
|
||||
configured for a specified set of credentials.
|
||||
"""
|
||||
|
||||
def __init__(self, credentials, service=None):
|
||||
if CONF.identity.auth_version == 'v2':
|
||||
identity_uri = CONF.identity.uri
|
||||
else:
|
||||
identity_uri = CONF.identity.uri_v3
|
||||
super(Manager, self).__init__(credentials, identity_uri)
|
|
@ -14,27 +14,50 @@
|
|||
|
||||
from oslo_config import cfg
|
||||
|
||||
service_option = cfg.BoolOpt('trove',
|
||||
default=True,
|
||||
help="Whether or not Trove is expected to be "
|
||||
"available")
|
||||
service_option = cfg.BoolOpt(
|
||||
'trove',
|
||||
default=True,
|
||||
help="Whether or not Trove is expected to be available"
|
||||
)
|
||||
|
||||
database_group = cfg.OptGroup(name='database',
|
||||
title='Database Service Options')
|
||||
database_group = cfg.OptGroup(
|
||||
name='database',
|
||||
title='Database Service Options'
|
||||
)
|
||||
|
||||
DatabaseGroup = [
|
||||
cfg.StrOpt('catalog_type',
|
||||
default='database',
|
||||
help="Catalog type of the Database service."),
|
||||
cfg.StrOpt('endpoint_type',
|
||||
default='publicURL',
|
||||
choices=['public', 'admin', 'internal',
|
||||
'publicURL', 'adminURL', 'internalURL'],
|
||||
help="The endpoint type to use for the Database service."),
|
||||
cfg.StrOpt('db_flavor_ref',
|
||||
default="1",
|
||||
help="Valid primary flavor to use in Database tests."),
|
||||
cfg.StrOpt('db_current_version',
|
||||
default="v1.0",
|
||||
help="Current database version to use in Database tests."),
|
||||
cfg.StrOpt(
|
||||
'catalog_type',
|
||||
default='database',
|
||||
help="Catalog type of the Database service."
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'endpoint_type',
|
||||
default='publicURL',
|
||||
choices=['public', 'admin', 'internal', 'publicURL', 'adminURL',
|
||||
'internalURL'],
|
||||
help="The endpoint type to use for the Database service."
|
||||
),
|
||||
cfg.IntOpt('database_build_timeout',
|
||||
default=1800,
|
||||
help='Timeout in seconds to wait for a database instance to '
|
||||
'build.'),
|
||||
cfg.StrOpt(
|
||||
'flavor_id',
|
||||
default="d2",
|
||||
help="The Nova flavor ID used for creating database instance."
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'subnet_cidr',
|
||||
default='10.1.1.0/24',
|
||||
help=('The Neutron CIDR format subnet to use for database network '
|
||||
'creation.')
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'volume_type',
|
||||
default="lvmdriver-1",
|
||||
help="The Cinder volume type used for creating database instance."
|
||||
),
|
||||
cfg.StrOpt('datastore_type', default="mysql"),
|
||||
cfg.StrOpt('datastore_version', default="5.7"),
|
||||
]
|
||||
|
|
|
@ -45,12 +45,8 @@ class TroveTempestPlugin(plugins.TempestPlugin):
|
|||
service_params = {
|
||||
'name': 'database',
|
||||
'service_version': 'database',
|
||||
'module_path': 'trove_tempest_plugin.services.database',
|
||||
'client_names': [
|
||||
'FlavorsClient',
|
||||
'LimitsClient',
|
||||
'VersionsClient'
|
||||
]
|
||||
'module_path': 'trove_tempest_plugin.services.client',
|
||||
'client_names': ['TroveClient']
|
||||
}
|
||||
service_params.update(service_config)
|
||||
return [service_params]
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
# Copyright 2018 Samsung Electronics
|
||||
# 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 oslo_serialization import jsonutils as json
|
||||
from six.moves.urllib import parse as urlparse
|
||||
|
||||
from tempest.lib.common import rest_client
|
||||
from tempest.lib import exceptions
|
||||
|
||||
|
||||
class TroveClient(rest_client.RestClient):
|
||||
def __init__(self, auth_provider, **kwargs):
|
||||
super(TroveClient, self).__init__(auth_provider, **kwargs)
|
||||
|
||||
def get_resource(self, obj, id, expected_status_code=200):
|
||||
url = '/%s/%s' % (obj, id)
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(expected_status_code, resp.status)
|
||||
|
||||
return rest_client.ResponseBody(resp, json.loads(body))
|
||||
|
||||
def list_resources(self, obj, expected_status_code=200, **filters):
|
||||
url = '/%s' % obj
|
||||
if filters:
|
||||
# Encode provided dict of fields into a series of key=value pairs
|
||||
# separated by '&' characters.
|
||||
#
|
||||
# The field value can be a sequence. Setting option doseq to True
|
||||
# enforces producing individual key-value pair for each element of
|
||||
# the sequence under the same key.
|
||||
#
|
||||
# e.g. {'foo': 'bar', 'baz': ['test1', 'test2']}
|
||||
# => foo=bar&baz=test1&baz=test2
|
||||
url += '?' + urlparse.urlencode(filters, doseq=True)
|
||||
|
||||
resp, body = self.get(url)
|
||||
self.expected_success(expected_status_code, resp.status)
|
||||
|
||||
return rest_client.ResponseBody(resp, json.loads(body))
|
||||
|
||||
def delete_resource(self, obj, id, ignore_notfound=False):
|
||||
try:
|
||||
resp, _ = self.delete('/{obj}/{id}'.format(obj=obj, id=id))
|
||||
return resp
|
||||
except exceptions.NotFound:
|
||||
if ignore_notfound:
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
def create_resource(self, obj, req_body, extra_headers={},
|
||||
expected_status_code=200):
|
||||
headers = {"Content-Type": "application/json"}
|
||||
headers = dict(headers, **extra_headers)
|
||||
url = '/%s' % obj
|
||||
|
||||
resp, body = self.post(url, json.dumps(req_body), headers=headers)
|
||||
self.expected_success(expected_status_code, resp.status)
|
||||
|
||||
return rest_client.ResponseBody(resp, json.loads(body))
|
|
@ -1,26 +0,0 @@
|
|||
# Copyright 2018 Samsung Electronics
|
||||
# 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 trove_tempest_plugin.services.database.flavors_client import FlavorsClient
|
||||
from trove_tempest_plugin.services.database.limits_client import LimitsClient
|
||||
from trove_tempest_plugin.services.database.versions_client import (
|
||||
VersionsClient)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'FlavorsClient',
|
||||
'LimitsClient',
|
||||
'VersionsClient'
|
||||
]
|
|
@ -1,47 +0,0 @@
|
|||
# Copyright 2018 Samsung Electronics
|
||||
# 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 oslo_serialization import jsonutils as json
|
||||
from six.moves.urllib import parse as urllib
|
||||
|
||||
from tempest.lib.common import rest_client
|
||||
|
||||
|
||||
class BaseClient(rest_client.RestClient):
|
||||
|
||||
def show_resource(self, uri, expected_status_code=200, **fields):
|
||||
if fields:
|
||||
# Encode provided dict of fields into a series of key=value pairs
|
||||
# separated by '&' characters.
|
||||
#
|
||||
# The field value can be a sequence. Setting option doseq to True
|
||||
# enforces producing individual key-value pair for each element of
|
||||
# the sequence under the same key.
|
||||
#
|
||||
# e.g. {'foo': 'bar', 'baz': ['test1', 'test2']}
|
||||
# => foo=bar&baz=test1&baz=test2
|
||||
uri += '?' + urllib.urlencode(fields, doseq=True)
|
||||
resp, body = self.get(uri)
|
||||
self.expected_success(expected_status_code, resp.status)
|
||||
body = json.loads(body)
|
||||
return rest_client.ResponseBody(resp, body)
|
||||
|
||||
def list_resources(self, uri, expected_status_code=200, **filters):
|
||||
if filters:
|
||||
uri += '?' + urllib.urlencode(filters, doseq=True)
|
||||
resp, body = self.get(uri)
|
||||
self.expected_success(expected_status_code, resp.status)
|
||||
body = json.loads(body)
|
||||
return rest_client.ResponseBody(resp, body)
|
|
@ -1,28 +0,0 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
# 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 trove_tempest_plugin.services.database import base_client
|
||||
|
||||
|
||||
class FlavorsClient(base_client.BaseClient):
|
||||
|
||||
uri = '/flavors'
|
||||
|
||||
def list_flavors(self):
|
||||
return self.list_resources(self.uri)
|
||||
|
||||
def show_flavor(self, flavor_id):
|
||||
uri = '%s/%s' % (self.uri, flavor_id)
|
||||
return self.show_resource(uri)
|
|
@ -1,25 +0,0 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
# 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 trove_tempest_plugin.services.database import base_client
|
||||
|
||||
|
||||
class LimitsClient(base_client.BaseClient):
|
||||
|
||||
uri = '/limits'
|
||||
|
||||
def list_limits(self):
|
||||
"""List all limits."""
|
||||
return self.list_resources(self.uri)
|
|
@ -1,30 +0,0 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
# 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 trove_tempest_plugin.services.database import base_client
|
||||
|
||||
|
||||
class VersionsClient(base_client.BaseClient):
|
||||
|
||||
uri = ''
|
||||
|
||||
def __init__(self, auth_provider, service, region, **kwargs):
|
||||
super(VersionsClient, self).__init__(
|
||||
auth_provider, service, region, **kwargs)
|
||||
self.skip_path()
|
||||
|
||||
def list_versions(self):
|
||||
"""List all versions."""
|
||||
return self.list_resources(self.uri)
|
|
@ -1,88 +0,0 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
# 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 tempest.common import utils
|
||||
from tempest.lib import decorators
|
||||
from testtools import testcase as testtools
|
||||
|
||||
from trove_tempest_plugin.tests import base_test
|
||||
|
||||
|
||||
class DatabaseFlavorsTest(base_test.BaseDatabaseTest):
|
||||
|
||||
@classmethod
|
||||
def setup_clients(cls):
|
||||
super(DatabaseFlavorsTest, cls).setup_clients()
|
||||
cls.client = cls.database_flavors_client
|
||||
|
||||
@testtools.attr('smoke')
|
||||
@decorators.idempotent_id('c94b825e-0132-4686-8049-8a4a2bc09525')
|
||||
def test_get_db_flavor(self):
|
||||
# The expected flavor details should be returned
|
||||
flavor = (self.client.show_flavor(self.db_flavor_ref)
|
||||
['flavor'])
|
||||
self.assertEqual(self.db_flavor_ref, str(flavor['id']))
|
||||
self.assertIn('ram', flavor)
|
||||
self.assertIn('links', flavor)
|
||||
self.assertIn('name', flavor)
|
||||
|
||||
@testtools.attr('smoke')
|
||||
@decorators.idempotent_id('685025d6-0cec-4673-8a8d-995cb8e0d3bb')
|
||||
def test_list_db_flavors(self):
|
||||
flavor = (self.client.show_flavor(self.db_flavor_ref)
|
||||
['flavor'])
|
||||
# List of all flavors should contain the expected flavor
|
||||
flavors = self.client.list_flavors()['flavors']
|
||||
self.assertIn(flavor, flavors)
|
||||
|
||||
def _check_values(self, names, db_flavor, os_flavor, in_db=True):
|
||||
for name in names:
|
||||
self.assertIn(name, os_flavor)
|
||||
if in_db:
|
||||
self.assertIn(name, db_flavor)
|
||||
self.assertEqual(str(db_flavor[name]), str(os_flavor[name]),
|
||||
"DB flavor differs from OS on '%s' value"
|
||||
% name)
|
||||
else:
|
||||
self.assertNotIn(name, db_flavor)
|
||||
|
||||
@testtools.attr('smoke')
|
||||
@decorators.idempotent_id('afb2667f-4ec2-4925-bcb7-313fdcffb80d')
|
||||
@utils.services('compute')
|
||||
def test_compare_db_flavors_with_os(self):
|
||||
db_flavors = self.client.list_flavors()['flavors']
|
||||
os_flavors = (self.os_flavors_client.list_flavors(detail=True)
|
||||
['flavors'])
|
||||
self.assertEqual(len(os_flavors), len(db_flavors),
|
||||
"OS flavors %s do not match DB flavors %s" %
|
||||
(os_flavors, db_flavors))
|
||||
for os_flavor in os_flavors:
|
||||
db_flavor =\
|
||||
self.client.show_flavor(os_flavor['id'])['flavor']
|
||||
if db_flavor['id']:
|
||||
self.assertIn('id', db_flavor)
|
||||
self.assertEqual(str(db_flavor['id']), str(os_flavor['id']),
|
||||
"DB flavor id differs from OS flavor id value"
|
||||
)
|
||||
else:
|
||||
self.assertIn('str_id', db_flavor)
|
||||
self.assertEqual(db_flavor['str_id'], str(os_flavor['id']),
|
||||
"DB flavor id differs from OS flavor id value"
|
||||
)
|
||||
|
||||
self._check_values(['name', 'ram', 'vcpus',
|
||||
'disk'], db_flavor, os_flavor)
|
||||
self._check_values(['swap'], db_flavor, os_flavor,
|
||||
in_db=False)
|
|
@ -1,36 +0,0 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
# 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 tempest.lib import decorators
|
||||
from tempest.lib import exceptions as lib_exc
|
||||
from testtools import testcase as testtools
|
||||
|
||||
from trove_tempest_plugin.tests import base_test
|
||||
|
||||
|
||||
class DatabaseFlavorsNegativeTest(base_test.BaseDatabaseTest):
|
||||
|
||||
@classmethod
|
||||
def setup_clients(cls):
|
||||
super(DatabaseFlavorsNegativeTest, cls).setup_clients()
|
||||
cls.client = cls.database_flavors_client
|
||||
|
||||
@testtools.attr('negative')
|
||||
@decorators.idempotent_id('f8e7b721-373f-4a64-8e9c-5327e975af3e')
|
||||
def test_get_non_existent_db_flavor(self):
|
||||
# flavor details are not returned for non-existent flavors
|
||||
self.assertRaises(lib_exc.NotFound,
|
||||
self.client.show_flavor, -1)
|
|
@ -1,47 +0,0 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
# 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 tempest.lib import decorators
|
||||
from testtools import testcase as testtools
|
||||
|
||||
from trove_tempest_plugin.tests import base_test
|
||||
|
||||
|
||||
class DatabaseLimitsTest(base_test.BaseDatabaseTest):
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(DatabaseLimitsTest, cls).resource_setup()
|
||||
cls.client = cls.database_limits_client
|
||||
|
||||
@testtools.attr('smoke')
|
||||
@decorators.idempotent_id('73024538-f316-4829-b3e9-b459290e137a')
|
||||
def test_absolute_limits(self):
|
||||
# Test to verify if all absolute limit parameters are
|
||||
# present when verb is ABSOLUTE
|
||||
limits = self.client.list_limits()['limits']
|
||||
expected_abs_limits = ['max_backups', 'max_volumes',
|
||||
'max_instances', 'verb']
|
||||
absolute_limit = [l for l in limits
|
||||
if l['verb'] == 'ABSOLUTE']
|
||||
self.assertEqual(1, len(absolute_limit), "One ABSOLUTE limit "
|
||||
"verb is allowed. Fetched %s"
|
||||
% len(absolute_limit))
|
||||
actual_abs_limits = absolute_limit[0].keys()
|
||||
missing_abs_limit = set(expected_abs_limits) - set(actual_abs_limits)
|
||||
self.assertEmpty(missing_abs_limit,
|
||||
"Failed to find the following absolute limit(s)"
|
||||
" in a fetched list: %s" %
|
||||
', '.join(str(a) for a in missing_abs_limit))
|
|
@ -1,41 +0,0 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
# 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 tempest.lib import decorators
|
||||
from testtools import testcase as testtools
|
||||
|
||||
from trove_tempest_plugin.tests import base_test
|
||||
|
||||
|
||||
class DatabaseVersionsTest(base_test.BaseDatabaseTest):
|
||||
|
||||
@classmethod
|
||||
def setup_clients(cls):
|
||||
super(DatabaseVersionsTest, cls).setup_clients()
|
||||
cls.client = cls.database_versions_client
|
||||
|
||||
@testtools.attr('smoke')
|
||||
@decorators.idempotent_id('6952cd77-90cd-4dca-bb60-8e2c797940cf')
|
||||
def test_list_db_versions(self):
|
||||
versions = self.client.list_versions()['versions']
|
||||
self.assertTrue(len(versions) > 0, "No database versions found")
|
||||
# List of all versions should contain the current version, and there
|
||||
# should only be one 'current' version
|
||||
current_versions = list()
|
||||
for version in versions:
|
||||
if 'CURRENT' == version['status']:
|
||||
current_versions.append(version['id'])
|
||||
self.assertEqual(1, len(current_versions))
|
||||
self.assertIn(self.db_current_version, current_versions)
|
|
@ -0,0 +1,252 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
# 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 oslo_log import log as logging
|
||||
from oslo_service import loopingcall
|
||||
import tenacity
|
||||
|
||||
from tempest import config
|
||||
from tempest.lib.common.utils import data_utils
|
||||
from tempest.lib.common.utils import test_utils
|
||||
from tempest.lib import exceptions
|
||||
from tempest import test
|
||||
|
||||
from trove_tempest_plugin.tests import utils
|
||||
|
||||
CONF = config.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseTroveTest(test.BaseTestCase):
|
||||
credentials = ('admin', 'primary')
|
||||
|
||||
@classmethod
|
||||
def get_resource_name(cls, resource_type):
|
||||
prefix = "trove-tempest-%s" % cls.__name__
|
||||
return data_utils.rand_name(resource_type, prefix=prefix)
|
||||
|
||||
@classmethod
|
||||
def skip_checks(cls):
|
||||
super(BaseTroveTest, cls).skip_checks()
|
||||
|
||||
if not CONF.service_available.trove:
|
||||
raise cls.skipException("Database service is not available.")
|
||||
|
||||
@classmethod
|
||||
def setup_clients(cls):
|
||||
super(BaseTroveTest, cls).setup_clients()
|
||||
|
||||
cls.client = cls.os_primary.database.TroveClient()
|
||||
cls.admin_client = cls.os_admin.database.TroveClient()
|
||||
|
||||
@classmethod
|
||||
def setup_credentials(cls):
|
||||
# Do not create network resources automatically.
|
||||
cls.set_network_resources()
|
||||
super(BaseTroveTest, cls).setup_credentials()
|
||||
|
||||
@classmethod
|
||||
@tenacity.retry(
|
||||
retry=tenacity.retry_if_exception_type(exceptions.Conflict),
|
||||
wait=tenacity.wait_incrementing(1, 1, 5),
|
||||
stop=tenacity.stop_after_attempt(15)
|
||||
)
|
||||
def _delete_network(cls, net_id):
|
||||
"""Make sure the network is deleted.
|
||||
|
||||
Neutron can be slow to clean up ports from the subnets/networks.
|
||||
Retry this delete a few times if we get a "Conflict" error to give
|
||||
neutron time to fully cleanup the ports.
|
||||
"""
|
||||
networks_client = cls.os_primary.networks_client
|
||||
try:
|
||||
networks_client.delete_network(net_id)
|
||||
except Exception:
|
||||
LOG.error('Unable to delete network %s', net_id)
|
||||
raise
|
||||
|
||||
@classmethod
|
||||
@tenacity.retry(
|
||||
retry=tenacity.retry_if_exception_type(exceptions.Conflict),
|
||||
wait=tenacity.wait_incrementing(1, 1, 5),
|
||||
stop=tenacity.stop_after_attempt(15)
|
||||
)
|
||||
def _delete_subnet(cls, subnet_id):
|
||||
"""Make sure the subnet is deleted.
|
||||
|
||||
Neutron can be slow to clean up ports from the subnets/networks.
|
||||
Retry this delete a few times if we get a "Conflict" error to give
|
||||
neutron time to fully cleanup the ports.
|
||||
"""
|
||||
subnets_client = cls.os_primary.subnets_client
|
||||
try:
|
||||
subnets_client.delete_subnet(subnet_id)
|
||||
except Exception:
|
||||
LOG.error('Unable to delete subnet %s', subnet_id)
|
||||
raise
|
||||
|
||||
@classmethod
|
||||
def _create_network(cls):
|
||||
"""Create database instance network."""
|
||||
networks_client = cls.os_primary.networks_client
|
||||
subnets_client = cls.os_primary.subnets_client
|
||||
routers_client = cls.os_primary.routers_client
|
||||
|
||||
network_kwargs = {"name": cls.get_resource_name("network")}
|
||||
result = networks_client.create_network(**network_kwargs)
|
||||
LOG.info('Private network created: %s', result['network'])
|
||||
cls.private_network = result['network']["id"]
|
||||
cls.addClassResourceCleanup(
|
||||
utils.wait_for_removal,
|
||||
cls._delete_network,
|
||||
networks_client.show_network,
|
||||
cls.private_network
|
||||
)
|
||||
|
||||
subnet_kwargs = {
|
||||
'name': cls.get_resource_name("subnet"),
|
||||
'network_id': cls.private_network,
|
||||
'cidr': CONF.database.subnet_cidr,
|
||||
'ip_version': 4
|
||||
}
|
||||
result = subnets_client.create_subnet(**subnet_kwargs)
|
||||
subnet_id = result['subnet']['id']
|
||||
LOG.info('Private subnet created: %s', result['subnet'])
|
||||
cls.addClassResourceCleanup(
|
||||
utils.wait_for_removal,
|
||||
cls._delete_subnet,
|
||||
subnets_client.show_subnet,
|
||||
subnet_id
|
||||
)
|
||||
|
||||
# In dev node, Trove instance needs to connect with control host
|
||||
router_params = {
|
||||
'name': cls.get_resource_name("router"),
|
||||
'external_gateway_info': {
|
||||
"network_id": CONF.network.public_network_id
|
||||
}
|
||||
}
|
||||
result = routers_client.create_router(**router_params)
|
||||
router_id = result['router']['id']
|
||||
LOG.info('Private router created: %s', result['router'])
|
||||
cls.addClassResourceCleanup(
|
||||
utils.wait_for_removal,
|
||||
routers_client.delete_router,
|
||||
routers_client.show_router,
|
||||
router_id
|
||||
)
|
||||
|
||||
routers_client.add_router_interface(router_id, subnet_id=subnet_id)
|
||||
LOG.info('Subnet %s added to the router %s', subnet_id, router_id)
|
||||
cls.addClassResourceCleanup(
|
||||
routers_client.remove_router_interface,
|
||||
router_id,
|
||||
subnet_id=subnet_id
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(BaseTroveTest, cls).resource_setup()
|
||||
|
||||
# Create network for database instance, use cls.private_network as the
|
||||
# network ID.
|
||||
cls._create_network()
|
||||
|
||||
@classmethod
|
||||
def create_instance(cls, database="test_db", username="test_user",
|
||||
password="password"):
|
||||
"""Create database instance.
|
||||
|
||||
Creating database instance is time-consuming, so we define this method
|
||||
as a class method, which means the instance is shared in a single
|
||||
TestCase. According to
|
||||
https://docs.openstack.org/tempest/latest/write_tests.html#adding-a-new-testcase,
|
||||
all test methods within a TestCase are assumed to be executed serially.
|
||||
"""
|
||||
name = cls.get_resource_name("instance")
|
||||
body = {
|
||||
"instance": {
|
||||
"name": name,
|
||||
"flavorRef": CONF.database.flavor_id,
|
||||
"volume": {
|
||||
"size": 1,
|
||||
"type": CONF.database.volume_type
|
||||
},
|
||||
"databases": [
|
||||
{
|
||||
"name": database
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
{
|
||||
"name": username,
|
||||
"password": password,
|
||||
}
|
||||
],
|
||||
"datastore": {
|
||||
"type": CONF.database.datastore_type,
|
||||
"version": CONF.database.datastore_version
|
||||
},
|
||||
"nics": [
|
||||
{
|
||||
"net-id": cls.private_network
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
res = cls.client.create_resource("instances", body)
|
||||
instance_id = res["instance"]["id"]
|
||||
cls.addClassResourceCleanup(cls.wait_for_instance_status, instance_id,
|
||||
need_delete=True, status="DELETED")
|
||||
|
||||
return instance_id
|
||||
|
||||
@classmethod
|
||||
def wait_for_instance_status(cls, id, status="ACTIVE", need_delete=False):
|
||||
def _wait():
|
||||
try:
|
||||
res = cls.client.get_resource("instances", id)
|
||||
except exceptions.NotFound:
|
||||
if need_delete or status == "DELETED":
|
||||
raise loopingcall.LoopingCallDone()
|
||||
return
|
||||
|
||||
if res["instance"]["status"] == status:
|
||||
raise loopingcall.LoopingCallDone()
|
||||
elif status != "ERROR" and res["instance"]["status"] == "ERROR":
|
||||
# If instance status goes to ERROR but is not expected, stop
|
||||
# waiting
|
||||
message = "Instance status is ERROR."
|
||||
caller = test_utils.find_test_caller()
|
||||
if caller:
|
||||
message = '({caller}) {message}'.format(caller=caller,
|
||||
message=message)
|
||||
raise exceptions.UnexpectedResponseCode(message)
|
||||
|
||||
if need_delete:
|
||||
cls.client.delete_resource("instances", id, ignore_notfound=True)
|
||||
|
||||
timer = loopingcall.FixedIntervalWithTimeoutLoopingCall(_wait)
|
||||
try:
|
||||
timer.start(interval=10,
|
||||
timeout=CONF.database.database_build_timeout).wait()
|
||||
except loopingcall.LoopingCallTimeOut:
|
||||
message = ("Instance %s is not in the expected status: %s" %
|
||||
(id, status))
|
||||
caller = test_utils.find_test_caller()
|
||||
if caller:
|
||||
message = '({caller}) {message}'.format(caller=caller,
|
||||
message=message)
|
||||
raise exceptions.TimeoutException(message)
|
|
@ -1,86 +0,0 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
# 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 tempest import config
|
||||
from tempest import test
|
||||
|
||||
from trove_tempest_plugin import clients
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class BaseDatabaseTest(test.BaseTestCase):
|
||||
"""Base test case class.
|
||||
|
||||
Includes parts common to API and scenario tests:
|
||||
* test case callbacks,
|
||||
* service clients initialization.
|
||||
"""
|
||||
|
||||
credentials = ['primary']
|
||||
client_manager = clients.Manager
|
||||
|
||||
@classmethod
|
||||
def skip_checks(cls):
|
||||
super(BaseDatabaseTest, cls).skip_checks()
|
||||
if not CONF.service_available.trove:
|
||||
skip_msg = ("%s skipped as trove is not available" % cls.__name__)
|
||||
raise cls.skipException(skip_msg)
|
||||
|
||||
@classmethod
|
||||
def setup_clients(cls):
|
||||
"""Setups service clients.
|
||||
|
||||
Tempest provides a convenient fabrication interface, which can be used
|
||||
to produce instances of clients configured with the required parameters
|
||||
and a selected set of credentials. Thanks to this interface, the
|
||||
complexity of client initialization is hidden from the developer. All
|
||||
parameters such as "catalog_type", "auth_provider", "build_timeout"
|
||||
etc. are read from Tempest configuration and then automatically
|
||||
installed in the clients.
|
||||
|
||||
The fabrication interface is enabled through the client manager, which
|
||||
is hooked to the class by the "client_manager" property.
|
||||
|
||||
To initialize a new client, one need to specify the set of credentials
|
||||
(primary, admin) to be used and the category of client (eg compute,
|
||||
image, database, etc.). Together, they constitute a proxy for the
|
||||
fabricators of specific client classes from a given category.
|
||||
|
||||
For example, initializing a new flavors client from the database
|
||||
category with primary privileges boils down to the following call:
|
||||
|
||||
flavors_client = cls.os_primary.database.FlavorsClient()
|
||||
|
||||
In order to initialize a new networks client from the compute category
|
||||
with administrator privilages:
|
||||
|
||||
networks_client = cls.os_admin.compute.NetworksClient()
|
||||
|
||||
Note, that selected set of credentials must be declared in the
|
||||
"credentials" property of this class.
|
||||
"""
|
||||
super(BaseDatabaseTest, cls).setup_clients()
|
||||
cls.database_flavors_client = cls.os_primary.database.FlavorsClient()
|
||||
cls.os_flavors_client = cls.os_primary.compute.FlavorsClient()
|
||||
cls.database_limits_client = cls.os_primary.database.LimitsClient()
|
||||
cls.database_versions_client = cls.os_primary.database.VersionsClient()
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(BaseDatabaseTest, cls).resource_setup()
|
||||
cls.catalog_type = CONF.database.catalog_type
|
||||
cls.db_flavor_ref = CONF.database.db_flavor_ref
|
||||
cls.db_current_version = CONF.database.db_current_version
|
|
@ -0,0 +1,29 @@
|
|||
# Copyright 2019 Catalyst Cloud Ltd.
|
||||
#
|
||||
# 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 tempest.lib import decorators
|
||||
|
||||
from trove_tempest_plugin.tests import base
|
||||
|
||||
|
||||
class TestInstanceBasic(base.BaseTroveTest):
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(TestInstanceBasic, cls).resource_setup()
|
||||
|
||||
cls.instance_id = cls.create_instance()
|
||||
cls.wait_for_instance_status(cls.instance_id)
|
||||
|
||||
@decorators.idempotent_id("40cf38ce-cfbf-11e9-8760-1458d058cfb2")
|
||||
def test_database_access(self):
|
||||
pass
|
|
@ -0,0 +1,51 @@
|
|||
# Copyright 2019 Catalyst Cloud Ltd.
|
||||
#
|
||||
# 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.
|
||||
import time
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from tempest.lib import exceptions
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def wait_for_removal(delete_func, show_func, *args, **kwargs):
|
||||
"""Call the delete function, then wait for it to be 'NotFound'
|
||||
|
||||
:param delete_func: The delete function to call.
|
||||
:param show_func: The show function to call looking for 'NotFound'.
|
||||
:param ID: The ID of the object to delete/show.
|
||||
:raises TimeoutException: The object did not achieve the status or ERROR in
|
||||
the check_timeout period.
|
||||
:returns: None
|
||||
"""
|
||||
check_timeout = 15
|
||||
try:
|
||||
delete_func(*args, **kwargs)
|
||||
except exceptions.NotFound:
|
||||
return
|
||||
|
||||
start = int(time.time())
|
||||
LOG.info('Waiting for object to be NotFound')
|
||||
while True:
|
||||
try:
|
||||
show_func(*args, **kwargs)
|
||||
except exceptions.NotFound:
|
||||
return
|
||||
|
||||
if int(time.time()) - start >= check_timeout:
|
||||
message = ('%s did not raise NotFound in %s seconds.' %
|
||||
(show_func.__name__, check_timeout))
|
||||
raise exceptions.TimeoutException(message)
|
||||
time.sleep(3)
|
Loading…
Reference in New Issue