Fix mariadb CI - trove-scenario-mariadb-single

- Trove supports MariaDB 10.4
- From MariaDB 10.3, Mariabackup is used instead of Percona XtraBackup
  for backup functionality
- Some log improvements

Change-Id: Ibaa6fd7273b98451097b32fb6b881008a236be9f
(cherry picked from commit 11b0b8d6f2)
This commit is contained in:
Lingxian Kong 2019-11-28 10:27:35 +13:00
parent 83ab791001
commit 2afa3b039b
19 changed files with 223 additions and 126 deletions

View File

@ -18,13 +18,14 @@ curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup |
wget https://repo.percona.com/apt/percona-release_latest.$(lsb_release -sc)_all.deb wget https://repo.percona.com/apt/percona-release_latest.$(lsb_release -sc)_all.deb
dpkg -i percona-release_latest.$(lsb_release -sc)_all.deb dpkg -i percona-release_latest.$(lsb_release -sc)_all.deb
apt-get update apt-get install -y -qq apt-transport-https ca-certificates
apt-get update -qq
# Disable password prompt # Disable password prompt
debconf-set-selections <<< "mariadb-server mysql-server/root_password password ''" debconf-set-selections <<< "mariadb-server mysql-server/root_password password ''"
debconf-set-selections <<< "mariadb-server mysql-server/root_password_again password ''" debconf-set-selections <<< "mariadb-server mysql-server/root_password_again password ''"
apt-get install -y --allow-unauthenticated mariadb-server mariadb-client galera-4 libmariadb3 mariadb-backup mariadb-common percona-xtrabackup-24 apt-get install -y -qq --allow-unauthenticated mariadb-server mariadb-client galera-4 libmariadb3 mariadb-backup mariadb-common
cat <<EOF >/etc/mysql/conf.d/no_perf_schema.cnf cat <<EOF >/etc/mysql/conf.d/no_perf_schema.cnf
[mysqld] [mysqld]

View File

@ -563,7 +563,7 @@ function cmd_set_datastore() {
VERSION="5.6" VERSION="5.6"
elif [ "$DATASTORE_TYPE" == "mariadb" ]; then elif [ "$DATASTORE_TYPE" == "mariadb" ]; then
PACKAGES=${PACKAGES:-"mariadb-server"} PACKAGES=${PACKAGES:-"mariadb-server"}
VERSION="10.1" VERSION="10.4"
elif [ "$DATASTORE_TYPE" == "mongodb" ]; then elif [ "$DATASTORE_TYPE" == "mongodb" ]; then
PACKAGES=${PACKAGES:-"mongodb-org"} PACKAGES=${PACKAGES:-"mongodb-org"}
VERSION="3.2" VERSION="3.2"

View File

@ -1430,7 +1430,13 @@ mariadb_opts = [
help='List of UDP ports and/or port ranges to open ' help='List of UDP ports and/or port ranges to open '
'in the security group (only applicable ' 'in the security group (only applicable '
'if trove_security_groups_support is True).'), 'if trove_security_groups_support is True).'),
cfg.StrOpt('backup_strategy', default='MariaDBInnoBackupEx', cfg.StrOpt('backup_namespace',
default='trove.guestagent.strategies.backup.experimental'
'.mariadb_impl',
help='Namespace to load backup strategies from.',
deprecated_name='backup_namespace',
deprecated_group='DEFAULT'),
cfg.StrOpt('backup_strategy', default='MariaBackup',
help='Default strategy to perform backups.', help='Default strategy to perform backups.',
deprecated_name='backup_strategy', deprecated_name='backup_strategy',
deprecated_group='DEFAULT'), deprecated_group='DEFAULT'),
@ -1451,12 +1457,6 @@ mariadb_opts = [
cfg.IntOpt('usage_timeout', default=400, cfg.IntOpt('usage_timeout', default=400,
help='Maximum time (in seconds) to wait for a Guest to become ' help='Maximum time (in seconds) to wait for a Guest to become '
'active.'), 'active.'),
cfg.StrOpt('backup_namespace',
default='trove.guestagent.strategies.backup.experimental'
'.mariadb_impl',
help='Namespace to load backup strategies from.',
deprecated_name='backup_namespace',
deprecated_group='DEFAULT'),
cfg.StrOpt('restore_namespace', cfg.StrOpt('restore_namespace',
default='trove.guestagent.strategies.restore.experimental' default='trove.guestagent.strategies.restore.experimental'
'.mariadb_impl', '.mariadb_impl',
@ -1468,8 +1468,8 @@ mariadb_opts = [
cfg.StrOpt('device_path', default='/dev/vdb', cfg.StrOpt('device_path', default='/dev/vdb',
help='Device path for volume if volume support is enabled.'), help='Device path for volume if volume support is enabled.'),
cfg.DictOpt('backup_incremental_strategy', cfg.DictOpt('backup_incremental_strategy',
default={'MariaDBInnoBackupEx': default={'MariaBackup':
'MariaDBInnoBackupExIncremental'}, 'MariaBackupIncremental'},
help='Incremental Backup Runner based on the default ' help='Incremental Backup Runner based on the default '
'strategy. For strategies that do not implement an ' 'strategy. For strategies that do not implement an '
'incremental backup, the runner will use the default full ' 'incremental backup, the runner will use the default full '

View File

@ -22,5 +22,4 @@ LOG = logging.getLogger(__name__)
def get_storage_strategy(storage_driver, ns=__name__): def get_storage_strategy(storage_driver, ns=__name__):
LOG.debug("Getting storage strategy: %s.", storage_driver)
return Strategy.get_strategy(storage_driver, ns) return Strategy.get_strategy(storage_driver, ns)

View File

@ -123,14 +123,13 @@ class BackupAgent(object):
sent=timeutils.utcnow_ts( sent=timeutils.utcnow_ts(
microsecond=True), microsecond=True),
**backup_state) **backup_state)
LOG.debug("Updated state for %s to %s.", LOG.info("Updated state for %s to %s.", backup_id, backup_state)
backup_id, backup_state)
def execute_backup(self, context, backup_info, def execute_backup(self, context, backup_info,
runner=RUNNER, extra_opts=EXTRA_OPTS, runner=RUNNER, extra_opts=EXTRA_OPTS,
incremental_runner=INCREMENTAL_RUNNER): incremental_runner=INCREMENTAL_RUNNER):
LOG.debug("Running backup %(id)s.", backup_info) LOG.info("Running backup %(id)s.", backup_info)
storage = get_storage_strategy( storage = get_storage_strategy(
CONF.storage_strategy, CONF.storage_strategy,
CONF.storage_namespace)(context) CONF.storage_namespace)(context)
@ -154,12 +153,8 @@ class BackupAgent(object):
parent_metadata, extra_opts) parent_metadata, extra_opts)
def execute_restore(self, context, backup_info, restore_location): def execute_restore(self, context, backup_info, restore_location):
try: try:
LOG.debug("Getting Restore Runner %(type)s.", backup_info)
restore_runner = self._get_restore_runner(backup_info['type']) restore_runner = self._get_restore_runner(backup_info['type'])
LOG.debug("Getting Storage Strategy.")
storage = get_storage_strategy( storage = get_storage_strategy(
CONF.storage_strategy, CONF.storage_strategy,
CONF.storage_namespace)(context) CONF.storage_namespace)(context)
@ -168,16 +163,15 @@ class BackupAgent(object):
checksum=backup_info['checksum'], checksum=backup_info['checksum'],
restore_location=restore_location) restore_location=restore_location)
backup_info['restore_location'] = restore_location backup_info['restore_location'] = restore_location
LOG.debug("Restoring instance from backup %(id)s to "
"%(restore_location)s.", backup_info) LOG.info("Restoring instance from backup %(id)s to "
"%(restore_location)s", backup_info)
content_size = runner.restore() content_size = runner.restore()
LOG.debug("Restore from backup %(id)s completed successfully " LOG.info("Restore from backup %(id)s completed successfully "
"to %(restore_location)s.", backup_info) "to %(restore_location)s", backup_info)
LOG.debug("Restore size: %s.", content_size) LOG.debug("Restore size: %s", content_size)
except Exception: except Exception:
LOG.exception("Error restoring backup %(id)s.", backup_info) LOG.exception("Error restoring backup %(id)s", backup_info)
raise raise
else: else:
LOG.debug("Restored backup %(id)s.", backup_info) LOG.debug("Restored backup %(id)s", backup_info)

View File

