diff options
author | Michael Basnight <mbasnight@gmail.com> | 2013-06-19 13:44:44 -0700 |
---|---|---|
committer | Michael Basnight <mbasnight@gmail.com> | 2013-06-24 14:11:15 -0700 |
commit | 7ab80b554dde5f1f1e61b6a8c45492c5ac27ed6b (patch) | |
tree | c2c850053b70561a3d15b784cf21c5bcad1c993f /trove/guestagent/strategies/restore | |
parent | 18664fa9d8b422d132c60bc0bb085b1ebcc1a9a7 (diff) | |
download | trove-7ab80b554dde5f1f1e61b6a8c45492c5ac27ed6b.tar.gz |
Rename from reddwarf to trove.
Implements Blueprint reddwarf-trove-rename
Change-Id: Ia9ee609bbc06a1d8b9d6917642529f30347541fd
Diffstat (limited to 'trove/guestagent/strategies/restore')
-rw-r--r-- | trove/guestagent/strategies/restore/__init__.py | 23 | ||||
-rw-r--r-- | trove/guestagent/strategies/restore/base.py | 199 | ||||
-rw-r--r-- | trove/guestagent/strategies/restore/impl.py | 57 |
3 files changed, 279 insertions, 0 deletions
diff --git a/trove/guestagent/strategies/restore/__init__.py b/trove/guestagent/strategies/restore/__init__.py new file mode 100644 index 00000000..76f4c43f --- /dev/null +++ b/trove/guestagent/strategies/restore/__init__.py @@ -0,0 +1,23 @@ +#Copyright 2013 Hewlett-Packard Development Company, L.P. + +#Licensed under the Apache License, Version 2.0 (the "License"); +#you may not use this file except in compliance with the License. +#You may obtain a copy of the License at +# +#http://www.apache.org/licenses/LICENSE-2.0 +# +#Unless required by applicable law or agreed to in writing, software +#distributed under the License is distributed on an "AS IS" BASIS, +#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#See the License for the specific language governing permissions and +#limitations under the License. + +from trove.guestagent.strategy import Strategy +from trove.openstack.common import log as logging + +LOG = logging.getLogger(__name__) + + +def get_restore_strategy(restore_driver, ns=__name__): + LOG.debug("Getting restore strategy: %s" % restore_driver) + return Strategy.get_strategy(restore_driver, ns) diff --git a/trove/guestagent/strategies/restore/base.py b/trove/guestagent/strategies/restore/base.py new file mode 100644 index 00000000..d824b523 --- /dev/null +++ b/trove/guestagent/strategies/restore/base.py @@ -0,0 +1,199 @@ +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +from trove.guestagent.strategy import Strategy +from trove.common import cfg +from trove.common import exception +from trove.common import utils +from trove.openstack.common import log as logging +from eventlet.green import subprocess +import tempfile +import pexpect +import os +import glob + +LOG = logging.getLogger(__name__) +CONF = cfg.CONF +CHUNK_SIZE = CONF.backup_chunk_size +BACKUP_USE_GZIP = CONF.backup_use_gzip_compression +BACKUP_USE_OPENSSL = CONF.backup_use_openssl_encryption +BACKUP_DECRYPT_KEY = CONF.backup_aes_cbc_key +RESET_ROOT_RETRY_TIMEOUT = 100 +RESET_ROOT_SLEEP_INTERVAL = 10 +RESET_ROOT_MYSQL_COMMAND = """ +UPDATE mysql.user SET Password=PASSWORD('') WHERE User='root'; +FLUSH PRIVILEGES; +""" + + +def mysql_is_running(): + try: + out, err = utils.execute_with_timeout( + "/usr/bin/mysqladmin", + "ping", run_as_root=True, root_helper="sudo") + LOG.info("The mysqld daemon is up and running.") + return True + except exception.ProcessExecutionError: + LOG.info("The mysqld daemon is not running.") + return False + + +def mysql_is_not_running(): + return not mysql_is_running() + + +def poll_until_then_raise(event, exception): + try: + utils.poll_until(event, + sleep_time=RESET_ROOT_SLEEP_INTERVAL, + time_out=RESET_ROOT_RETRY_TIMEOUT) + except exception.PollTimeOut: + raise exception + + +class RestoreError(Exception): + """Error running the Backup Command.""" + + +class RestoreRunner(Strategy): + """ Base class for Restore Strategy implementations """ + """Restore a database from a previous backup.""" + + __strategy_type__ = 'restore_runner' + __strategy_ns__ = 'trove.guestagent.strategies.restore' + + # The actual system calls to run the restore and prepare + restore_cmd = None + prepare_cmd = None + + # The backup format type + restore_type = None + + # Decryption Parameters + is_zipped = BACKUP_USE_GZIP + is_encrypted = BACKUP_USE_OPENSSL + decrypt_key = BACKUP_DECRYPT_KEY + + def __init__(self, restore_stream, **kwargs): + self.restore_stream = restore_stream + self.restore_location = kwargs.get('restore_location', + '/var/lib/mysql') + self.restore_cmd = (self.decrypt_cmd + + self.unzip_cmd + + (self.base_restore_cmd % kwargs)) + self.prepare_cmd = self.base_prepare_cmd % kwargs \ + if hasattr(self, 'base_prepare_cmd') else None + super(RestoreRunner, self).__init__() + + def __enter__(self): + """Return the runner""" + return self + + def __exit__(self, exc_type, exc_value, traceback): + """Clean up everything.""" + if exc_type is not None: + return False + + if hasattr(self, 'process'): + try: + self.process.terminate() + except OSError: + # Already stopped + pass + utils.raise_if_process_errored(self.process, RestoreError) + + return True + + def restore(self): + self._pre_restore() + content_length = self._run_restore() + self._run_prepare() + self._post_restore() + return content_length + + def _run_restore(self): + with self.restore_stream as stream: + self.process = subprocess.Popen(self.restore_cmd, shell=True, + stdin=subprocess.PIPE, + stderr=subprocess.PIPE) + self.pid = self.process.pid + content_length = 0 + chunk = stream.read(CHUNK_SIZE) + while chunk: + self.process.stdin.write(chunk) + content_length += len(chunk) + chunk = stream.read(CHUNK_SIZE) + self.process.stdin.close() + LOG.info("Restored %s bytes from swift via xbstream." + % content_length) + + return content_length + + def _run_prepare(self): + if hasattr(self, 'prepare_cmd'): + LOG.info("Running innobackupex prepare...") + self.prep_retcode = utils.execute(self.prepare_cmd, + shell=True) + LOG.info("Innobackupex prepare finished successfully") + + def _spawn_with_init_file(self, temp_file): + child = pexpect.spawn("sudo mysqld_safe --init-file=%s" % + temp_file.name) + try: + i = child.expect(['Starting mysqld daemon']) + if i == 0: + LOG.info("Starting mysqld daemon") + except pexpect.TIMEOUT as e: + LOG.error("wait_and_close_proc failed: %s" % e) + finally: + # There is a race condition here where we kill mysqld before + # the init file been executed. We need to ensure mysqld is up. + poll_until_then_raise(mysql_is_running, + RestoreError("Reset root password failed: " + "mysqld did not start!")) + LOG.info("Root password reset successfully!") + LOG.info("Cleaning up the temp mysqld process...") + child.delayafterclose = 1 + child.delayafterterminate = 1 + child.close(force=True) + utils.execute_with_timeout("sudo", "killall", "mysqld") + poll_until_then_raise(mysql_is_not_running, + RestoreError("Reset root password failed: " + "mysqld did not stop!")) + + def _reset_root_password(self): + #Create temp file with reset root password + with tempfile.NamedTemporaryFile() as fp: + fp.write(RESET_ROOT_MYSQL_COMMAND) + fp.flush() + utils.execute_with_timeout("sudo", "chmod", "a+r", fp.name) + self._spawn_with_init_file(fp) + + def _delete_old_binlogs(self): + filelist = glob.glob(self.restore_location + "/ib_logfile*") + for f in filelist: + os.unlink(f) + + @property + def decrypt_cmd(self): + if self.is_encrypted: + return ('openssl enc -d -aes-256-cbc -salt -pass pass:%s | ' + % self.decrypt_key) + else: + return '' + + @property + def unzip_cmd(self): + return 'gzip -d -c | ' if self.is_zipped else '' diff --git a/trove/guestagent/strategies/restore/impl.py b/trove/guestagent/strategies/restore/impl.py new file mode 100644 index 00000000..5119210e --- /dev/null +++ b/trove/guestagent/strategies/restore/impl.py @@ -0,0 +1,57 @@ +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from trove.guestagent.strategies.restore import base +from trove.openstack.common import log as logging +from trove.common import utils +import trove.guestagent.manager.mysql_service as dbaas + +LOG = logging.getLogger(__name__) + + +class MySQLDump(base.RestoreRunner): + """ Implementation of Restore Strategy for MySQLDump """ + __strategy_name__ = 'mysqldump' + base_restore_cmd = ('mysql ' + '--password=%(password)s ' + '-u %(user)s') + + def _pre_restore(self): + pass + + def _post_restore(self): + pass + + +class InnoBackupEx(base.RestoreRunner): + """ Implementation of Restore Strategy for InnoBackupEx """ + __strategy_name__ = 'innobackupex' + base_restore_cmd = 'sudo xbstream -x -C %(restore_location)s' + base_prepare_cmd = ('sudo innobackupex --apply-log %(restore_location)s' + ' --defaults-file=%(restore_location)s/backup-my.cnf' + ' --ibbackup xtrabackup 2>/tmp/innoprepare.log') + + def _pre_restore(self): + app = dbaas.MySqlApp(dbaas.MySqlAppStatus.get()) + app.stop_db() + + def _post_restore(self): + utils.execute_with_timeout("sudo", "chown", "-R", "-f", + "mysql", self.restore_location) + self._delete_old_binlogs() + self._reset_root_password() + app = dbaas.MySqlApp(dbaas.MySqlAppStatus.get()) + app.start_mysql() |