263 lines
9.7 KiB
Python
263 lines
9.7 KiB
Python
# (c) Copyright 2017 Mirantis, Inc
|
|
#
|
|
# 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 os
|
|
import shutil
|
|
import socket
|
|
import subprocess
|
|
import tempfile
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log
|
|
|
|
from freezer.common import client_manager
|
|
from freezer.engine import engine
|
|
from freezer.engine.osbrick import client as brick_client
|
|
from freezer.engine.tar import tar
|
|
from freezer.utils import utils
|
|
from freezer.utils import winutils
|
|
|
|
LOG = log.getLogger(__name__)
|
|
CONF = cfg.CONF
|
|
|
|
|
|
class OsbrickEngine(engine.BackupEngine):
|
|
def __init__(self, storage, **kwargs):
|
|
super(OsbrickEngine, self).__init__(storage=storage)
|
|
self.client = client_manager.get_client_manager(CONF)
|
|
self.cinder = self.client.create_cinder()
|
|
self.volume_info = None
|
|
|
|
self.compression_algo = kwargs.get('compression')
|
|
self.encrypt_pass_file = kwargs.get('encrypt_key')
|
|
self.dereference_symlink = kwargs.get('symlinks')
|
|
self.exclude = kwargs.get('exclude')
|
|
self.storage = storage
|
|
self.is_windows = winutils.is_windows()
|
|
self.dry_run = kwargs.get('dry_run', False)
|
|
self.max_segment_size = kwargs.get('max_segment_size')
|
|
|
|
@property
|
|
def name(self):
|
|
return "osbrick"
|
|
|
|
def metadata(self, backup_resource):
|
|
"""Construct metadata"""
|
|
return {
|
|
"engine_name": self.name,
|
|
"volume_info": self.volume_info,
|
|
"encryption": bool(self.encrypt_pass_file)
|
|
}
|
|
|
|
@staticmethod
|
|
def is_active(client_manager, id):
|
|
get_res = client_manager.get(id)
|
|
return get_res.status == 'available'
|
|
|
|
def backup_data(self, backup_path, manifest_path):
|
|
LOG.info("Starting os-brick engine backup stream")
|
|
volume = self.cinder.volumes.get(backup_path)
|
|
self.volume_info = volume.to_dict()
|
|
|
|
snapshot = self.cinder.volume_snapshots.create(backup_path, force=True)
|
|
LOG.info("[*] Creating volume snapshot")
|
|
utils.wait_for(
|
|
OsbrickEngine.is_active,
|
|
1,
|
|
100,
|
|
message="Waiting for volume {0} snapshot to become "
|
|
"active".format(backup_path),
|
|
kwargs={"client_manager": self.cinder.volume_snapshots,
|
|
"id": snapshot.id}
|
|
)
|
|
|
|
LOG.info("[*] Converting snapshot to volume")
|
|
backup_volume = self.cinder.volumes.create(snapshot.size,
|
|
snapshot_id=snapshot.id)
|
|
|
|
utils.wait_for(
|
|
OsbrickEngine.is_active,
|
|
1,
|
|
100,
|
|
message="Waiting for backup volume {0} to become "
|
|
"active".format(backup_volume.id),
|
|
kwargs={"client_manager": self.cinder.volumes,
|
|
"id": backup_volume.id}
|
|
)
|
|
|
|
try:
|
|
tmpdir = tempfile.mkdtemp()
|
|
except Exception:
|
|
LOG.error("Unable to create a tmp directory")
|
|
raise
|
|
|
|
LOG.info("[*] Trying to attach the volume to localhost")
|
|
brickclient = brick_client.Client(volumes_client=self.cinder)
|
|
|
|
attach_info = brickclient.attach(backup_volume.id,
|
|
socket.gethostname(),
|
|
tmpdir)
|
|
|
|
if not os.path.ismount(tmpdir):
|
|
subprocess.check_output(['sudo', 'mount', '-t', 'ext4',
|
|
attach_info.get('path'), tmpdir])
|
|
|
|
cwd = os.getcwd()
|
|
os.chdir(tmpdir)
|
|
|
|
tar_engine = tar.TarEngine(self.compression_algo,
|
|
self.dereference_symlink,
|
|
self.exclude, self.storage,
|
|
self.max_segment_size,
|
|
self.encrypt_pass_file, self.dry_run)
|
|
|
|
for data_chunk in tar_engine.backup_data('.', manifest_path):
|
|
yield data_chunk
|
|
|
|
os.chdir(cwd)
|
|
|
|
LOG.info("[*] Detaching volume")
|
|
subprocess.check_output(['sudo', 'umount', tmpdir])
|
|
shutil.rmtree(tmpdir)
|
|
brickclient.detach(backup_volume.id)
|
|
|
|
utils.wait_for(
|
|
OsbrickEngine.is_active,
|
|
1,
|
|
100,
|
|
message="Waiting for backup volume {0} to become "
|
|
"active".format(backup_volume.id),
|
|
kwargs={"client_manager": self.cinder.volumes,
|
|
"id": backup_volume.id}
|
|
)
|
|
|
|
LOG.info("[*] Removing backup volume and snapshot")
|
|
self.cinder.volumes.delete(backup_volume.id)
|
|
self.cinder.volume_snapshots.delete(snapshot, force=True)
|
|
|
|
LOG.info('Backup process completed')
|
|
|
|
def restore_level(self, restore_path, read_pipe, backup, except_queue):
|
|
try:
|
|
LOG.info("Restoring volume {} using os-brick engine".format(
|
|
restore_path))
|
|
new_volume = False
|
|
metadata = backup.metadata()
|
|
if (not self.encrypt_pass_file and
|
|
metadata.get("encryption", False)):
|
|
raise Exception("Cannot restore encrypted backup without key")
|
|
volume_info = metadata.get("volume_info")
|
|
try:
|
|
backup_volume = self.cinder.volumes.get(restore_path)
|
|
except Exception:
|
|
new_volume = True
|
|
LOG.info("[*] Volume doesn't exists, creating a new one")
|
|
backup_volume = self.cinder.volumes.create(volume_info['size'])
|
|
|
|
utils.wait_for(
|
|
OsbrickEngine.is_active,
|
|
1,
|
|
100,
|
|
message="Waiting for backup volume {0} to become "
|
|
"active".format(backup_volume.id),
|
|
kwargs={"client_manager": self.cinder.volumes,
|
|
"id": backup_volume.id}
|
|
)
|
|
|
|
if backup_volume.attachments:
|
|
LOG.info('Volume is used, creating a copy from snapshot')
|
|
snapshot = self.cinder.volume_snapshots.create(
|
|
backup_volume.id, force=True)
|
|
utils.wait_for(
|
|
OsbrickEngine.is_active,
|
|
1,
|
|
100,
|
|
message="Waiting for volume {0} snapshot to become "
|
|
"active".format(backup_volume.id),
|
|
kwargs={"client_manager": self.cinder.volume_snapshots,
|
|
"id": snapshot.id}
|
|
)
|
|
|
|
LOG.info("[*] Converting snapshot to volume")
|
|
backup_volume = self.cinder.volumes.create(
|
|
snapshot.size, snapshot_id=snapshot.id)
|
|
|
|
utils.wait_for(
|
|
OsbrickEngine.is_active,
|
|
1,
|
|
100,
|
|
message="Waiting for backup volume {0} to become "
|
|
"active".format(backup_volume.id),
|
|
kwargs={"client_manager": self.cinder.volumes,
|
|
"id": backup_volume.id}
|
|
)
|
|
|
|
backup_volume = self.cinder.volumes.get(backup_volume.id)
|
|
if backup_volume.status != 'available':
|
|
raise RuntimeError('Unable to use volume for restore data')
|
|
|
|
try:
|
|
tmpdir = tempfile.mkdtemp()
|
|
except Exception:
|
|
LOG.error("Unable to create a tmp directory")
|
|
raise
|
|
|
|
LOG.info("[*] Trying to attach the volume to localhost")
|
|
brickclient = brick_client.Client(volumes_client=self.cinder)
|
|
attach_info = brickclient.attach(backup_volume.id,
|
|
socket.gethostname(),
|
|
tmpdir)
|
|
|
|
if not os.path.ismount(tmpdir):
|
|
if new_volume:
|
|
subprocess.check_output(['sudo', 'mkfs.ext4',
|
|
attach_info.get('path')])
|
|
|
|
subprocess.check_output(['sudo', 'mount', '-t', 'ext4',
|
|
attach_info.get('path'),
|
|
tmpdir])
|
|
|
|
tar_engine = tar.TarEngine(self.compression_algo,
|
|
self.dereference_symlink,
|
|
self.exclude, self.storage,
|
|
self.max_segment_size,
|
|
self.encrypt_pass_file, self.dry_run)
|
|
|
|
tar_engine.restore_level(tmpdir, read_pipe, backup,
|
|
except_queue)
|
|
|
|
subprocess.check_output(['sudo', 'umount', tmpdir])
|
|
shutil.rmtree(tmpdir)
|
|
|
|
LOG.info("[*] Detaching volume")
|
|
brickclient.detach(backup_volume.id)
|
|
|
|
utils.wait_for(
|
|
OsbrickEngine.is_active,
|
|
1,
|
|
100,
|
|
message="Waiting for backup volume {0} to become "
|
|
"active".format(backup_volume.id),
|
|
kwargs={"client_manager": self.cinder.volumes,
|
|
"id": backup_volume.id}
|
|
)
|
|
|
|
LOG.info('Restore process completed')
|
|
|
|
except Exception as e:
|
|
LOG.exception(e)
|
|
except_queue.put(e)
|
|
raise
|