@ -111,8 +111,8 @@ class Manager(periodic_task.PeriodicTasks):
try: try:
return repl_strategy.get_instance(self.manager) return repl_strategy.get_instance(self.manager)
except Exception as ex: except Exception as ex:
LOG.debug("Cannot get replication instance for '%(manager)s': " LOG.warning("Cannot get replication instance for '%(manager)s': "
"%(msg)s", {'manager': self.manager, 'msg': str(ex)}) "%(msg)s", {'manager': self.manager, 'msg': str(ex)})
return None return None
@ -315,6 +315,7 @@ class Manager(periodic_task.PeriodicTasks):
except Exception as ex: except Exception as ex:
LOG.exception("An error occurred applying modules: " LOG.exception("An error occurred applying modules: "
"%s", str(ex)) "%s", str(ex))
# The following block performs single-instance initialization. # The following block performs single-instance initialization.
# Failures will be recorded, but won't stop the provisioning # Failures will be recorded, but won't stop the provisioning
# or change the instance state. # or change the instance state.

View File

@ -184,7 +184,8 @@ class MySqlManager(manager.Manager):
return self.mysql_admin().disable_root() return self.mysql_admin().disable_root()
def _perform_restore(self, backup_info, context, restore_location, app): def _perform_restore(self, backup_info, context, restore_location, app):
LOG.info("Restoring database from backup %s.", backup_info['id']) LOG.info("Restoring database from backup %s, backup_info: %s",
backup_info['id'], backup_info)
try: try:
backup.restore(context, backup_info, restore_location) backup.restore(context, backup_info, restore_location)
except Exception: except Exception:
@ -202,9 +203,12 @@ class MySqlManager(manager.Manager):
app = self.mysql_app(self.mysql_app_status.get()) app = self.mysql_app(self.mysql_app_status.get())
app.install_if_needed(packages) app.install_if_needed(packages)
if device_path: if device_path:
# stop and do not update database LOG.info('Prepare the storage for %s', device_path)
app.stop_db( app.stop_db(
do_not_start_on_reboot=self.volume_do_not_start_on_reboot) do_not_start_on_reboot=self.volume_do_not_start_on_reboot
)
device = volume.VolumeDevice(device_path) device = volume.VolumeDevice(device_path)
# unmount if device is already mounted # unmount if device is already mounted
device.unmount_device(device_path) device.unmount_device(device_path)
@ -219,13 +223,15 @@ class MySqlManager(manager.Manager):
service.MYSQL_OWNER, service.MYSQL_OWNER,
recursive=False, as_root=True) recursive=False, as_root=True)
LOG.debug("Mounted the volume at %s.", mount_point) LOG.debug("Mounted the volume at %s", mount_point)
# We need to temporarily update the default my.cnf so that # We need to temporarily update the default my.cnf so that
# mysql will start after the volume is mounted. Later on it # mysql will start after the volume is mounted. Later on it
# will be changed based on the config template # will be changed based on the config template
# (see MySqlApp.secure()) and restart. # (see MySqlApp.secure()) and restart.
app.set_data_dir(mount_point + '/data') app.set_data_dir(mount_point + '/data')
app.start_mysql() app.start_mysql()
LOG.info('Finish to prepare the storage for %s', device_path)
if backup_info: if backup_info:
self._perform_restore(backup_info, context, self._perform_restore(backup_info, context,
mount_point + "/data", app) mount_point + "/data", app)
@ -337,7 +343,8 @@ class MySqlManager(manager.Manager):
def get_replication_snapshot(self, context, snapshot_info, def get_replication_snapshot(self, context, snapshot_info,
replica_source_config=None): replica_source_config=None):
LOG.debug("Getting replication snapshot.") LOG.info("Getting replication snapshot, snapshot_info: %s",
snapshot_info)
app = self.mysql_app(self.mysql_app_status.get()) app = self.mysql_app(self.mysql_app_status.get())
self.replication.enable_as_master(app, replica_source_config) self.replication.enable_as_master(app, replica_source_config)

View File

