diff --git a/backends.py b/backends.py index 24808d1fe89185e571fbb0136bc06fa63878ecd6..6c661551b47d07f75d15e123ebb4c717126b4165 100644 --- a/backends.py +++ b/backends.py @@ -31,37 +31,6 @@ class VMUtils_omni(vmutils10.VMUtils10): return nics -class Flavor(object): - def __init__(self, flavor_name, cpucores_num, ram_capacity, disk_capacity, flavor_id=None): - self.flavor_id = flavor_id or self.generate_unique_id() # 初始化虚拟机规格的唯一ID - self.flavor_name = flavor_name # 虚拟机规格名称 - self.cpucores_num = str(cpucores_num) # CPU核心数(转换为字符串类型) - self.ram_capacity = ram_capacity # 内存大小(MB) - self.disk_capacity = disk_capacity # 磁盘大小(GB) - - def generate_unique_id(self): - # 生成唯一ID的方法,使用时间戳和随机数结合 - timestamp = int(time.time() * 1000) # 当前时间的毫秒级别时间戳 - random_part = random.randint(0, 1000) # 0到1000之间的随机数 - return f'{timestamp}-{random_part}' - - def to_dict(self): - # 将Flavor对象转换为字典 - return { - 'flavor_id': self.flavor_id, - 'flavor_name': self.flavor_name, - 'cpucores_num': self.cpucores_num, - 'ram_capacity': self.ram_capacity, - 'disk_capacity': self.disk_capacity - } - - @classmethod - def from_dict(cls, data): - # 从字典创建Flavor对象的类方法 - return cls(data['flavor_name'], int(data['cpucores_num']), data['ram_capacity'], data['disk_capacity'], - data.get('flavor_id')) - - class VMOps(object): _ROOT_DISK_CTRL_ADDR = 0 diff --git a/eulerlauncher/backends/flavor_handler.py b/eulerlauncher/backends/flavor_handler.py new file mode 100644 index 0000000000000000000000000000000000000000..0e4716da3db2f486c7d899e4644fe1014b4cfdb1 --- /dev/null +++ b/eulerlauncher/backends/flavor_handler.py @@ -0,0 +1,71 @@ +import json +import ssl + +from eulerlauncher.utils.objs import Flavor + + +ssl._create_default_https_context = ssl._create_unverified_context + + +class FlavorHandler(object): + + def __init__(self, conf, work_dir, flavor_dir, flavor_record_file, logger) -> None: + self.conf = conf + self.work_dir = work_dir + self.flavor_dir = flavor_dir + self.flavor_record_file = flavor_record_file + self.LOG = logger + + def create_flavor(self, flavor_name, cpucores_num, ram_capacity, disk_capacity): + # 创建虚拟机规格 + flavor = Flavor(flavor_name, cpucores_num, ram_capacity, disk_capacity) + self.save_flavor(flavor) + + def delete_flavor(self, flavor_id): + # 删除虚拟机规格 + flavors = self.load_flavors() + flavor_to_delete = None + + # 查找要删除的虚拟机规格 + for flavor in flavors: + if flavor.flavor_id == flavor_id: + flavor_to_delete = flavor + break + + if flavor_to_delete: + flavors.remove(flavor_to_delete) + self.save_flavors(flavors) + print(f"Flavor with flavor_id {flavor_id} has been deleted.") + else: + print(f"Flavor with flavor_id {flavor_id} not found.") + + def save_flavor(self, flavor): + # 将Flavor对象保存到配置文件 + flavors = self.load_flavors() + flavors.append(flavor.to_dict()) + with open(self.flavor_record_file, 'w') as f: + json.dump(flavors, f) + + def load_flavors(self): + try: + # 从配置文件加载Flavor对象列表 + with open('flavors.json', 'r') as f: + data = json.load(f) + return [Flavor.from_dict(item) for item in data] + except FileNotFoundError: + return [] # 如果配置文件不存在,返回空列表 + + def print_all_flavors(self): + flavors = self.load_flavors() + if not flavors: + print("No virtual machine flavors found.") + return + + print("All Virtual Machine Flavors:") + for flavor in flavors: + print(f"Flavor ID: {flavor.flavor_id}") + print(f"Flavor Name: {flavor.flavor_name}") + print(f"CPU Cores: {flavor.cpucores_num}") + print(f"RAM Capacity: {flavor.ram_capacity} MB") + print(f"Disk Capacity: {flavor.disk_capacity} GB") + print("-------------") \ No newline at end of file diff --git a/eulerlauncher/backends/win/vmops.py b/eulerlauncher/backends/win/vmops.py index dea10a816879c98f4b498783c4af46f7b84af953..489b6646cb7c9cc7405a1947c784d76def944c65 100644 --- a/eulerlauncher/backends/win/vmops.py +++ b/eulerlauncher/backends/win/vmops.py @@ -32,36 +32,6 @@ class VMUtils_omni(vmutils10.VMUtils10): nics = self._get_vm_nics(vm_name) return nics -class Flavor(object): - def __init__(self, flavor_name, cpucores_num, ram_capacity, disk_capacity, flavor_id=None): - self.flavor_id = flavor_id or self.generate_unique_id() # 初始化虚拟机规格的唯一ID - self.flavor_name = flavor_name # 虚拟机规格名称 - self.cpucores_num = str(cpucores_num) # CPU核心数(转换为字符串类型) - self.ram_capacity = ram_capacity # 内存大小(MB) - self.disk_capacity = disk_capacity # 磁盘大小(GB) - - def generate_unique_id(self): - # 生成唯一ID的方法,使用时间戳和随机数结合 - timestamp = int(time.time() * 1000) # 当前时间的毫秒级别时间戳 - random_part = random.randint(0, 1000) # 0到1000之间的随机数 - return f'{timestamp}-{random_part}' - - def to_dict(self): - # 将Flavor对象转换为字典 - return { - 'flavor_id': self.flavor_id, - 'flavor_name': self.flavor_name, - 'cpucores_num': self.cpucores_num, - 'ram_capacity': self.ram_capacity, - 'disk_capacity': self.disk_capacity - } - - @classmethod - def from_dict(cls, data): - # 从字典创建Flavor对象的类方法 - return cls(data['flavor_name'], int(data['cpucores_num']), data['ram_capacity'], data['disk_capacity'], - data.get('flavor_id')) - class VMOps(object): _ROOT_DISK_CTRL_ADDR = 0 @@ -140,60 +110,6 @@ class VMOps(object): return info - def create_flavor(self, flavor_name, cpucores_num, ram_capacity, disk_capacity): - # 创建虚拟机规格 - flavor = Flavor(flavor_name, cpucores_num, ram_capacity, disk_capacity) - self.save_flavor(flavor) - - def delete_flavor(self, flavor_id): - # 删除虚拟机规格 - flavors = self.load_flavors() - flavor_to_delete = None - - # 查找要删除的虚拟机规格 - for flavor in flavors: - if flavor.flavor_id == flavor_id: - flavor_to_delete = flavor - break - - if flavor_to_delete: - flavors.remove(flavor_to_delete) - self.save_flavors(flavors) - print(f"Flavor with flavor_id {flavor_id} has been deleted.") - else: - print(f"Flavor with flavor_id {flavor_id} not found.") - - def save_flavor(self, flavor): - # 将Flavor对象保存到配置文件 - flavors = self.load_flavors() - flavors.append(flavor.to_dict()) - with open('flavors.json', 'w') as f: - json.dump(flavors, f) - - def load_flavors(self): - try: - # 从配置文件加载Flavor对象列表 - with open('flavors.json', 'r') as f: - data = json.load(f) - return [Flavor.from_dict(item) for item in data] - except FileNotFoundError: - return [] # 如果配置文件不存在,返回空列表 - - def print_all_flavors(self): - flavors = self.load_flavors() - if not flavors: - print("No virtual machine flavors found.") - return - - print("All Virtual Machine Flavors:") - for flavor in flavors: - print(f"Flavor ID: {flavor.flavor_id}") - print(f"Flavor Name: {flavor.flavor_name}") - print(f"CPU Cores: {flavor.cpucores_num}") - print(f"RAM Capacity: {flavor.ram_capacity} MB") - print(f"Disk Capacity: {flavor.disk_capacity} GB") - print("-------------") - def create_vm(self, vm_name, vnuma_enabled, vm_gen, instance_path, meta_data): self._vmutils.create_vm(vm_name, diff --git a/eulerlauncher/grpcs/client.py b/eulerlauncher/grpcs/client.py index 61ad24347c34b93821e9a86990d354fb481657b8..da7f0f9c6a5114fefbe0a252949261a06e722de2 100644 --- a/eulerlauncher/grpcs/client.py +++ b/eulerlauncher/grpcs/client.py @@ -3,9 +3,10 @@ import os from eulerlauncher.grpcs.eulerlauncher_grpc import images_pb2_grpc from eulerlauncher.grpcs.eulerlauncher_grpc import instances_pb2_grpc -from eulerlauncher.grpcs import images, instances +from eulerlauncher.grpcs.eulerlauncher_grpc import flavors_pb2_grpc +from eulerlauncher.grpcs import images, instances, flavors from eulerlauncher.utils import constants -from eulerlauncher.utils import utils as omnivirt_utils +from eulerlauncher.utils import utils as eulerlauncher_utils class Client(object): @@ -16,11 +17,13 @@ class Client(object): images_client = images_pb2_grpc.ImageGrpcServiceStub(channel) instances_client = instances_pb2_grpc.InstanceGrpcServiceStub(channel) + flavors_client = flavors_pb2_grpc.FlavorGrpcServiceStub(channel) self._images = images.Image(images_client) self._instances = instances.Instance(instances_client) + self._flavors = flavors.Flavor(flavors_client) - @omnivirt_utils.response2dict + @eulerlauncher_utils.response2dict def list_images(self, filters=None): """ [IMAGE] List images @@ -30,14 +33,14 @@ class Client(object): return self._images.list() - @omnivirt_utils.response2dict + @eulerlauncher_utils.response2dict def download_image(self, name): """ Download image """ return self._images.download(name) - @omnivirt_utils.response2dict + @eulerlauncher_utils.response2dict def load_image(self, name, path): """ Load local image file """ @@ -64,14 +67,38 @@ class Client(object): return self._images.load(name, path) - @omnivirt_utils.response2dict + @eulerlauncher_utils.response2dict def delete_image(self, name): """ Delete the requested image """ return self._images.delete(name) - @omnivirt_utils.response2dict + @eulerlauncher_utils.response2dict + def list_flavors(self, filters=None): + """ [IMAGE] List flavors + + :param filters(list): None + :return: dict -- list of flavors' info + """ + + return self._flavors.list() + + @eulerlauncher_utils.response2dict + def create_flavor(self, name): + """ Create a new flavor + """ + + return self._flavors.create(name) + + @eulerlauncher_utils.response2dict + def delete_flavor(self, name): + """ Delete the requested image + """ + + return self._flavors.delete(name) + + @eulerlauncher_utils.response2dict def list_instances(self): """ List instances :return: dict -- list of instances' info @@ -79,7 +106,7 @@ class Client(object): return self._instances.list() - @omnivirt_utils.response2dict + @eulerlauncher_utils.response2dict def create_instance(self, name, image, arch): """ Create instance :return: dict -- dict of instance's info @@ -88,21 +115,21 @@ class Client(object): return self._instances.create(name, image, arch) - @omnivirt_utils.response2dict + @eulerlauncher_utils.response2dict def delete_instance(self, name): """ Delete the requested instance """ return self._instances.delete(name) - @omnivirt_utils.response2dict + @eulerlauncher_utils.response2dict def take_snapshot(self, vm_name, snapshot_name, export_path): """ Take snapshot """ return self._instances.take_snapshot(vm_name, snapshot_name, export_path) - @omnivirt_utils.response2dict + @eulerlauncher_utils.response2dict def export_development_image(self, vm_name, image_name, export_path, pwd): """ Export Python/Go/Java development image """ diff --git a/eulerlauncher/grpcs/flavors.py b/eulerlauncher/grpcs/flavors.py new file mode 100644 index 0000000000000000000000000000000000000000..67ca766cb51782d94f98eed562aaf57254515cad --- /dev/null +++ b/eulerlauncher/grpcs/flavors.py @@ -0,0 +1,24 @@ +from eulerlauncher.grpcs.eulerlauncher_grpc import flavors_pb2 + + +class Flavor(object): + def __init__(self, client): + self.client = client + + def list(self): + """Get list of flavors""" + request = flavors_pb2.ListFlavorsRequest + response = self.client.list_flavors(request) + return response + + def create(self): + """Create a new flavor""" + request = flavors_pb2.CreateFlavorRequest + response = self.clent.create_flavor(request) + return response + + def delete(self, name): + """Delete the requested flavor""" + request = flavors_pb2.DeleteFlavorRequest(name=name) + response = self.client.delete_image(request) + return response diff --git a/eulerlauncher/services/flavor_service.py b/eulerlauncher/services/flavor_service.py new file mode 100644 index 0000000000000000000000000000000000000000..5406a8eb3e669fe49d9aaffcc18a4b389ea01f95 --- /dev/null +++ b/eulerlauncher/services/flavor_service.py @@ -0,0 +1,35 @@ +import logging +import os + +from eulerlauncher.backends import flavor_handler +from eulerlauncher.grpcs.eulerlauncher_grpc import flavors_pb2, flavors_pb2_grpc + +LOG = logging.getLogger(__name__) + + +class FlavorService(flavors_pb2_grpc.FlavorGrpcServiceServicer): + ''' + The Flavor GRPC Handler + ''' + + def __init__(self, arch, host_os, conf, svc_base_dir) -> None: + self.CONF = conf + self.svc_base_dir = svc_base_dir + self.work_dir = self.CONF.conf.get('default', 'work_dir') + self.flavor_dir = os.path.join(self.work_dir, 'flavor') + self.flavor_record_file = os.path.join(self.flavor_dir, 'flavors.json') + self.backend = flavor_handler.FlavorHandler( + self.CONF, self.work_dir, self.flavor_dir, self.flavor_record_file, + LOG, self.svc_base_dir) + + def list_flavors(self, request, context): + LOG.debug(f"Get request to list all flavors ...") + pass + + def create_flavor(self, request, context): + LOG.debug(f"Get request to create flavor ...") + pass + + def delete_flavor(self, request, context): + LOG.debug(f"Get request to delete a flavor ...") + pass \ No newline at end of file diff --git a/eulerlauncher/services/imager_service.py b/eulerlauncher/services/imager_service.py index d38d65b5db9ae4aa0e0aab96188996fd56fa0ffd..0699a4f745c61c1793f7d0fe7ec4c7184cc617c5 100644 --- a/eulerlauncher/services/imager_service.py +++ b/eulerlauncher/services/imager_service.py @@ -4,8 +4,8 @@ import os from eulerlauncher.backends.mac import image_handler as mac_image_handler from eulerlauncher.backends.win import image_handler as win_image_handler from eulerlauncher.grpcs.eulerlauncher_grpc import images_pb2, images_pb2_grpc -from eulerlauncher.utils import constants as omni_constants -from eulerlauncher.utils import utils as omni_utils +from eulerlauncher.utils import constants as launcher_constants +from eulerlauncher.utils import utils as launcher_utils LOG = logging.getLogger(__name__) @@ -32,7 +32,7 @@ class ImagerService(images_pb2_grpc.ImageGrpcServiceServicer): def list_images(self, request, context): LOG.debug(f"Get request to list images ...") - all_images = omni_utils.load_json_data(self.img_record_file) + all_images = launcher_utils.load_json_data(self.img_record_file) ret = [] for _, images in all_images.items(): @@ -41,9 +41,9 @@ class ImagerService(images_pb2_grpc.ImageGrpcServiceServicer): image.name = img['name'] image.location = img['location'] image.status = img['status'] - if image.status == omni_constants.IMAGE_STATUS_DOWNLOADING: + if image.status == launcher_constants.IMAGE_STATUS_DOWNLOADING: image.status = self.backend.download_progress_bar(image.name) - elif image.status == omni_constants.IMAGE_STATUS_LOADING: + elif image.status == launcher_constants.IMAGE_STATUS_LOADING: image.status = self.backend.load_progress_bar(image.name) ret.append(image) LOG.debug(f"Responded: {ret}") @@ -51,14 +51,14 @@ class ImagerService(images_pb2_grpc.ImageGrpcServiceServicer): def download_image(self, request, context): LOG.debug(f"Get request to download image: {request.name} ...") - all_images = omni_utils.load_json_data(self.img_record_file) + all_images = launcher_utils.load_json_data(self.img_record_file) if request.name not in all_images['remote'].keys(): LOG.debug(f'Image: {request.name} not valid for download') msg = f'Error: Image {request.name} is not valid for download, please check image name from REMOTE IMAGE LIST using "images" command ...' return images_pb2.GeneralImageResponse(ret=1, msg=msg) - @omni_utils.asyncwrapper + @launcher_utils.asyncwrapper def do_download(images, name): self.backend.download_and_transform(images, name) @@ -71,16 +71,16 @@ class ImagerService(images_pb2_grpc.ImageGrpcServiceServicer): LOG.debug(f"Get request to load image: {request.name} from path: {request.path} ...") - supported, fmt = omni_utils.check_file_tail( - request.path, omni_constants.IMAGE_LOAD_SUPPORTED_TYPES + omni_constants.IMAGE_LOAD_SUPPORTED_TYPES_COMPRESSED) + supported, fmt = launcher_utils.check_file_tail( + request.path, launcher_constants.IMAGE_LOAD_SUPPORTED_TYPES + launcher_constants.IMAGE_LOAD_SUPPORTED_TYPES_COMPRESSED) if not supported: - supported_fmt = ', '.join(omni_constants.IMAGE_LOAD_SUPPORTED_TYPES + omni_constants.IMAGE_LOAD_SUPPORTED_TYPES_COMPRESSED) + supported_fmt = ', '.join(launcher_constants.IMAGE_LOAD_SUPPORTED_TYPES + launcher_constants.IMAGE_LOAD_SUPPORTED_TYPES_COMPRESSED) msg = f'Unsupported image format, the current supported formats are: {supported_fmt}.' return images_pb2.GeneralImageResponse(ret=1, msg=msg) - all_images = omni_utils.load_json_data(self.img_record_file) + all_images = launcher_utils.load_json_data(self.img_record_file) msg = f'Loading: {request.name}, this might take a while, please check image status with "images" command.' update = False @@ -91,7 +91,7 @@ class ImagerService(images_pb2_grpc.ImageGrpcServiceServicer): msg = f'Replacing: {request.name}, with new image file: {request.path}, this might take a while, please check image status with "images" command.' update = True - @omni_utils.asyncwrapper + @launcher_utils.asyncwrapper def do_load(images, name, path, fmt, update): self.backend.load_and_transform(images, name, path, fmt, update) @@ -101,7 +101,7 @@ class ImagerService(images_pb2_grpc.ImageGrpcServiceServicer): def delete_image(self, request, context): LOG.debug(f"Get request to delete image: {request.name} ...") - images = omni_utils.load_json_data(self.img_record_file) + images = launcher_utils.load_json_data(self.img_record_file) ret = self.backend.delete_image(images, request.name) if ret == 0: msg = f'Image: {request.name} has been successfully deleted.' diff --git a/eulerlauncher/utils/objs.py b/eulerlauncher/utils/objs.py index 714e26d3247e638c9c7748080bb0550d2f9006d5..0e54a53cac0e1d0886d0c67b4d170d0943959ad3 100644 --- a/eulerlauncher/utils/objs.py +++ b/eulerlauncher/utils/objs.py @@ -1,5 +1,7 @@ -import os import configparser +import os +import random +import time from eulerlauncher.utils import exceptions from eulerlauncher.utils import constants @@ -45,6 +47,37 @@ class Image(object): self.path = img_dict['path'] +class Flavor(object): + def __init__(self, flavor_name, cpucores_num, ram_capacity, disk_capacity, flavor_id=None): + self.flavor_id = flavor_id or self.generate_unique_id() # 初始化虚拟机规格的唯一ID + self.flavor_name = flavor_name # 虚拟机规格名称 + self.cpucores_num = str(cpucores_num) # CPU核心数(转换为字符串类型) + self.ram_capacity = ram_capacity # 内存大小(MB) + self.disk_capacity = disk_capacity # 磁盘大小(GB) + + def generate_unique_id(self): + # 生成唯一ID的方法,使用时间戳和随机数结合 + timestamp = int(time.time() * 1000) # 当前时间的毫秒级别时间戳 + random_part = random.randint(0, 1000) # 0到1000之间的随机数 + return f'{timestamp}-{random_part}' + + def to_dict(self): + # 将Flavor对象转换为字典 + return { + 'flavor_id': self.flavor_id, + 'flavor_name': self.flavor_name, + 'cpucores_num': self.cpucores_num, + 'ram_capacity': self.ram_capacity, + 'disk_capacity': self.disk_capacity + } + + @classmethod + def from_dict(cls, data): + # 从字典创建Flavor对象的类方法 + return cls(data['flavor_name'], int(data['cpucores_num']), data['ram_capacity'], data['disk_capacity'], + data.get('flavor_id')) + + class Conf(object): def __init__(self, config_file) -> None: