diff --git a/capsulflask/virt_model.py b/capsulflask/virt_model.py deleted file mode 100644 index 1043efc..0000000 --- a/capsulflask/virt_model.py +++ /dev/null @@ -1,193 +0,0 @@ -import subprocess -import re -import sys -import json - -from flask import current_app -from time import sleep -from os.path import join -from subprocess import run - -from capsulflask.db import get_model -from capsulflask.shared import my_exec_info_message, VirtualMachine - -def validate_capsul_id(id): - if not re.match(r"^(cvm|capsul)-[a-z0-9]{10}$", id): - raise ValueError(f"vm id \"{id}\" must match \"^capsul-[a-z0-9]{{10}}$\"") - -class VirtualizationInterface: - def capacity_avaliable(self, additional_ram_bytes: int) -> bool: - pass - - def get(self, id: str, get_ssh_host_keys: bool) -> VirtualMachine: - pass - - def list_ids(self) -> list: - pass - - def create(self, email: str, id: str, template_image_file_name: str, vcpus: int, memory: int, ssh_authorized_keys: list): - pass - - def destroy(self, email: str, id: str): - pass - -class MockVirtualization(VirtualizationInterface): - def capacity_avaliable(self, additional_ram_bytes): - return True - - def get(self, id, get_ssh_host_keys): - validate_capsul_id(id) - - if get_ssh_host_keys: - ssh_host_keys = json.loads("""[ - {"key_type":"ED25519", "content":"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN8cna0zeKSKl/r8whdn/KmDWhdzuWRVV0GaKIM+eshh", "sha256":"V4X2apAF6btGAfS45gmpldknoDX0ipJ5c6DLfZR2ttQ"}, - {"key_type":"RSA", "content":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCvotgzgEP65JUQ8S8OoNKy1uEEPEAcFetSp7QpONe6hj4wPgyFNgVtdoWdNcU19dX3hpdse0G8OlaMUTnNVuRlbIZXuifXQ2jTtCFUA2mmJ5bF+XjGm3TXKMNGh9PN+wEPUeWd14vZL+QPUMev5LmA8cawPiU5+vVMLid93HRBj118aCJFQxLgrdP48VPfKHFRfCR6TIjg1ii3dH4acdJAvlmJ3GFB6ICT42EmBqskz2MPe0rIFxH8YohCBbAbrbWYcptHt4e48h4UdpZdYOhEdv89GrT8BF2C5cbQ5i9qVpI57bXKrj8hPZU5of48UHLSpXG8mbH0YDiOQOfKX/Mt", "sha256":"ghee6KzRnBJhND2kEUZSaouk7CD6o6z2aAc8GPkV+GQ"}, - {"key_type":"ECDSA", "content":"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLLgOoATz9R4aS2kk7vWoxX+lshK63t9+5BIHdzZeFE1o+shlcf0Wji8cN/L1+m3bi0uSETZDOAWMP3rHLJj9Hk=", "sha256":"aCYG1aD8cv/TjzJL0bi9jdabMGksdkfa7R8dCGm1yYs"} - ]""") - return VirtualMachine(id, ipv4="1.1.1.1", ssh_host_keys=ssh_host_keys) - - return VirtualMachine(id, ipv4="1.1.1.1") - - def list_ids(self) -> list: - return get_model().all_non_deleted_vm_ids() - - def create(self, email: str, id: str, template_image_file_name: str, vcpus: int, memory_mb: int, ssh_authorized_keys: list): - validate_capsul_id(id) - current_app.logger.info(f"mock create: {id} for {email}") - sleep(1) - - def destroy(self, email: str, id: str): - current_app.logger.info(f"mock destroy: {id} for {email}") - - -class ShellScriptVirtualization(VirtualizationInterface): - - def validate_completed_process(self, completedProcess, email=None): - emailPart = "" - if email != None: - emailPart = f"for {email}" - - if completedProcess.returncode != 0: - raise RuntimeError(f"""{" ".join(completedProcess.args)} failed {emailPart} with exit code {completedProcess.returncode} - stdout: - {completedProcess.stdout} - stderr: - {completedProcess.stderr} - """) - - def capacity_avaliable(self, additional_ram_bytes): - my_args=[join(current_app.root_path, 'shell_scripts/capacity-avaliable.sh'), str(additional_ram_bytes)] - completedProcess = run(my_args, capture_output=True) - - if completedProcess.returncode != 0: - current_app.logger.error(f""" - capacity-avaliable.sh exited {completedProcess.returncode} with - stdout: - {completedProcess.stdout} - stderr: - {completedProcess.stderr} - """) - return False - - lines = completedProcess.stdout.splitlines() - output = lines[len(lines)-1] - if not output == b"yes": - current_app.logger.error(f"capacity-avaliable.sh exited 0 and returned {output} but did not return \"yes\" ") - return False - - return True - - def get(self, id, get_ssh_host_keys): - validate_capsul_id(id) - completedProcess = run([join(current_app.root_path, 'shell_scripts/get.sh'), id], capture_output=True) - self.validate_completed_process(completedProcess) - ipaddr_lines = completedProcess.stdout.splitlines() - if len(ipaddr_lines) == 0: - return None - - ipaddr = ipaddr_lines[0].decode("utf-8") - - if not re.match(r"^([0-9]{1,3}\.){3}[0-9]{1,3}$", ipaddr): - return None - - if get_ssh_host_keys: - try: - completedProcess2 = run([join(current_app.root_path, 'shell_scripts/ssh-keyscan.sh'), ipaddr], capture_output=True) - self.validate_completed_process(completedProcess2) - ssh_host_keys = json.loads(completedProcess2.stdout.decode("utf-8")) - return VirtualMachine(id, ipv4=ipaddr, ssh_host_keys=ssh_host_keys) - except: - current_app.logger.warning(f""" - failed to ssh-keyscan {id} at {ipaddr}: - {my_exec_info_message(sys.exc_info())}""" - ) - - return VirtualMachine(id, ipv4=ipaddr) - - def list_ids(self) -> list: - completedProcess = run([join(current_app.root_path, 'shell_scripts/list-ids.sh')], capture_output=True) - self.validate_completed_process(completedProcess) - return list(map(lambda x: x.decode("utf-8"), completedProcess.stdout.splitlines() )) - - def create(self, email: str, id: str, template_image_file_name: str, vcpus: int, memory_mb: int, ssh_authorized_keys: list): - validate_capsul_id(id) - - if not re.match(r"^[a-zA-Z0-9/_.-]+$", template_image_file_name): - raise ValueError(f"template_image_file_name \"{template_image_file_name}\" must match \"^[a-zA-Z0-9/_.-]+$\"") - - for ssh_public_key in ssh_authorized_keys: - if not re.match(r"^(ssh|ecdsa)-[0-9A-Za-z+/_=@. -]+$", ssh_public_key): - raise ValueError(f"ssh_public_key \"{ssh_public_key}\" must match \"^(ssh|ecdsa)-[0-9A-Za-z+/_=@. -]+$\"") - - if vcpus < 1 or vcpus > 8: - raise ValueError(f"vcpus \"{vcpus}\" must match 1 <= vcpus <= 8") - - if memory_mb < 512 or memory_mb > 16384: - raise ValueError(f"memory_mb \"{memory_mb}\" must match 512 <= memory_mb <= 16384") - - ssh_keys_string = "\n".join(ssh_authorized_keys) - - completedProcess = run([ - join(current_app.root_path, 'shell_scripts/create.sh'), - id, - template_image_file_name, - str(vcpus), - str(memory_mb), - ssh_keys_string - ], capture_output=True) - - self.validate_completed_process(completedProcess, email) - lines = completedProcess.stdout.splitlines() - status = lines[len(lines)-1].decode("utf-8") - - vmSettings = f""" - id={id} - template_image_file_name={template_image_file_name} - vcpus={str(vcpus)} - memory={str(memory_mb)} - ssh_authorized_keys={ssh_keys_string} - """ - - if not status == "success": - raise ValueError(f"""failed to create vm for {email} with: - {vmSettings} - stdout: - {completedProcess.stdout} - stderr: - {completedProcess.stderr} - """) - - def destroy(self, email: str, id: str): - validate_capsul_id(id) - completedProcess = run([join(current_app.root_path, 'shell_scripts/destroy.sh'), id], capture_output=True) - self.validate_completed_process(completedProcess, email) - lines = completedProcess.stdout.splitlines() - status = lines[len(lines)-1].decode("utf-8") - - if not status == "success": - raise ValueError(f"""failed to destroy vm "{id}" for {email}: - stdout: - {completedProcess.stdout} - stderr: - {completedProcess.stderr} - """)