Reinstate removed api tests

Story: 2008365
Task: 41273

Change-Id: Ib08bf8951352c978b88a4746aa7b44e0b8cec177
This commit is contained in:
Sam Morrison 2020-11-05 10:32:52 +11:00
parent 2581bc079a
commit 0716623b79
29 changed files with 1216 additions and 26 deletions

View File

@ -25,14 +25,13 @@
This job is for stable branch prior to Ussuri for testing
on py2.
required-projects:
- openstack/neutron
- openstack/trove
- openstack/trove-tempest-plugin
- openstack/tempest
- opendev.org/openstack/neutron
- opendev.org/openstack/trove
- opendev.org/openstack/trove-tempest-plugin
- opendev.org/openstack/tempest
vars:
tox_envlist: all
devstack_localrc:
TEMPEST_PLUGINS: /opt/stack/trove-tempest-plugin
USE_PYTHON3: False
devstack_plugins:
trove: https://opendev.org/openstack/trove
@ -51,14 +50,15 @@
- job:
name: trove-tempest-plugin
parent: devstack-tempest
nodeset: trove-ubuntu-bionic
timeout: 7800
description: |
This job is for testing on py3 which is Ussuri onwards.
required-projects: &base_required_projects
- openstack/python-troveclient
- openstack/trove
- openstack/trove-tempest-plugin
- openstack/tempest
- opendev.org/openstack/python-troveclient
- opendev.org/openstack/trove
- opendev.org/openstack/trove-tempest-plugin
- opendev.org/openstack/tempest
irrelevant-files:
- ^.*\.rst$
- ^doc/.*$
@ -67,26 +67,16 @@
vars: &base_vars
tox_envlist: all
tempest_concurrency: 1
devstack_localrc:
TEMPEST_PLUGINS: /opt/stack/trove-tempest-plugin
USE_PYTHON3: true
devstack_local_conf:
post-config:
$TROVE_CONF:
DEFAULT:
usage_timeout: 1800
usage_timeout: 600
devstack_plugins:
trove: https://opendev.org/openstack/trove.git
devstack_services:
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
@ -98,11 +88,12 @@
s-object: true
s-proxy: true
tempest: true
tempest_test_regex: ^trove_tempest_plugin\.tests.scenario\.test_instance_basic\.TestInstanceBasicMySQL\.test_database_access
tempest_test_regex: ^trove_tempest_plugin
- job:
name: trove-tempest-ipv6-only
parent: devstack-tempest-ipv6
nodeset: trove-ubuntu-bionic
description: |
Trove devstack tempest tests job for IPv6-only deployment
required-projects: *base_required_projects

View File

View File

@ -0,0 +1,24 @@
# 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.common.utils import data_utils
def rand_name():
"""Return a safe name to use for a user or a DB name
Some datastores have limits on size and characters
"""
return data_utils.rand_name().replace('-', '')[:16]

View File

