freezer/freezer/engine/osbrick/osbrick.py

261 lines
9.4 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
}
@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()
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