Add tests: datastore upgrade

Change-Id: Idd42169182df809232b1f2ab3bdc8e6d4289c798
This commit is contained in:
Lingxian Kong 2020-06-15 18:02:29 +12:00
parent 76a3612853
commit 6186071009
8 changed files with 183 additions and 64 deletions

View File

@ -73,4 +73,15 @@ DatabaseGroup = [
default="lvmdriver-1", default="lvmdriver-1",
help="The Cinder volume type used for creating database instance." help="The Cinder volume type used for creating database instance."
), ),
cfg.DictOpt(
'default_datastore_versions',
default={'mysql': '5.7.29'},
help='The default datastore versions used to create instance',
),
cfg.DictOpt(
'pre_upgrade_datastore_versions',
default={'mysql': '5.7.29'},
help='The datastore versions used to create instances that need to be '
'upgrade.',
),
] ]

View File

@ -198,6 +198,38 @@ class BaseTroveTest(test.BaseTestCase):
"instances", cls.instance_id)['instance'] "instances", cls.instance_id)['instance']
cls.instance_ip = cls.get_instance_ip(cls.instance) cls.instance_ip = cls.get_instance_ip(cls.instance)
def assert_single_item(self, items, **props):
return self.assert_multiple_items(items, 1, **props)[0]
def assert_multiple_items(self, items, count, **props):
"""Check if a object is in a list of objects.
e.g. props is a sub-dict, items is a list of dicts.
"""
def _matches(item, **props):
for prop_name, prop_val in props.items():
v = item[prop_name] if isinstance(
item, dict) else getattr(item, prop_name)
if v != prop_val:
return False
return True
filtered_items = list(
[item for item in items if _matches(item, **props)]
)
found = len(filtered_items)
if found != count:
LOG.info("[FAIL] items=%s, expected_props=%s", str(items), props)
self.fail("Wrong number of items found [props=%s, "
"expected=%s, found=%s]" % (props, count, found))
return filtered_items
@classmethod @classmethod
def create_instance(cls, name=None, datastore_version=None, def create_instance(cls, name=None, datastore_version=None,
database=constants.DB_NAME, username=constants.DB_USER, database=constants.DB_NAME, username=constants.DB_USER,
@ -212,16 +244,25 @@ class BaseTroveTest(test.BaseTestCase):
""" """
name = name or cls.get_resource_name("instance") name = name or cls.get_resource_name("instance")
# Get datastore version # Get datastore version. Get from API if the default ds version is not
# configured.
if not datastore_version: if not datastore_version:
res = cls.client.list_resources("datastores") default_versions = CONF.database.default_datastore_versions
for d in res['datastores']: datastore_version = default_versions.get(cls.datastore)
if d['name'] == cls.datastore:
if d.get('default_version'): if not datastore_version:
datastore_version = d['default_version'] res = cls.client.list_resources("datastores")
else: for d in res['datastores']:
datastore_version = d['versions'][0]['name'] if d['name'] == cls.datastore:
break if d.get('default_version'):
datastore_version = d['default_version']
else:
datastore_version = d['versions'][0]['name']
break
if not datastore_version:
message = ('Failed to get available datastore version.')
raise exceptions.TempestException(message)
body = { body = {
"instance": { "instance": {

View File

@ -14,20 +14,22 @@
import time import time
from oslo_log import log as logging from oslo_log import log as logging
from tempest import config
from tempest.lib import decorators from tempest.lib import decorators
from trove_tempest_plugin.tests import base as trove_base from trove_tempest_plugin.tests import base as trove_base
from trove_tempest_plugin.tests import constants
from trove_tempest_plugin.tests import utils from trove_tempest_plugin.tests import utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
CONF = config.CONF
def get_db_version(ip, username='test_user', password='password'): def get_db_version(ip, username=constants.DB_USER, password=constants.DB_PASS):
LOG.info('Trying to access the database %s', ip) LOG.info('Trying to access the database %s', ip)
db_url = f'mysql+pymysql://{username}:{password}@{ip}:3306' db_url = f'mysql+pymysql://{username}:{password}@{ip}:3306'
db_engine = utils.init_engine(db_url) db_client = utils.SQLClient(db_url)
db_client = utils.SQLClient(db_engine)
cmd = "SELECT @@GLOBAL.innodb_version;" cmd = "SELECT @@GLOBAL.innodb_version;"
ret = db_client.execute(cmd) ret = db_client.execute(cmd)
@ -35,30 +37,68 @@ def get_db_version(ip, username='test_user', password='password'):
class TestInstanceActionsBase(trove_base.BaseTroveTest): class TestInstanceActionsBase(trove_base.BaseTroveTest):
@classmethod
def init_db(cls, *args, **kwargs):
pass
def insert_data_upgrade(self, *args, **kwargs):
pass
def verify_data_upgrade(self, *args, **kwargs):
pass
@classmethod
def resource_setup(cls):
super(TestInstanceActionsBase, cls).resource_setup()
# Initialize database
cls.init_db(cls.instance_ip, constants.DB_USER, constants.DB_PASS,
constants.DB_NAME)
@decorators.idempotent_id("be6dd514-27d6-11ea-a56a-98f2b3cc23a0") @decorators.idempotent_id("be6dd514-27d6-11ea-a56a-98f2b3cc23a0")
def test_instance_upgrade(self): def test_instance_upgrade(self):
datastore = self.instance['datastore']['type'] cur_version = self.instance['datastore']['version']
version = self.instance['datastore']['version'] cfg_versions = CONF.database.pre_upgrade_datastore_versions
new_version = version ds_version = cfg_versions.get(self.datastore)
datastore = self.client.get_resource("datastores", datastore) if not ds_version:
for v in datastore['datastore']['versions']: # Fall back to the instance datastore version. In this case, we are
if v['name'] != version: # still testing the upgrade API but the datastore version doesn't
new_version = v['name'] # change actually.
break ds_version = cur_version
LOG.info('Using datastore %s for instance upgrading', new_version) name = self.get_resource_name("pre-upgrade")
LOG.info(f'Creating instance {name} with datastore version '
f'{ds_version} for upgrade')
instance = self.create_instance(name=name,
datastore_version=ds_version)
self.wait_for_instance_status(instance['id'])
instance = self.client.get_resource(
"instances", instance['id'])['instance']
instance_ip = self.get_instance_ip(instance)
body = { # Insert data before upgrading
"instance": { self.init_db(instance_ip, constants.DB_USER, constants.DB_PASS,
"datastore_version": new_version constants.DB_NAME)
} self.insert_data_upgrade(instance_ip, constants.DB_USER,
} constants.DB_PASS, constants.DB_NAME)
self.client.patch_resource('instances', self.instance_id, body)
time.sleep(3) new_version = cur_version
self.wait_for_instance_status(self.instance_id) LOG.info(f"Upgrading instance {instance['id']} using datastore "
f"{new_version}")
body = {"instance": {"datastore_version": new_version}}
self.client.patch_resource('instances', instance['id'], body)
time.sleep(3) # Wait in case the instance status hasn't changed yet.
actual = get_db_version(self.instance_ip) time.sleep(5)
self.wait_for_instance_status(instance['id'])
actual = get_db_version(instance_ip)
self.assertEqual(new_version, actual)
self.assertEqual(actual, new_version) self.verify_data_upgrade(instance_ip, constants.DB_USER,
constants.DB_PASS, constants.DB_NAME)
# Delete the new instance explicitly to avoid too many instances
# during the test.
self.wait_for_instance_status(instance['id'],
expected_status="DELETED",
need_delete=True)

View File

@ -27,8 +27,7 @@ class TestInstanceBasicMySQLBase(trove_base.BaseTroveTest):
LOG.info('Trying to access the database %s', ip) LOG.info('Trying to access the database %s', ip)
db_url = f'mysql+pymysql://{username}:{password}@{ip}:3306' db_url = f'mysql+pymysql://{username}:{password}@{ip}:3306'
db_engine = utils.init_engine(db_url) db_client = utils.SQLClient(db_url)
db_client = utils.SQLClient(db_engine)
cmd = "SELECT 1;" cmd = "SELECT 1;"
db_client.execute(cmd) db_client.execute(cmd)

View File

@ -27,16 +27,11 @@ class TestBackupMySQL(base_backup.TestBackupBase):
LOG.info(f"Inserting data to database {database} on {ip}") LOG.info(f"Inserting data to database {database} on {ip}")
db_url = f'mysql+pymysql://{username}:{password}@{ip}:3306/{database}' db_url = f'mysql+pymysql://{username}:{password}@{ip}:3306/{database}'
db_engine = utils.init_engine(db_url) db_client = utils.SQLClient(db_url)
db_client = utils.SQLClient(db_engine)
cmds = [ cmds = [
"CREATE TABLE Persons (PersonID int, LastName varchar(255), " "CREATE TABLE Persons (ID int, String varchar(255));",
"FirstName varchar(255), Address varchar(255), City " "insert into Persons VALUES (1, 'Lingxian Kong');",
"varchar(255));",
"insert into Persons VALUES (1, 'Kong', 'Lingxian', '150 Willis "
"Street', 'Wellington');"
] ]
db_client.execute(cmds) db_client.execute(cmds)
@ -46,19 +41,16 @@ class TestBackupMySQL(base_backup.TestBackupBase):
f"incremental backup") f"incremental backup")
db_url = f'mysql+pymysql://{username}:{password}@{ip}:3306/{database}' db_url = f'mysql+pymysql://{username}:{password}@{ip}:3306/{database}'
db_engine = utils.init_engine(db_url) db_client = utils.SQLClient(db_url)
db_client = utils.SQLClient(db_engine)
cmds = [ cmds = [
"insert into Persons VALUES (99, 'OpenStack', 'Trove', " "insert into Persons VALUES (99, 'OpenStack');"
"'150 Willis Street', 'Wellington');"
] ]
db_client.execute(cmds) db_client.execute(cmds)
def verify_data(self, ip, username, password, database): def verify_data(self, ip, username, password, database):
db_url = f'mysql+pymysql://{username}:{password}@{ip}:3306/{database}' db_url = f'mysql+pymysql://{username}:{password}@{ip}:3306/{database}'
db_engine = utils.init_engine(db_url) db_client = utils.SQLClient(db_url)
db_client = utils.SQLClient(db_engine)
cmd = "select * from Persons;" cmd = "select * from Persons;"
ret = db_client.execute(cmd) ret = db_client.execute(cmd)
@ -67,14 +59,12 @@ class TestBackupMySQL(base_backup.TestBackupBase):
self.assertEqual(1, len(rows)) self.assertEqual(1, len(rows))
result = dict(zip(keys, rows[0])) result = dict(zip(keys, rows[0]))
expected = {'PersonID': 1, 'LastName': 'Kong', 'FirstName': 'Lingxian', expected = {'ID': 1, 'String': 'Lingxian Kong'}
'Address': '150 Willis Street', 'City': 'Wellington'}
self.assertEqual(expected, result) self.assertEqual(expected, result)
def verify_data_inc(self, ip, username, password, database): def verify_data_inc(self, ip, username, password, database):
db_url = f'mysql+pymysql://{username}:{password}@{ip}:3306/{database}' db_url = f'mysql+pymysql://{username}:{password}@{ip}:3306/{database}'
db_engine = utils.init_engine(db_url) db_client = utils.SQLClient(db_url)
db_client = utils.SQLClient(db_engine)
cmd = "select * from Persons;" cmd = "select * from Persons;"
ret = db_client.execute(cmd) ret = db_client.execute(cmd)
@ -87,13 +77,7 @@ class TestBackupMySQL(base_backup.TestBackupBase):
actual.append(dict(zip(keys, rows[index]))) actual.append(dict(zip(keys, rows[index])))
expected = [ expected = [
{ {'ID': 1, 'String': 'Lingxian Kong'},
'PersonID': 1, 'LastName': 'Kong', 'FirstName': 'Lingxian', {'ID': 99, 'String': 'OpenStack'},
'Address': '150 Willis Street', 'City': 'Wellington'
},
{
'PersonID': 99, 'LastName': 'OpenStack', 'FirstName': 'Trove',
'Address': '150 Willis Street', 'City': 'Wellington'
},
] ]
self.assertEqual(expected, actual) self.assertEqual(expected, actual)

View File

@ -11,13 +11,57 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from oslo_log import log as logging
from trove_tempest_plugin.tests.scenario import base_actions from trove_tempest_plugin.tests.scenario import base_actions
from trove_tempest_plugin.tests import utils
LOG = logging.getLogger(__name__)
class TestInstanceActionsMySQL(base_actions.TestInstanceActionsBase): class TestInstanceActionsMySQL(base_actions.TestInstanceActionsBase):
datastore = 'mysql' datastore = 'mysql'
@classmethod
def init_db(cls, ip, username, password, database):
LOG.info(f"Initializing database {database} on {ip}")
class TestInstanceActionsMariaDB(base_actions.TestInstanceActionsBase): db_url = f'mysql+pymysql://{username}:{password}@{ip}:3306/{database}'
db_client = utils.SQLClient(db_url)
cmds = [
"CREATE TABLE Persons (ID int, String varchar(255));",
]
db_client.execute(cmds)
def insert_data_upgrade(self, ip, username, password, database):
LOG.info(f"Inserting data to database {database} on {ip} for "
f"datastore upgrade")
db_url = f'mysql+pymysql://{username}:{password}@{ip}:3306/{database}'
db_client = utils.SQLClient(db_url)
cmds = [
"insert into Persons VALUES (99, 'Upgrade');"
]
db_client.execute(cmds)
def verify_data_upgrade(self, ip, username, password, database):
db_url = f'mysql+pymysql://{username}:{password}@{ip}:3306/{database}'
db_client = utils.SQLClient(db_url)
cmd = "select * from Persons;"
ret = db_client.execute(cmd)
keys = ret.keys()
rows = ret.fetchall()
self.assertGreaterEqual(len(rows), 1)
result = []
for index in range(len(rows)):
result.append(dict(zip(keys, rows[index])))
expected = {'ID': 99, 'String': 'Upgrade'}
self.assert_single_item(result, **expected)
class TestInstanceActionsMariaDB(TestInstanceActionsMySQL):
datastore = 'mariadb' datastore = 'mariadb'

View File

@ -18,5 +18,5 @@ class TestInstanceBasicMySQL(base_basic.TestInstanceBasicMySQLBase):
datastore = 'mysql' datastore = 'mysql'
class TestInstanceBasicMariaDB(base_basic.TestInstanceBasicMySQLBase): class TestInstanceBasicMariaDB(TestInstanceBasicMySQL):
datastore = 'mariadb' datastore = 'mariadb'

View File

@ -56,8 +56,8 @@ def init_engine(db_url):
class SQLClient(object): class SQLClient(object):
def __init__(self, engine): def __init__(self, url):
self.engine = engine self.engine = init_engine(url)
def execute(self, cmds, **kwargs): def execute(self, cmds, **kwargs):
try: try: