Merge "Add Backup/Restore support for Couchbase"
This commit is contained in:
commit
5869b3cabb
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
109
trove/guestagent/strategies/backup/couchbase_impl.py
Normal file
109
trove/guestagent/strategies/backup/couchbase_impl.py
Normal 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
|
200
trove/guestagent/strategies/restore/couchbase_impl.py
Normal file
200
trove/guestagent/strategies/restore/couchbase_impl.py
Normal 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.")
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user