Disks Service
This commit is contained in:
parent
271007044a
commit
7f5b5209a1
10 changed files with 256 additions and 0 deletions
31
backend/disks/disk.py
Normal file
31
backend/disks/disk.py
Normal 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())
|
||||
60
backend/disks/disk_paths.py
Normal file
60
backend/disks/disk_paths.py
Normal 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']}"
|
||||
)
|
||||
30
backend/disks/disk_utils.py
Normal file
30
backend/disks/disk_utils.py
Normal 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
19
backend/disks/disks.py
Normal 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()
|
||||
41
backend/disks/request_handler.py
Normal file
41
backend/disks/request_handler.py
Normal 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()
|
||||
59
docs/backend/disks.md
Normal file
59
docs/backend/disks.md
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# Disks
|
||||
|
||||
``` bash
|
||||
cd /opt/instant-workstation/backend/disks
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
=== "Fedora"
|
||||
|
||||
``` bash
|
||||
sudo dnf install python qemu-img
|
||||
```
|
||||
|
||||
=== "Ubuntu"
|
||||
|
||||
``` bash
|
||||
sudo apt install python-is-python3 qemu-utils
|
||||
```
|
||||
|
||||
``` bash
|
||||
sudo ln -s /opt/instant-workstation/infrastructure/systemd/disks.service /etc/systemd/system/.
|
||||
```
|
||||
|
||||
``` bash
|
||||
sudo systemctl daemon-reload
|
||||
```
|
||||
|
||||
``` bash
|
||||
sudo systemctl enable disks
|
||||
```
|
||||
|
||||
``` bash
|
||||
sudo systemctl start disks
|
||||
```
|
||||
|
||||
``` bash
|
||||
sudo systemctl status disks
|
||||
```
|
||||
|
||||
## Updates
|
||||
|
||||
### Service
|
||||
|
||||
``` bash
|
||||
git pull
|
||||
```
|
||||
|
||||
``` bash
|
||||
sudo systemctl daemon-reload
|
||||
```
|
||||
|
||||
``` bash
|
||||
sudo systemctl restart disks
|
||||
```
|
||||
|
||||
``` bash
|
||||
sudo systemctl status disks
|
||||
```
|
||||
|
|
@ -2,4 +2,5 @@
|
|||
|
||||
| Service | Language | Toolchain | Host | Port | Last Updated |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| [Disks](disks.md) | Python | CPython | 192.168.88.251 | 50005 | 2026-10-22 |
|
||||
| [Workstations](workstations.md) | JavaScript | Node.js | 192.168.88.228 | 50000 | 2026-09-02 |
|
||||
|
|
@ -7,7 +7,9 @@ architecture-beta
|
|||
group backend(server)[Backend]
|
||||
group frontend(internet)[Frontend]
|
||||
|
||||
service disks(server)[Disks] in backend
|
||||
service workstations(server)[Workstations] in backend
|
||||
|
||||
service header(internet)[Header] in frontend
|
||||
|
||||
header:R --> L:workstations
|
||||
|
|
|
|||
12
infrastructure/systemd/disks.service
Normal file
12
infrastructure/systemd/disks.service
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
[Service]
|
||||
ExecStart=python disks.py
|
||||
Restart=on-failure
|
||||
User=instant-workstation
|
||||
WorkingDirectory=/opt/instant-workstation/backend/disks
|
||||
|
||||
[Unit]
|
||||
After=remote-fs.target
|
||||
Wants=remote-fs.target
|
||||
|
|
@ -13,6 +13,7 @@ markdown_extensions:
|
|||
nav:
|
||||
- Backend:
|
||||
- backend/index.md
|
||||
- Disks: backend/disks.md
|
||||
- Workstations: backend/workstations.md
|
||||
- Frontend:
|
||||
- frontend/index.md
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue