Disks Service

This commit is contained in:
Henrik Brüsecke 2026-10-22 19:38:18 +03:00
parent 271007044a
commit 7f5b5209a1
10 changed files with 256 additions and 0 deletions

31
backend/disks/disk.py Normal file
View file

@ -0,0 +1,31 @@
import pathlib
import threading
import weakref
import disk_paths
import disk_utils
DICTIONARY_LOCK = threading.Lock()
DISK_LOCKS = weakref.WeakValueDictionary()
def create_disk(request):
paths = disk_paths.DiskPaths(request)
workstation = request['workstation']
with disk_lock(paths):
if not paths.target_path.exists():
disk_utils.create_os_disk(paths)
if workstation['cpu'] == 'ARM' and not paths.nvram_target_path.exists():
disk_utils.create_nvram_disk(paths)
def delete_disk(request):
paths = disk_paths.DiskPaths(request)
with disk_lock(paths):
paths.target_path.unlink(missing_ok=True)
paths.nvram_target_path.unlink(missing_ok=True)
def disk_lock(paths):
with DICTIONARY_LOCK:
return DISK_LOCKS.setdefault(paths.target_filename, threading.Lock())

View file

@ -0,0 +1,60 @@
import pathlib
class DiskPaths:
STORAGE_HOSTS_PATH = pathlib.Path("/var/opt/instant-workstation/storage-hosts")
def __init__(self, request):
self.persistence_type = request['persistence_type']
self.storage_host = request['storage_host']
self.workstation = request['workstation']
self.user_id = request['user_id']
@property
def nvram_filename(self):
return f"{self.workstation_key}_NVRAM.img"
@property
def nvram_path(self):
return self.storage_host_path / 'firmware' / self.nvram_filename
@property
def nvram_target_filename(self):
return f"{self.user_id}_{self.nvram_filename}"
@property
def nvram_target_path(self):
return self.storage_host_path / self.persistence_type / self.nvram_target_filename
@property
def staging_path(self):
return self.storage_host_path / 'staging'
@property
def storage_host_path(self):
return self.STORAGE_HOSTS_PATH / self.storage_host
@property
def target_filename(self):
return f"{self.user_id}_{self.template_filename}"
@property
def target_path(self):
return self.storage_host_path / self.persistence_type / self.target_filename
@property
def template_filename(self):
return f"{self.workstation_key}.qcow2"
@property
def template_path(self):
return self.storage_host_path / 'template' / self.template_filename
@property
def workstation_key(self):
return (
f"{self.workstation['name']}_"
f"{self.workstation['version']}_"
f"{self.workstation['cpu']}_"
f"{self.workstation['bitness']}_"
f"{self.workstation['endianness']}"
)

View file

@ -0,0 +1,30 @@
import pathlib
import shutil
import subprocess
import tempfile
def create_os_disk(paths):
with tempfile.TemporaryDirectory(dir=paths.staging_path) as temporary_directory:
temporary_path = pathlib.Path(temporary_directory) / 'disk.qcow2'
subprocess.run(
args=[
'qemu-img', 'create',
'--format', 'qcow2',
'--backing', paths.template_path,
'--backing-format', 'qcow2',
temporary_path
],
check=True,
timeout=10
)
temporary_path.replace(paths.target_path)
def create_nvram_disk(paths):
with tempfile.TemporaryDirectory(dir=paths.staging_path) as temporary_directory:
temporary_path = pathlib.Path(temporary_directory) / 'NVRAM.img'
shutil.copyfile(paths.nvram_path, temporary_path)
temporary_path.replace(paths.nvram_target_path)

19
backend/disks/disks.py Normal file
View file

@ -0,0 +1,19 @@
import http.server
import signal
import request_handler
def shutdown(_signum, _frame):
raise SystemExit(0)
SERVER_ADDRESS = ('', 50005)
SERVER = http.server.ThreadingHTTPServer(SERVER_ADDRESS, request_handler.RequestHandler)
SERVER.daemon_threads = False
signal.signal(signal.SIGINT, shutdown)
signal.signal(signal.SIGTERM, shutdown)
try:
SERVER.serve_forever()
finally:
SERVER.server_close()

View file

@ -0,0 +1,41 @@
import http.server
import json
import disk
class RequestHandler(http.server.BaseHTTPRequestHandler):
protocol_version = 'HTTP/1.1'
timeout = 10
def do_DELETE(self):
try:
disk.delete_disk(self.body())
except Exception as error:
self.log_error("Disk deletion failed: %s", error)
self.send_error(500)
return
self.send_success()
def do_POST(self):
try:
disk.create_disk(self.body())
except Exception as error:
self.log_error("Disk creation failed: %s", error)
self.send_error(500)
return
self.send_success()
def body(self):
content_length = int(self.headers.get('Content-Length', 0))
return json.loads(self.rfile.read(content_length))
def send_success(self):
self.close_connection = True
self.send_response(200)
self.send_header('Content-Length', '0')
self.send_header('Connection', 'close')
self.end_headers()