capsul-flask/capsulflask/operation_model.py

185 lines
5.8 KiB
Python
Raw Normal View History

import subprocess
import re
import requests
import json
import asyncio
from typing import List
from aiohttp import ClientSession
from flask import current_app
from time import sleep
from os.path import join
from subprocess import run
2020-05-11 01:23:00 +00:00
from capsulflask.db_model import OnlineHost
2020-05-11 01:23:00 +00:00
from capsulflask.db import get_model
def validate_capsul_id(id):
2020-05-13 18:56:43 +00:00
if not re.match(r"^(cvm|capsul)-[a-z0-9]{10}$", id):
2020-05-11 01:23:00 +00:00
raise ValueError(f"vm id \"{id}\" must match \"^capsul-[a-z0-9]{{10}}$\"")
class VirtualMachine:
def __init__(self, id, ipv4=None, ipv6=None):
self.id = id
self.ipv4 = ipv4
self.ipv6 = ipv6
class OperationInterface:
2020-05-13 18:56:43 +00:00
def capacity_avaliable(self, additional_ram_bytes: int) -> bool:
pass
def get(self, id: str) -> 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_public_keys: list):
pass
2020-05-11 01:23:00 +00:00
def destroy(self, email: str, id: str):
pass
class MockVirtualization(OperationInterface):
2020-05-13 18:56:43 +00:00
def capacity_avaliable(self, additional_ram_bytes):
return True
2020-05-11 01:23:00 +00:00
def get(self, id):
validate_capsul_id(id)
2020-05-11 01:23:00 +00:00
return VirtualMachine(id, ipv4="1.1.1.1")
def list_ids(self) -> list:
return get_model().all_non_deleted_vm_ids()
2020-05-11 01:23:00 +00:00
def create(self, email: str, id: str, template_image_file_name: str, vcpus: int, memory_mb: int, ssh_public_keys: list):
validate_capsul_id(id)
2020-05-16 04:19:01 +00:00
current_app.logger.info(f"mock create: {id} for {email}")
sleep(1)
2020-05-11 01:23:00 +00:00
def destroy(self, email: str, id: str):
2020-05-16 04:19:01 +00:00
current_app.logger.info(f"mock destroy: {id} for {email}")
2020-05-11 01:23:00 +00:00
class GoshtOperation(OperationInterface):
async def post_json(self, method: str, url: str, body: str, session: ClientSession, **kwargs) -> str:
resp = await session.request(method=method, url=url, json=body, **kwargs)
resp.raise_for_status()
return await resp.text()
async def make_requests(self, online_hosts: List[OnlineHost], body: str, **kwargs) -> None:
async with ClientSession() as session:
tasks = []
for host in online_hosts:
tasks.append(
self.post_json(method="POST", url=host.url, body=body, session=session, **kwargs)
)
results = await asyncio.gather(*tasks)
# do something with results
def validate_completed_process(self, completedProcess, email=None):
2020-05-11 01:23:00 +00:00
emailPart = ""
if email != None:
emailPart = f"for {email}"
if completedProcess.returncode != 0:
2020-05-11 01:23:00 +00:00
raise RuntimeError(f"""{" ".join(completedProcess.args)} failed {emailPart} with exit code {completedProcess.returncode}
stdout:
{completedProcess.stdout}
stderr:
{completedProcess.stderr}
""")
2020-05-13 18:56:43 +00:00
def capacity_avaliable(self, additional_ram_bytes):
online_hosts = get_model().get_online_hosts()
payload = json.dumps(dict(type="capacity_avaliable", additional_ram_bytes=""))
get_model().create_operation(online_hosts, None, payload)
2020-05-13 18:56:43 +00:00
2020-05-13 18:56:43 +00:00
def get(self, id):
validate_capsul_id(id)
completedProcess = run([join(current_app.root_path, 'shell_scripts/get.sh'), id], capture_output=True)
self.validate_completed_process(completedProcess)
lines = completedProcess.stdout.splitlines()
ipaddr = lines[0].decode("utf-8")
if not re.match(r"^([0-9]{1,3}\.){3}[0-9]{1,3}$", ipaddr):
return None
return VirtualMachine(id, ipv4=ipaddr)
def list_ids(self) -> list:
2020-05-13 18:56:43 +00:00
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_public_keys: list):
validate_capsul_id(id)
2020-10-30 02:25:45 +00:00
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/_.-]+$\"")
2020-10-30 02:25:45 +00:00
for ssh_public_key in ssh_public_keys:
2020-05-11 21:24:37 +00:00
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_public_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_public_keys={ssh_keys_string}
"""
if not status == "success":
2020-05-11 01:23:00 +00:00
raise ValueError(f"""failed to create vm for {email} with:
{vmSettings}
stdout:
{completedProcess.stdout}
stderr:
{completedProcess.stderr}
""")
2020-05-11 01:23:00 +00:00
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":
2020-05-11 01:23:00 +00:00
raise ValueError(f"""failed to destroy vm "{id}" for {email}:
stdout:
{completedProcess.stdout}
stderr:
{completedProcess.stderr}
""")