@ -0,0 +1,112 @@
# Copyright 2019 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.
import re
import time
from tempest.lib import exceptions as lib_exc
def wait_for_db_instance_status(client, instance_id, status,
failure_pattern='ERROR'):
"""Wait for a db instance to reach a given status"""
start = int(time.time())
fail_regexp = re.compile(failure_pattern)
if status == 'ACTIVE':
status = ['ACTIVE', 'HEALTHY']
else:
status = [status]
while True:
try:
body = client.show_db_instance(instance_id)['instance']
except lib_exc.NotFound:
if status == ['DELETE_COMPLETE']:
return
instance_name = body['name']
instance_status = body['status']
if instance_status in status:
return body
if fail_regexp.search(instance_status):
raise KeyError("Instance in ERROR state")
if int(time.time()) - start >= client.build_timeout:
message = ('DB Instance %s failed to reach %s status'
'(current: %s) within the required time (%s s).' %
(instance_name, status, instance_status,
client.build_timeout))
raise lib_exc.TimeoutException(message)
time.sleep(client.build_interval)
def wait_for_db_instance_decommission(client, instance_id):
"""Wait for a db instance decomission"""
start = int(time.time())
while True:
try:
client.show_db_instance(instance_id)['instance']
except lib_exc.NotFound:
return
if int(time.time()) - start >= client.build_timeout:
message = ('DB Instance %s deletion failed '
'within the required time (%s s).' %
(instance_id, client.build_timeout))
raise lib_exc.TimeoutException(message)
time.sleep(client.build_interval)
def wait_for_backup_status(client, backup_id, status,
failure_pattern='FAILED'):
"""Wait for a backup to reach a given status"""
start = int(time.time())
fail_regexp = re.compile(failure_pattern)
while True:
try:
body = client.show_backup(backup_id)['backup']
except lib_exc.NotFound:
if status == 'DELETE_COMPLETE':
return
backup_name = body['name']
backup_status = body['status']
if backup_status == status:
return body
if fail_regexp.search(backup_status):
raise KeyError("Backup in FAILED state")
if int(time.time()) - start >= client.build_timeout:
message = ('Backup %s failed to reach %s status'
'(current: %s) within the required time (%s s).' %
(backup_name, status, backup_status,
client.build_timeout))
raise lib_exc.TimeoutException(message)
time.sleep(client.build_interval)
def wait_for_backup_delete(client, backup_id):
"""Wait for a backup to be deleted"""
start = int(time.time())
while True:
try:
client.show_backup(backup_id)['backup']
except lib_exc.NotFound:
return
if int(time.time()) - start >= client.build_timeout:
message = ('Backup %s deletion failed '
'within the required time (%s s).' %
(backup_id, client.build_timeout))
raise lib_exc.TimeoutException(message)
time.sleep(client.build_interval)

View File

@ -38,10 +38,28 @@ DatabaseGroup = [
'internalURL'],
help="The endpoint type to use for the Database service."
),
cfg.StrOpt('db_current_version',
default="v1.0",
help="Current database version to use in database tests."),
cfg.ListOpt(
'enabled_datastores',
default=['mysql']
),
cfg.StrOpt('datastore_type',
default="MySQL",
help="Type of the Database"),
cfg.StrOpt('datastore_version',
default=None,
help="Specific datastore version to use (optional)"),
cfg.StrOpt('availability_zone',
default='nova',
help="Availability zone of the db instance to use"),
cfg.IntOpt('volume_size',
default=1,
help="Volume size for the db instances"),
cfg.StrOpt('dns_name_server',
default=None,
help="The DNS server used to query trove instance domain name"),
cfg.DictOpt(
'default_datastore_versions',
default={'mysql': '5.7.29'},
@ -54,29 +72,33 @@ DatabaseGroup = [
'upgrade.',
),
cfg.IntOpt('database_build_timeout',
default=1800,
default=300,
help='Timeout in seconds to wait for a database instance to '
'build.'),
cfg.IntOpt(
'database_restore_timeout',
default=3600,
default=120,
help='Timeout in seconds to wait for a database instance to '
'be restored.'
),
cfg.IntOpt(
'backup_wait_timeout',
default=600,
default=120,
help='Timeout in seconds to wait for a backup to be completed.'
),
cfg.StrOpt(
'flavor_id',
default="d2",
help="The Nova flavor ID used for creating database instance."
help="The Nova flavor ID used for creating database instance.",
deprecated_group='database',
deprecated_name='db_flavor_ref',
),
cfg.StrOpt(
'resize_flavor_id',
default="d3",
help="The Nova flavor ID used for resizing database instance."
help="The Nova flavor ID used for resizing database instance.",
deprecated_group='database',
deprecated_name='db_flavor_ref_alt',
),
cfg.StrOpt(
'shared_network',

View File

@ -0,0 +1,59 @@
# 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_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
class DatabaseBackupsClient(rest_client.RestClient):
def list_backups(self, params=None):
"""List all available backups."""
url = 'backups'
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def create_backup(self, instance_id, name, description=None, parent=None,
incremental=False):
"""Create a backup."""
url = 'backups'
data = {'instance': instance_id,
'name': name,
'incremental': int(incremental)}
post_body = json.dumps({"backup": data})
resp, body = self.post(url, body=post_body)
self.expected_success(202, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def delete_backup(self, backup_id):
"""Delete the backup"""
url = 'backups/%s' % backup_id
resp, body = self.delete(url)
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
def show_backup(self, backup_id):
"""Show backups."""
url = 'backups/%s' % backup_id
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)

View File

@ -0,0 +1,31 @@
# 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_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
class DatabaseDatastoresClient(rest_client.RestClient):
def list_db_datastores(self, params=None):
"""List all available datastores."""
url = 'datastores'
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)

View File

@ -0,0 +1,37 @@
# 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_serialization import jsonutils as json
from six.moves import urllib
from tempest.lib.common import rest_client
class DatabaseFlavorsClient(rest_client.RestClient):
def list_db_flavors(self, params=None):
url = 'flavors'
if params:
url += '?%s' % urllib.parse.urlencode(params)
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def show_db_flavor(self, db_flavor_id):
resp, body = self.get("flavors/%s" % db_flavor_id)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)

