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 |
|
| 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 |
|
| [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 backend(server)[Backend]
|
||||||
group frontend(internet)[Frontend]
|
group frontend(internet)[Frontend]
|
||||||
|
|
||||||
|
service disks(server)[Disks] in backend
|
||||||
service workstations(server)[Workstations] in backend
|
service workstations(server)[Workstations] in backend
|
||||||
|
|
||||||
service header(internet)[Header] in frontend
|
service header(internet)[Header] in frontend
|
||||||
|
|
||||||
header:R --> L:workstations
|
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:
|
nav:
|
||||||
- Backend:
|
- Backend:
|
||||||
- backend/index.md
|
- backend/index.md
|
||||||
|
- Disks: backend/disks.md
|
||||||
- Workstations: backend/workstations.md
|
- Workstations: backend/workstations.md
|
||||||
- Frontend:
|
- Frontend:
|
||||||
- frontend/index.md
|
- frontend/index.md
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue