diff --git a/.gitignore b/.gitignore index 7c9d0f2ebe2b0f9c6cf7986c8a2ec63695701469..0f5908931a247c2cdd3af168aa29e182a601f1a9 100644 --- a/.gitignore +++ b/.gitignore @@ -26,12 +26,12 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST +.DS_Store # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest -*.spec # Installer logs pip-log.txt diff --git a/docs/developer-manual.md b/docs/developer-manual.md index 01ff36e4963a68ca89d89007414817be9ad1fee6..ae88c758e3017e831bb04e822e9929e8437283d2 100644 --- a/docs/developer-manual.md +++ b/docs/developer-manual.md @@ -64,7 +64,7 @@ EulerLauncher可执行文件包括以下几个部分: 项目源码中已包含用于构建EulerLauncher的Spec脚本`EulerLauncher-MacOS.spec`, 若非必要,请勿修改该文件,使用一下命令开始构建: ``` Shell - pyinstaller --clean --noconfirm specs/EulerLauncher-MacOS.spec + pyinstaller --clean --noconfirm specs/EulerLauncher-Mac.spec ``` 构建`eulerlauncher` CLI 及 `install` 脚本, cli与install之间有依赖关系,请严格按照下面的顺序进行构建: diff --git a/docs/mac-user-manual.md b/docs/mac-user-manual.md index f7d5771451beea723b5a921b66090329ebfb7b05..74f6b1a24c31109ba76d8ec9db8d84c3714b7e44 100644 --- a/docs/mac-user-manual.md +++ b/docs/mac-user-manual.md @@ -23,11 +23,14 @@ Homebrew是一款Mac OS平台下的软件包管理工具,拥有安装、卸载 ### 安装Qemu及wget -**EulerLauncher**在MacOS上运行依赖于`QEMU`,镜像下载依赖于`wget`,使用`Homebrew`可以非常方便的下载和管理此类软件,使用以下命令进行安装: +**EulerLauncher**在MacOS上运行依赖于`QEMU`和`libvirt`,镜像下载依赖于`wget`,使用`Homebrew`可以非常方便的下载和管理此类软件,使用以下命令进行安装: ``` Shell brew install qemu brew install wget +brew install libvirt +brew tap jeffreywildman/homebrew-virt-manager +brew install virt-viewer ``` ### 配置sudo免密码权限 @@ -58,18 +61,6 @@ brew install wget 2. 配置**EulerLauncher**: - - 查看`qemu`及`wget`所处位置,`qemu`二进制文件在不同架构下名称不同,请根据自身情况选择正确的名称(Apple Silicon: qemu-system-aarch64; Intel: qemu-system-x86_64): - ``` Shell - which wget - which qemu-system-{host_arch} - ``` - 参考输出: - ``` - /opt/homebrew/bin/wget - /opt/homebrew/bin/qemu-system-aarch64 - ``` - 查看完成后,记录路径结果,在接下来的步骤中将会使用到。 - - 打开`eulerlauncher.conf`并进行配置: ``` Shell sudo vi /Library/Application\ Support/org.openeuler.eulerlauncher/eulerlauncher.conf @@ -80,8 +71,6 @@ brew install wget [default] log_dir = # 日志文件位置(xxx.log) work_dir = # eulerlauncher工作目录,用于存储虚拟机镜像、虚拟机文件等 - wget_dir = # wget的可执行文件路径,请参考上一步的内容进行配置 - qemu_dir = # qemu的可执行文件路径,请参考上一步的内容进行配置 debug = True [vm] @@ -125,7 +114,7 @@ eulerlauncher images 2. 下载远端镜像 ```Shell -eulerlauncher download-image 22.03-LTS +eulerlauncher image download 22.03-LTS Downloading: 22.03-LTS, this might take a while, please check image status with "images" command. ``` @@ -133,7 +122,7 @@ Downloading: 22.03-LTS, this might take a while, please check image status with 镜像下载请求是一个异步请求,具体的下载动作将在后台完成,具体耗时与你的网络情况相关,整体的镜像下载流程包括下载、解压缩、格式转换等相关子流程,在下载过程中可以通过 `image` 命令随时查看下载进展与镜像状态: ```Shell -eulerlauncher images +eulerlauncher image list +-----------+----------+--------------+ | Images | Location | Status | @@ -148,7 +137,7 @@ eulerlauncher images 当镜像状态转变为 `Ready` 时,表示镜像下载完成,处于 `Ready` 状态的镜像可被用来创建虚拟机: ```Shell -eulerlauncher images +eulerlauncher image list +-----------+----------+--------------+ | Images | Location | Status | @@ -164,7 +153,7 @@ eulerlauncher images 用户也可以加载自定义镜像或预先下载到本地的镜像到EulerLauncher中用于创建自定义虚拟机: ```Shell -eulerlauncher load-image --path {image_file_path} IMAGE_NAME +eulerlauncher image load --path {image_file_path} IMAGE_NAME ``` 当前支持加载的镜像格式有 `xxx.qcow2.xz`,`xxx.qcow2` @@ -172,7 +161,7 @@ eulerlauncher load-image --path {image_file_path} IMAGE_NAME 例如: ```Shell -eulerlauncher load-image --path /opt/openEuler-22.03-LTS-x86_64.qcow2.xz 2203-load +eulerlauncher image load --path /opt/openEuler-22.03-LTS-x86_64.qcow2.xz 2203-load Loading: 2203-load, this might take a while, please check image status with "images" command. ``` @@ -180,7 +169,7 @@ Loading: 2203-load, this might take a while, please check image status with "ima 将位于 `/opt` 目录下的 `openEuler-22.03-LTS-x86_64.qcow2.xz` 加载到EulerLauncher系统中,并命名为 `2203-load`,与下载命令一样,加载命令也是一个异步命令,用户需要用镜像列表命令查询镜像状态直到显示为 `Ready`, 但相对于直接下载镜像,加载镜像的速度会快很多: ```Shell -eulerlauncher images +eulerlauncher image list +-----------+----------+--------------+ | Images | Location | Status | @@ -190,7 +179,7 @@ eulerlauncher images | 2203-load | Local | Loading | +-----------+----------+--------------+ -eulerlauncher images +eulerlauncher image list +-----------+----------+--------------+ | Images | Location | Status | @@ -206,7 +195,7 @@ eulerlauncher images 通过下面的命令将镜像从EulerLauncher系统中删除: ```Shell -eulerlauncher delete-image 2203-load +eulerlauncher image delete 2203-load Image: 2203-load has been successfully deleted. ``` @@ -216,7 +205,7 @@ Image: 2203-load has been successfully deleted. 1. 获取虚拟机列表: ```shell -eulerlauncher list +eulerlauncher instance list +----------+-----------+---------+---------------+ | Name | Image | State | IP | @@ -236,22 +225,38 @@ eulerlauncher list ```Shell ssh root@{instance_ip} ``` + 若使用的是openEuler社区提供的官方镜像,则默认用户为 `root` 默认密码为 `openEuler12#$` 3. 创建虚拟机 ```Shell -eulerlauncher launch --image {image_name} {instance_name} +eulerlauncher instance launch --image {image_name} {instance_name} ``` 通过 `--image` 指定镜像,同时指定虚拟机名称。 4. 删除虚拟机 ```Shell -eulerlauncher delete-instance {instance_name} +eulerlauncher instance delete {instance_name} ``` 根据虚拟机名称删除指定的虚拟机。 +5. 暂停虚拟机 +```Shell +eulerlauncher instance suspend {instance_name} +``` + +6. 恢复虚拟机 +```Shell +eulerlauncher instance resume {instance_name} +``` + +7. 虚拟机控制台 +```Shell +eulerlauncher instance console {instance_name} +``` + [1]: https://developer.apple.com/documentation/vmnet [2]: https://gitee.com/openeuler/eulerlauncher/releases \ No newline at end of file diff --git a/etc/bin/config-env.bat b/etc/bin/config-env.bat index b77e085b36775b692013a4e9f0047d10b9db221c..e39481017277140d79de2c6ea4a658cf6ca29640 100644 --- a/etc/bin/config-env.bat +++ b/etc/bin/config-env.bat @@ -1,8 +1,8 @@ @ECHO OFF REM usage: append_system_path "path" set CURR_DIR=%~dp0 -set QEMU_DIR=%~dp0qemu-img\ +set qemu_bin=%~dp0qemu-img\ SET Key="HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" FOR /F "usebackq tokens=2*" %%A IN (`REG QUERY %Key% /v PATH`) DO Set CurrPath=%%B ECHO %CurrPath% > system_path_bak.txt -SETX PATH "%CurrPath%";%CURR_DIR%;%QEMU_DIR% /m \ No newline at end of file +SETX PATH "%CurrPath%";%CURR_DIR%;%qemu_bin% /m \ No newline at end of file diff --git a/etc/eulerlauncher.conf b/etc/eulerlauncher.conf index a101a510a7e4508fa09797c0573d0299cf152f04..66b2c0d284b2a317a6bd869588462a29324e0227 100644 --- a/etc/eulerlauncher.conf +++ b/etc/eulerlauncher.conf @@ -1,8 +1,6 @@ [default] -log_dir = -work_dir = -wget_dir = -qemu_dir = +log_dir = +work_dir = debug = True [vm] diff --git a/etc/supported_images.json b/etc/supported_images.json index 91d1c3adf141164a6bfced355dd09ffaeb65f11b..0d306db40d1db9c4dc161dc2c6dc05de54aa8e09 100644 --- a/etc/supported_images.json +++ b/etc/supported_images.json @@ -1,14 +1,14 @@ { "x86_64": { + "20.03-LTS": "https://mirrors.tuna.tsinghua.edu.cn/openeuler/openEuler-20.03-LTS/virtual_machine_img/x86_64/openEuler-20.03-LTS-x86_64.qcow2.xz", "22.03-LTS": "https://mirrors.tuna.tsinghua.edu.cn/openeuler/openEuler-22.03-LTS/virtual_machine_img/x86_64/openEuler-22.03-LTS-x86_64.qcow2.xz", - "22.09": "https://mirrors.tuna.tsinghua.edu.cn/openeuler/openEuler-22.09/virtual_machine_img/x86_64/openEuler-22.09-x86_64.qcow2.xz", - "23.03": "https://mirrors.tuna.tsinghua.edu.cn/openeuler/openEuler-23.03/virtual_machine_img/x86_64/openEuler-23.03-x86_64.qcow2.xz" + "24.03-LTS": "https://mirrors.tuna.tsinghua.edu.cn/openeuler/openEuler-24.03/virtual_machine_img/x86_64/openEuler-24.03-LTS-x86_64.qcow2.xz" }, "aarch64": { + "20.03-LTS": "https://mirrors.tuna.tsinghua.edu.cn/openeuler/openEuler-20.03-LTS/virtual_machine_img/aarch64/openEuler-20.03-LTS-aarch64.qcow2.xz", "22.03-LTS": "https://mirrors.tuna.tsinghua.edu.cn/openeuler/openEuler-22.03-LTS/virtual_machine_img/aarch64/openEuler-22.03-LTS-aarch64.qcow2.xz", - "22.09": "https://mirrors.tuna.tsinghua.edu.cn/openeuler/openEuler-22.09/virtual_machine_img/aarch64/openEuler-22.09-aarch64.qcow2.xz", - "23.03": "https://mirrors.tuna.tsinghua.edu.cn/openeuler/openEuler-23.03/virtual_machine_img/aarch64/openEuler-23.03-aarch64.qcow2.xz" + "24.03-LTS": "https://mirrors.tuna.tsinghua.edu.cn/openeuler/openEuler-24.03-LTS/virtual_machine_img/aarch64/openEuler-24.03-LTS-aarch64.qcow2.xz" } } diff --git a/eulerlauncher/backends/image_handler.py b/eulerlauncher/backends/image_handler.py new file mode 100644 index 0000000000000000000000000000000000000000..4382fa2472e9bb7810edd8de7a608bab9ee07ec0 --- /dev/null +++ b/eulerlauncher/backends/image_handler.py @@ -0,0 +1,140 @@ +import lzma +import wget +import os +import subprocess +import shutil +import ssl + +from eulerlauncher.utils import constants +from eulerlauncher.utils import utils + + +ssl._create_default_https_context = ssl._create_unverified_context + + +class MacImageHandler(object): + + def __init__(self, CONF, work_dir, image_dir, LOG) -> None: + self.CONF = CONF + self.work_dir = work_dir + self.image_dir = image_dir + self.image_record_path = os.path.join(image_dir, 'images.json') + self.LOG = LOG + + + def list_images(self): + image_record = utils.load_json_data(self.image_record_path) + all_images = list(image_record["remote"].values()) + list(image_record["local"].values()) + return all_images + + + def download_image(self, name): + image_record = utils.load_json_data(self.image_record_path) + if name not in image_record['remote'].keys(): + self.LOG.debug(f'Image: {name} not valid for download') + return 1 + + @utils.asyncwrapper + def download_and_transform(name): + image_record = utils.load_json_data(self.image_record_path) + image_url = image_record['remote'][name]['path'] + image_file = wget.filename_from_url(image_url) + image_path = os.path.join(self.image_dir, image_file) + + # Download the image + self.LOG.debug(f'Downloading image: {name} from remote repo ...') + image_record['local'][name] = { + 'name': name, + 'location': constants.IMAGE_LOCATION_LOCAL, + 'status': constants.IMAGE_STATE_MAP[2], + 'path': image_url + } + utils.save_json_data(self.image_record_path, image_record) + wget_bin = shutil.which('wget_bin') + download_cmd = [wget_bin, image_url, + '-O', image_path, + '--no-check-certificate', + '--user-agent', 'Mozilla'] + self.LOG.debug(' '.join(download_cmd)) + subprocess.call(' '.join(download_cmd), shell=True) + self.LOG.debug(f'Image: {name} succesfully downloaded from remote repo ...') + + # Decompress the image + self.LOG.debug(f'Decompressing image: {image_file} ...') + with open(image_path, 'rb') as pr, open(os.path.join(self.image_dir, name), 'wb') as pw: + data = pr.read() + data_dec = lzma.decompress(data) + pw.write(data_dec) + + self.LOG.debug(f'Cleanup temp files ...') + os.remove(image_path) + + # Record local image + image_record = utils.load_json_data(self.image_record_path) + image_record['local'][name]['status'] = constants.IMAGE_STATE_MAP[4] + image_record['local'][name]['path'] = os.path.join(self.image_dir, name) + utils.save_json_data(self.image_record_path, image_record) + self.LOG.debug(f'Image: {name} is ready ...') + + download_and_transform(name) + return 0 + + + def delete_image(self, name): + image_record = utils.load_json_data(self.image_record_path) + if name not in image_record['local'].keys(): + self.LOG.debug(f'Image: {name} not valid for delete') + return 1 + + image_path = image_record['local'][name]['path'] + self.LOG.debug(f'Deleting: {name} from image database ...') + os.remove(image_path) + del image_record['local'][name] + utils.save_json_data(self.image_record_path, image_record) + self.LOG.debug(f'Image: {name} succesfully deleted ...') + return 0 + + + def load_image(self, name, path): + if not os.path.exists(path): + self.LOG.debug(f'Image: {path} does not exist') + return 1 + + supported, fmt = utils.check_format(path, constants.IMAGE_LOAD_SUPPORTED_TYPES) + if not supported: + self.LOG.debug(f'Image: {name} not valid for load') + return 2 + + @utils.asyncwrapper + def load_and_transform(name, path): + image_record = utils.load_json_data(self.image_record_path) + image_path = os.path.join(self.image_dir, name) + supported, fmt = utils.check_format(path, constants.IMAGE_LOAD_SUPPORTED_TYPES) + self.LOG.debug(f'Loading image: {name} from image file: {path} ...') + image_record['local'][name] = { + 'name': name, + 'location': constants.IMAGE_LOCATION_LOCAL, + 'status': constants.IMAGE_STATE_MAP[3], + 'path': '' + } + utils.save_json_data(self.image_record_path, image_record) + + if fmt == 'qcow2': + shutil.copyfile(path, image_path) + else: + # Decompress the image + self.LOG.debug(f'Decompressing image file: {path} ...') + with open(path, 'rb') as pr, open(image_path, 'wb') as pw: + data = pr.read() + data_dec = lzma.decompress(data) + pw.write(data_dec) + + # Record local image + image_record = utils.load_json_data(self.image_record_path) + image_record['local'][name]['status'] = constants.IMAGE_STATE_MAP[4] + image_record['local'][name]['path'] = image_path + utils.save_json_data(self.image_record_path, image_record) + self.LOG.debug(f'Image: {name} is ready ...') + + load_and_transform(name, path) + return 0 \ No newline at end of file diff --git a/eulerlauncher/backends/instance_handler.py b/eulerlauncher/backends/instance_handler.py new file mode 100644 index 0000000000000000000000000000000000000000..844bddd731935b6d691b7925e53d97a1a7c12db9 --- /dev/null +++ b/eulerlauncher/backends/instance_handler.py @@ -0,0 +1,165 @@ +import os +import libvirt +import shutil +import platform +import subprocess + +from eulerlauncher.utils import constants +from eulerlauncher.utils import utils + +class MacInstanceHandler(object): + + def __init__(self, CONF, work_dir, instance_dir, image_dir, LOG) -> None: + self.CONF = CONF + self.work_dir = work_dir + self.instance_dir = instance_dir + self.instance_record_path = os.path.join(instance_dir, 'instances.json') + self.image_dir = image_dir + self.image_record_path = os.path.join(image_dir, 'images.json') + self.LOG = LOG + + + def list_instances(self): + instance_record = utils.load_json_data(self.instance_record_path) + all_instances = list(instance_record.values()) + return all_instances + + + def create_instance(self, name, image): + image_record = utils.load_json_data(self.image_record_path) + if image not in image_record['local'].keys(): + self.LOG.debug(f'Image: {image} is not available locally') + return 1 + + instance_record = utils.load_json_data(self.instance_record_path) + if name in instance_record.keys(): + self.LOG.debug(f'Instance: {name} already exist') + return 2 + + instance_path = os.path.join(self.instance_dir, name) + os.makedirs(instance_path) + image_path = image_record['local'][image]['path'] + disk_path = shutil.copyfile(image_path, os.path.join(instance_path, image)) + host_arch_raw = platform.uname().machine + host_arch = constants.ARCH_MAP[host_arch_raw] + xml_path = os.path.join('/Library/Application Support/org.openeuler.eulerlauncher/','libvirt-' + host_arch + '.xml') + xml = utils.load_xml_data(xml_path) + vcpu = self.CONF.get('vm', 'cpu_num') + ram = self.CONF.get('vm', 'memory') + qemu_bin = shutil.which('qemu-system-aarch64') + mac_address = utils.generate_mac_address() + + utils.xml_find_and_set(xml, 'name', value=name) + utils.xml_find_and_set(xml, 'vcpu', value=vcpu) + utils.xml_find_and_set(xml, 'memory', value=ram) + utils.xml_find_and_set(xml, 'devices/emulator', value=qemu_bin) + utils.xml_find_and_set(xml, 'devices/disk/source', 'file', disk_path) + qemu_arg = 'driver=virtio-net-pci,netdev=eth0,mac=' + mac_address + utils.xml_find_and_set(xml, 'qemu:commandline/qemu:arg[2]', 'value', qemu_arg) + utils.save_xml_data(xml_path, xml) + + try: + conn = libvirt.open("qemu:///system") + with open(xml_path, 'r') as pr: + dom = conn.createLinux(pr.read()) + + with open(os.path.join(instance_path, name), 'w') as pw: + xml_dec = dom.XMLDesc() + pw.write(xml_dec) + + ip_address = utils.parse_ip_address(mac_address) + + instance_record[name] = { + 'id': dom.ID(), + 'name': name, + 'state': constants.INSTANCE_STATE_MAP[dom.state()[0]], + 'vcpu': vcpu, + 'ram': ram, + 'image': image, + 'mac_address': mac_address, + 'ip_address': ip_address, + 'path': instance_path + } + utils.save_json_data(self.instance_record_path, instance_record) + conn.close() + except Exception: + self.LOG.debug(f'Libvirt error creating instance: {name}') + shutil.rmtree(instance_path) + return 3 + self.LOG.debug(f'Instance: {name} succesfully created ...') + return 0 + + + def delete_instance(self, name): + instance_record = utils.load_json_data(self.instance_record_path) + if name not in instance_record.keys(): + self.LOG.debug(f'Instance: {name} does not exist') + return 1 + + conn = libvirt.open("qemu:///system") + dom = conn.lookupByName(name) + dom.destroy() + # Cleanup files and records + instance_path = instance_record[name]['path'] + shutil.rmtree(instance_path) + del instance_record[name] + + utils.save_json_data(self.instance_record_path, instance_record) + conn.close() + self.LOG.debug(f'Instance: {name} succesfully killed ...') + return 0 + + + def suspend_instance(self, name): + instance_record = utils.load_json_data(self.instance_record_path) + if name not in instance_record.keys(): + self.LOG.debug(f'Instance: {name} does not exist') + return 1 + + conn = libvirt.open("qemu:///system") + dom = conn.lookupByName(name) + dom.suspend() + + instance_record[name]['state'] = constants.INSTANCE_STATE_MAP[dom.state()[0]] + + utils.save_json_data(self.instance_record_path, instance_record) + conn.close() + self.LOG.debug(f'Instance: {name} succesfully suspended ...') + return 0 + + + def resume_instance(self, name): + instance_record = utils.load_json_data(self.instance_record_path) + if name not in instance_record.keys(): + self.LOG.debug(f'Instance: {name} does not exist') + return 1 + + conn = libvirt.open("qemu:///system") + dom = conn.lookupByName(name) + dom.resume() + + instance_record[name]['state'] = constants.INSTANCE_STATE_MAP[dom.state()[0]] + + utils.save_json_data(self.instance_record_path, instance_record) + conn.close() + self.LOG.debug(f'Instance: {name} succesfully resumed ...') + return 0 + + + def console_instance(self, name): + instance_record = utils.load_json_data(self.instance_record_path) + if name not in instance_record.keys(): + self.LOG.debug(f'Instance: {name} does not exist') + return 1 + + # instance_path = instance_record[name]['path'] + # xml_path = os.path.join(instance_path, name) + # xml = utils.load_xml_data(xml_path) + # address = utils.xml_find_and_set(xml, 'devices/graphics[@type="vnc"]/listen', "address") + # port = utils.xml_find_and_set(xml, 'devices/graphics[@type="vnc"]', "port") + + virt_viewer_bin = shutil.which('virt-viewer') + virt_viewer_cmd = ['sudo', virt_viewer_bin, name] + subprocess.Popen(' '.join(virt_viewer_cmd), shell=True, preexec_fn=os.setsid) + + return 0 \ No newline at end of file diff --git a/eulerlauncher/backends/mac/image_handler.py b/eulerlauncher/backends/mac/image_handler.py deleted file mode 100644 index f9c8ec6753466f9183fc2d0a6a2fde0e08ac2f1e..0000000000000000000000000000000000000000 --- a/eulerlauncher/backends/mac/image_handler.py +++ /dev/null @@ -1,118 +0,0 @@ -import copy -import lzma -import wget -import os -import subprocess -import shutil -import ssl - -from eulerlauncher.utils import constants -from eulerlauncher.utils import utils as omni_utils -from eulerlauncher.utils import objs - - -ssl._create_default_https_context = ssl._create_unverified_context - - -class MacImageHandler(object): - - def __init__(self, conf, work_dir, image_dir, image_record_file, - logger, base_dir) -> None: - self.conf = conf - self.work_dir = work_dir - self.image_dir = image_dir - self.image_record_file = image_record_file - self.base_dir = base_dir - self.wget_bin = conf.conf.get('default', 'wget_dir') - self.LOG = logger - - - def download_and_transform(self, images, img_to_download): - - # Download the image - img_name = wget.filename_from_url(images['remote'][img_to_download]['path']) - img_dict = copy.deepcopy(images['remote'][img_to_download]) - - if not os.path.exists(os.path.join(self.image_dir, img_name)): - self.LOG.debug(f'Downloading image: {img_to_download} from remote repo ...') - img_dict['location'] = constants.IMAGE_LOCATION_LOCAL - img_dict['status'] = constants.IMAGE_STATUS_DOWNLOADING - images['local'][img_to_download] = img_dict - omni_utils.save_json_data(self.image_record_file, images) - - download_cmd = [self.wget_bin, images['remote'][img_to_download]['path'], - '-O', os.path.join(self.image_dir, img_name), '--no-check-certificate'] - self.LOG.debug(' '.join(download_cmd)) - subprocess.call(' '.join(download_cmd), shell=True) - #wget.download(url=images['remote'][img_to_download]['path'], out=os.path.join(self.image_dir, img_name), bar=None) - self.LOG.debug(f'Image: {img_to_download} succesfully downloaded from remote repo ...') - - # Decompress the image - self.LOG.debug(f'Decompressing image file: {img_name} ...') - qcow2_name = img_name[:-3] - with open(os.path.join(self.image_dir, img_name), 'rb') as pr, open(os.path.join(self.image_dir, qcow2_name), 'wb') as pw: - data = pr.read() - data_dec = lzma.decompress(data) - pw.write(data_dec) - - self.LOG.debug(f'Cleanup temp files ...') - os.remove(os.path.join(self.image_dir, img_name)) - - # Record local image - img_dict['status'] = constants.IMAGE_STATUS_READY - img_dict['path'] = os.path.join(self.image_dir, qcow2_name) - images['local'][img_to_download] = img_dict - omni_utils.save_json_data(self.image_record_file, images) - self.LOG.debug(f'Image: {img_to_download} is ready ...') - - - def delete_image(self, images, img_to_delete): - if img_to_delete not in images['local'].keys(): - return 1 - else: - return self._delete_image(images, img_to_delete) - - def _delete_image(self, images, img_to_delete): - img_path = images['local'][img_to_delete]['path'] - # TODO: Raise error message if image file not exists - if os.path.exists(img_path): - self.LOG.debug(f'Deleting: {img_path} ...') - os.remove(img_path) - - self.LOG.debug(f'Deleting: {img_to_delete} from image database ...') - del images['local'][img_to_delete] - omni_utils.save_json_data(self.image_record_file, images) - - return 0 - - def load_and_transform(self, images, img_to_load, path, fmt, update=False): - - if update: - self._delete_image(images, img_to_load) - - image = objs.Image() - image.name = img_to_load - image.path = '' - image.location = constants.IMAGE_LOCATION_LOCAL - image.status = constants.IMAGE_STATUS_LOADING - images['local'][image.name] = image.to_dict() - omni_utils.save_json_data(self.image_record_file, images) - - if fmt == 'qcow2': - qcow2_name = f'{img_to_load}.qcow2' - shutil.copyfile(path, os.path.join(self.image_dir, qcow2_name)) - else: - # Decompress the image - self.LOG.debug(f'Decompressing image file: {path} ...') - qcow2_name = f'{img_to_load}.qcow2' - with open(path, 'rb') as pr, open(os.path.join(self.image_dir, qcow2_name), 'wb') as pw: - data = pr.read() - data_dec = lzma.decompress(data) - pw.write(data_dec) - - # Record local image - image.path = os.path.join(self.image_dir, qcow2_name) - image.status = constants.IMAGE_STATUS_READY - images['local'][image.name] = image.to_dict() - omni_utils.save_json_data(self.image_record_file, images) - self.LOG.debug(f'Image: {qcow2_name} is ready ...') diff --git a/eulerlauncher/backends/mac/instance_handler.py b/eulerlauncher/backends/mac/instance_handler.py deleted file mode 100644 index a53e78d81925ece983d717e04f3151860cc6b26e..0000000000000000000000000000000000000000 --- a/eulerlauncher/backends/mac/instance_handler.py +++ /dev/null @@ -1,177 +0,0 @@ -import os -import psutil -import shutil -import signal -import subprocess -import sys -import time - -from oslo_utils import uuidutils - -from eulerlauncher.utils import constants -from eulerlauncher.utils import utils as omni_utils -from eulerlauncher.utils import objs -from eulerlauncher.backends.mac import qemu - - -class MacInstanceHandler(object): - - def __init__(self, conf, work_dir, instance_dir, image_dir, - image_record_file, logger, base_dir) -> None: - self.conf = conf - self.work_dir = work_dir - self.instance_dir = instance_dir - self.instance_record_file = os.path.join(instance_dir, 'instances.json') - self.image_dir = image_dir - self.image_record_file = image_record_file - self.driver = qemu.QemuDriver(self.conf, logger) - self.running_instances = {} - self.instance_pids = [] - self.base_dir = base_dir - self.LOG = logger - - def list_instances(self): - instances = omni_utils.load_json_data(self.instance_record_file)['instances'] - vm_list = [] - - for instance in instances.values(): - vm = objs.Instance(name=instance['name']) - vm.uuid = instance['uuid'] - vm.mac = instance['mac_address'] - vm.info = None - vm.vm_state = self._check_vm_state(instance) - if not instance['ip_address']: - ip_address = self._parse_ip_addr(vm.mac) - vm.ip = ip_address - else: - vm.ip = instance['ip_address'] - vm.image = instance['image'] - vm_list.append(vm) - - return vm_list - - def _check_vm_state(self, instance): - if instance['identification']['type'] == 'pid': - instance_pid = instance['identification']['id'] - if instance_pid in psutil.pids() and \ - psutil.Process(instance_pid).status() == 'running' and \ - psutil.Process(instance_pid).name().startswith('qemu'): - return constants.VM_STATE_MAP[2] - else: - return constants.VM_STATE_MAP[3] - else: - return constants.VM_STATE_MAP[99] - - def _parse_ip_addr(self, mac_addr): - ip = '' - cmd = 'arp -a' - start_time = time.time() - while(ip == '' and time.time() - start_time < 20): - pr = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE) - arp_result = pr.stdout.decode('utf-8').split('\n') - founded = False - for str in arp_result: - # The result for 'arp -a' in MacOS is different with Linux, it erase - # the first 0 if the first digit is 0 for this mac section, add it - # back before compare - try: - arp_ip = str.split(' ')[1].replace("(", "").replace(")", "") - mac = str.split(' ')[3].replace("(", "").replace(")", "") - except IndexError: - continue - mac_list = mac.split(':') - for i in range(0, len(mac_list)): - if len(mac_list[i]) == 1: - mac_list[i] = '0' + mac_list[i] - mac_0 = ':'.join(mac_list) - if mac_addr == mac_0: - ip = arp_ip - founded = True - break - if founded: - break - - return ip - - def check_names(self, name, all_instances): - try: - all_instances['instances'][name] - return 1 - except KeyError: - return 0 - - def create_instance(self, name, image_id, instance_record, all_instances, all_images): - # Create dir for the instance - vm_uuid = uuidutils.generate_uuid() - vm_dict = { - 'name': name, - 'uuid': vm_uuid, - 'image': image_id, - 'vm_state': constants.VM_STATE_MAP[99], - 'ip_address': 'N/A', - 'mac_address': omni_utils.generate_mac(), - 'identification': { - 'type': 'pid', - 'id': None - } - } - - instance_path = os.path.join(self.instance_dir, name) - os.makedirs(instance_path) - img_path = all_images['local'][image_id]['path'] - - root_disk_path = shutil.copyfile(img_path, os.path.join(instance_path, image_id + '.qcow2')) - - vm_process = self.driver.create_vm(name, vm_uuid, vm_dict['mac_address'], root_disk_path) - - self.running_instances[vm_process.pid] = vm_process - self.instance_pids.append(vm_process.pid) - - vm_dict['identification']['id'] = vm_process.pid - - vm_ip = self._parse_ip_addr(vm_dict['mac_address']) - vm_dict['ip_address'] = vm_ip - - instance_record_dict = { - 'name': name, - 'uuid': vm_dict['uuid'], - 'image': image_id, - 'path': instance_path, - 'mac_address': vm_dict['mac_address'], - 'ip_address': vm_dict['ip_address'], - 'identification': vm_dict['identification'] - } - - all_instances['instances'][name] = instance_record_dict - omni_utils.save_json_data(instance_record, all_instances) - - return { - 'name': name, - 'vm_state': self._check_vm_state(vm_dict), - 'image': image_id, - 'ip_address': vm_dict['ip_address'] - } - - def delete_instance(self, name, instance_record, all_instances): - # Delete instance process - instance = all_instances['instances'][name] - if instance['identification']['type'] == 'pid': - instance_pid = instance['identification']['id'] - if instance_pid in psutil.pids() and \ - psutil.Process(instance_pid).is_running(): - psutil.Process(instance_pid).kill() - self.LOG.debug(f'Instance: {name} with PID {instance_pid} succesfully killed ...') - else: - self.LOG.debug(f'Instance: {name} with PID {instance_pid} already stopped, skip ...') - else: - self.LOG.debug(f'Instance: {name} unable to handled, skip ...') - - # Cleanup files and records - instance_dir = instance['path'] - shutil.rmtree(instance_dir) - del all_instances['instances'][name] - - omni_utils.save_json_data(instance_record, all_instances) - - return 0 - diff --git a/eulerlauncher/backends/mac/qemu.py b/eulerlauncher/backends/mac/qemu.py deleted file mode 100644 index 2bdd900d0db4bd6a6f408db32a47875c535b12b2..0000000000000000000000000000000000000000 --- a/eulerlauncher/backends/mac/qemu.py +++ /dev/null @@ -1,30 +0,0 @@ -import platform -import subprocess -import os - -from eulerlauncher.utils import constants - - -class QemuDriver(object): - - def __init__(self, conf, logger) -> None: - host_arch_raw = platform.uname().machine - host_arch = constants.ARCH_MAP[host_arch_raw] - self.qemu_bin = conf.conf.get('default', 'qemu_dir') - self.uefi_file = os.path.join('/Library/Application\ Support/org.openeuler.eulerlauncher/','edk2-' + host_arch + '-code.fd') - self.uefi_params = ',if=pflash,format=raw,readonly=on' - self.vm_cpu = conf.conf.get('vm', 'cpu_num') - self.vm_ram = conf.conf.get('vm', 'memory') - self.LOG = logger - - def create_vm(self, vm_name, vm_uuid, vm_mac, vm_root_disk): - qemu_cmd = [ - self.qemu_bin, '-machine', 'virt,highmem=off', '-name', vm_name, '-uuid', vm_uuid, - '-accel hvf', '-drive', 'file=' + self.uefi_file + self.uefi_params, '-cpu host', - '-nic', 'vmnet-shared,model=virtio-net-pci,mac=' + vm_mac, - '-drive', 'file=' + vm_root_disk, '-device', 'virtio-scsi-pci,id=scsi0', - '-smp', self.vm_cpu, '-m', self.vm_ram + 'M', '-monitor none -chardev null,id=char0', - '-serial chardev:char0 -nographic'] - self.LOG.debug(' '.join(qemu_cmd)) - instance_process = subprocess.Popen(' '.join(qemu_cmd), shell=True) - return instance_process diff --git a/eulerlauncher/backends/win/__init__.py b/eulerlauncher/backends/win/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/eulerlauncher/backends/win/image_handler.py b/eulerlauncher/backends/win/image_handler.py deleted file mode 100644 index 9f09f4d13bcea66cc39a418e200ccabfdcd582dd..0000000000000000000000000000000000000000 --- a/eulerlauncher/backends/win/image_handler.py +++ /dev/null @@ -1,123 +0,0 @@ -import copy -import lzma -import wget -import os -import shutil -import ssl - -from eulerlauncher.backends.win import powershell -from eulerlauncher.utils import constants -from eulerlauncher.utils import utils as omni_utils -from eulerlauncher.utils import objs - - -ssl._create_default_https_context = ssl._create_unverified_context - - -class WinImageHandler(object): - - def __init__(self, conf, work_dir, image_dir, image_record_file, logger) -> None: - self.conf = conf - self.work_dir = work_dir - self.image_dir = image_dir - self.image_record_file = image_record_file - self.LOG = logger - - def download_and_transform(self, images, img_to_download): - - # Download the image - img_name = wget.filename_from_url(images['remote'][img_to_download]['path']) - img_dict = copy.deepcopy(images['remote'][img_to_download]) - - if not os.path.exists(os.path.join(self.image_dir, img_name)): - self.LOG.debug(f'Downloading image: {img_to_download} from remote repo ...') - img_dict['location'] = constants.IMAGE_LOCATION_LOCAL - img_dict['status'] = constants.IMAGE_STATUS_DOWNLOADING - images['local'][img_to_download] = img_dict - omni_utils.save_json_data(self.image_record_file, images) - wget.download(url=images['remote'][img_to_download]['path'], out=os.path.join(self.image_dir, img_name), bar=None) - self.LOG.debug(f'Image: {img_to_download} succesfully downloaded from remote repo ...') - - # Decompress the image - self.LOG.debug(f'Decompressing image file: {img_name} ...') - qcow2_name = img_name[:-3] - with open(os.path.join(self.image_dir, img_name), 'rb') as pr, open(os.path.join(self.image_dir, qcow2_name), 'wb') as pw: - data = pr.read() - data_dec = lzma.decompress(data) - pw.write(data_dec) - - # Convert the qcow2 img to vhdx - vhdx_name = img_to_download + '.vhdx' - self.LOG.debug(f'Converting image file: {img_name} to {vhdx_name} ...') - with powershell.PowerShell('GBK') as ps: - cmd = 'qemu-img convert -O vhdx {0} {1}' - outs, errs = ps.run(cmd.format(os.path.join(self.image_dir, qcow2_name), os.path.join(self.image_dir, vhdx_name))) - - self.LOG.debug(f'Cleanup temp files ...') - os.remove(os.path.join(self.image_dir, qcow2_name)) - - # Record local image - img_dict['status'] = constants.IMAGE_STATUS_READY - img_dict['path'] = os.path.join(self.image_dir, vhdx_name) - images['local'][img_to_download] = img_dict - omni_utils.save_json_data(self.image_record_file, images) - self.LOG.debug(f'Image: {img_to_download} is ready ...') - - def delete_image(self, images, img_to_delete): - if img_to_delete not in images['local'].keys(): - return 1 - else: - return self._delete_image(images, img_to_delete) - - def _delete_image(self, images, img_to_delete): - img_path = images['local'][img_to_delete]['path'] - if os.path.exists(img_path): - self.LOG.debug(f'Deleting: {img_path} ...') - os.remove(img_path) - - self.LOG.debug(f'Deleting: {img_to_delete} from image database ...') - del images['local'][img_to_delete] - omni_utils.save_json_data(self.image_record_file, images) - - return 0 - - def load_and_transform(self, images, img_to_load, path, fmt, update=False): - - if update: - self._delete_image(images, img_to_load) - - image = objs.Image() - image.name = img_to_load - image.path = '' - image.location = constants.IMAGE_LOCATION_LOCAL - image.status = constants.IMAGE_STATUS_LOADING - images['local'][image.name] = image.to_dict() - omni_utils.save_json_data(self.image_record_file, images) - - if fmt == 'qcow2': - qcow2_name = f'{img_to_load}.qcow2' - shutil.copyfile(path, os.path.join(self.image_dir, qcow2_name)) - else: - # Decompress the image - self.LOG.debug(f'Decompressing image file: {path} ...') - qcow2_name = f'{img_to_load}.qcow2' - with open(path, 'rb') as pr, open(os.path.join(self.image_dir, qcow2_name), 'wb') as pw: - data = pr.read() - data_dec = lzma.decompress(data) - pw.write(data_dec) - - # Convert the qcow2 img to vhdx - vhdx_name = img_to_load + '.vhdx' - self.LOG.debug(f'Converting image file: {qcow2_name} to {vhdx_name} ...') - with powershell.PowerShell('GBK') as ps: - cmd = 'qemu-img convert -O vhdx {0} {1}' - outs, errs = ps.run(cmd.format(os.path.join(self.image_dir, qcow2_name), os.path.join(self.image_dir, vhdx_name))) - self.LOG.debug(f'Cleanup temp files ...') - os.remove(os.path.join(self.image_dir, qcow2_name)) - - # Record local image - image.path = os.path.join(self.image_dir, vhdx_name) - image.status = constants.IMAGE_STATUS_READY - images['local'][image.name] = image.to_dict() - omni_utils.save_json_data(self.image_record_file, images) - self.LOG.debug(f'Image: {vhdx_name} is ready ...') diff --git a/eulerlauncher/backends/win/instance_handler.py b/eulerlauncher/backends/win/instance_handler.py deleted file mode 100644 index 7b27055300490a449c7efdfa61fdb997e03cc2c0..0000000000000000000000000000000000000000 --- a/eulerlauncher/backends/win/instance_handler.py +++ /dev/null @@ -1,200 +0,0 @@ -import os -import shutil -import time - -from oslo_utils import uuidutils -from os_win import constants as os_win_const -from os_win import exceptions as os_win_exc - -from eulerlauncher.backends.win import powershell -from eulerlauncher.backends.win import vmops -from eulerlauncher.utils import constants -from eulerlauncher.utils import utils as omni_utils -from eulerlauncher.utils import objs - - -_vmops = vmops.VMOps() - -class WinInstanceHandler(object): - - def __init__(self, conf, work_dir, instance_dir, image_dir, image_record_file, logger) -> None: - self.conf = conf - self.work_dir = work_dir - self.instance_dir = instance_dir - self.instance_record_file = os.path.join(instance_dir, 'instances.json') - self.image_dir = image_dir - self.image_record_file = image_record_file - self.LOG = logger - - def list_instances(self): - vms = _vmops.list_instances() - return vms - - def check_names(self, name, all_instances): - ret = _vmops.check_all_instance_names(name) - return ret - - def create_instance(self, name, image_id, instance_record, all_instances, all_images): - # Create dir for the instance - vm_dict = { - 'name': name, - 'uuid': uuidutils.generate_uuid(), - 'image': image_id, - 'vm_state': constants.VM_STATE_MAP[99], - 'ip_address': 'N/A', - 'mac_address': 'N/A', - 'identification': { - 'type': 'name', - 'id': name - } - } - - instance_path = os.path.join(self.instance_dir, name) - os.makedirs(instance_path) - img_path = all_images['local'][image_id]['path'] - - root_disk_path = shutil.copyfile(img_path, os.path.join(instance_path, image_id + '.vhdx')) - _vmops.build_and_run_vm(name, vm_dict['uuid'], image_id, False, 2, instance_path, root_disk_path) - - info = _vmops.get_info(name) - vm_dict['vm_state'] = constants.VM_STATE_MAP[info['EnabledState']] - ip = _vmops.get_instance_ip_addr(name) - if ip: - vm_dict['ip_address'] = ip - - instance_record_dict = { - 'name': name, - 'uuid': vm_dict['uuid'], - 'image': image_id, - 'path': instance_path, - 'mac_address': vm_dict['mac_address'], - 'ip_address': vm_dict['ip_address'], - 'identification': vm_dict['identification'] - } - - all_instances['instances'][name] = instance_record_dict - omni_utils.save_json_data(instance_record, all_instances) - - return { - 'name': name, - 'vm_state': vm_dict['vm_state'], - 'image': image_id, - 'ip_address': vm_dict['ip_address'] - } - - def delete_instance(self, name, instance_record, all_instances): - # Delete instance - _vmops.delete_instance(name) - - # Cleanup files and records - instance_dir = all_instances['instances'][name]['path'] - shutil.rmtree(instance_dir) - del all_instances['instances'][name] - - omni_utils.save_json_data(instance_record, all_instances) - - return 0 - - def reboot(self, instance, reboot_type='soft'): - """Reboot the specified instance.""" - self.LOG.debug("Rebooting instance", instance=instance) - - if reboot_type == 'soft': - if self._soft_shutdown(instance): - self.power_on(instance) - return - - self._set_vm_state(instance, - os_win_const.HYPERV_VM_STATE_REBOOT) - - def _soft_shutdown(self, instance, - timeout=5, - retry_interval=1): - """Perform a soft shutdown on the VM. - - :return: True if the instance was shutdown within time limit, - False otherwise. - """ - self.LOG.debug("Performing Soft shutdown on instance", instance=instance) - - while timeout > 0: - # Perform a soft shutdown on the instance. - # Wait maximum timeout for the instance to be shutdown. - # If it was not shutdown, retry until it succeeds or a maximum of - # time waited is equal to timeout. - wait_time = min(retry_interval, timeout) - try: - self.LOG.debug("Soft shutdown instance, timeout remaining: %d", - timeout, instance=instance) - self._vmutils.soft_shutdown_vm(instance.name) - if self._wait_for_power_off(instance.name, wait_time): - self.LOG.info("Soft shutdown succeeded.", - instance=instance) - return True - except os_win_exc.HyperVException as e: - # Exception is raised when trying to shutdown the instance - # while it is still booting. - self.LOG.debug("Soft shutdown failed: %s", e, instance=instance) - time.sleep(wait_time) - - timeout -= retry_interval - - self.LOG.warning("Timed out while waiting for soft shutdown.", - instance=instance) - return False - - def pause(self, instance): - """Pause VM instance.""" - self.LOG.debug("Pause instance", instance=instance) - self._set_vm_state(instance, - os_win_const.HYPERV_VM_STATE_PAUSED) - - def unpause(self, instance): - """Unpause paused VM instance.""" - self.LOG.debug("Unpause instance", instance=instance) - self._set_vm_state(instance, - os_win_const.HYPERV_VM_STATE_ENABLED) - - def suspend(self, instance): - """Suspend the specified instance.""" - self.LOG.debug("Suspend instance", instance=instance) - self._set_vm_state(instance, - os_win_const.HYPERV_VM_STATE_SUSPENDED) - - def resume(self, instance): - """Resume the suspended VM instance.""" - self.LOG.debug("Resume instance", instance=instance) - self._set_vm_state(instance, - os_win_const.HYPERV_VM_STATE_ENABLED) - - def power_off(self, instance, timeout=0, retry_interval=0): - """Power off the specified instance.""" - self.LOG.debug("Power off instance", instance=instance) - - # We must make sure that the console log workers are stopped, - # otherwise we won't be able to delete or move the VM log files. - self._serial_console_ops.stop_console_handler(instance.name) - - if retry_interval <= 0: - retry_interval = SHUTDOWN_TIME_INCREMENT - - try: - if timeout and self._soft_shutdown(instance, - timeout, - retry_interval): - return - - self._set_vm_state(instance, - os_win_const.HYPERV_VM_STATE_DISABLED) - except os_win_exc.HyperVVMNotFoundException: - # The manager can call the stop API after receiving instance - # power off events. If this is triggered when the instance - # is being deleted, it might attempt to power off an unexisting - # instance. We'll just pass in this case. - self.LOG.debug("Instance not found. Skipping power off", - instance=instance) - - def power_on(self, instance): - """Power on the specified instance.""" - self.LOG.debug("Power on instance", instance=instance) - self._set_vm_state(instance, os_win_const.HYPERV_VM_STATE_ENABLED) diff --git a/eulerlauncher/backends/win/powershell.py b/eulerlauncher/backends/win/powershell.py deleted file mode 100644 index ac6a60d89e26b004a12905b44d9c7b1f39554ec8..0000000000000000000000000000000000000000 --- a/eulerlauncher/backends/win/powershell.py +++ /dev/null @@ -1,49 +0,0 @@ -import os -from glob import glob -import subprocess as sp - - -class PowerShell: - # from scapy - def __init__(self, coding, ): - cmd = [self._where('PowerShell.exe'), - "-NoLogo", "-NonInteractive", # Do not print headers - "-Command", "-"] # Listen commands from stdin - startupinfo = sp.STARTUPINFO() - startupinfo.dwFlags |= sp.STARTF_USESHOWWINDOW - self.popen = sp.Popen(cmd, stdout=sp.PIPE, stdin=sp.PIPE, stderr=sp.STDOUT, startupinfo=startupinfo) - self.coding = coding - - def __enter__(self): - return self - - def __exit__(self, a, b, c): - self.popen.kill() - - def run(self, cmd, timeout=15): - b_cmd = cmd.encode(encoding=self.coding) - try: - b_outs, errs = self.popen.communicate(b_cmd, timeout=timeout) - except sp.TimeoutExpired: - self.popen.kill() - b_outs, errs = self.popen.communicate() - outs = b_outs.decode(encoding=self.coding) - return outs, errs - - @staticmethod - def _where(filename, dirs=None, env="PATH"): - """Find file in current dir, in deep_lookup cache or in system path""" - if dirs is None: - dirs = [] - if not isinstance(dirs, list): - dirs = [dirs] - if glob(filename): - return filename - paths = [os.curdir] + os.environ[env].split(os.path.pathsep) + dirs - try: - return next(os.path.normpath(match) - for path in paths - for match in glob(os.path.join(path, filename)) - if match) - except (StopIteration, RuntimeError): - raise IOError("File not found: %s" % filename) diff --git a/eulerlauncher/backends/win/vmops.py b/eulerlauncher/backends/win/vmops.py deleted file mode 100644 index b8e4c75d4193bf3e7a807104b25b580bf98c27ae..0000000000000000000000000000000000000000 --- a/eulerlauncher/backends/win/vmops.py +++ /dev/null @@ -1,183 +0,0 @@ -import json - -from os_win import constants as os_win_const -from os_win import exceptions as os_win_exc -from os_win.utils.compute import vmutils10 -from os_win import utilsfactory -from oslo_utils import uuidutils - -from eulerlauncher.utils import objs -from eulerlauncher.utils import constants -from eulerlauncher.utils import utils as omni_utils -from eulerlauncher.backends.win import powershell - -SWITCH_NAME = 'Default Switch' - - -class VMUtils_omni(vmutils10.VMUtils10): - - def __init__(self) -> None: - super().__init__() - - def get_instance_notes(self, instance_name): - instance_notes = self._get_instance_notes(instance_name) - return instance_notes - - def get_vm_nic_uids(self, vm_name): - nics = self._get_vm_nics(vm_name) - return nics - -class VMOps(object): - _ROOT_DISK_CTRL_ADDR = 0 - - def __init__(self, virtapi=None): - self._virtapi = virtapi - self._vmutils = VMUtils_omni() - self._netutils = utilsfactory.get_networkutils() - self._hostutils = utilsfactory.get_hostutils() - - def _set_vm_state(self, instance, req_state): - instance_name = instance.name - self._vmutils.set_vm_state(instance_name, req_state) - - def list_instance_uuids(self): - instance_uuids = [] - for (instance_name, notes) in self._vmutils.list_instance_notes(): - if notes and uuidutils.is_uuid_like(notes[0]): - instance_uuids.append(str(notes[0])) - else: - pass - #LOG.debug("Notes not found or not resembling a GUID for " - # "instance: %s", instance_name) - return instance_uuids - - def check_all_instance_names(self, name): - instance_names = self._vmutils.list_instances() - if name in instance_names: - return 1 - else: - return 0 - - def list_instances(self): - instance_names = self._vmutils.list_instances() - vm_list = [] - for instance_name in instance_names: - vm = objs.Instance(name=instance_name) - meta = self.get_meta(instance_name) - if not meta or not meta['creator'] == 'eulerlauncher': - continue - else: - vm.metadata = meta - vm.uuid = meta['uuid'] - info = self.get_info(instance_name) - vm.info = info - vm.vm_state = constants.VM_STATE_MAP[info['EnabledState']] - ip_address = self.get_instance_ip_addr(instance_name) - vm.ip = ip_address - vm.image = meta['image'] - vm_list.append(vm) - - return vm_list - - - def get_instance_ip_addr(self, instance_name): - nic_name = instance_name + '_eth0' - nic = self.get_vm_nics(instance_name, nic_name) - mac_address = omni_utils.format_mac_addr(nic.Address) - with powershell.PowerShell('GBK') as ps: - outs, errs = ps.run('arp -a | findstr /i {}'.format(mac_address)) - ip_address = outs.strip(' ').split(' ')[0] - - return ip_address - - - def get_info(self, instance): - """Get information about the VM.""" - # LOG.debug("get_info called for instance", instance=instance) - - instance_name = instance - if not self._vmutils.vm_exists(instance_name): - raise # exception.InstanceNotFound(instance_id=instance.uuid) - - info = self._vmutils.get_vm_summary_info(instance_name) - - return info - - def create_vm(self, vm_name, vnuma_enabled, vm_gen, instance_path, - meta_data): - self._vmutils.create_vm(vm_name, - vnuma_enabled, - vm_gen, - instance_path, - [json.dumps(meta_data)]) - - def build_and_run_vm(self, vm_name, uuid, image_name, vnuma_enabled, vm_gen, instance_path, root_disk_path): - meta_data = { - 'uuid': uuid, - 'image': image_name, - 'creator': 'eulerlauncher' - } - - # Create an instance - self.create_vm(vm_name, vnuma_enabled, vm_gen, instance_path, meta_data) - # Create a scsi controller for this instance - self._vmutils.create_scsi_controller(vm_name) - # Attach the root disk to the driver - self.attach_disk(vm_name, root_disk_path, constants.DISK) - # Set default flavor - self._vmutils.update_vm(vm_name, 4096, 0, '2', 0, False, 0) - # Start the instance - self.power_up(vm_name) - nic_name = vm_name + '_eth0' - self.add_nic(vm_name, nic_name) - self.connect_vnic_to_switch(SWITCH_NAME, nic_name) - return 0 - - def get_meta(self, instance_name, expect_existing=False): - try: - instance_notes = self._vmutils.get_instance_notes(instance_name) - if instance_notes: - return json.loads(instance_notes[0]) - else: - return instance_notes - except os_win_exc.HyperVVMNotFoundException: - raise - - def delete_instance(self, vm_name): - # Stop the VM first. - self._vmutils.stop_vm_jobs(vm_name) - self._vmutils.set_vm_state(vm_name, os_win_const.HYPERV_VM_STATE_DISABLED) - self._vmutils.destroy_vm(vm_name) - - while(1): - if not self._vmutils.vm_exists(vm_name): - break - return 0 - - def get_vm_disks(self, vm_name): - return self._vmutils.get_vm_disks(vm_name) - - def attach_disk(self, instance_name, path, drive_type): - self._vmutils.attach_scsi_drive(instance_name, path, drive_type) - - def power_up(self, instance_name): - req_state = os_win_const.HYPERV_VM_STATE_ENABLED - self._vmutils.set_vm_state(instance_name, req_state) - - def add_nic(self, instance_name, nic_name): - self._vmutils.create_nic(instance_name, nic_name) - - def get_vm_nics(self, instance_name, nic_name): - return self._vmutils._get_nic_data_by_name(nic_name) - - def list_switch_ports(self, switch_name): - return self._netutils.get_switch_ports(switch_name) - - def connect_vnic_to_switch(self, switch_name, vnic_name): - self._netutils.connect_vnic_to_vswitch(switch_name, vnic_name) - - def get_switch_port(self, switch_name, port_id): - return self._netutils.get_port_by_id(port_id, switch_name) - - def get_host_ips(self): - return self._hostutils.get_local_ips() diff --git a/eulerlauncher/cli.py b/eulerlauncher/cli.py index 56972a1573780b1c5660695f3e7a3cc55009d430..e46cec50dea3df3fc748fdcfbaa528a1d38d6d96 100644 --- a/eulerlauncher/cli.py +++ b/eulerlauncher/cli.py @@ -6,35 +6,18 @@ from eulerlauncher.grpcs import client launcher_client = client.Client() -# List all instances on the host -@click.command() -def list(): - - try: - ret = launcher_client.list_instances() - except Exception: - print('Calling to EulerLauncherd daemon failed, please check EulerLauncherd daemon status ...') - else: - tb = pt.PrettyTable() - - tb.field_names = ["Name", "Image", "State", "IP"] - try: - for instance in ret['instances']: - tb.add_row( - [instance['name'], - instance['image'], - instance['vmState'], - instance['ipAddress']]) - except KeyError: - pass - - print(tb) +@click.group( + name="image", + help="Command for managing images " +) +def image(): + pass # List all usable images -@click.command() -def images(): +@image.command() +def list(): try: ret = launcher_client.list_images() @@ -52,9 +35,9 @@ def images(): print(tb) -@click.command() +@image.command() @click.argument('name') -def download_image(name): +def download(name): try: ret = launcher_client.download_image(name) @@ -64,10 +47,10 @@ def download_image(name): print(ret['msg']) -@click.command() +@image.command() @click.argument('name') @click.option('--path', help='Image file to load') -def load_image(name, path): +def load(name, path): try: ret = launcher_client.load_image(name, path) @@ -77,9 +60,9 @@ def load_image(name, path): print(ret['msg']) -@click.command() +@image.command() @click.argument('name') -def delete_image(name): +def delete(name): try: ret = launcher_client.delete_image(name) @@ -89,9 +72,56 @@ def delete_image(name): print(ret['msg']) -@click.command() +@click.group( + name="instance", + help="Command for managing instances " +) +def instance(): + pass + + +@instance.command() +@click.argument('vm_name') +@click.option('--image', help='Image to build instance') +def launch(vm_name, image): + + try: + ret = launcher_client.create_instance(vm_name, image) + except Exception: + print('Calling to EulerLauncherd daemon failed, please check EulerLauncherd daemon status ...') + else: + print(ret['msg']) + + +# List all instances on the host +@instance.command() +def list(): + + try: + ret = launcher_client.list_instances() + except Exception: + print('Calling to EulerLauncherd daemon failed, please check EulerLauncherd daemon status ...') + else: + tb = pt.PrettyTable() + + tb.field_names = ["Name", "Image", "State", "IP"] + + try: + for instance in ret['instances']: + tb.add_row( + [instance['name'], + instance['image'], + instance['vmState'], + instance['ipAddress']]) + except KeyError: + pass + + print(tb) + + +@instance.command() @click.argument('name') -def delete_instance(name): +def delete(name): try: ret = launcher_client.delete_instance(name) @@ -100,43 +130,49 @@ def delete_instance(name): else: print(ret['msg']) -@click.command() -@click.argument('vm_name') -@click.option('--image', help='Image to build vm') -def launch(vm_name, image): + +@instance.command() +@click.argument('name') +def suspend(name): try: - ret = launcher_client.create_instance(vm_name, image) + ret = launcher_client.suspend_instance(name) except Exception: print('Calling to EulerLauncherd daemon failed, please check EulerLauncherd daemon status ...') else: + print(ret['msg']) - if ret['ret'] == 1: - tb = pt.PrettyTable() - tb.field_names = ["Name", "Image", "State", "IP"] - tb.add_row( - [ret['instance']['name'], - ret['instance']['image'], - ret['instance']['vmState'], - ret['instance']['ipAddress']]) - print(tb) - - else: - print(ret['msg']) +@instance.command() +@click.argument('name') +def resume(name): + + try: + ret = launcher_client.resume_instance(name) + except Exception: + print('Calling to EulerLauncherd daemon failed, please check EulerLauncherd daemon status ...') + else: + print(ret['msg']) + + +@instance.command() +@click.argument('name') +def console(name): + + try: + ret = launcher_client.console_instance(name) + except Exception: + print('Calling to EulerLauncherd daemon failed, please check EulerLauncherd daemon status ...') + else: + print(ret['msg']) @click.group() -def cli(): +def entrance(): pass if __name__ == '__main__': - cli.add_command(list) - cli.add_command(images) - cli.add_command(download_image) - cli.add_command(load_image) - cli.add_command(launch) - cli.add_command(delete_image) - cli.add_command(delete_instance) - cli() \ No newline at end of file + entrance.add_command(image) + entrance.add_command(instance) + entrance() \ No newline at end of file diff --git a/eulerlauncher/eulerlauncherd.py b/eulerlauncher/eulerlauncherd.py index 4bd0be7542a1457d53209c23dc5428ae0fb9b252..c4b550dffd275b71710af36bc7f485081198ea09 100644 --- a/eulerlauncher/eulerlauncherd.py +++ b/eulerlauncher/eulerlauncherd.py @@ -1,5 +1,4 @@ import argparse -from concurrent import futures import grpc import logging import os @@ -8,15 +7,16 @@ import platform import pystray import requests import signal -import subprocess import sys import time +import configparser +from concurrent import futures from eulerlauncher.grpcs.eulerlauncher_grpc import images_pb2, images_pb2_grpc from eulerlauncher.grpcs.eulerlauncher_grpc import instances_pb2, instances_pb2_grpc -from eulerlauncher.services import imager_service, instance_service +from eulerlauncher.services import image_service, instance_service +from eulerlauncher.utils import exceptions from eulerlauncher.utils import constants -from eulerlauncher.utils import objs from eulerlauncher.utils import utils @@ -29,12 +29,11 @@ if host_os_raw != 'Windows': parser = argparse.ArgumentParser() parser.add_argument('conf_file', help='Configuration file for the application', type=str) -parser.add_argument('base_dir', help='The base work directory of the daemon') -def config_logging(config): - log_dir = config.conf.get('default', 'log_dir') - debug = config.conf.get('default', 'debug') +def init_log(CONF): + log_dir = CONF.get('default', 'log_dir') + debug = CONF.get('default', 'debug') if not os.path.exists(log_dir): os.makedirs(log_dir) @@ -50,63 +49,63 @@ def config_logging(config): filename=log_file, level=log_level, filemode='a+') -def init(arch, config, LOG): - work_dir = config.conf.get('default', 'work_dir') +def init_workdir(arch, CONF, LOG): + LOG.debug('Initializing work directory ...') + work_dir = CONF.get('default', 'work_dir') image_dir = os.path.join(work_dir, 'images') instance_dir = os.path.join(work_dir, 'instances') - instance_record_file = os.path.join(instance_dir, 'instances.json') - img_record_file = os.path.join(image_dir, 'images.json') + instance_record_path = os.path.join(instance_dir, 'instances.json') + image_record_path = os.path.join(image_dir, 'images.json') - LOG.debug('Initializing EulerLauncherd ...') LOG.debug('Checking for work directory ...') if not os.path.exists(work_dir): LOG.debug('Create %s as working directory ...' % work_dir) os.makedirs(work_dir) - LOG.debug('Checking for instances directory ...') + + LOG.debug('Checking for instance directory ...') if not os.path.exists(instance_dir): - LOG.debug('Create %s as working directory ...' % work_dir) + LOG.debug('Create %s as instance directory ...' % instance_dir) os.makedirs(instance_dir) LOG.debug('Checking for instance database ...') - if not os.path.exists(instance_record_file): + if not os.path.exists(instance_record_path): + LOG.debug('Create %s as instance database ...' % instance_record_path) instances = { - 'instances': {} } - utils.save_json_data(instance_record_file, instances) + utils.save_json_data(instance_record_path, instances) LOG.debug('Checking for image directory ...') if not os.path.exists(image_dir): LOG.debug('Create %s as image directory ...' % image_dir) os.makedirs(image_dir) - LOG.debug('Checking for image database ...') - remote_img_resp = requests.get(IMG_URL, verify=False) - remote_imgs = remote_img_resp.json()[arch] - if not os.path.exists(img_record_file): - images = {} - for name, path in remote_imgs.items(): - image = objs.Image() - image.name = name - image.path = path - image.location = constants.IMAGE_LOCATION_REMOTE - image.status = constants.IMAGE_STATUS_DOWLOADABLE - images[image.name] = image.to_dict() - - image_body = { - 'remote': images, + if not os.path.exists(image_record_path): + LOG.debug('Create %s as image database ...' % image_record_path) + remote_image_resp = requests.get(IMG_URL, verify=False) + remote_images = remote_image_resp.json()[arch] + image_record = { + 'remote': {}, 'local': {} } - utils.save_json_data(img_record_file, image_body) + for name, path in remote_images.items(): + image_record['remote'][name] = { + 'name': name, + 'path': path, + 'location': constants.IMAGE_LOCATION_REMOTE, + 'status': constants.IMAGE_STATE_MAP[1] + } + utils.save_json_data(image_record_path, image_record) -def serve(arch, host_os, CONF, LOG, base_dir): + +def serve(host_arch, host_os, CONF, LOG): ''' - Run the EulerLauncherd Service + Run the EulerLauncherd service ''' server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) - images_pb2_grpc.add_ImageGrpcServiceServicer_to_server(imager_service.ImagerService(arch, host_os, CONF, base_dir), server) - instances_pb2_grpc.add_InstanceGrpcServiceServicer_to_server(instance_service.InstanceService(arch, host_os, CONF, base_dir), server) - server.add_insecure_port('[::]:50052') + images_pb2_grpc.add_ImageGrpcServiceServicer_to_server(image_service.ImageService(host_arch, host_os, CONF, LOG), server) + instances_pb2_grpc.add_InstanceGrpcServiceServicer_to_server(instance_service.InstanceService(host_arch, host_os, CONF, LOG), server) + server.add_insecure_port('localhost:50052') server.start() - LOG.debug('EulerLauncherd Service Started ...') + LOG.debug('EulerLauncherd service started ...') if host_os == 'Win': return server @@ -121,25 +120,24 @@ def serve(arch, host_os, CONF, LOG, base_dir): while True: time.sleep(1) -def init_launcherd(conf, base_dir): - CONF = objs.Conf(conf) +def init_launcherd(conf_file): + CONF = configparser.ConfigParser() + if not os.path.exists(conf_file): + raise exceptions.NoSuchFile(file=conf_file) + CONF.read(conf_file) - config_logging(CONF) + init_log(CONF) LOG = logging.getLogger(__name__) - + LOG.debug('Initializing EulerLauncherd ...') + host_arch_raw = platform.uname().machine - host_os_raw = platform.uname().system - host_arch = constants.ARCH_MAP[host_arch_raw] + host_os_raw = platform.uname().system host_os = constants.OS_MAP[host_os_raw] - try: - init(host_arch, CONF, LOG) - except Exception as e: - LOG.debug('Error: ' + str(e)) - return str(e) - else: - return serve(host_arch, host_os, CONF, LOG, base_dir) + init_workdir(host_arch, CONF, LOG) + + return serve(host_arch, host_os, CONF, LOG) if __name__ == '__main__': @@ -147,35 +145,28 @@ if __name__ == '__main__': if host_os_raw != 'Windows': args = parser.parse_args() conf_file = args.conf_file - base_dir = args.base_dir else: conf_file = os.path.join(os.getcwd(), 'etc', 'eulerlauncher.conf') - base_dir = None - try: - pass - except Exception as e: - print('Error: ' + str(e)) + + if host_os_raw != 'Windows': + init_launcherd(conf_file) else: - if host_os_raw != 'Windows': - init_launcherd(conf_file, base_dir) - else: - try: - logo = PIL.Image.open(os.path.join(os.getcwd(), 'etc', 'favicon.png')) - - def on_clicked(icon, item): - icon.stop() - - icon = pystray.Icon('EulerLauncher', logo, menu=pystray.Menu( - pystray.MenuItem('Exit EulerLauncher', on_clicked) - )) - - except Exception as e: - print('Error: ' + str(e)) - sys.exit(0) - - server = init_launcherd(conf_file, base_dir) - - icon.run() - server.stop(None) + try: + logo = PIL.Image.open(os.path.join(os.getcwd(), 'etc', 'favicon.png')) + + def on_clicked(icon, item): + icon.stop() + + icon = pystray.Icon('EulerLauncher', logo, menu=pystray.Menu( + pystray.MenuItem('Exit EulerLauncher', on_clicked) + )) + + except Exception as e: + print('Error: ' + str(e)) sys.exit(0) - + + server = init_launcherd(conf_file) + + icon.run() + server.stop(None) + sys.exit(0) \ No newline at end of file diff --git a/eulerlauncher/grpcs/client.py b/eulerlauncher/grpcs/client.py index 66adb9afa25f9ddfecc4e33297e7d7cea29ddfaf..1fcd17761efa72db2f0923e860540ab9277da792 100644 --- a/eulerlauncher/grpcs/client.py +++ b/eulerlauncher/grpcs/client.py @@ -1,11 +1,9 @@ import grpc -import os from eulerlauncher.grpcs.eulerlauncher_grpc import images_pb2, images_pb2_grpc from eulerlauncher.grpcs.eulerlauncher_grpc import instances_pb2, instances_pb2_grpc from eulerlauncher.grpcs import images, instances -from eulerlauncher.utils import constants -from eulerlauncher.utils import utils as omnivirt_utils +from eulerlauncher.utils import utils class Client(object): @@ -20,7 +18,8 @@ class Client(object): self._images = images.Image(images_client) self._instances = instances.Instance(instances_client) - @omnivirt_utils.response2dict + + @utils.response2dict def list_images(self, filters=None): """ [IMAGE] List images @@ -30,48 +29,32 @@ class Client(object): return self._images.list() - @omnivirt_utils.response2dict + + @utils.response2dict def download_image(self, name): """ Download image """ return self._images.download(name) - @omnivirt_utils.response2dict + + @utils.response2dict def load_image(self, name, path): """ Load local image file """ - if not os.path.exists(path): - err_msg = { - 'ret': 1, - 'msg': f'No such file or directory: {path}, please check again.' - } - return err_msg - - supported = False - for tp in constants.IMAGE_LOAD_SUPPORTED_TYPES: - if path.endswith(tp): - supported = True - break - - if not supported: - err_msg = { - 'ret': 1, - 'msg': f'Image file format does not supported: {path}, please check again.' - } - return err_msg - return self._images.load(name, path) - @omnivirt_utils.response2dict + + @utils.response2dict def delete_image(self, name): """ Delete the requested image """ return self._images.delete(name) - @omnivirt_utils.response2dict + + @utils.response2dict def list_instances(self): """ List instances :return: dict -- list of instances' info @@ -79,7 +62,8 @@ class Client(object): return self._instances.list() - @omnivirt_utils.response2dict + + @utils.response2dict def create_instance(self, name, image): """ Create instance :return: dict -- dict of instance's info @@ -87,9 +71,32 @@ class Client(object): return self._instances.create(name, image) - @omnivirt_utils.response2dict + + @utils.response2dict def delete_instance(self, name): """ Delete the requested instance """ return self._instances.delete(name) + + @utils.response2dict + def suspend_instance(self, name): + """ Suspend the requested instance + """ + + return self._instances.suspend(name) + + @utils.response2dict + def resume_instance(self, name): + """ Resume the requested instance + """ + + return self._instances.resume(name) + + + @utils.response2dict + def console_instance(self, name): + """ connect the requested instance + """ + + return self._instances.console(name) \ No newline at end of file diff --git a/eulerlauncher/grpcs/eulerlauncher_grpc/images.proto b/eulerlauncher/grpcs/eulerlauncher_grpc/images.proto index 26996d9dfd1c61817d7d3fc9fbc077fe3b02136b..f5097688c9ac48e844585e3eed087c61546ffea5 100644 --- a/eulerlauncher/grpcs/eulerlauncher_grpc/images.proto +++ b/eulerlauncher/grpcs/eulerlauncher_grpc/images.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -package omnivirt; +package eulerlauncher; option cc_generic_services = true; @@ -27,19 +27,23 @@ message ListImageResponse { repeated Image images = 1; } + message DownloadImageRequest { string name = 1; } + message LoadImageRequest { string name = 1; string path = 2; } + message DeleteImageRequest { string name = 1; } + message GeneralImageResponse { uint32 ret = 1; string msg = 2; diff --git a/eulerlauncher/grpcs/eulerlauncher_grpc/images_pb2.py b/eulerlauncher/grpcs/eulerlauncher_grpc/images_pb2.py index 6b2f860ebc20149da3530d08e18adeca7a89c574..ab1e0b60d70db0bac7be3380076f46a07793deb9 100644 --- a/eulerlauncher/grpcs/eulerlauncher_grpc/images_pb2.py +++ b/eulerlauncher/grpcs/eulerlauncher_grpc/images_pb2.py @@ -1,11 +1,22 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! -# source: images.proto +# NO CHECKED-IN PROTOBUF GENCODE +# source: eulerlauncher/grpcs/eulerlauncher_grpc/images.proto +# Protobuf Python Version: 5.28.1 """Generated protocol buffer code.""" -from google.protobuf.internal import builder as _builder from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 28, + 1, + '', + 'eulerlauncher/grpcs/eulerlauncher_grpc/images.proto' +) # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -13,28 +24,28 @@ _sym_db = _symbol_database.Default() -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0cimages.proto\x12\x08omnivirt\"7\n\x05Image\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x10\n\x08location\x18\x02 \x01(\t\x12\x0e\n\x06status\x18\x03 \x01(\t\"\x12\n\x10ListImageRequest\"4\n\x11ListImageResponse\x12\x1f\n\x06images\x18\x01 \x03(\x0b\x32\x0f.omnivirt.Image\"$\n\x14\x44ownloadImageRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\".\n\x10LoadImageRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04path\x18\x02 \x01(\t\"\"\n\x12\x44\x65leteImageRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"0\n\x14GeneralImageResponse\x12\x0b\n\x03ret\x18\x01 \x01(\r\x12\x0b\n\x03msg\x18\x02 \x01(\t2\xcc\x02\n\x10ImageGrpcService\x12H\n\x0blist_images\x12\x1a.omnivirt.ListImageRequest\x1a\x1b.omnivirt.ListImageResponse\"\x00\x12R\n\x0e\x64ownload_image\x12\x1e.omnivirt.DownloadImageRequest\x1a\x1e.omnivirt.GeneralImageResponse\"\x00\x12J\n\nload_image\x12\x1a.omnivirt.LoadImageRequest\x1a\x1e.omnivirt.GeneralImageResponse\"\x00\x12N\n\x0c\x64\x65lete_image\x12\x1c.omnivirt.DeleteImageRequest\x1a\x1e.omnivirt.GeneralImageResponse\"\x00\x42\x03\x80\x01\x01\x62\x06proto3') - -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'images_pb2', globals()) -if _descriptor._USE_C_DESCRIPTORS == False: +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n3eulerlauncher/grpcs/eulerlauncher_grpc/images.proto\x12\reulerlauncher\"7\n\x05Image\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x10\n\x08location\x18\x02 \x01(\t\x12\x0e\n\x06status\x18\x03 \x01(\t\"\x12\n\x10ListImageRequest\"9\n\x11ListImageResponse\x12$\n\x06images\x18\x01 \x03(\x0b\x32\x14.eulerlauncher.Image\"$\n\x14\x44ownloadImageRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\".\n\x10LoadImageRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04path\x18\x02 \x01(\t\"\"\n\x12\x44\x65leteImageRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"0\n\x14GeneralImageResponse\x12\x0b\n\x03ret\x18\x01 \x01(\r\x12\x0b\n\x03msg\x18\x02 \x01(\t2\xf4\x02\n\x10ImageGrpcService\x12R\n\x0blist_images\x12\x1f.eulerlauncher.ListImageRequest\x1a .eulerlauncher.ListImageResponse\"\x00\x12\\\n\x0e\x64ownload_image\x12#.eulerlauncher.DownloadImageRequest\x1a#.eulerlauncher.GeneralImageResponse\"\x00\x12T\n\nload_image\x12\x1f.eulerlauncher.LoadImageRequest\x1a#.eulerlauncher.GeneralImageResponse\"\x00\x12X\n\x0c\x64\x65lete_image\x12!.eulerlauncher.DeleteImageRequest\x1a#.eulerlauncher.GeneralImageResponse\"\x00\x42\x03\x80\x01\x01\x62\x06proto3') - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'\200\001\001' - _IMAGE._serialized_start=26 - _IMAGE._serialized_end=81 - _LISTIMAGEREQUEST._serialized_start=83 - _LISTIMAGEREQUEST._serialized_end=101 - _LISTIMAGERESPONSE._serialized_start=103 - _LISTIMAGERESPONSE._serialized_end=155 - _DOWNLOADIMAGEREQUEST._serialized_start=157 - _DOWNLOADIMAGEREQUEST._serialized_end=193 - _LOADIMAGEREQUEST._serialized_start=195 - _LOADIMAGEREQUEST._serialized_end=241 - _DELETEIMAGEREQUEST._serialized_start=243 - _DELETEIMAGEREQUEST._serialized_end=277 - _GENERALIMAGERESPONSE._serialized_start=279 - _GENERALIMAGERESPONSE._serialized_end=327 - _IMAGEGRPCSERVICE._serialized_start=330 - _IMAGEGRPCSERVICE._serialized_end=662 +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'eulerlauncher.grpcs.eulerlauncher_grpc.images_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\200\001\001' + _globals['_IMAGE']._serialized_start=70 + _globals['_IMAGE']._serialized_end=125 + _globals['_LISTIMAGEREQUEST']._serialized_start=127 + _globals['_LISTIMAGEREQUEST']._serialized_end=145 + _globals['_LISTIMAGERESPONSE']._serialized_start=147 + _globals['_LISTIMAGERESPONSE']._serialized_end=204 + _globals['_DOWNLOADIMAGEREQUEST']._serialized_start=206 + _globals['_DOWNLOADIMAGEREQUEST']._serialized_end=242 + _globals['_LOADIMAGEREQUEST']._serialized_start=244 + _globals['_LOADIMAGEREQUEST']._serialized_end=290 + _globals['_DELETEIMAGEREQUEST']._serialized_start=292 + _globals['_DELETEIMAGEREQUEST']._serialized_end=326 + _globals['_GENERALIMAGERESPONSE']._serialized_start=328 + _globals['_GENERALIMAGERESPONSE']._serialized_end=376 + _globals['_IMAGEGRPCSERVICE']._serialized_start=379 + _globals['_IMAGEGRPCSERVICE']._serialized_end=751 # @@protoc_insertion_point(module_scope) diff --git a/eulerlauncher/grpcs/eulerlauncher_grpc/images_pb2_grpc.py b/eulerlauncher/grpcs/eulerlauncher_grpc/images_pb2_grpc.py index 28eb2df9c2085b818c685fe2958f4ffc8bc90904..2eeacd4800b6ef005009035bb5ecf46d047e645d 100644 --- a/eulerlauncher/grpcs/eulerlauncher_grpc/images_pb2_grpc.py +++ b/eulerlauncher/grpcs/eulerlauncher_grpc/images_pb2_grpc.py @@ -1,8 +1,28 @@ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! """Client and server classes corresponding to protobuf-defined services.""" import grpc +import warnings -from eulerlauncher.grpcs.eulerlauncher_grpc import images_pb2 as images__pb2 +from eulerlauncher.grpcs.eulerlauncher_grpc import images_pb2 as eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2 + +GRPC_GENERATED_VERSION = '1.68.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + f' but the generated code in eulerlauncher/grpcs/eulerlauncher_grpc/images_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) class ImageGrpcServiceStub(object): @@ -15,25 +35,25 @@ class ImageGrpcServiceStub(object): channel: A grpc.Channel. """ self.list_images = channel.unary_unary( - '/omnivirt.ImageGrpcService/list_images', - request_serializer=images__pb2.ListImageRequest.SerializeToString, - response_deserializer=images__pb2.ListImageResponse.FromString, - ) + '/eulerlauncher.ImageGrpcService/list_images', + request_serializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.ListImageRequest.SerializeToString, + response_deserializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.ListImageResponse.FromString, + _registered_method=True) self.download_image = channel.unary_unary( - '/omnivirt.ImageGrpcService/download_image', - request_serializer=images__pb2.DownloadImageRequest.SerializeToString, - response_deserializer=images__pb2.GeneralImageResponse.FromString, - ) + '/eulerlauncher.ImageGrpcService/download_image', + request_serializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.DownloadImageRequest.SerializeToString, + response_deserializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.GeneralImageResponse.FromString, + _registered_method=True) self.load_image = channel.unary_unary( - '/omnivirt.ImageGrpcService/load_image', - request_serializer=images__pb2.LoadImageRequest.SerializeToString, - response_deserializer=images__pb2.GeneralImageResponse.FromString, - ) + '/eulerlauncher.ImageGrpcService/load_image', + request_serializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.LoadImageRequest.SerializeToString, + response_deserializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.GeneralImageResponse.FromString, + _registered_method=True) self.delete_image = channel.unary_unary( - '/omnivirt.ImageGrpcService/delete_image', - request_serializer=images__pb2.DeleteImageRequest.SerializeToString, - response_deserializer=images__pb2.GeneralImageResponse.FromString, - ) + '/eulerlauncher.ImageGrpcService/delete_image', + request_serializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.DeleteImageRequest.SerializeToString, + response_deserializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.GeneralImageResponse.FromString, + _registered_method=True) class ImageGrpcServiceServicer(object): @@ -68,28 +88,29 @@ def add_ImageGrpcServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'list_images': grpc.unary_unary_rpc_method_handler( servicer.list_images, - request_deserializer=images__pb2.ListImageRequest.FromString, - response_serializer=images__pb2.ListImageResponse.SerializeToString, + request_deserializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.ListImageRequest.FromString, + response_serializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.ListImageResponse.SerializeToString, ), 'download_image': grpc.unary_unary_rpc_method_handler( servicer.download_image, - request_deserializer=images__pb2.DownloadImageRequest.FromString, - response_serializer=images__pb2.GeneralImageResponse.SerializeToString, + request_deserializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.DownloadImageRequest.FromString, + response_serializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.GeneralImageResponse.SerializeToString, ), 'load_image': grpc.unary_unary_rpc_method_handler( servicer.load_image, - request_deserializer=images__pb2.LoadImageRequest.FromString, - response_serializer=images__pb2.GeneralImageResponse.SerializeToString, + request_deserializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.LoadImageRequest.FromString, + response_serializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.GeneralImageResponse.SerializeToString, ), 'delete_image': grpc.unary_unary_rpc_method_handler( servicer.delete_image, - request_deserializer=images__pb2.DeleteImageRequest.FromString, - response_serializer=images__pb2.GeneralImageResponse.SerializeToString, + request_deserializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.DeleteImageRequest.FromString, + response_serializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.GeneralImageResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( - 'omnivirt.ImageGrpcService', rpc_method_handlers) + 'eulerlauncher.ImageGrpcService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('eulerlauncher.ImageGrpcService', rpc_method_handlers) # This class is part of an EXPERIMENTAL API. @@ -107,11 +128,21 @@ class ImageGrpcService(object): wait_for_ready=None, timeout=None, metadata=None): - return grpc.experimental.unary_unary(request, target, '/omnivirt.ImageGrpcService/list_images', - images__pb2.ListImageRequest.SerializeToString, - images__pb2.ListImageResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + return grpc.experimental.unary_unary( + request, + target, + '/eulerlauncher.ImageGrpcService/list_images', + eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.ListImageRequest.SerializeToString, + eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.ListImageResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) @staticmethod def download_image(request, @@ -124,11 +155,21 @@ class ImageGrpcService(object): wait_for_ready=None, timeout=None, metadata=None): - return grpc.experimental.unary_unary(request, target, '/omnivirt.ImageGrpcService/download_image', - images__pb2.DownloadImageRequest.SerializeToString, - images__pb2.GeneralImageResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + return grpc.experimental.unary_unary( + request, + target, + '/eulerlauncher.ImageGrpcService/download_image', + eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.DownloadImageRequest.SerializeToString, + eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.GeneralImageResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) @staticmethod def load_image(request, @@ -141,11 +182,21 @@ class ImageGrpcService(object): wait_for_ready=None, timeout=None, metadata=None): - return grpc.experimental.unary_unary(request, target, '/omnivirt.ImageGrpcService/load_image', - images__pb2.LoadImageRequest.SerializeToString, - images__pb2.GeneralImageResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + return grpc.experimental.unary_unary( + request, + target, + '/eulerlauncher.ImageGrpcService/load_image', + eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.LoadImageRequest.SerializeToString, + eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.GeneralImageResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) @staticmethod def delete_image(request, @@ -158,8 +209,18 @@ class ImageGrpcService(object): wait_for_ready=None, timeout=None, metadata=None): - return grpc.experimental.unary_unary(request, target, '/omnivirt.ImageGrpcService/delete_image', - images__pb2.DeleteImageRequest.SerializeToString, - images__pb2.GeneralImageResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + return grpc.experimental.unary_unary( + request, + target, + '/eulerlauncher.ImageGrpcService/delete_image', + eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.DeleteImageRequest.SerializeToString, + eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_images__pb2.GeneralImageResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/eulerlauncher/grpcs/eulerlauncher_grpc/instances.proto b/eulerlauncher/grpcs/eulerlauncher_grpc/instances.proto index c2e751cb2fc354d7f0903a9bc7529f278501167d..5bac8b8c6908a566be5e3fab2c11a49886226ae7 100644 --- a/eulerlauncher/grpcs/eulerlauncher_grpc/instances.proto +++ b/eulerlauncher/grpcs/eulerlauncher_grpc/instances.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -package omnivirt; +package eulerlauncher; option cc_generic_services = true; @@ -8,6 +8,9 @@ service InstanceGrpcService { rpc list_instances (ListInstancesRequest) returns (ListInstancesResponse) {} rpc create_instance (CreateInstanceRequest) returns (CreateInstanceResponse) {} rpc delete_instance (DeleteInstanceRequest) returns (DeleteInstanceResponse) {} + rpc suspend_instance (SuspendInstanceRequest) returns (SuspendInstanceResponse) {} + rpc resume_instance (ResumeInstanceRequest) returns (ResumeInstanceResponse) {} + rpc console_instance (ConsoleInstanceRequest) returns (ConsoleInstanceResponse) {} } @@ -27,22 +30,59 @@ message ListInstancesResponse { repeated Instance instances = 1; } + message CreateInstanceRequest { string name = 1; string image = 2; } + message CreateInstanceResponse { uint32 ret = 1; string msg = 2; optional Instance instance = 3; } + message DeleteInstanceRequest { string name = 1; } + message DeleteInstanceResponse { uint32 ret = 1; string msg = 2; } + + +message SuspendInstanceRequest { + string name = 1; +} + + +message SuspendInstanceResponse { + uint32 ret = 1; + string msg = 2; +} + + +message ResumeInstanceRequest { + string name = 1; +} + + +message ResumeInstanceResponse { + uint32 ret = 1; + string msg = 2; +} + + +message ConsoleInstanceRequest { + string name = 1; +} + + +message ConsoleInstanceResponse { + uint32 ret = 1; + string msg = 2; +} \ No newline at end of file diff --git a/eulerlauncher/grpcs/eulerlauncher_grpc/instances_pb2.py b/eulerlauncher/grpcs/eulerlauncher_grpc/instances_pb2.py index 9877833f058ff6798506e937cee9f7ce7d1e555e..598e434f2535d94f0b177c8f532545b04b55c4c0 100644 --- a/eulerlauncher/grpcs/eulerlauncher_grpc/instances_pb2.py +++ b/eulerlauncher/grpcs/eulerlauncher_grpc/instances_pb2.py @@ -1,11 +1,22 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! -# source: instances.proto +# NO CHECKED-IN PROTOBUF GENCODE +# source: eulerlauncher/grpcs/eulerlauncher_grpc/instances.proto +# Protobuf Python Version: 5.28.1 """Generated protocol buffer code.""" -from google.protobuf.internal import builder as _builder from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 28, + 1, + '', + 'eulerlauncher/grpcs/eulerlauncher_grpc/instances.proto' +) # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -13,28 +24,40 @@ _sym_db = _symbol_database.Default() -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0finstances.proto\x12\x08omnivirt\"M\n\x08Instance\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05image\x18\x02 \x01(\t\x12\x10\n\x08vm_state\x18\x03 \x01(\t\x12\x12\n\nip_address\x18\x04 \x01(\t\"\x16\n\x14ListInstancesRequest\">\n\x15ListInstancesResponse\x12%\n\tinstances\x18\x01 \x03(\x0b\x32\x12.omnivirt.Instance\"4\n\x15\x43reateInstanceRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05image\x18\x02 \x01(\t\"j\n\x16\x43reateInstanceResponse\x12\x0b\n\x03ret\x18\x01 \x01(\r\x12\x0b\n\x03msg\x18\x02 \x01(\t\x12)\n\x08instance\x18\x03 \x01(\x0b\x32\x12.omnivirt.InstanceH\x00\x88\x01\x01\x42\x0b\n\t_instance\"%\n\x15\x44\x65leteInstanceRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"2\n\x16\x44\x65leteInstanceResponse\x12\x0b\n\x03ret\x18\x01 \x01(\r\x12\x0b\n\x03msg\x18\x02 \x01(\t2\x9a\x02\n\x13InstanceGrpcService\x12S\n\x0elist_instances\x12\x1e.omnivirt.ListInstancesRequest\x1a\x1f.omnivirt.ListInstancesResponse\"\x00\x12V\n\x0f\x63reate_instance\x12\x1f.omnivirt.CreateInstanceRequest\x1a .omnivirt.CreateInstanceResponse\"\x00\x12V\n\x0f\x64\x65lete_instance\x12\x1f.omnivirt.DeleteInstanceRequest\x1a .omnivirt.DeleteInstanceResponse\"\x00\x42\x03\x80\x01\x01\x62\x06proto3') - -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'instances_pb2', globals()) -if _descriptor._USE_C_DESCRIPTORS == False: +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n6eulerlauncher/grpcs/eulerlauncher_grpc/instances.proto\x12\reulerlauncher\"M\n\x08Instance\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05image\x18\x02 \x01(\t\x12\x10\n\x08vm_state\x18\x03 \x01(\t\x12\x12\n\nip_address\x18\x04 \x01(\t\"\x16\n\x14ListInstancesRequest\"C\n\x15ListInstancesResponse\x12*\n\tinstances\x18\x01 \x03(\x0b\x32\x17.eulerlauncher.Instance\"4\n\x15\x43reateInstanceRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05image\x18\x02 \x01(\t\"o\n\x16\x43reateInstanceResponse\x12\x0b\n\x03ret\x18\x01 \x01(\r\x12\x0b\n\x03msg\x18\x02 \x01(\t\x12.\n\x08instance\x18\x03 \x01(\x0b\x32\x17.eulerlauncher.InstanceH\x00\x88\x01\x01\x42\x0b\n\t_instance\"%\n\x15\x44\x65leteInstanceRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"2\n\x16\x44\x65leteInstanceResponse\x12\x0b\n\x03ret\x18\x01 \x01(\r\x12\x0b\n\x03msg\x18\x02 \x01(\t\"&\n\x16SuspendInstanceRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"3\n\x17SuspendInstanceResponse\x12\x0b\n\x03ret\x18\x01 \x01(\r\x12\x0b\n\x03msg\x18\x02 \x01(\t\"%\n\x15ResumeInstanceRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"2\n\x16ResumeInstanceResponse\x12\x0b\n\x03ret\x18\x01 \x01(\r\x12\x0b\n\x03msg\x18\x02 \x01(\t\"&\n\x16\x43onsoleInstanceRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"3\n\x17\x43onsoleInstanceResponse\x12\x0b\n\x03ret\x18\x01 \x01(\r\x12\x0b\n\x03msg\x18\x02 \x01(\t2\xe4\x04\n\x13InstanceGrpcService\x12]\n\x0elist_instances\x12#.eulerlauncher.ListInstancesRequest\x1a$.eulerlauncher.ListInstancesResponse\"\x00\x12`\n\x0f\x63reate_instance\x12$.eulerlauncher.CreateInstanceRequest\x1a%.eulerlauncher.CreateInstanceResponse\"\x00\x12`\n\x0f\x64\x65lete_instance\x12$.eulerlauncher.DeleteInstanceRequest\x1a%.eulerlauncher.DeleteInstanceResponse\"\x00\x12\x63\n\x10suspend_instance\x12%.eulerlauncher.SuspendInstanceRequest\x1a&.eulerlauncher.SuspendInstanceResponse\"\x00\x12`\n\x0fresume_instance\x12$.eulerlauncher.ResumeInstanceRequest\x1a%.eulerlauncher.ResumeInstanceResponse\"\x00\x12\x63\n\x10\x63onsole_instance\x12%.eulerlauncher.ConsoleInstanceRequest\x1a&.eulerlauncher.ConsoleInstanceResponse\"\x00\x42\x03\x80\x01\x01\x62\x06proto3') - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'\200\001\001' - _INSTANCE._serialized_start=29 - _INSTANCE._serialized_end=106 - _LISTINSTANCESREQUEST._serialized_start=108 - _LISTINSTANCESREQUEST._serialized_end=130 - _LISTINSTANCESRESPONSE._serialized_start=132 - _LISTINSTANCESRESPONSE._serialized_end=194 - _CREATEINSTANCEREQUEST._serialized_start=196 - _CREATEINSTANCEREQUEST._serialized_end=248 - _CREATEINSTANCERESPONSE._serialized_start=250 - _CREATEINSTANCERESPONSE._serialized_end=356 - _DELETEINSTANCEREQUEST._serialized_start=358 - _DELETEINSTANCEREQUEST._serialized_end=395 - _DELETEINSTANCERESPONSE._serialized_start=397 - _DELETEINSTANCERESPONSE._serialized_end=447 - _INSTANCEGRPCSERVICE._serialized_start=450 - _INSTANCEGRPCSERVICE._serialized_end=732 +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'eulerlauncher.grpcs.eulerlauncher_grpc.instances_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'\200\001\001' + _globals['_INSTANCE']._serialized_start=73 + _globals['_INSTANCE']._serialized_end=150 + _globals['_LISTINSTANCESREQUEST']._serialized_start=152 + _globals['_LISTINSTANCESREQUEST']._serialized_end=174 + _globals['_LISTINSTANCESRESPONSE']._serialized_start=176 + _globals['_LISTINSTANCESRESPONSE']._serialized_end=243 + _globals['_CREATEINSTANCEREQUEST']._serialized_start=245 + _globals['_CREATEINSTANCEREQUEST']._serialized_end=297 + _globals['_CREATEINSTANCERESPONSE']._serialized_start=299 + _globals['_CREATEINSTANCERESPONSE']._serialized_end=410 + _globals['_DELETEINSTANCEREQUEST']._serialized_start=412 + _globals['_DELETEINSTANCEREQUEST']._serialized_end=449 + _globals['_DELETEINSTANCERESPONSE']._serialized_start=451 + _globals['_DELETEINSTANCERESPONSE']._serialized_end=501 + _globals['_SUSPENDINSTANCEREQUEST']._serialized_start=503 + _globals['_SUSPENDINSTANCEREQUEST']._serialized_end=541 + _globals['_SUSPENDINSTANCERESPONSE']._serialized_start=543 + _globals['_SUSPENDINSTANCERESPONSE']._serialized_end=594 + _globals['_RESUMEINSTANCEREQUEST']._serialized_start=596 + _globals['_RESUMEINSTANCEREQUEST']._serialized_end=633 + _globals['_RESUMEINSTANCERESPONSE']._serialized_start=635 + _globals['_RESUMEINSTANCERESPONSE']._serialized_end=685 + _globals['_CONSOLEINSTANCEREQUEST']._serialized_start=687 + _globals['_CONSOLEINSTANCEREQUEST']._serialized_end=725 + _globals['_CONSOLEINSTANCERESPONSE']._serialized_start=727 + _globals['_CONSOLEINSTANCERESPONSE']._serialized_end=778 + _globals['_INSTANCEGRPCSERVICE']._serialized_start=781 + _globals['_INSTANCEGRPCSERVICE']._serialized_end=1393 # @@protoc_insertion_point(module_scope) diff --git a/eulerlauncher/grpcs/eulerlauncher_grpc/instances_pb2_grpc.py b/eulerlauncher/grpcs/eulerlauncher_grpc/instances_pb2_grpc.py index 970781c046dbdda278277ff639fd0e0addcb983a..271412742427cc88f87fd1e377f821bc6953e53d 100644 --- a/eulerlauncher/grpcs/eulerlauncher_grpc/instances_pb2_grpc.py +++ b/eulerlauncher/grpcs/eulerlauncher_grpc/instances_pb2_grpc.py @@ -1,8 +1,28 @@ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! """Client and server classes corresponding to protobuf-defined services.""" import grpc +import warnings -from eulerlauncher.grpcs.eulerlauncher_grpc import instances_pb2 as instances__pb2 +from eulerlauncher.grpcs.eulerlauncher_grpc import instances_pb2 as eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2 + +GRPC_GENERATED_VERSION = '1.68.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + f' but the generated code in eulerlauncher/grpcs/eulerlauncher_grpc/instances_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) class InstanceGrpcServiceStub(object): @@ -15,20 +35,35 @@ class InstanceGrpcServiceStub(object): channel: A grpc.Channel. """ self.list_instances = channel.unary_unary( - '/omnivirt.InstanceGrpcService/list_instances', - request_serializer=instances__pb2.ListInstancesRequest.SerializeToString, - response_deserializer=instances__pb2.ListInstancesResponse.FromString, - ) + '/eulerlauncher.InstanceGrpcService/list_instances', + request_serializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.ListInstancesRequest.SerializeToString, + response_deserializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.ListInstancesResponse.FromString, + _registered_method=True) self.create_instance = channel.unary_unary( - '/omnivirt.InstanceGrpcService/create_instance', - request_serializer=instances__pb2.CreateInstanceRequest.SerializeToString, - response_deserializer=instances__pb2.CreateInstanceResponse.FromString, - ) + '/eulerlauncher.InstanceGrpcService/create_instance', + request_serializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.CreateInstanceRequest.SerializeToString, + response_deserializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.CreateInstanceResponse.FromString, + _registered_method=True) self.delete_instance = channel.unary_unary( - '/omnivirt.InstanceGrpcService/delete_instance', - request_serializer=instances__pb2.DeleteInstanceRequest.SerializeToString, - response_deserializer=instances__pb2.DeleteInstanceResponse.FromString, - ) + '/eulerlauncher.InstanceGrpcService/delete_instance', + request_serializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.DeleteInstanceRequest.SerializeToString, + response_deserializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.DeleteInstanceResponse.FromString, + _registered_method=True) + self.suspend_instance = channel.unary_unary( + '/eulerlauncher.InstanceGrpcService/suspend_instance', + request_serializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.SuspendInstanceRequest.SerializeToString, + response_deserializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.SuspendInstanceResponse.FromString, + _registered_method=True) + self.resume_instance = channel.unary_unary( + '/eulerlauncher.InstanceGrpcService/resume_instance', + request_serializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.ResumeInstanceRequest.SerializeToString, + response_deserializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.ResumeInstanceResponse.FromString, + _registered_method=True) + self.console_instance = channel.unary_unary( + '/eulerlauncher.InstanceGrpcService/console_instance', + request_serializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.ConsoleInstanceRequest.SerializeToString, + response_deserializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.ConsoleInstanceResponse.FromString, + _registered_method=True) class InstanceGrpcServiceServicer(object): @@ -52,28 +87,62 @@ class InstanceGrpcServiceServicer(object): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def suspend_instance(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def resume_instance(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def console_instance(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def add_InstanceGrpcServiceServicer_to_server(servicer, server): rpc_method_handlers = { 'list_instances': grpc.unary_unary_rpc_method_handler( servicer.list_instances, - request_deserializer=instances__pb2.ListInstancesRequest.FromString, - response_serializer=instances__pb2.ListInstancesResponse.SerializeToString, + request_deserializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.ListInstancesRequest.FromString, + response_serializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.ListInstancesResponse.SerializeToString, ), 'create_instance': grpc.unary_unary_rpc_method_handler( servicer.create_instance, - request_deserializer=instances__pb2.CreateInstanceRequest.FromString, - response_serializer=instances__pb2.CreateInstanceResponse.SerializeToString, + request_deserializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.CreateInstanceRequest.FromString, + response_serializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.CreateInstanceResponse.SerializeToString, ), 'delete_instance': grpc.unary_unary_rpc_method_handler( servicer.delete_instance, - request_deserializer=instances__pb2.DeleteInstanceRequest.FromString, - response_serializer=instances__pb2.DeleteInstanceResponse.SerializeToString, + request_deserializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.DeleteInstanceRequest.FromString, + response_serializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.DeleteInstanceResponse.SerializeToString, + ), + 'suspend_instance': grpc.unary_unary_rpc_method_handler( + servicer.suspend_instance, + request_deserializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.SuspendInstanceRequest.FromString, + response_serializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.SuspendInstanceResponse.SerializeToString, + ), + 'resume_instance': grpc.unary_unary_rpc_method_handler( + servicer.resume_instance, + request_deserializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.ResumeInstanceRequest.FromString, + response_serializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.ResumeInstanceResponse.SerializeToString, + ), + 'console_instance': grpc.unary_unary_rpc_method_handler( + servicer.console_instance, + request_deserializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.ConsoleInstanceRequest.FromString, + response_serializer=eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.ConsoleInstanceResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( - 'omnivirt.InstanceGrpcService', rpc_method_handlers) + 'eulerlauncher.InstanceGrpcService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('eulerlauncher.InstanceGrpcService', rpc_method_handlers) # This class is part of an EXPERIMENTAL API. @@ -91,11 +160,21 @@ class InstanceGrpcService(object): wait_for_ready=None, timeout=None, metadata=None): - return grpc.experimental.unary_unary(request, target, '/omnivirt.InstanceGrpcService/list_instances', - instances__pb2.ListInstancesRequest.SerializeToString, - instances__pb2.ListInstancesResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + return grpc.experimental.unary_unary( + request, + target, + '/eulerlauncher.InstanceGrpcService/list_instances', + eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.ListInstancesRequest.SerializeToString, + eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.ListInstancesResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) @staticmethod def create_instance(request, @@ -108,11 +187,21 @@ class InstanceGrpcService(object): wait_for_ready=None, timeout=None, metadata=None): - return grpc.experimental.unary_unary(request, target, '/omnivirt.InstanceGrpcService/create_instance', - instances__pb2.CreateInstanceRequest.SerializeToString, - instances__pb2.CreateInstanceResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + return grpc.experimental.unary_unary( + request, + target, + '/eulerlauncher.InstanceGrpcService/create_instance', + eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.CreateInstanceRequest.SerializeToString, + eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.CreateInstanceResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) @staticmethod def delete_instance(request, @@ -125,8 +214,99 @@ class InstanceGrpcService(object): wait_for_ready=None, timeout=None, metadata=None): - return grpc.experimental.unary_unary(request, target, '/omnivirt.InstanceGrpcService/delete_instance', - instances__pb2.DeleteInstanceRequest.SerializeToString, - instances__pb2.DeleteInstanceResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + return grpc.experimental.unary_unary( + request, + target, + '/eulerlauncher.InstanceGrpcService/delete_instance', + eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.DeleteInstanceRequest.SerializeToString, + eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.DeleteInstanceResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def suspend_instance(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/eulerlauncher.InstanceGrpcService/suspend_instance', + eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.SuspendInstanceRequest.SerializeToString, + eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.SuspendInstanceResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def resume_instance(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/eulerlauncher.InstanceGrpcService/resume_instance', + eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.ResumeInstanceRequest.SerializeToString, + eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.ResumeInstanceResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def console_instance(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/eulerlauncher.InstanceGrpcService/console_instance', + eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.ConsoleInstanceRequest.SerializeToString, + eulerlauncher_dot_grpcs_dot_eulerlauncher__grpc_dot_instances__pb2.ConsoleInstanceResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/eulerlauncher/grpcs/instances.py b/eulerlauncher/grpcs/instances.py index ec6bc292e07034c3048cb506ae546462e468b134..73a4c8c202c6c7f79cab8765fe73f90a8972b919 100644 --- a/eulerlauncher/grpcs/instances.py +++ b/eulerlauncher/grpcs/instances.py @@ -4,20 +4,44 @@ class Instance(object): def __init__(self, client): self.client = client + def list(self): """Get list of instance""" request = instances_pb2.ListInstancesRequest() response = self.client.list_instances(request) return response + def create(self, name, image): """Create instance""" request = instances_pb2.CreateInstanceRequest(name=name, image=image) response = self.client.create_instance(request) return response + def delete(self, name): """Delete instance""" request = instances_pb2.DeleteInstanceRequest(name=name) response = self.client.delete_instance(request) return response + + + def suspend(self, name): + """Suspend instance""" + request = instances_pb2.SuspendInstanceRequest(name=name) + response = self.client.suspend_instance(request) + return response + + + def resume(self, name): + """Resume instance""" + request = instances_pb2.ResumeInstanceRequest(name=name) + response = self.client.resume_instance(request) + return response + + + def console(self, name): + """Connect instance""" + request = instances_pb2.ConsoleInstanceRequest(name=name) + response = self.client.console_instance(request) + return response \ No newline at end of file diff --git a/eulerlauncher/install.py b/eulerlauncher/install.py index 30db7ec10654d26e81197fa2149aaab49fef37b7..547ba3bffda0d24c56b3fd4086778db0c4034cf5 100644 --- a/eulerlauncher/install.py +++ b/eulerlauncher/install.py @@ -1,7 +1,6 @@ import os import subprocess - if __name__ == '__main__': base_dir = os.path.dirname(__file__) diff --git a/eulerlauncher/macos-gui.py b/eulerlauncher/macos-gui.py index 3562e4f6db2f6a590aa21af157161104c6efda4d..fe4e2011bf63d87ad56d6373ca18b82608f7dc81 100644 --- a/eulerlauncher/macos-gui.py +++ b/eulerlauncher/macos-gui.py @@ -1,37 +1,22 @@ import os import PIL.Image -import platform import pystray import subprocess +import shutil import signal import sys -from eulerlauncher.utils import constants -from eulerlauncher.utils import objs - - CONF_DIR_SHELL = '/Library/Application\ Support/org.openeuler.eulerlauncher/eulerlauncher.conf' CONF_DIR = '/Library/Application Support/org.openeuler.eulerlauncher/eulerlauncher.conf' -# Avoid create zombie children in MacOS and Linux -signal.signal(signal.SIGCHLD, signal.SIG_IGN) if __name__ == '__main__': try: - host_arch_raw = platform.uname().machine - host_arch = constants.ARCH_MAP[host_arch_raw] base_dir = os.path.dirname(__file__) - - - conf_file = CONF_DIR logo_file = os.path.join(base_dir,'./etc/favicon.png') - - CONF = objs.Conf(conf_file) - logo = PIL.Image.open(logo_file) def on_clicked(icon, item): - icon.stop() icon = pystray.Icon('EulerLauncher', logo, menu=pystray.Menu( @@ -41,11 +26,24 @@ if __name__ == '__main__': except Exception as e: print('Error: ' + str(e)) else: - launcherd_cmd = ['sudo', os.path.join(base_dir,'./bin/EulerLauncherd'), CONF_DIR_SHELL, base_dir] + os.environ['PATH'] += ':/opt/homebrew/bin:/opt/homebrew/sbin' + + libvirtd_bin = shutil.which('libvirtd') + libvirtd_cmd = ['sudo', libvirtd_bin] + libvirtd = subprocess.Popen(' '.join(libvirtd_cmd), shell=True, preexec_fn=os.setsid) + + virtlogd_bin = shutil.which('virtlogd') + virtlogd_cmd = ['sudo', virtlogd_bin] + virtlogd = subprocess.Popen(' '.join(virtlogd_cmd), shell=True, preexec_fn=os.setsid) + + launcherd_bin = os.path.join(base_dir, './bin/eulerlauncherd') + launcherd_cmd = ['sudo', launcherd_bin, CONF_DIR_SHELL] launcherd = subprocess.Popen(' '.join(launcherd_cmd), shell=True, preexec_fn=os.setsid) def term_handler(signum, frame): subprocess.check_call(['sudo', 'kill', str(launcherd.pid)]) + subprocess.check_call(['sudo', 'kill', str(virtlogd.pid)]) + subprocess.check_call(['sudo', 'kill', str(libvirtd.pid)]) # Avoid create orphan children in MacOS and Linux signal.signal(signal.SIGTERM, term_handler) @@ -55,4 +53,9 @@ if __name__ == '__main__': # Shutdown eulerlauncherd, we created it with sudo, so kill it with sudo subprocess.check_call(['sudo', 'kill', str(launcherd.pid)]) os.waitpid(launcherd.pid, 0) + subprocess.check_call(['sudo', 'kill', str(virtlogd.pid)]) + os.waitpid(virtlogd.pid, 0) + subprocess.check_call(['sudo', 'kill', str(libvirtd.pid)]) + os.waitpid(libvirtd.pid, 0) + sys.exit(0) \ No newline at end of file diff --git a/eulerlauncher/services/image_service.py b/eulerlauncher/services/image_service.py new file mode 100644 index 0000000000000000000000000000000000000000..ee65b6cb32da5d7a16318af120bf4a97391e9784 --- /dev/null +++ b/eulerlauncher/services/image_service.py @@ -0,0 +1,75 @@ +import os + +from eulerlauncher.grpcs.eulerlauncher_grpc import images_pb2, images_pb2_grpc +from eulerlauncher.utils import constants + + +class ImageService(images_pb2_grpc.ImageGrpcServiceServicer): + ''' + The Image GRPC Handler + ''' + + def __init__(self, arch, host_os, CONF, LOG) -> None: + self.CONF = CONF + self.LOG = LOG + self.work_dir = self.CONF.get('default', 'work_dir') + self.image_dir = os.path.join(self.work_dir, 'images') + self.image_record_path = os.path.join(self.image_dir, 'images.json') + if host_os == 'Win': + pass + # from eulerlauncher.backends.win import image_handler as win_image_handler + # self.backend = win_image_handler.WinImageHandler( + # self.CONF, self.work_dir, self.image_dir, self.LOG) + elif host_os == 'MacOS': + from eulerlauncher.backends import image_handler as mac_image_handler + self.backend = mac_image_handler.MacImageHandler( + self.CONF, self.work_dir, self.image_dir, self.LOG) + + + def list_images(self, request, context): + self.LOG.debug(f"Get request to list images ...") + all_images = self.backend.list_images() + ret = [] + for image in all_images: + ret.append({ + 'name': image['name'], + 'location': image['location'], + 'status': image['status'] + }) + return images_pb2.ListImageResponse(images=ret) + + + def download_image(self, request, context): + self.LOG.debug(f"Get request to download image: {request.name} ...") + ret = self.backend.download_image(request.name) + msg = '' + if ret == 0: + msg = f'Downloading: {request.name}, this might take a while, please check image status with "images" command.' + elif ret == 1: + msg = f'Image: {request.name} is valid for download, please check image name from REMOTE IMAGE LIST using "images" command ...' + return images_pb2.GeneralImageResponse(ret=ret, msg=msg) + + + def delete_image(self, request, context): + self.LOG.debug(f"Get request to delete image: {request.name} ...") + ret = self.backend.delete_image(request.name) + msg = '' + if ret == 0: + msg = f'Image: {request.name} has been successfully deleted.' + elif ret == 1: + msg = f'Image: {request.name} does not exist, please check again.' + return images_pb2.GeneralImageResponse(ret=ret, msg=msg) + + + def load_image(self, request, context): + self.LOG.debug(f"Get request to load image: {request.name} from path: {request.path} ...") + ret = self.backend.load_image(request.name, request.path) + msg = '' + if ret == 0: + msg = f'Loading: {request.name}, this might take a while, please check image status with "images" command.' + elif ret == 1: + msg = f'Image: {request.path} does not exist, please check again.' + elif ret == 2: + supported_fmt = ', '.join(constants.IMAGE_LOAD_SUPPORTED_TYPES) + msg = f'Unsupported image format, the current supported format are: {supported_fmt}.' + return images_pb2.GeneralImageResponse(ret=ret, msg=msg) diff --git a/eulerlauncher/services/imager_service.py b/eulerlauncher/services/imager_service.py deleted file mode 100644 index 0d1fb8eb82d23aa81d1b05117536a59856cc39e9..0000000000000000000000000000000000000000 --- a/eulerlauncher/services/imager_service.py +++ /dev/null @@ -1,106 +0,0 @@ -import logging -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 - - -LOG = logging.getLogger(__name__) - - -class ImagerService(images_pb2_grpc.ImageGrpcServiceServicer): - ''' - The Imager 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.image_dir = os.path.join(self.work_dir, 'images') - self.img_record_file = os.path.join(self.image_dir, 'images.json') - if host_os == 'Win': - self.backend = win_image_handler.WinImageHandler( - self.CONF, self.work_dir, self.image_dir, self.img_record_file, LOG) - elif host_os == 'MacOS': - self.backend = mac_image_handler.MacImageHandler( - self.CONF, self.work_dir, self.image_dir, self.img_record_file, - LOG, self.svc_base_dir) - - 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) - - ret = [] - for _, images in all_images.items(): - for _, img in images.items(): - image = images_pb2.Image() - image.name = img['name'] - image.location = img['location'] - image.status = img['status'] - ret.append(image) - LOG.debug(f"Responded: {ret}") - return images_pb2.ListImageResponse(images=ret) - - 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) - - 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 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 - def do_download(images, name): - self.backend.download_and_transform(images, name) - - do_download(all_images, request.name) - - msg = f'Downloading: {request.name}, this might take a while, please check image status with "images" command.' - return images_pb2.GeneralImageResponse(ret=0, msg=msg) - - def load_image(self, request, context): - 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) - - if not supported: - supported_fmt = ', '.join(omni_constants.IMAGE_LOAD_SUPPORTED_TYPES) - msg = f'Unsupported image format, the current supported format are: {supported_fmt}.' - - return images_pb2.GeneralImageResponse(ret=1, msg=msg) - - all_images = omni_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 - - local_images = all_images['local'] - if request.name in local_images.keys(): - LOG.debug(f"Image: {request.name} already existed, replace it with: {request.path} ...") - 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 - def do_load(images, name, path, fmt, update): - self.backend.load_and_transform(images, name, path, fmt, update) - - do_load(all_images, request.name, request.path, fmt, update) - - return images_pb2.GeneralImageResponse(ret=0, msg=msg) - - 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) - ret = self.backend.delete_image(images, request.name) - if ret == 0: - msg = f'Image: {request.name} has been successfully deleted.' - elif ret == 1: - msg = f'Image: {request.name} does not exist, please check again.' - - return images_pb2.GeneralImageResponse(ret=1, msg=msg) diff --git a/eulerlauncher/services/instance_service.py b/eulerlauncher/services/instance_service.py index bea002e913f7bceab9ce1b0dea845477c430938b..d585e1ed8858a2edc53ed5ff215278f0e7ca08ef 100644 --- a/eulerlauncher/services/instance_service.py +++ b/eulerlauncher/services/instance_service.py @@ -1,81 +1,100 @@ -import logging import os from eulerlauncher.grpcs.eulerlauncher_grpc import instances_pb2, instances_pb2_grpc -from eulerlauncher.utils import utils -LOG = logging.getLogger(__name__) - class InstanceService(instances_pb2_grpc.InstanceGrpcServiceServicer): ''' The Instance 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') + def __init__(self, host_arch, host_os, CONF, LOG) -> None: + self.CONF = CONF + self.LOG = LOG + self.work_dir = self.CONF.get('default', 'work_dir') self.instance_dir = os.path.join(self.work_dir, 'instances') - self.instance_record_file = os.path.join(self.instance_dir, 'instances.json') + self.instance_record_path = os.path.join(self.instance_dir, 'instances.json') self.image_dir = os.path.join(self.work_dir, 'images') - self.img_record_file = os.path.join(self.image_dir, 'images.json') + self.image_record_path = os.path.join(self.image_dir, 'images.json') if host_os == 'Win': - from eulerlauncher.backends.win import instance_handler as win_instance_handler - self.backend = win_instance_handler.WinInstanceHandler( - self.CONF, self.work_dir, self.instance_dir, self.image_dir, self.img_record_file, LOG) + pass + # from eulerlauncher.backends.win import instance_handler as win_instance_handler + # self.backend = win_instance_handler.WinInstanceHandler( + # self.CONF, self.work_dir, self.instance_dir, self.image_dir, self.LOG) elif host_os == 'MacOS': - from eulerlauncher.backends.mac import instance_handler as mac_instance_handler + from eulerlauncher.backends import instance_handler as mac_instance_handler self.backend = mac_instance_handler.MacInstanceHandler( - self.CONF, self.work_dir, self.instance_dir, self.image_dir, - self.img_record_file, LOG, self.svc_base_dir) + self.CONF, self.work_dir, self.instance_dir, self.image_dir, self.LOG) - def list_instances(self, request, context): - LOG.debug(f"Get request to list instances ...") - instances_obj = self.backend.list_instances() + def list_instances(self, request, context): + self.LOG.debug(f"Get request to list instances ...") + all_instances = self.backend.list_instances() ret = [] - for vm_obj in instances_obj: - instance_dict = { - 'name': vm_obj.name, - 'image': vm_obj.image, - 'vm_state': vm_obj.vm_state, - 'ip_address': vm_obj.ip if vm_obj.ip else 'N/A' - } - ret.append(instance_dict) - + for instance in all_instances: + ret.append({ + 'name': instance['name'], + 'image': instance['image'], + 'vm_state': instance['state'], + 'ip_address': instance['ip_address'] + }) return instances_pb2.ListInstancesResponse(instances=ret) + def create_instance(self, request, context): - LOG.debug(f"Get request to create instance: {request.name} with image {request.image} ...") - - all_img = utils.load_json_data(self.img_record_file) - if request.image not in all_img['local'].keys(): + self.LOG.debug(f"Get request to create instance: {request.name} with image: {request.image} ...") + ret = self.backend.create_instance(request.name, request.image) + msg = '' + if ret == 0: + msg = f'Successfully created instance: {request.name} with image: {request.image}.' + elif ret == 1: msg = f'Error: Image "{request.image}" is not available locally, please check again or (down)load it before using ...' - return instances_pb2.CreateInstanceResponse(ret=2, msg=msg) - - all_instances = utils.load_json_data(self.instance_record_file) - if request.name in all_instances['instances'].keys(): + elif ret == 2: msg = f'Error: Instance with name {request.name} already exist, please specify another name.' - return instances_pb2.CreateInstanceResponse(ret=2, msg=msg) - - check_result = self.backend.check_names(request.name, all_instances) - if check_result == 1: - msg = f'Error: Instance with name {request.name} already exist in exixting Hyper-V or Qemu backend, please specify another name.' - return instances_pb2.CreateInstanceResponse(ret=2, msg=msg) - - vm = self.backend.create_instance( - request.name, request.image, self.instance_record_file, all_instances, all_img) - msg = f'Successfully created {request.name} with image {request.image}' - return instances_pb2.CreateInstanceResponse(ret=1, msg=msg, instance=vm) + elif ret == 3: + msg = f'Error: Libvirt error creating instance: {request.name}' + return instances_pb2.CreateInstanceResponse(ret=ret, msg=msg) + def delete_instance(self, request, context): - LOG.debug(f"Get request to delete instance: {request.name} ...") - all_instances = utils.load_json_data(self.instance_record_file) - if request.name not in all_instances['instances'].keys(): + self.LOG.debug(f"Get request to delete instance: {request.name} ...") + ret = self.backend.delete_instance(request.name) + msg = '' + if ret == 0: + msg = f'Successfully deleted instance: {request.name}.' + elif ret == 1: + msg = f'Error: Instance with name {request.name} does not exist.' + return instances_pb2.DeleteInstanceResponse(ret=ret, msg=msg) + + + def suspend_instance(self, request, context): + self.LOG.debug(f"Get request to suspend instance: {request.name} ...") + ret = self.backend.suspend_instance(request.name) + msg = '' + if ret == 0: + msg = f'Successfully suspended instance: {request.name}.' + elif ret == 1: + msg = f'Error: Instance with name {request.name} does not exist.' + return instances_pb2.SuspendInstanceResponse(ret=ret, msg=msg) + + + def resume_instance(self, request, context): + self.LOG.debug(f"Get request to resume instance: {request.name} ...") + ret = self.backend.resume_instance(request.name) + msg = '' + if ret == 0: + msg = f'Successfully resumed instance: {request.name}.' + elif ret == 1: + msg = f'Error: Instance with name {request.name} does not exist.' + return instances_pb2.ResumeInstanceResponse(ret=ret, msg=msg) + + + def console_instance(self, request, context): + self.LOG.debug(f"Get request to connect instance: {request.name} ...") + ret = self.backend.console_instance(request.name) + msg = '' + if ret == 0: + msg = f'Successfully connected instance: {request.name}.' + elif ret == 1: msg = f'Error: Instance with name {request.name} does not exist.' - return instances_pb2.DeleteInstanceResponse(ret=2, msg=msg) - - self.backend.delete_instance(request.name, self.instance_record_file, all_instances) - msg = f'Successfully deleted instance: {request.name}.' - return instances_pb2.DeleteInstanceResponse(ret=1, msg=msg) + return instances_pb2.ConsoleInstanceResponse(ret=ret, msg=msg) \ No newline at end of file diff --git a/eulerlauncher/utils/constants.py b/eulerlauncher/utils/constants.py index 9383d2d5f2d0edf75c689631eb017ec0d846b989..daca935303639babf32e222736ffc0533dd40b27 100644 --- a/eulerlauncher/utils/constants.py +++ b/eulerlauncher/utils/constants.py @@ -1,37 +1,35 @@ -STORAGE_PROTOCOL_ISCSI = 'iscsi' -STORAGE_PROTOCOL_FC = 'fibre_channel' -STORAGE_PROTOCOL_SMBFS = 'smbfs' -STORAGE_PROTOCOL_RBD = 'rbd' - -DISK = "VHD" - -IMAGE_LOCATION_REMOTE = 'Remote' -IMAGE_LOCATION_LOCAL = 'Local' - -IMAGE_STATUS_INIT = 'N/A' -IMAGE_STATUS_DOWLOADABLE = 'Downloadable' -IMAGE_STATUS_DOWNLOADING = 'Downloading' -IMAGE_STATUS_LOADING = 'Loading' -IMAGE_STATUS_READY = 'Ready' - -IMAGE_LOAD_SUPPORTED_TYPES = ['qcow2.xz', 'qcow2'] - -ARCH_MAP = { - 'AMD64': 'x86_64', - 'arm64': 'aarch64', - 'x86_64': 'x86_64' -} - -VM_STATE_MAP = { - 2: 'Running', - 3: 'Stopped', - 10: 'Rebooting', - 32768: 'Paused', - 32769: 'Suspended', - 99: 'N/A' - } - -OS_MAP = { - 'Darwin': 'MacOS', - 'Windows': 'Win' +IMAGE_LOCATION_REMOTE = 'Remote' +IMAGE_LOCATION_LOCAL = 'Local' + +INSTANCE_STATE_MAP = { + 0: 'N/A', + 1: 'Running', + 2: 'Blocked', + 3: 'Paused', + 4: 'Shutdown', + 5: 'Shutoff', + 6: 'Crashed', + 7: 'Suspended', + 99: 'N/A' +} + +IMAGE_STATE_MAP = { + 0: 'N/A', + 1: 'Downloadable', + 2: 'Downloading', + 3: 'Loading', + 4: 'Ready' +} + +IMAGE_LOAD_SUPPORTED_TYPES = ['qcow2.xz', 'qcow2'] + +ARCH_MAP = { + 'AMD64': 'x86_64', + 'arm64': 'aarch64', + 'x86_64': 'x86_64' +} + +OS_MAP = { + 'Darwin': 'MacOS', + 'Windows': 'Win' } \ No newline at end of file diff --git a/eulerlauncher/utils/objs.py b/eulerlauncher/utils/objs.py deleted file mode 100644 index 714e26d3247e638c9c7748080bb0550d2f9006d5..0000000000000000000000000000000000000000 --- a/eulerlauncher/utils/objs.py +++ /dev/null @@ -1,54 +0,0 @@ -import os -import configparser - -from eulerlauncher.utils import exceptions -from eulerlauncher.utils import constants - -class Instance(object): - - def __init__(self, name='') -> None: - self.name = name - self.uuid = '' - self.identifier = {} - self.metadata = None - self.vm_state = None - self.vcpu = None - self.ram = None - self.disk = None - self.info = None - self.image = None - self.ip = 'N/A' - self.mac = 'N/A' - - -class Image(object): - - def __init__(self) -> None: - self.name = '' - self.location = '' - self.status = constants.IMAGE_STATUS_INIT - self.path = '' - - def to_dict(self): - image_dict = { - 'name': self.name, - 'location': self.location, - 'status': self.status, - 'path': self.path - } - return image_dict - - def from_dict(self, img_dict): - self.name = img_dict['name'] - self.location = img_dict['location'] - self.status = img_dict['status'] - self.path = img_dict['path'] - - -class Conf(object): - - def __init__(self, config_file) -> None: - self.conf = configparser.ConfigParser() - if not os.path.exists(config_file): - raise exceptions.NoSuchFile(file=config_file) - self.conf.read(config_file) diff --git a/eulerlauncher/utils/utils.py b/eulerlauncher/utils/utils.py index 8381c74aaddaa9d9418fdc9e08dfa1c014ab1e7b..530eb76753678c47b24a839655a8871ed3d696fd 100644 --- a/eulerlauncher/utils/utils.py +++ b/eulerlauncher/utils/utils.py @@ -1,17 +1,14 @@ import functools import json -import os import random -from threading import Thread import uuid - +import subprocess +import time +from threading import Thread +from lxml import etree from google.protobuf.json_format import MessageToDict -from eulerlauncher.utils import exceptions -from eulerlauncher.utils import objs - - def asyncwrapper(fn): def wrapper(*args, **kwargs): thr = Thread(target=fn, args=args, kwargs=kwargs) @@ -30,37 +27,56 @@ def response2dict(fn): return wrap -def parse_config(args): - if len(args) != 2 or args[0] != '--config-file': - raise exceptions.NoConfigFileProvided - if not os.path.exists(args[1]): - raise exceptions.NoSuchFile(file=args[1]) - - return objs.Conf(args[1]) - +def check_format(file_name, to_check): + + ret = False + ret_fmt = None -def format_mac_addr(mac_str): - ret = '' - if len(mac_str) != 12: - return ret - mac_low = mac_str.lower() - for i in range(0, 5): - ret = ret + mac_low[2 * i] + mac_low[2 * i + 1] + '-' - ret = ret + mac_low[-2] + mac_low[-1] + for fmt in to_check: + if file_name.endswith(fmt): + ret = True + ret_fmt = fmt + break - return ret + return ret, ret_fmt + def load_json_data(json_file): with open(json_file, 'r', encoding='utf-8') as fr: - data = json.load(fr) + data = json.load(fr) return data + +def xml_find_and_set(xml, xpath, attribute=None, value=None): + namespaces = xml.getroot().nsmap + elements = xml.xpath(xpath, namespaces=namespaces) + if attribute is not None: + if value is not None: + elements[0].set(attribute, value) + return elements[0].get(attribute) + else: + if value is not None: + elements[0].text = value + return elements[0].text + + def save_json_data(json_file, data): with open(json_file, 'w', encoding='utf-8') as fw: - json.dump(data, fw, indent=4, ensure_ascii=False) + json.dump(data, fw, indent=4, ensure_ascii=False) -def generate_mac(): + +def load_xml_data(xml_file): + data = etree.parse(xml_file) + return data + + +def save_xml_data(xml_file, data): + with open(xml_file, 'wb') as fw: + fw.write(etree.tostring(data, pretty_print=True, encoding='utf-8')) + + +def generate_mac_address(): local_mac = uuid.uuid1().hex[-12:] mac = [random.randint(0x00, 0xff), random.randint(0x00, 0xff)] @@ -70,25 +86,34 @@ def generate_mac(): return (':'.join(s)) -def catch_exception(func): - - def wrap(*args, **kwargs): - try: - return func(*args, **kwargs) - except Exception: - raise exceptions.OmniVirtdNotAvailable - - return wrap -def check_file_tail(file_name, to_check): - - ret = False - ret_fmt = None - - for fmt in to_check: - if file_name.endswith(fmt): - ret = True - ret_fmt = fmt +def parse_ip_address(mac_address): + ip_address = '' + cmd = 'arp -a' + start_time = time.time() + while(ip_address == '' and time.time() - start_time < 20): + pr = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE) + arp_result = pr.stdout.decode('utf-8').split('\n') + founded = False + for str in arp_result: + # The result for 'arp -a' in MacOS is different with Linux, it erase + # the first 0 if the first digit is 0 for this mac section, add it + # back before compare + try: + arp_ip = str.split(' ')[1].replace("(", "").replace(")", "") + mac = str.split(' ')[3].replace("(", "").replace(")", "") + except IndexError: + continue + mac_list = mac.split(':') + for i in range(0, len(mac_list)): + if len(mac_list[i]) == 1: + mac_list[i] = '0' + mac_list[i] + mac_0 = ':'.join(mac_list) + if mac_address == mac_0: + ip_address = arp_ip + founded = True + break + if founded: break - return ret, ret_fmt + return ip_address diff --git a/requirements-win.txt b/requirements-win.txt deleted file mode 100644 index b67b36dc4415cf837515d153a8423b9e6e45fb9a..0000000000000000000000000000000000000000 --- a/requirements-win.txt +++ /dev/null @@ -1,24 +0,0 @@ -argparse -click -configparser -dnspython -grpcio -grpcio-tools -lxml -os-win -oslo.concurrency -oslo.config -oslo.context -oslo.i18n -oslo.log -oslo.serialization -oslo.utils -Pillow -prettytable -protobuf -psutil -pystray -PyYAML -six -urllib3 -wget diff --git a/requirements.txt b/requirements.txt index db5e0f68c92003b71fc38050787376e243cb4a4a..339a5ec972071f40d113861bcf8c4bd91bbf8b93 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,9 +4,7 @@ configparser dnspython grpcio grpcio-tools -pkg-config lxml -os-win oslo.concurrency oslo.config oslo.context @@ -23,3 +21,4 @@ PyYAML six urllib3 wget +libvirt-python \ No newline at end of file diff --git a/resources/libvirt/libvirt-aarch64.xml b/resources/libvirt/libvirt-aarch64.xml new file mode 100644 index 0000000000000000000000000000000000000000..ab03dbb9a846fd80654c81611e756ec4348d0bf7 --- /dev/null +++ b/resources/libvirt/libvirt-aarch64.xml @@ -0,0 +1,103 @@ + + + + + + + hvm + + + + + + + + + + + + + + + +
+ + + + + + +
+ + + + + + +
+ + + + + + +
+ + + + + + +
+ + + + +
+ + + + +
+ + +