forked from uncloud/uncloud
208 lines
6.1 KiB
Python
208 lines
6.1 KiB
Python
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 uncloud.common.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")
|