View File

@ -0,0 +1,234 @@
# 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.
import time
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
class DatabaseInstancesClient(rest_client.RestClient):
def list_db_instances(self, params=None):
"""List all available instances."""
url = 'instances'
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def create_db_instance(self, params):
"""Create an instance."""
url = 'instances'
headers = self.get_headers()
resp, body = self.post(url, headers=headers, body=params)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def show_db_instance(self, instance_id):
"""Show the db instance"""
url = 'instances/%s' % instance_id
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def update_db_instance(self, instance_id, **kwargs):
"""Updates the db instance"""
url = 'instances/%s' % instance_id
post_body = json.dumps({'instance': kwargs})
resp, body = self.patch(url, post_body)
self.expected_success(202, resp.status)
if body:
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def _action(self, instance_id, action_name, **kwargs):
post_body = json.dumps({action_name: kwargs})
resp, body = self.post('instances/%s/action' % instance_id,
post_body)
self.expected_success(202, resp.status)
if body:
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def restart_db_instance(self, instance_id):
"""Restart the db instance"""
return self._action(instance_id, "restart")
def upgrade_db_instance(self, instance_id, datastore_version):
body = json.dumps({"instance": {
"datastore_version": datastore_version}})
resp, body = self.patch('instances/%s' % instance_id,
body)
self.expected_success(202, resp.status)
if body:
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def resize_db_instance(self, instance_id, flavor_id):
return self._action(instance_id, "resize", flavorRef=flavor_id)
def resize_db_instance_volume(self, instance_id, new_size):
return self._action(instance_id, "resize", volume={'size': new_size})
def delete_db_instance(self, instance_id):
"""Delete the db instance"""
url = 'instances/%s' % instance_id
resp, body = self.delete(url)
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
def list_databases(self, instance_id):
"""List all databases on an instance."""
resp, body = self.get('instances/%s/databases' % instance_id)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def create_database(self, instance_id, name):
"""Creates a database on an instance"""
post_body = json.dumps({"databases": [{"name": name}]})
resp, body = self.post('instances/%s/databases' % instance_id,
post_body)
self.expected_success(202, resp.status)
if body:
body = json.loads(body)
time.sleep(2)
return rest_client.ResponseBody(resp, body)
def delete_database(self, instance_id, name):
"""Deletes a database on an instance"""
resp, body = self.delete('instances/%s/databases/%s' % (instance_id,
name))
self.expected_success(202, resp.status)
if body:
body = json.loads(body)
time.sleep(2)
return rest_client.ResponseBody(resp, body)
def root_show(self, instance_id):
"""Shows if root has ever been enabled on an instance"""
url = 'instances/%s/root' % instance_id
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def root_enable(self, instance_id):
"""Enables root on an instance"""
url = 'instances/%s/root' % instance_id
resp, body = self.post(url, body=json.dumps({}))
self.expected_success(200, resp.status)
if body:
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def root_disable(self, instance_id):
"""Disables root on an instance"""
url = 'instances/%s/root' % instance_id
resp, body = self.delete(url)
self.expected_success(204, resp.status)
if body:
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def list_users(self, instance_id):
"""List all users on an instance."""
resp, body = self.get('instances/%s/users' % instance_id)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def show_user(self, instance_id, name):
"""Get a user on an instance."""
resp, body = self.get('instances/%s/users/%s' % (instance_id, name))
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def create_user(self, instance_id, name, password, databases=[]):
"""Creates a user on an instance"""
post_body = json.dumps({"users": [{"name": name,
"password": password,
"databases": databases}]})
resp, body = self.post('instances/%s/users' % instance_id,
post_body)
self.expected_success(202, resp.status)
if body:
body = json.loads(body)
time.sleep(2)
return rest_client.ResponseBody(resp, body)
def update_user(self, instance_id, name, **kwargs):
"""Updates the user"""
url = 'instances/%s/users/%s' % (instance_id, name)
post_body = json.dumps({'user': kwargs})
resp, body = self.put(url, post_body)
self.expected_success(202, resp.status)
if body:
body = json.loads(body)
time.sleep(2)
return rest_client.ResponseBody(resp, body)
def delete_user(self, instance_id, name):
"""Updates the user"""
url = 'instances/%s/users/%s' % (instance_id, name)
resp, body = self.delete(url)
self.expected_success(202, resp.status)
if body:
body = json.loads(body)
time.sleep(2)
return rest_client.ResponseBody(resp, body)
def grant_user_access(self, instance_id, name, databases):
"""Grants a user access to a database"""
url = 'instances/%s/users/%s/databases' % (instance_id, name)
databases = [{'name': x} for x in databases]
post_body = json.dumps({'databases': databases})
resp, body = self.put(url, post_body)
self.expected_success(202, resp.status)
if body:
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def revoke_user_access(self, instance_id, name, database):
"""Revokes a user access to a database"""
url = 'instances/%s/users/%s/databases/%s' % (instance_id, name,
database)
resp, body = self.delete(url)
self.expected_success(202, resp.status)
if body:
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def show_user_access(self, instance_id, name):
"""Shows access details of a user of an instanceqq"""
url = 'instances/%s/users/%s/databases' % (instance_id, name)
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def list_backups(self, instance_id):
"""List all backups on an instance."""
resp, body = self.get('instances/%s/backups' % instance_id)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)

