Merge "Add Backup/Restore support for Couchbase"

This commit is contained in:
Jenkins 2014-07-21 05:09:44 +00:00 committed by Gerrit Code Review
commit 5869b3cabb
11 changed files with 525 additions and 18 deletions

View File

@ -83,10 +83,8 @@ ignore_users = os_admin
ignore_dbs = lost+found, mysql, information_schema
# Strategy information for backups
backup_namespace = trove.guestagent.strategies.backup.mysql_impl
# Additional commandline options to be passed to the backup runner (by strategy). For example:
# backup_runner_options = InnoBackupEx:--no-lock, MySQLDump:--events --routines --triggers
restore_namespace = trove.guestagent.strategies.restore.mysql_impl
storage_strategy = SwiftStorage
storage_namespace = trove.guestagent.strategies.storage.swift
backup_swift_container = database_backups
@ -96,4 +94,3 @@ backup_aes_cbc_key = "default_aes_cbc_key"
backup_use_snet = False
backup_chunk_size = 65536
backup_segment_max_size = 2147483648

View File

@ -300,6 +300,10 @@ mysql_opts = [
"instance-create as the 'password' field."),
cfg.IntOpt('usage_timeout', default=400,
help='Timeout to wait for a guest to become active.'),
cfg.StrOpt('backup_namespace',
default='trove.guestagent.strategies.backup.mysql_impl'),
cfg.StrOpt('restore_namespace',
default='trove.guestagent.strategies.restore.mysql_impl'),
]
# Percona
@ -327,6 +331,10 @@ percona_opts = [
"instance-create as the 'password' field."),
cfg.IntOpt('usage_timeout', default=450,
help='Timeout to wait for a guest to become active.'),
cfg.StrOpt('backup_namespace',
default='trove.guestagent.strategies.backup.mysql_impl'),
cfg.StrOpt('restore_namespace',
default='trove.guestagent.strategies.restore.mysql_impl'),
]
# Redis
@ -388,7 +396,7 @@ couchbase_opts = [
help='List of UDP ports and/or port ranges to open'
' in the security group (only applicable '
'if trove_security_groups_support is True).'),
cfg.StrOpt('backup_strategy', default=None,
cfg.StrOpt('backup_strategy', default='CbBackup',
help='Default strategy to perform backups.'),
cfg.StrOpt('mount_point', default='/var/lib/couchbase',
help="Filesystem path for mounting "
@ -400,6 +408,10 @@ couchbase_opts = [
'service during instance-create. The generated password for '
'the root user is immediately returned in the response of '
"instance-create as the 'password' field."),
cfg.StrOpt('backup_namespace',
default='trove.guestagent.strategies.backup.couchbase_impl'),
cfg.StrOpt('restore_namespace',
default='trove.guestagent.strategies.restore.couchbase_impl')
]
# MongoDB

View File

@ -35,16 +35,18 @@ MANAGER = CONF.datastore_manager
# If datastore manager is not mentioned in guest
# configuration file, would be used mysql as datastore_manager by the default
STRATEGY = CONF.get('mysql' if not MANAGER else MANAGER).backup_strategy
NAMESPACE = CONF.backup_namespace
RUNNER = get_backup_strategy(STRATEGY, NAMESPACE)
BACKUP_NAMESPACE = CONF.get(
"mysql" if not MANAGER else MANAGER).backup_namespace
RESTORE_NAMESPACE = CONF.get(
"mysql" if not MANAGER else MANAGER).restore_namespace
RUNNER = get_backup_strategy(STRATEGY, BACKUP_NAMESPACE)
EXTRA_OPTS = CONF.backup_runner_options.get(STRATEGY, '')
# Try to get the incremental strategy or return the default 'backup_strategy'
INCREMENTAL = CONF.backup_incremental_strategy.get(STRATEGY,
STRATEGY)
INCREMENTAL_RUNNER = get_backup_strategy(INCREMENTAL, NAMESPACE)
INCREMENTAL_RUNNER = get_backup_strategy(INCREMENTAL, BACKUP_NAMESPACE)
class BackupAgent(object):
@ -52,9 +54,10 @@ class BackupAgent(object):
def _get_restore_runner(self, backup_type):
"""Returns the RestoreRunner associated with this backup type."""
try:
runner = get_restore_strategy(backup_type, CONF.restore_namespace)
runner = get_restore_strategy(backup_type, RESTORE_NAMESPACE)
except ImportError:
raise UnknownBackupType("Unknown Backup type: %s" % backup_type)
raise UnknownBackupType("Unknown Backup type: %s in namespace %s"
% (backup_type, RESTORE_NAMESPACE))
return runner
def execute_backup(self, context, backup_info,

View File

@ -17,6 +17,8 @@ import os
from trove.common import cfg
from trove.common import exception
from trove.common import instance as rd_instance
from trove.guestagent import backup
from trove.guestagent import dbaas
from trove.guestagent import volume
from trove.guestagent.datastore.couchbase import service
@ -77,6 +79,11 @@ class Manager(periodic_task.PeriodicTasks):
if root_password:
self.app.enable_root(root_password)
self.app.initial_setup()
if backup_info:
LOG.debug('Now going to perform restore.')
self._perform_restore(backup_info,
context,
mount_point)
self.app.complete_install_or_restart()
LOG.info(_('"prepare" couchbase call has finished.'))
@ -157,13 +164,28 @@ class Manager(periodic_task.PeriodicTasks):
def is_root_enabled(self, context):
return os.path.exists(system.pwd_file)
def _perform_restore(self, backup_info, context, restore_location, app):
raise exception.DatastoreOperationNotSupported(
operation='_perform_restore', datastore=MANAGER)
def _perform_restore(self, backup_info, context, restore_location):
"""
Restores all couchbase buckets and their documents from the
backup.
"""
LOG.info(_("Restoring database from backup %s") %
backup_info['id'])
try:
backup.restore(context, backup_info, restore_location)
except Exception as e:
LOG.error(_("Error performing restore from backup %s") %
backup_info['id'])
LOG.error(e)
self.status.set_status(rd_instance.ServiceStatuses.FAILED)
raise
LOG.info(_("Restored database successfully"))
def create_backup(self, context, backup_info):
raise exception.DatastoreOperationNotSupported(
operation='create_backup', datastore=MANAGER)
"""
Backup all couchbase buckets and their documents.
"""
backup.backup(context, backup_info)
def mount_volume(self, context, device_path=None, mount_point=None):
device = volume.VolumeDevice(device_path)

View File

@ -16,7 +16,12 @@ from trove.common import cfg
CONF = cfg.CONF
TIME_OUT = 1200
COUCHBASE_DUMP_DIR = '/tmp/backups'
COUCHBASE_CONF_DIR = '/etc/couchbase'
COUCHBASE_WEBADMIN_PORT = '8091'
COUCHBASE_REST_API = 'http://localhost:' + COUCHBASE_WEBADMIN_PORT
BUCKETS_JSON = '/buckets.json'
SECRET_KEY = '/secret_key'
SERVICE_CANDIDATES = ["couchbase-server"]
cmd_couchbase_status = ('sudo /opt/couchbase/bin/couchbase-cli server-info '
'-c %(IP)s:8091 -u root -p %(PWD)s')
@ -38,4 +43,4 @@ cmd_set_swappiness = 'sudo sysctl vm.swappiness=0'
cmd_update_sysctl_conf = ('echo "vm.swappiness = 0" | sudo tee -a '
'/etc/sysctl.conf')
cmd_reset_pwd = 'sudo /opt/couchbase/bin/cbreset_password %(IP)s:8091'
pwd_file = COUCHBASE_CONF_DIR + '/secret_key'
pwd_file = COUCHBASE_CONF_DIR + SECRET_KEY

View File

@ -58,6 +58,13 @@ def to_gb(bytes):
return round(size, 2)
def to_mb(bytes):
if bytes == 0:
return 0.0
size = bytes / 1024.0 ** 2
return round(size, 2)
def get_filesystem_volume_stats(fs_path):
try:
stats = os.statvfs(fs_path)

View File

@ -0,0 +1,109 @@
# Copyright (c) 2014 eBay Software 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 trove.common import exception
from trove.common import utils
from trove.guestagent.datastore.couchbase import service
from trove.guestagent.datastore.couchbase import system
from trove.guestagent.strategies.backup import base
from trove.openstack.common.gettextutils import _
from trove.openstack.common import log as logging
LOG = logging.getLogger(__name__)
OUTFILE = '/tmp' + system.BUCKETS_JSON
class CbBackup(base.BackupRunner):
"""
Implementation of Backup Strategy for Couchbase.
"""
__strategy_name__ = 'cbbackup'
pre_backup_commands = [
'rm -rf ' + system.COUCHBASE_DUMP_DIR,
'mkdir -p ' + system.COUCHBASE_DUMP_DIR,
]
post_backup_commands = [
'rm -rf ' + system.COUCHBASE_DUMP_DIR,
]
@property
def cmd(self):
"""
Creates backup dump dir, tars it up, and encrypts it.
"""
cmd = 'tar cPf - ' + system.COUCHBASE_DUMP_DIR
return cmd + self.zip_cmd + self.encrypt_cmd
def _save_buckets_config(self, password):
url = system.COUCHBASE_REST_API + '/pools/default/buckets'
utils.execute_with_timeout('curl -u root:' + password +
' ' + url + ' > ' + OUTFILE,
shell=True, timeout=300)
def _backup(self, password):
utils.execute_with_timeout('/opt/couchbase/bin/cbbackup ' +
system.COUCHBASE_REST_API + ' ' +
system.COUCHBASE_DUMP_DIR +
' -u root -p ' + password,
shell=True, timeout=600)
def _run_pre_backup(self):
try:
for cmd in self.pre_backup_commands:
utils.execute_with_timeout(cmd, shell=True)
root = service.CouchbaseRootAccess()
pw = root.get_password()
self._save_buckets_config(pw)
with open(OUTFILE, "r") as f:
out = f.read()
if out != "[]":
d = json.loads(out)
all_memcached = True
for i in range(len(d)):
bucket_type = d[i]["bucketType"]
if bucket_type != "memcached":
all_memcached = False
break
if not all_memcached:
self._backup(pw)
else:
LOG.info(_("All buckets are memcached. "
"Skipping backup."))
utils.execute_with_timeout("mv " + OUTFILE + " " +
system.COUCHBASE_DUMP_DIR,
shell=True)
if pw != "password":
# Not default password, backup generated root password
utils.execute_with_timeout('sudo cp ' + system.pwd_file +
' ' + system.COUCHBASE_DUMP_DIR,
shell=True)
except exception.ProcessExecutionError as p:
LOG.error(p)
raise p
def _run_post_backup(self):
try:
for cmd in self.post_backup_commands:
utils.execute_with_timeout(cmd, shell=True)
except exception.ProcessExecutionError as p:
LOG.error(p)
raise p

View File

@ -0,0 +1,200 @@
# Copyright (c) 2014 eBay Software 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
import os.path
import time
from trove.common import exception
from trove.common import utils
from trove.guestagent import dbaas
from trove.guestagent.datastore.couchbase import service
from trove.guestagent.datastore.couchbase import system
from trove.guestagent.strategies.restore import base
from trove.openstack.common import log as logging
LOG = logging.getLogger(__name__)
class CbBackup(base.RestoreRunner):
"""
Implementation of Restore Strategy for Couchbase.
"""
__strategy_name__ = 'cbbackup'
base_restore_cmd = 'sudo tar xPf -'
def __init__(self, *args, **kwargs):
super(CbBackup, self).__init__(*args, **kwargs)
def pre_restore(self):
try:
utils.execute_with_timeout("rm -rf " + system.COUCHBASE_DUMP_DIR,
shell=True)
except exception.ProcessExecutionError as p:
LOG.error(p)
raise p
def post_restore(self):
try:
# Root enabled for the backup
pwd_file = system.COUCHBASE_DUMP_DIR + system.SECRET_KEY
if os.path.exists(pwd_file):
with open(pwd_file, "r") as f:
pw = f.read().rstrip("\n")
root = service.CouchbaseRootAccess()
root.set_password(pw)
# Get current root password
root = service.CouchbaseRootAccess()
root_pwd = root.get_password()
# Iterate through each bucket config
buckets_json = system.COUCHBASE_DUMP_DIR + system.BUCKETS_JSON
with open(buckets_json, "r") as f:
out = f.read()
if out == "[]":
# No buckets or data to restore. Done.
return
d = json.loads(out)
for i in range(len(d)):
bucket_name = d[i]["name"]
bucket_type = d[i]["bucketType"]
if bucket_type == "membase":
bucket_type = "couchbase"
ram = int(dbaas.to_mb(d[i]["quota"]["ram"]))
auth_type = d[i]["authType"]
password = d[i]["saslPassword"]
port = d[i]["proxyPort"]
replica_number = d[i]["replicaNumber"]
replica_index = 1 if d[i]["replicaIndex"] else 0
threads = d[i]["threadsNumber"]
flush = 1 if "flush" in d[i]["controllers"] else 0
# cbrestore requires you to manually create dest buckets
create_bucket_cmd = ('curl -X POST -u root:' + root_pwd +
' -d name="' +
bucket_name + '"' +
' -d bucketType="' +
bucket_type + '"' +
' -d ramQuotaMB="' +
str(ram) + '"' +
' -d authType="' +
auth_type + '"' +
' -d saslPassword="' +
password + '"' +
' -d proxyPort="' +
str(port) + '"' +
' -d replicaNumber="' +
str(replica_number) + '"' +
' -d replicaIndex="' +
str(replica_index) + '"' +
' -d threadsNumber="' +
str(threads) + '"' +
' -d flushEnabled="' +
str(flush) + '" ' +
system.COUCHBASE_REST_API +
'/pools/default/buckets')
utils.execute_with_timeout(create_bucket_cmd,
shell=True, timeout=300)
if bucket_type == "memcached":
continue
# Wait for couchbase (membase) bucket creation to complete
# (follows same logic as --wait for couchbase-cli)
timeout_in_seconds = 120
start = time.time()
bucket_exist = False
while ((time.time() - start) <= timeout_in_seconds and
not bucket_exist):
url = (system.COUCHBASE_REST_API +
'/pools/default/buckets/')
outfile = system.COUCHBASE_DUMP_DIR + '/buckets.all'
utils.execute_with_timeout('curl -u root:' + root_pwd +
' ' + url + ' > ' + outfile,
shell=True, timeout=300)
with open(outfile, "r") as file:
out = file.read()
buckets = json.loads(out)
for bucket in buckets:
if bucket["name"] == bucket_name:
bucket_exist = True
break
if not bucket_exist:
time.sleep(2)
if not bucket_exist:
raise base.RestoreError("Failed to create bucket '%s' "
"within %s seconds"
% (bucket_name,
timeout_in_seconds))
# Query status
# (follows same logic as --wait for couchbase-cli)
healthy = False
while ((time.time() - start) <= timeout_in_seconds):
url = (system.COUCHBASE_REST_API +
'/pools/default/buckets/' +
bucket_name)
outfile = system.COUCHBASE_DUMP_DIR + '/' + bucket_name
utils.execute_with_timeout('curl -u root:' + root_pwd +
' ' + url + ' > ' + outfile,
shell=True, timeout=300)
all_node_ready = True
with open(outfile, "r") as file:
out = file.read()
bucket = json.loads(out)
for node in bucket["nodes"]:
if node["status"] != "healthy":
all_node_ready = False
break
if not all_node_ready:
time.sleep(2)
else:
healthy = True
break
if not healthy:
raise base.RestoreError("Bucket '%s' is created but "
"not ready to use within %s "
"seconds"
% (bucket_name,
timeout_in_seconds))
# Restore
restore_cmd = ('/opt/couchbase/bin/cbrestore ' +
system.COUCHBASE_DUMP_DIR + ' ' +
system.COUCHBASE_REST_API +
' --bucket-source=' + bucket_name +
' --bucket-destination=' + bucket_name +
' -u root' + ' -p ' + root_pwd)
try:
utils.execute_with_timeout(restore_cmd,
shell=True,
timeout=300)
except exception.ProcessExecutionError as p:
# cbrestore fails or hangs at times:
# http://www.couchbase.com/issues/browse/MB-10832
# Retrying typically works
LOG.error(p)
LOG.error("cbrestore failed. Retrying...")
utils.execute_with_timeout(restore_cmd,
shell=True,
timeout=300)
except exception.ProcessExecutionError as p:
LOG.error(p)
raise base.RestoreError("Couchbase restore failed.")

View File

@ -19,9 +19,12 @@ import hashlib
import os
import testtools
from trove.common import utils
from trove.common.context import TroveContext
from trove.conductor import api as conductor_api
from trove.guestagent.common import operating_system
from trove.guestagent.strategies.backup import mysql_impl
from trove.guestagent.strategies.backup import couchbase_impl
from trove.guestagent.strategies.restore.base import RestoreRunner
from trove.backup.models import BackupState
from trove.guestagent.backup import backupagent
@ -205,6 +208,18 @@ class BackupAgentTest(testtools.TestCase):
str_innobackup_manifest = 'innobackupex.xbstream.gz.enc'
self.assertEqual(inno_backup_ex.manifest, str_innobackup_manifest)
def test_backup_impl_CbBackup(self):
operating_system.get_ip_address = Mock(return_value="1.1.1.1")
utils.execute_with_timeout = Mock(return_value=None)
cbbackup = couchbase_impl.CbBackup('cbbackup', extra_opts='')
self.assertIsNotNone(cbbackup)
str_cbbackup_cmd = ("tar cPf - /tmp/backups | "
"gzip | openssl enc -aes-256-cbc -salt -pass "
"pass:default_aes_cbc_key")
self.assertEqual(str_cbbackup_cmd, cbbackup.cmd)
self.assertIsNotNone(cbbackup.manifest)
self.assertIn('gz.enc', cbbackup.manifest)
def test_backup_base(self):
"""This test is for
guestagent/strategies/backup/base

View File

@ -20,6 +20,7 @@ import trove.guestagent.strategies.restore.base as restoreBase
from trove.guestagent.strategies.backup import mysql_impl
from trove.common import utils
from trove.common import exception
BACKUP_XTRA_CLS = ("trove.guestagent.strategies.backup."
"mysql_impl.InnoBackupEx")
@ -33,6 +34,10 @@ BACKUP_SQLDUMP_CLS = ("trove.guestagent.strategies.backup."
"mysql_impl.MySQLDump")
RESTORE_SQLDUMP_CLS = ("trove.guestagent.strategies.restore."
"mysql_impl.MySQLDump")
BACKUP_CBBACKUP_CLS = ("trove.guestagent.strategies.backup."
"couchbase_impl.CbBackup")
RESTORE_CBBACKUP_CLS = ("trove.guestagent.strategies.restore."
"couchbase_impl.CbBackup")
PIPE = " | "
ZIP = "gzip"
UNZIP = "gzip -d -c"
@ -64,6 +69,10 @@ PREPARE = ("sudo innobackupex --apply-log /var/lib/mysql "
"--ibbackup xtrabackup 2>/tmp/innoprepare.log")
CRYPTO_KEY = "default_aes_cbc_key"
CBBACKUP_CMD = "tar cPf - /tmp/backups"
CBBACKUP_RESTORE = "sudo tar xPf -"
class GuestAgentBackupTest(testtools.TestCase):
@ -244,3 +253,116 @@ class GuestAgentBackupTest(testtools.TestCase):
location="filename", checksum="md5")
self.assertEqual(restr.restore_cmd,
DECRYPT + PIPE + UNZIP + PIPE + SQLDUMP_RESTORE)
def test_backup_encrypted_cbbackup_command(self):
backupBase.BackupRunner.is_encrypted = True
backupBase.BackupRunner.encrypt_key = CRYPTO_KEY
RunnerClass = utils.import_class(BACKUP_CBBACKUP_CLS)
utils.execute_with_timeout = mock.Mock(return_value=None)
bkp = RunnerClass(12345)
self.assertIsNotNone(bkp)
self.assertEqual(
CBBACKUP_CMD + PIPE + ZIP + PIPE + ENCRYPT, bkp.command)
self.assertIn("gz.enc", bkp.manifest)
def test_backup_not_encrypted_cbbackup_command(self):
backupBase.BackupRunner.is_encrypted = False
backupBase.BackupRunner.encrypt_key = CRYPTO_KEY
RunnerClass = utils.import_class(BACKUP_CBBACKUP_CLS)
utils.execute_with_timeout = mock.Mock(return_value=None)
bkp = RunnerClass(12345)
self.assertIsNotNone(bkp)
self.assertEqual(CBBACKUP_CMD + PIPE + ZIP, bkp.command)
self.assertIn("gz", bkp.manifest)
def test_restore_decrypted_cbbackup_command(self):
restoreBase.RestoreRunner.is_zipped = True
restoreBase.RestoreRunner.is_encrypted = False
RunnerClass = utils.import_class(RESTORE_CBBACKUP_CLS)
restr = RunnerClass(None, restore_location="/tmp",
location="filename", checksum="md5")
self.assertEqual(restr.restore_cmd, UNZIP + PIPE + CBBACKUP_RESTORE)
def test_restore_encrypted_cbbackup_command(self):
restoreBase.RestoreRunner.is_zipped = True
restoreBase.RestoreRunner.is_encrypted = True
restoreBase.RestoreRunner.decrypt_key = CRYPTO_KEY
RunnerClass = utils.import_class(RESTORE_CBBACKUP_CLS)
restr = RunnerClass(None, restore_location="/tmp",
location="filename", checksum="md5")
self.assertEqual(restr.restore_cmd,
DECRYPT + PIPE + UNZIP + PIPE + CBBACKUP_RESTORE)
class CouchbaseBackupTests(testtools.TestCase):
def setUp(self):
super(CouchbaseBackupTests, self).setUp()
self.backup_runner = utils.import_class(
BACKUP_CBBACKUP_CLS)
def tearDown(self):
super(CouchbaseBackupTests, self).tearDown()
def test_backup_success(self):
self.backup_runner.__exit__ = mock.Mock()
self.backup_runner.run = mock.Mock()
self.backup_runner._run_pre_backup = mock.Mock()
self.backup_runner._run_post_backup = mock.Mock()
utils.execute_with_timeout = mock.Mock(return_value=None)
with self.backup_runner(12345):
pass
self.assertTrue(self.backup_runner.run)
self.assertTrue(self.backup_runner._run_pre_backup)
self.assertTrue(self.backup_runner._run_post_backup)
def test_backup_failed_due_to_run_backup(self):
self.backup_runner.run = mock.Mock(
side_effect=exception.ProcessExecutionError('test'))
self.backup_runner._run_pre_backup = mock.Mock()
self.backup_runner._run_post_backup = mock.Mock()
utils.execute_with_timeout = mock.Mock(return_value=None)
self.assertRaises(exception.ProcessExecutionError,
self.backup_runner(12345).__enter__)
class CouchbaseRestoreTests(testtools.TestCase):
def setUp(self):
super(CouchbaseRestoreTests, self).setUp()
self.restore_runner = utils.import_class(
RESTORE_CBBACKUP_CLS)(
'swift', location='http://some.where',
checksum='True_checksum',
restore_location='/tmp/somewhere')
def tearDown(self):
super(CouchbaseRestoreTests, self).tearDown()
def test_restore_success(self):
expected_content_length = 123
self.restore_runner._run_restore = mock.Mock(
return_value=expected_content_length)
self.restore_runner.pre_restore = mock.Mock()
self.restore_runner.post_restore = mock.Mock()
actual_content_length = self.restore_runner.restore()
self.assertEqual(
expected_content_length, actual_content_length)
def test_restore_failed_due_to_pre_restore(self):
self.restore_runner.post_restore = mock.Mock()
self.restore_runner.pre_restore = mock.Mock(
side_effect=exception.ProcessExecutionError('Error'))
self.restore_runner._run_restore = mock.Mock()
self.assertRaises(exception.ProcessExecutionError,
self.restore_runner.restore)
def test_restore_failed_due_to_run_restore(self):
self.restore_runner.pre_restore = mock.Mock()
self.restore_runner._run_restore = mock.Mock(
side_effect=exception.ProcessExecutionError('Error'))
self.restore_runner.post_restore = mock.Mock()
self.assertRaises(exception.ProcessExecutionError,
self.restore_runner.restore)

View File

@ -16,6 +16,7 @@ import testtools
from mock import MagicMock
from trove.common.context import TroveContext
from trove.guestagent import volume
from trove.guestagent import backup
from trove.guestagent.common import operating_system
from trove.guestagent.datastore.couchbase import service as couch_service
from trove.guestagent.datastore.couchbase import manager as couch_manager
@ -62,8 +63,17 @@ class GuestAgentCouchbaseManagerTest(testtools.TestCase):
def test_prepare_device_path_true(self):
self._prepare_dynamic()
def _prepare_dynamic(self, device_path='/dev/vdb', is_db_installed=True,
backup_info=None):
def test_prepare_from_backup(self):
self._prepare_dynamic(backup_id='backup_id_123abc')
def _prepare_dynamic(self, device_path='/dev/vdb', backup_id=None):
# covering all outcomes is starting to cause trouble here
backup_info = {'id': backup_id,
'location': 'fake-location',
'type': 'CbBackup',
'checksum': 'fake-checksum'} if backup_id else None
mock_status = MagicMock()
self.manager.appStatus = mock_status
@ -79,6 +89,7 @@ class GuestAgentCouchbaseManagerTest(testtools.TestCase):
return_value=None)
couch_service.CouchbaseApp.complete_install_or_restart = MagicMock(
return_value=None)
backup.restore = MagicMock(return_value=None)
#invocation
self.manager.prepare(self.context, self.packages, None, 2048,
@ -91,6 +102,10 @@ class GuestAgentCouchbaseManagerTest(testtools.TestCase):
self.packages)
couch_service.CouchbaseApp.complete_install_or_restart.\
assert_any_call()
if backup_info:
backup.restore.assert_any_call(self.context,
backup_info,
'/var/lib/couchbase')
def test_restart(self):
mock_status = MagicMock()