Improve the basic test case for mysql

Change-Id: I9202d1eaae517afc181973344b14903879fac24e
This commit is contained in:
Lingxian Kong 2019-12-09 11:38:48 +13:00
parent 605a0d3a51
commit 18e399b9e0
7 changed files with 145 additions and 34 deletions

View File

@ -12,3 +12,5 @@ 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
SQLAlchemy!=1.1.5,!=1.1.6,!=1.1.7,!=1.1.8,>=1.0.10 # MIT
PyMySQL>=0.7.6 # MIT License

View File

@ -38,6 +38,10 @@ DatabaseGroup = [
'internalURL'],
help="The endpoint type to use for the Database service."
),
cfg.ListOpt(
'enabled_datastores',
default=['mysql']
),
cfg.IntOpt('database_build_timeout',
default=1800,
help='Timeout in seconds to wait for a database instance to '
@ -47,6 +51,12 @@ DatabaseGroup = [
default="d2",
help="The Nova flavor ID used for creating database instance."
),
cfg.StrOpt(
'shared_network',
default="private",
help=('Pre-defined network name or ID used for creating database '
'instance.')
),
cfg.StrOpt(
'subnet_cidr',
default='10.1.1.0/24',
@ -58,6 +68,4 @@ DatabaseGroup = [
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"),
]

View File

@ -14,6 +14,7 @@
# under the License.
from oslo_log import log as logging
from oslo_service import loopingcall
from oslo_utils import uuidutils
import tenacity
from tempest import config
@ -30,6 +31,7 @@ LOG = logging.getLogger(__name__)
class BaseTroveTest(test.BaseTestCase):
credentials = ('admin', 'primary')
datastore = None
@classmethod
def get_resource_name(cls, resource_type):
@ -43,6 +45,11 @@ class BaseTroveTest(test.BaseTestCase):
if not CONF.service_available.trove:
raise cls.skipException("Database service is not available.")
if cls.datastore not in CONF.database.enabled_datastores:
raise cls.skipException(
"Datastore %s is not enabled." % cls.datastore
)
@classmethod
def setup_clients(cls):
super(BaseTroveTest, cls).setup_clients()
@ -103,6 +110,22 @@ class BaseTroveTest(test.BaseTestCase):
subnets_client = cls.os_primary.subnets_client
routers_client = cls.os_primary.routers_client
if CONF.database.shared_network:
private_network = CONF.database.shared_network
if not uuidutils.is_uuid_like(private_network):
networks = networks_client.list_networks()['networks']
for net in networks:
if net['name'] == private_network:
private_network = net['id']
break
else:
raise exceptions.NotFound(
'Shared network %s not found' % private_network
)
cls.private_network = private_network
return
network_kwargs = {"name": cls.get_resource_name("network")}
result = networks_client.create_network(**network_kwargs)
LOG.info('Private network created: %s', result['network'])
@ -163,6 +186,9 @@ class BaseTroveTest(test.BaseTestCase):
# network ID.
cls._create_network()
cls.instance_id = cls.create_instance()
cls.wait_for_instance_status(cls.instance_id)
@classmethod
def create_instance(cls, database="test_db", username="test_user",
password="password"):
@ -175,6 +201,17 @@ class BaseTroveTest(test.BaseTestCase):
all test methods within a TestCase are assumed to be executed serially.
"""
name = cls.get_resource_name("instance")
# Get datastore version
res = cls.client.list_resources("datastores")
for d in res['datastores']:
if d['name'] == cls.datastore:
if d.get('default_version'):
datastore_version = d['default_version']
else:
datastore_version = d['versions'][0]['name']
break
body = {
"instance": {
"name": name,
@ -195,8 +232,8 @@ class BaseTroveTest(test.BaseTestCase):
}
],
"datastore": {
"type": CONF.database.datastore_type,
"version": CONF.database.datastore_version
"type": cls.datastore,
"version": datastore_version
},
"nics": [
{
@ -220,10 +257,12 @@ class BaseTroveTest(test.BaseTestCase):
res = cls.client.get_resource("instances", id)
except exceptions.NotFound:
if need_delete or status == "DELETED":
LOG.info('Instance %s is deleted', id)
raise loopingcall.LoopingCallDone()
return
if res["instance"]["status"] == status:
LOG.info('Instance %s becomes %s', id, status)
raise loopingcall.LoopingCallDone()
elif status != "ERROR" and res["instance"]["status"] == "ERROR":
# If instance status goes to ERROR but is not expected, stop

View File

@ -0,0 +1,53 @@
# 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 oslo_utils import netutils
from tempest.lib import decorators
from trove_tempest_plugin.tests import base
from trove_tempest_plugin.tests import utils
LOG = logging.getLogger(__name__)
class TestMySQLInstanceBasic(base.BaseTroveTest):
datastore = 'mysql'
def _access_db(self, ip, username='test_user', password='password'):
db_engine = utils.LocalSqlClient.init_engine(ip, username, password)
db_client = utils.LocalSqlClient(db_engine)
LOG.info('Trying to access the database %s', ip)
with db_client:
cmd = "SELECT 1;"
db_client.execute(cmd)
@decorators.idempotent_id("40cf38ce-cfbf-11e9-8760-1458d058cfb2")
def test_database_access(self):
res = self.client.get_resource("instances", self.instance_id)
ips = res["instance"].get('ip', [])
# TODO(lxkong): IPv6 needs to be supported.
v4_ip = None
for ip in ips:
if netutils.is_valid_ipv4(ip):
v4_ip = ip
break
self.assertIsNotNone(v4_ip)
time.sleep(5)
self._access_db(v4_ip)

View File

@ -1,29 +0,0 @@
# 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

View File

@ -14,7 +14,7 @@
import time
from oslo_log import log as logging
import sqlalchemy
from tempest.lib import exceptions
LOG = logging.getLogger(__name__)
@ -49,3 +49,41 @@ def wait_for_removal(delete_func, show_func, *args, **kwargs):
(show_func.__name__, check_timeout))
raise exceptions.TimeoutException(message)
time.sleep(3)
class LocalSqlClient(object):
"""A sqlalchemy wrapper to manage transactions."""
def __init__(self, engine):
self.engine = engine
def __enter__(self):
self.conn = self.engine.connect()
self.trans = self.conn.begin()
return self.conn
def __exit__(self, type, value, traceback):
if self.trans:
if type is not None:
self.trans.rollback()
else:
self.trans.commit()
self.conn.close()
def execute(self, t, **kwargs):
try:
return self.conn.execute(t, kwargs)
except Exception as e:
self.trans.rollback()
self.trans = None
raise exceptions.TempestException(
'Failed to execute database command %s, error: %s' %
(t, str(e))
)
@staticmethod
def init_engine(host, user, password):
return sqlalchemy.create_engine(
"mysql+pymysql://%s:%s@%s:3306" % (user, password, host),
pool_recycle=1800
)