View File

@ -0,0 +1,31 @@
# 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_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
class DatabaseLimitsClient(rest_client.RestClient):
def list_db_limits(self, params=None):
"""List all limits."""
url = 'limits'
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)

View File

@ -0,0 +1,37 @@
# 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_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
class DatabaseVersionsClient(rest_client.RestClient):
def __init__(self, auth_provider, service, region, **kwargs):
super(DatabaseVersionsClient, self).__init__(
auth_provider, service, region, **kwargs)
self.skip_path()
def list_db_versions(self, params=None):
"""List all versions."""
url = ''
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)

View File

@ -0,0 +1,158 @@
# 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.
import json
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
import tempest.test
from trove_tempest_plugin.common import waiters
from trove_tempest_plugin.services.database.json import backups_client
from trove_tempest_plugin.services.database.json import datastores_client
from trove_tempest_plugin.services.database.json import flavors_client
from trove_tempest_plugin.services.database.json import instances_client
from trove_tempest_plugin.services.database.json import limits_client
from trove_tempest_plugin.services.database.json import versions_client
CONF = config.CONF
class BaseDatabaseTest(tempest.test.BaseTestCase):
"""Base test case class for all Database API tests."""
credentials = ['primary']
@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):
super(BaseDatabaseTest, cls).setup_clients()
default_params = config.service_client_config()
# NOTE: Tempest uses timeout values of compute API if project specific
# timeout values don't exist.
default_params_with_timeout_values = {
'build_interval': CONF.compute.build_interval,
'build_timeout': CONF.database.database_build_timeout
}
default_params_with_timeout_values.update(default_params)
cls.database_flavors_client = flavors_client.DatabaseFlavorsClient(
cls.os_primary.auth_provider,
CONF.database.catalog_type,
CONF.identity.region,
**default_params_with_timeout_values)
cls.os_flavors_client = cls.os_primary.flavors_client
cls.database_limits_client = limits_client.DatabaseLimitsClient(
cls.os_primary.auth_provider,
CONF.database.catalog_type,
CONF.identity.region,
**default_params_with_timeout_values)
cls.database_versions_client = versions_client.DatabaseVersionsClient(
cls.os_primary.auth_provider,
CONF.database.catalog_type,
CONF.identity.region,
**default_params_with_timeout_values)
cls.database_datastores_client =\
datastores_client.DatabaseDatastoresClient(
cls.os_primary.auth_provider,
CONF.database.catalog_type,
CONF.identity.region,
**default_params_with_timeout_values)
cls.database_instances_client =\
instances_client.DatabaseInstancesClient(
cls.os_primary.auth_provider,
CONF.database.catalog_type,
CONF.identity.region,
**default_params_with_timeout_values)
cls.database_backups_client =\
backups_client.DatabaseBackupsClient(
cls.os_primary.auth_provider,
CONF.database.catalog_type,
CONF.identity.region,
**default_params_with_timeout_values)
@classmethod
def resource_setup(cls):
super(BaseDatabaseTest, cls).resource_setup()
cls.catalog_type = CONF.database.catalog_type
cls.flavor_id = CONF.database.flavor_id
cls.resize_flavor_id = CONF.database.resize_flavor_id
cls.db_current_version = CONF.database.db_current_version
cls.datastore_type = CONF.database.datastore_type
cls.datastore_version = CONF.database.datastore_version
cls.availability_zone = CONF.database.availability_zone
cls.volume_size = CONF.database.volume_size
cls.dns_name_server = CONF.database.dns_name_server
@classmethod
def create_test_instance(cls, backup_id=None, datastore_version=None):
"""Wrapper utility that returns a test serinstancever.
This wrapper utility calls the common create test instance and
returns a test instance. The purpose of this wrapper is to minimize
the impact on the code of the tests already using this
function.
:param validatable: Whether the server will connectable via db protocol
:param validation_resources: Dictionary of validation resources as
returned by `get_class_validation_resources`.
:param kwargs: Extra arguments are passed down to the
`create_test_instance` call.
"""
name = data_utils.rand_name(cls.__name__ + "-instance")
tenant_network = cls.get_tenant_network()
instance_dict = {
"users": [],
"availability_zone": CONF.database.availability_zone,
"flavorRef": CONF.database.flavor_id,
"volume": {"size": CONF.database.volume_size},
"databases": [],
"datastore": {"type": CONF.database.datastore_type},
"name": name,
"nics": [{"network_id": tenant_network['id']}],
}
if CONF.database.datastore_version and not datastore_version:
datastore_version = CONF.database.datastore_version
if datastore_version:
instance_dict["datastore"]["version"] = datastore_version
if backup_id:
instance_dict["restorePoint"] = {"backupRef": backup_id}
post_body = json.dumps({'instance': instance_dict})
instance = cls.client.create_db_instance(post_body)['instance']
# For each instance schedule wait and delete, so we first delete all
# and then wait for all
cls.addClassResourceCleanup(
waiters.wait_for_db_instance_decommission,
cls.database_instances_client, instance['id'])
cls.addClassResourceCleanup(
test_utils.call_and_ignore_notfound_exc,
cls.database_instances_client.delete_db_instance, instance['id'])
waiters.wait_for_db_instance_status(cls.client, instance['id'],
'ACTIVE')
return instance

