import shutil import subprocess as sp import os import stat from abc import ABC from . import logger from os.path import join as join_path from ucloud.settings import settings as config class ImageStorageHandler(ABC): handler_name = 'base' def __init__(self, image_base, vm_base): self.image_base = image_base self.vm_base = vm_base def import_image(self, image_src, image_dest, protect=False): """Put an image at the destination :param image_src: An Image file :param image_dest: A path where :param src: is to be put. :param protect: If protect is true then the dest is protect (readonly etc) The obj must exist on filesystem. """ raise NotImplementedError() def make_vm_image(self, image_path, path): """Copy image from src to dest :param image_path: A path :param path: A path src and destination must be on same storage system i.e both on file system or both on CEPH etc. """ raise NotImplementedError() def resize_vm_image(self, path, size): """Resize image located at :param path: :param path: The file which is to be resized :param size: Size must be in Megabytes """ raise NotImplementedError() def delete_vm_image(self, path): raise NotImplementedError() def execute_command(self, command, report=True, error_origin=None): if not error_origin: error_origin = self.handler_name command = list(map(str, command)) try: sp.check_output(command, stderr=sp.PIPE) except sp.CalledProcessError as e: _stderr = e.stderr.decode('utf-8').strip() if report: logger.exception('%s:- %s', error_origin, _stderr) return False return True def vm_path_string(self, path): raise NotImplementedError() def qemu_path_string(self, path): raise NotImplementedError() def is_vm_image_exists(self, path): raise NotImplementedError() class FileSystemBasedImageStorageHandler(ImageStorageHandler): handler_name = 'Filesystem' def import_image(self, src, dest, protect=False): dest = join_path(self.image_base, dest) try: shutil.copy(src, dest) if protect: os.chmod(dest, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) except Exception as e: logger.exception(e) return False return True def make_vm_image(self, src, dest): src = join_path(self.image_base, src) dest = join_path(self.vm_base, dest) try: shutil.copyfile(src, dest) except Exception as e: logger.exception(e) return False return True def resize_vm_image(self, path, size): path = join_path(self.vm_base, path) command = ["qemu-img", "resize", "-f", "raw", path, "{}M".format(size)] if self.execute_command(command): return True else: self.delete_vm_image(path) return False def delete_vm_image(self, path): path = join_path(self.vm_base, path) try: os.remove(path) except Exception as e: logger.exception(e) return False return True def vm_path_string(self, path): return join_path(self.vm_base, path) def qemu_path_string(self, path): return self.vm_path_string(path) def is_vm_image_exists(self, path): path = join_path(self.vm_base, path) command = ["ls", path] return self.execute_command(command, report=False) class CEPHBasedImageStorageHandler(ImageStorageHandler): handler_name = 'Ceph' def import_image(self, src, dest, protect=False): dest = join_path(self.image_base, dest) import_command = ["rbd", "import", src, dest] commands = [import_command] if protect: snap_create_command = ["rbd", "snap", "create", "{}@protected".format(dest)] snap_protect_command = ["rbd", "snap", "protect", "{}@protected".format(dest)] commands.append(snap_create_command) commands.append(snap_protect_command) result = True for command in commands: result = result and self.execute_command(command) return result def make_vm_image(self, src, dest): src = join_path(self.image_base, src) dest = join_path(self.vm_base, dest) command = ["rbd", "clone", "{}@protected".format(src), dest] return self.execute_command(command) def resize_vm_image(self, path, size): path = join_path(self.vm_base, path) command = ["rbd", "resize", path, "--size", size] return self.execute_command(command) def delete_vm_image(self, path): path = join_path(self.vm_base, path) command = ["rbd", "rm", path] return self.execute_command(command) def vm_path_string(self, path): return join_path(self.vm_base, path) def qemu_path_string(self, path): return "rbd:{}".format(self.vm_path_string(path)) def is_vm_image_exists(self, path): path = join_path(self.vm_base, path) command = ["rbd", "info", path] return self.execute_command(command, report=False) def get_storage_handler(): __storage_backend = config['storage']['storage_backend'] if __storage_backend == 'filesystem': return FileSystemBasedImageStorageHandler( vm_base=config['storage']['vm_dir'], image_base=config['storage']['image_dir'] ) elif __storage_backend == 'ceph': return CEPHBasedImageStorageHandler( vm_base=config['storage']['ceph_vm_pool'], image_base=config['storage']['ceph_image_pool'] ) else: raise Exception('Unknown Image Storage Handler')