@ -95,20 +95,28 @@ def clear_expired_password():
out, err = utils.execute("cat", secret_file, out, err = utils.execute("cat", secret_file,
run_as_root=True, root_helper="sudo") run_as_root=True, root_helper="sudo")
except exception.ProcessExecutionError: except exception.ProcessExecutionError:
LOG.exception("/root/.mysql_secret does not exist.") LOG.warning("/root/.mysql_secret does not exist.")
return else:
m = re.match('# The random password set for the root user at .*: (.*)', m = re.match('# The random password set for the root user at .*: (.*)',
out) out)
if m: if m:
try: try:
out, err = utils.execute("mysqladmin", "-p%s" % m.group(1), out, err = utils.execute("mysqladmin", "-p%s" % m.group(1),
"password", "", run_as_root=True, "password", "", run_as_root=True,
root_helper="sudo") root_helper="sudo")
except exception.ProcessExecutionError: except exception.ProcessExecutionError:
LOG.exception("Cannot change mysql password.") LOG.exception("Cannot change mysql password.")
return return
operating_system.remove(secret_file, force=True, as_root=True) operating_system.remove(secret_file, force=True, as_root=True)
LOG.debug("Expired password removed.") LOG.debug("Expired password removed.")
# The root user password will be changed in app.secure_root() later on
LOG.debug('Initializae the root password to empty')
try:
utils.execute("mysqladmin", "--user=root", "password", "",
run_as_root=True, root_helper="sudo")
except Exception:
LOG.exception("Failed to initializae the root password")
def load_mysqld_options(): def load_mysqld_options():
@ -145,17 +153,19 @@ class BaseMySqlAppStatus(service.BaseDbStatus):
def _get_actual_db_status(self): def _get_actual_db_status(self):
try: try:
out, err = utils.execute_with_timeout( utils.execute_with_timeout(
"/usr/bin/mysqladmin", "/usr/bin/mysqladmin",
"ping", run_as_root=True, root_helper="sudo", "ping", run_as_root=True, root_helper="sudo",
log_output_on_error=True) log_output_on_error=True)
LOG.info("MySQL Service Status is RUNNING.") LOG.debug("MySQL Service Status is RUNNING.")
return rd_instance.ServiceStatuses.RUNNING return rd_instance.ServiceStatuses.RUNNING
except exception.ProcessExecutionError: except exception.ProcessExecutionError:
LOG.exception("Failed to get database status.") LOG.warning("Failed to get database status.")
try: try:
out, err = utils.execute_with_timeout("/bin/ps", "-C", out, _ = utils.execute_with_timeout(
"mysqld", "h") "/bin/ps", "-C", "mysqld", "h",
log_output_on_error=True
)
pid = out.split()[0] pid = out.split()[0]
# TODO(rnirmal): Need to create new statuses for instances # TODO(rnirmal): Need to create new statuses for instances
# where the mysql service is up, but unresponsive # where the mysql service is up, but unresponsive
@ -163,7 +173,7 @@ class BaseMySqlAppStatus(service.BaseDbStatus):
{'pid': pid}) {'pid': pid})
return rd_instance.ServiceStatuses.BLOCKED return rd_instance.ServiceStatuses.BLOCKED
except exception.ProcessExecutionError: except exception.ProcessExecutionError:
LOG.exception("Process execution failed.") LOG.warning("Process execution failed.")
mysql_args = load_mysqld_options() mysql_args = load_mysqld_options()
pid_file = mysql_args.get('pid_file', pid_file = mysql_args.get('pid_file',
['/var/run/mysqld/mysqld.pid'])[0] ['/var/run/mysqld/mysqld.pid'])[0]
@ -298,6 +308,7 @@ class BaseMySqlAdmin(object):
mydb.character_set, mydb.character_set,
mydb.collate) mydb.collate)
t = text(str(cd)) t = text(str(cd))
LOG.debug('Creating database, command: %s', str(cd))
client.execute(t) client.execute(t)
def create_user(self, users): def create_user(self, users):
@ -319,6 +330,7 @@ class BaseMySqlAdmin(object):
g = sql_query.Grant(permissions='ALL', database=mydb.name, g = sql_query.Grant(permissions='ALL', database=mydb.name,
user=user.name, host=user.host) user=user.name, host=user.host)
t = text(str(g)) t = text(str(g))
LOG.debug('Creating user, command: %s', str(g))
client.execute(t) client.execute(t)
def delete_database(self, database): def delete_database(self, database):
@ -569,11 +581,12 @@ class BaseKeepAliveConnection(interfaces.PoolListener):
else: else:
raise raise
# MariaDB seems to timeout the client in a different # MariaDB seems to timeout the client in a different
# way than MySQL and PXC, which manifests itself as # way than MySQL and PXC
# an invalid packet sequence. Handle it as well.
except pymysql_err.InternalError as ex: except pymysql_err.InternalError as ex:
if "Packet sequence number wrong" in str(ex): if "Packet sequence number wrong" in str(ex):
raise exc.DisconnectionError() raise exc.DisconnectionError()
elif 'Connection was killed' in str(ex):
raise exc.DisconnectionError()
else: else:
raise raise
@ -717,6 +730,7 @@ class BaseMySqlApp(object):
def secure(self, config_contents): def secure(self, config_contents):
LOG.debug("Securing MySQL now.") LOG.debug("Securing MySQL now.")
clear_expired_password() clear_expired_password()
LOG.debug("Generating admin password.") LOG.debug("Generating admin password.")
admin_password = utils.generate_random_password() admin_password = utils.generate_random_password()
engine = sqlalchemy.create_engine( engine = sqlalchemy.create_engine(

View File

@ -22,5 +22,4 @@ LOG = logging.getLogger(__name__)
def get_backup_strategy(backup_driver, ns=__name__): def get_backup_strategy(backup_driver, ns=__name__):
LOG.debug("Getting backup strategy: %s.", backup_driver)
return Strategy.get_strategy(backup_driver, ns) return Strategy.get_strategy(backup_driver, ns)

View File

@ -1,28 +1,70 @@
# Copyright 2016 Tesora Inc. # Copyright 2019 Catalyst Cloud Ltd.
# All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License");
# not use this file except in compliance with the License. You may obtain # you may not use this file except in compliance with the License.
# a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # distributed under the License is distributed on an "AS IS" BASIS,
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# License for the specific language governing permissions and limitations # See the License for the specific language governing permissions and
# under the License. # limitations under the License.
import re
from trove.guestagent.datastore.experimental.mariadb.service import MariaDBApp from oslo_log import log as logging
from trove.guestagent.datastore.mysql.service import MySqlAppStatus
from trove.guestagent.strategies.backup import mysql_impl from trove.guestagent.datastore.mysql import service as mysql_service
from trove.guestagent.datastore.mysql_common import service as common_service
from trove.guestagent.strategies.backup import base
LOG = logging.getLogger(__name__)
BACKUP_LOG = '/tmp/mariabackup.log'
class MariaDBInnoBackupEx(mysql_impl.InnoBackupEx): class MariaBackup(base.BackupRunner):
"""Implementation of Backup Strategy for mariabackup."""
__strategy_name__ = 'mariabackup'
def _build_app(self): @property
return MariaDBApp(MySqlAppStatus.get()) def user_and_pass(self):
return (' --user=%(user)s --password=%(password)s --host=127.0.0.1 ' %
{'user': common_service.ADMIN_USER_NAME,
'password': mysql_service.MySqlApp.get_auth_password()})
@property
def cmd(self):
cmd = ('sudo mariabackup --backup --stream=xbstream' +
self.user_and_pass + ' 2>' + BACKUP_LOG)
return cmd + self.zip_cmd + self.encrypt_cmd
def check_process(self):
"""Check the output of mariabackup command for 'completed OK!'.
Return True if no error, otherwise return False.
"""
LOG.debug('Checking mariabackup process output.')
with open(BACKUP_LOG, 'r') as backup_log:
output = backup_log.read()
if not output:
LOG.error("mariabackup log file empty.")
return False
LOG.debug(output)
last_line = output.splitlines()[-1].strip()
if not re.search('completed OK!', last_line):
LOG.error("mariabackup command failed.")
return False
return True
@property
def filename(self):
return '%s.xbstream' % self.base_filename
class MariaDBInnoBackupExIncremental(MariaDBInnoBackupEx): class MariaBackupIncremental(MariaBackup):
pass pass

View File

@ -17,16 +17,10 @@
from oslo_log import log as logging from oslo_log import log as logging
from trove.common import cfg from trove.common import cfg
from trove.guestagent.backup.backupagent import BackupAgent
from trove.guestagent.strategies import backup from trove.guestagent.strategies import backup
from trove.guestagent.strategies.replication import mysql_base from trove.guestagent.strategies.replication import mysql_base
AGENT = BackupAgent()
CONF = cfg.CONF CONF = cfg.CONF
REPL_BACKUP_NAMESPACE = 'trove.guestagent.strategies.backup' \
'.experimental.mariadb_impl'
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -35,17 +29,27 @@ class MariaDBGTIDReplication(mysql_base.MysqlReplicationBase):
@property @property
def repl_backup_runner(self): def repl_backup_runner(self):
return backup.get_backup_strategy('MariaDBInnoBackupEx', return backup.get_backup_strategy(
REPL_BACKUP_NAMESPACE) CONF.mariadb.backup_strategy,
CONF.mariadb.backup_namespace
)
@property @property
def repl_incr_backup_runner(self): def repl_incr_backup_runner(self):
return backup.get_backup_strategy('MariaDBInnoBackupExIncremental', strategy = CONF.mariadb.backup_incremental_strategy.get(
REPL_BACKUP_NAMESPACE) CONF.mariadb.backup_strategy, CONF.mariadb.backup_strategy
)
return backup.get_backup_strategy(
strategy,
CONF.mariadb.backup_namespace
)
@property @property
def repl_backup_extra_opts(self): def repl_backup_extra_opts(self):
return CONF.backup_runner_options.get('MariaDBInnoBackupEx', '') return CONF.backup_runner_options.get(
CONF.mariadb.backup_strategy, ''
)
def connect_to_master(self, service, snapshot): def connect_to_master(self, service, snapshot):
logging_config = snapshot['log_position'] logging_config = snapshot['log_position']

View File

@ -107,6 +107,7 @@ class MysqlReplicationBase(base.Replication):
incremental_runner=self.repl_incr_backup_runner) incremental_runner=self.repl_incr_backup_runner)
else: else:
LOG.debug("Using existing backup created for previous replica.") LOG.debug("Using existing backup created for previous replica.")
LOG.debug("Replication snapshot %(snapshot_id)s used for replica " LOG.debug("Replication snapshot %(snapshot_id)s used for replica "
"number %(replica_number)d.", "number %(replica_number)d.",
{'snapshot_id': snapshot_id, {'snapshot_id': snapshot_id,

View File

@ -1,28 +1,59 @@
# Copyright 2016 Tesora Inc. # Copyright 2019 Catalyst Cloud Ltd.
# All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License");
# not use this file except in compliance with the License. You may obtain # you may not use this file except in compliance with the License.
# a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # distributed under the License is distributed on an "AS IS" BASIS,
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# License for the specific language governing permissions and limitations # See the License for the specific language governing permissions and
# under the License. # limitations under the License.
from trove.guestagent.datastore.experimental.mariadb.service import MariaDBApp from oslo_log import log as logging
from trove.guestagent.datastore.mysql.service import MySqlAppStatus
from trove.guestagent.common import operating_system
from trove.guestagent.datastore.experimental.mariadb import service
from trove.guestagent.datastore.mysql_common import service as mysql_service
from trove.guestagent.strategies.restore import mysql_impl from trove.guestagent.strategies.restore import mysql_impl
LOG = logging.getLogger(__name__)
class MariaDBInnoBackupEx(mysql_impl.InnoBackupEx):
def _build_app(self):
return MariaDBApp(MySqlAppStatus.get())
class MariaDBInnoBackupExIncremental(MariaDBInnoBackupEx): class MariaBackup(mysql_impl.InnoBackupEx):
__strategy_name__ = 'mariabackup'
base_restore_cmd = ('sudo mbstream -x -C %(restore_location)s '
'2>/tmp/xbstream_extract.log')
@property
def app(self):
if self._app is None:
self._app = service.MariaDBApp(
mysql_service.BaseMySqlAppStatus.get()
)
return self._app
def post_restore(self):
operating_system.chown(self.restore_location, 'mysql', None,
force=True, as_root=True)
# When using Mariabackup from versions prior to MariaDB 10.2.10, you
# would also have to remove any pre-existing InnoDB redo log files.
self._delete_old_binlogs()
self.app.start_mysql()
LOG.debug("Finished post restore.")
def check_process(self):
LOG.debug('Checking return code of mbstream restore process.')
return_code = self.process.wait()
if return_code != 0:
LOG.error('mbstream exited with %s', return_code)
return False
return True
class MariaBackupIncremental(MariaBackup):
pass pass

View File

@ -57,7 +57,7 @@ class MySQLRestoreMixin(object):
def mysql_is_not_running(self): def mysql_is_not_running(self):
try: try:
utils.execute_with_timeout("/usr/bin/pgrep", "mysqld") utils.execute_with_timeout("/usr/bin/pgrep", "mysqld")
LOG.info("MySQL is still running.") LOG.debug("MySQL is still running.")
return False return False
except exception.ProcessExecutionError: except exception.ProcessExecutionError:
LOG.debug("MySQL is not running.") LOG.debug("MySQL is not running.")
@ -218,7 +218,7 @@ class InnoBackupEx(base.RestoreRunner, MySQLRestoreMixin):
utils.clean_out(self.restore_location) utils.clean_out(self.restore_location)
def _run_prepare(self): def _run_prepare(self):
LOG.debug("Running innobackupex prepare: %s.", self.prepare_cmd) LOG.info("Running innobackupex prepare: %s.", self.prepare_cmd)
self.prep_retcode = utils.execute(self.prepare_cmd, shell=True) self.prep_retcode = utils.execute(self.prepare_cmd, shell=True)
LOG.info("Innobackupex prepare finished successfully.") LOG.info("Innobackupex prepare finished successfully.")
@ -247,7 +247,7 @@ class InnoBackupEx(base.RestoreRunner, MySQLRestoreMixin):
LOG.debug('Checking return code of xbstream restore process.') LOG.debug('Checking return code of xbstream restore process.')
return_code = self.process.wait() return_code = self.process.wait()
if return_code != 0: if return_code != 0:
LOG.erro('xbstream exited with %s', return_code) LOG.error('xbstream exited with %s', return_code)
return False return False
LOG.debug('Checking xbstream restore process stderr output.') LOG.debug('Checking xbstream restore process stderr output.')

View File

@ -330,8 +330,8 @@ class Manager(periodic_task.PeriodicTasks):
master_instance_tasks = BuiltInstanceTasks.load(context, slave_of_id) master_instance_tasks = BuiltInstanceTasks.load(context, slave_of_id)
server_group = master_instance_tasks.server_group server_group = master_instance_tasks.server_group
scheduler_hints = srv_grp.ServerGroup.convert_to_hint(server_group) scheduler_hints = srv_grp.ServerGroup.convert_to_hint(server_group)
LOG.info("Using scheduler hints %s for creating instance %s", LOG.debug("Using scheduler hints %s for creating instance %s",
scheduler_hints, instance_id) scheduler_hints, instance_id)
try: try:
for replica_index in range(0, len(ids)): for replica_index in range(0, len(ids)):
@ -344,14 +344,17 @@ class Manager(periodic_task.PeriodicTasks):
snapshot = instance_tasks.get_replication_master_snapshot( snapshot = instance_tasks.get_replication_master_snapshot(
context, slave_of_id, flavor, replica_backup_id, context, slave_of_id, flavor, replica_backup_id,
replica_number=replica_number) replica_number=replica_number)
replica_backup_id = snapshot['dataset']['snapshot_id'] replica_backup_id = snapshot['dataset']['snapshot_id']
replica_backup_created = (replica_backup_id is not None) replica_backup_created = (replica_backup_id is not None)
instance_tasks.create_instance( instance_tasks.create_instance(
flavor, image_id, databases, users, datastore_manager, flavor, image_id, databases, users, datastore_manager,
packages, volume_size, replica_backup_id, packages, volume_size, replica_backup_id,
availability_zone, root_passwords[replica_index], availability_zone, root_passwords[replica_index],
nics, overrides, None, snapshot, volume_type, nics, overrides, None, snapshot, volume_type,
modules, scheduler_hints) modules, scheduler_hints)
replicas.append(instance_tasks) replicas.append(instance_tasks)
except Exception: except Exception:
# if it's the first replica, then we shouldn't continue # if it's the first replica, then we shouldn't continue
@ -390,8 +393,8 @@ class Manager(periodic_task.PeriodicTasks):
scheduler_hints = srv_grp.ServerGroup.build_scheduler_hint( scheduler_hints = srv_grp.ServerGroup.build_scheduler_hint(
context, locality, instance_id context, locality, instance_id
) )
LOG.info("Using scheduler hints %s for creating instance %s", LOG.debug("Using scheduler hints %s for creating instance %s",
scheduler_hints, instance_id) scheduler_hints, instance_id)
instance_tasks = FreshInstanceTasks.load(context, instance_id) instance_tasks = FreshInstanceTasks.load(context, instance_id)
instance_tasks.create_instance( instance_tasks.create_instance(

View File

@ -424,7 +424,8 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
error_message = '' error_message = ''
error_details = '' error_details = ''
try: try:
LOG.info("Waiting for instance %s up and running", self.id) LOG.info("Waiting for instance %s up and running with "
"timeout %ss", self.id, timeout)
utils.poll_until(self._service_is_active, utils.poll_until(self._service_is_active,
sleep_time=CONF.usage_sleep_time, sleep_time=CONF.usage_sleep_time,
time_out=timeout) time_out=timeout)
@ -621,7 +622,8 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
if backup: if backup:
backup_id = backup.id backup_id = backup.id
else: else:
LOG.debug('Skipping replication backup, as none is required.') LOG.debug('Will skip replication master backup')
snapshot_info = { snapshot_info = {
'name': "Replication snapshot for %s" % self.id, 'name': "Replication snapshot for %s" % self.id,
'description': "Backup image used to initialize " 'description': "Backup image used to initialize "
@ -1094,7 +1096,7 @@ class BuiltInstanceTasks(BuiltInstance, NotifyMixin, ConfigurationMixin):
self.guest.create_backup(backup_info) self.guest.create_backup(backup_info)
def backup_required_for_replication(self): def backup_required_for_replication(self):
LOG.debug("Seeing if replication backup is required for instance %s.", LOG.debug("Check if replication backup is required for instance %s.",
self.id) self.id)
return self.guest.backup_required_for_replication() return self.guest.backup_required_for_replication()
@ -1106,7 +1108,9 @@ class BuiltInstanceTasks(BuiltInstance, NotifyMixin, ConfigurationMixin):
rep_source_config = self._render_replica_source_config(flavor) rep_source_config = self._render_replica_source_config(flavor)
result = self.guest.get_replication_snapshot( result = self.guest.get_replication_snapshot(
snapshot_info, rep_source_config.config_contents) snapshot_info, rep_source_config.config_contents)
LOG.debug("Got replication snapshot from guest successfully.")
LOG.info("Finnished getting replication snapshot for "
"instance %s", self.id)
return result return result
except Exception: except Exception:
LOG.exception("Failed to get replication snapshot from %s.", LOG.exception("Failed to get replication snapshot from %s.",

View File

@ -6,8 +6,6 @@ socket = /var/run/mysqld/mysqld.sock
nice = 0 nice = 0
[mysqld] [mysqld]
ignore_builtin_innodb
plugin_load=innodb=ha_innodb.so
user = mysql user = mysql
port = 3306 port = 3306
basedir = /usr basedir = /usr

View File

@ -318,7 +318,8 @@ register(
register( register(
["mariadb_supported"], ["mariadb_supported"],
single=[common_groups, single=[common_groups,
backup_incremental_groups, backup_groups,
# backup_incremental_groups,
configuration_groups, configuration_groups,
database_actions_groups, database_actions_groups,
root_actions_groups, root_actions_groups,

View File

@ -158,7 +158,7 @@ class DbaasTest(trove_testtools.TestCase):
with patch.object(mysql_common_service.utils, 'execute', with patch.object(mysql_common_service.utils, 'execute',
return_value=(secret_content, None)): return_value=(secret_content, None)):
mysql_common_service.clear_expired_password() mysql_common_service.clear_expired_password()
self.assertEqual(2, mysql_common_service.utils.execute.call_count) self.assertEqual(3, mysql_common_service.utils.execute.call_count)
self.assertEqual(1, mock_remove.call_count) self.assertEqual(1, mock_remove.call_count)
@patch.object(operating_system, 'remove') @patch.object(operating_system, 'remove')
@ -166,7 +166,7 @@ class DbaasTest(trove_testtools.TestCase):
with patch.object(mysql_common_service.utils, 'execute', with patch.object(mysql_common_service.utils, 'execute',
return_value=('', None)): return_value=('', None)):
mysql_common_service.clear_expired_password() mysql_common_service.clear_expired_password()
self.assertEqual(1, mysql_common_service.utils.execute.call_count) self.assertEqual(2, mysql_common_service.utils.execute.call_count)
mock_remove.assert_not_called() mock_remove.assert_not_called()
@patch.object(operating_system, 'remove') @patch.object(operating_system, 'remove')
@ -184,16 +184,14 @@ class DbaasTest(trove_testtools.TestCase):
self.assertEqual(2, mysql_common_service.utils.execute.call_count) self.assertEqual(2, mysql_common_service.utils.execute.call_count)
mock_remove.assert_not_called() mock_remove.assert_not_called()
@patch('trove.guestagent.datastore.mysql_common.service.LOG')
@patch.object(operating_system, 'remove') @patch.object(operating_system, 'remove')
@patch.object(mysql_common_service.utils, 'execute', @patch.object(mysql_common_service.utils, 'execute',
side_effect=ProcessExecutionError) side_effect=[ProcessExecutionError, (None, None)])
def test_fail_retrieve_secret_content_clear_expired_password(self, def test_fail_retrieve_secret_content_clear_expired_password(self,
mock_execute, mock_execute,
mock_remove, mock_remove):
mock_logging):
mysql_common_service.clear_expired_password() mysql_common_service.clear_expired_password()
self.assertEqual(1, mock_execute.call_count) self.assertEqual(2, mock_execute.call_count)
mock_remove.assert_not_called() mock_remove.assert_not_called()
@patch.object(operating_system, 'read_file', @patch.object(operating_system, 'read_file',