View File

@ -0,0 +1,33 @@
# 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.api.database import base
class DatabaseDatastoresTest(base.BaseDatabaseTest):
@classmethod
def resource_setup(cls):
super(DatabaseDatastoresTest, cls).resource_setup()
cls.client = cls.database_datastores_client
@testtools.attr('smoke')
@decorators.idempotent_id('e4cdcadf-51bc-41ec-8cc6-530a3da08d10')
def test_datastores(self):
datastores = self.client.list_db_datastores()['datastores']
self.assertTrue(len(datastores) > 0, "No available datastores found")

View File

@ -0,0 +1,58 @@
# 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.api.database import base
class DatabaseFlavorsTest(base.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_db_flavor(self.flavor_id)
['flavor'])
self.assertEqual(self.flavor_id, str(flavor['str_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_db_flavor(self.flavor_id)
['flavor'])
# List of all flavors should contain the expected flavor
flavors = self.client.list_db_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)

View File

@ -0,0 +1,36 @@
# 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.api.database import base
class DatabaseFlavorsNegativeTest(base.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_db_flavor, -1)

View File

@ -0,0 +1,46 @@
# Copyright 2019 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 exceptions as lib_exc
from trove_tempest_plugin.common import waiters
from trove_tempest_plugin.tests.api.database import base
class WithInstanceBaseTest(base.BaseDatabaseTest):
def setUp(self):
# Normally we use the same server with all test cases,
# but if it has an issue, we build a new one
super(WithInstanceBaseTest, self).setUp()
# Check if the server is in a clean state after test
try:
waiters.wait_for_db_instance_status(self.client, self.instance_id,
'ACTIVE')
except lib_exc.NotFound:
instance = self.create_test_instance()
self.__class__.instance_id = instance['id']
@classmethod
def setup_clients(cls):
super(WithInstanceBaseTest, cls).setup_clients()
cls.client = cls.database_instances_client
@classmethod
def resource_setup(cls, datastore_version=None):
super(WithInstanceBaseTest, cls).resource_setup()
instance = cls.create_test_instance(
datastore_version=datastore_version)
cls.instance_id = instance['id']

View File

@ -0,0 +1,119 @@
# Copyright 2019 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 trove_tempest_plugin.common import utils
from trove_tempest_plugin.common import waiters
from trove_tempest_plugin.tests.api.database.instances import base
class InstanceActionsTest(base.WithInstanceBaseTest):
@decorators.idempotent_id('ace549b3-eee0-4502-bf20-7594d4bf4856')
def test_restart_server(self):
self.client.restart_db_instance(self.instance_id)
waiters.wait_for_db_instance_status(self.client, self.instance_id,
'ACTIVE')
@decorators.idempotent_id('39eff887-008b-4a93-bead-6ddc969117c3')
def test_resize_server(self):
self.client.resize_db_instance(self.instance_id,
self.resize_flavor_id)
waiters.wait_for_db_instance_status(self.client, self.instance_id,
'ACTIVE')
instance = self.client.show_db_instance(self.instance_id)['instance']
self.assertEqual(self.resize_flavor_id, instance['flavor']['id'])
@decorators.idempotent_id('bba1be08-409b-4916-b9a8-6ec03425cd6e')
def test_resize_volume(self):
new_size = 2
self.client.resize_db_instance_volume(self.instance_id, new_size)
waiters.wait_for_db_instance_status(self.client, self.instance_id,
'ACTIVE')
instance = self.client.show_db_instance(self.instance_id)['instance']
self.assertEqual(new_size, instance['volume']['size'])
@decorators.idempotent_id('215fbcbe-40d3-4a8f-9fe1-55ea9e8fb814')
def test_update_name(self):
new_name = 'new-name'
self.client.update_db_instance(self.instance_id, name=new_name)
waiters.wait_for_db_instance_status(self.client, self.instance_id,
'ACTIVE')
# Verify the name of the instance has changed
instance = self.client.show_db_instance(self.instance_id)['instance']
self.assertEqual(new_name, instance['name'])
@decorators.idempotent_id('38b5462a-308e-4cb8-9530-cc6741c95501')
def test_list_create_delete_database(self):
name = utils.rand_name()
self.client.create_database(self.instance_id, name=name)
databases = self.client.list_databases(self.instance_id)['databases']
databases = [x['name'] for x in databases]
self.assertIn(name, databases)
self.client.delete_database(self.instance_id, name=name)
databases = self.client.list_databases(self.instance_id)['databases']
databases = [x['name'] for x in databases]
self.assertNotIn(name, databases)
@decorators.idempotent_id('bf4840fe-8cf8-46a1-8371-13021e87c690')
def test_enable_disable_root(self):
root_show = self.client.root_show(self.instance_id)
self.assertFalse(root_show['rootEnabled'])
root_enable = self.client.root_enable(self.instance_id)
self.assertIn('password', list(root_enable['user'].keys()))
# TODO(sorrison) Test connection with root user/password
root_show = self.client.root_show(self.instance_id)
self.assertTrue(root_show['rootEnabled'])
self.client.root_disable(self.instance_id)
root_show = self.client.root_show(self.instance_id)
# Show root show's if root as ever been enabled so disabling should
# have no impact
self.assertTrue(root_show['rootEnabled'])
@decorators.idempotent_id('9f11d15b-9640-4c33-a7db-c78224763014')
def test_list_create_delete_user(self):
name = utils.rand_name()
self.client.create_user(self.instance_id, name=name, password='secret')
users = self.client.list_users(self.instance_id)['users']
users = [x['name'] for x in users]
self.assertIn(name, users)
self.client.delete_user(self.instance_id, name=name)
users = self.client.list_users(self.instance_id)['users']
users = [x['name'] for x in users]
self.assertNotIn(name, users)
@decorators.idempotent_id('6f8b8350-f2a9-47b2-a108-8e3653cb9b57')
def test_grant_revoke_list_access(self):
user = utils.rand_name()
db = utils.rand_name()
self.client.create_user(self.instance_id, name=user, password='secret')
self.client.create_database(self.instance_id, name=db)
access = self.client.show_user_access(self.instance_id, user)
self.assertEqual([], access['databases'])
self.client.grant_user_access(self.instance_id, user, [db])
access = self.client.show_user_access(self.instance_id, user)
access = [x['name'] for x in access['databases']]
self.assertIn(db, access)
self.client.revoke_user_access(self.instance_id, user, db)
access = self.client.show_user_access(self.instance_id, user)
access = [x['name'] for x in access['databases']]
self.assertNotIn(db, access)
@decorators.idempotent_id('b13ff6fb-6214-416b-8aea-23dc3c24d00e')
def test_list_backups(self):
backups = self.client.list_backups(self.instance_id)
self.assertEqual([], backups['backups'])

View File

@ -0,0 +1,74 @@
# 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.common.utils import test_utils
from tempest.lib import decorators
from trove_tempest_plugin.common import utils
from trove_tempest_plugin.common import waiters
from trove_tempest_plugin.tests.api.database.instances import base
class InstanceBackupsTest(base.WithInstanceBaseTest):
@classmethod
def setup_clients(cls):
super(InstanceBackupsTest, cls).setup_clients()
cls.backup_client = cls.database_backups_client
def _add_cleanup(self, backup_id):
self.addClassResourceCleanup(
waiters.wait_for_backup_delete,
self.backup_client, backup_id)
self.addClassResourceCleanup(
test_utils.call_and_ignore_notfound_exc,
self.backup_client.delete_backup, backup_id)
@decorators.idempotent_id('e4c2bf6d-e619-4d9b-a79b-67f5463a8705')
def test_list_create_delete_backup(self):
name = utils.rand_name()
backup = self.backup_client.create_backup(self.instance_id, name)
backup_id = backup['backup']['id']
waiters.wait_for_backup_status(self.backup_client, backup_id,
'COMPLETED')
backups = self.backup_client.list_backups()
backup_ids = [x['id'] for x in backups['backups']]
self.assertIn(backup_id, backup_ids)
self.backup_client.delete_backup(backup_id)
waiters.wait_for_backup_delete(self.backup_client, backup_id)
backups = self.backup_client.list_backups()
backup_ids = [x['id'] for x in backups['backups']]
self.assertNotIn(backup_id, backup_ids)
@decorators.idempotent_id('2ddab860-b487-481f-b89e-9ad06d5f6286')
def test_backup_incremental(self):
name = utils.rand_name()
backup = self.backup_client.create_backup(self.instance_id, name)
parent_id = backup['backup']['id']
self._add_cleanup(parent_id)
waiters.wait_for_backup_status(self.backup_client, parent_id,
'COMPLETED')
backup = self.backup_client.create_backup(self.instance_id, name,
incremental=True,
parent=parent_id)
backup_id = backup['backup']['id']
self._add_cleanup(backup_id)
waiters.wait_for_backup_status(self.backup_client, backup_id,
'COMPLETED')
backup = self.backup_client.show_backup(backup_id)
self.assertEqual(parent_id, backup['backup']['parent_id'])

View File

@ -0,0 +1,47 @@
# 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.api.database import base
class DatabaseLimitsTest(base.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_db_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))

View File

@ -0,0 +1,41 @@
# 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.api.database import base
class DatabaseVersionsTest(base.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_db_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)