Исправлены ошибки по итогам аудита: формат образов, CD-ROM, действия с машинами, firewall, VNC
- формат образа диска определяется через qemu-img info (облачные .img — это qcow2) - ISO подключается/извлекается через virsh change-media, без угадывания устройств - действия start/shutdown/delete сообщают об ошибке virsh; удаление UEFI-машин (--nvram) - правила iptables/ufw для удалённых пробросов портов теперь удаляются - VNC машин слушает только 127.0.0.1; virtuality-ctl reconfigure чинит старые машины - зелёное уведомление не показывается, если действие вернуло ошибку - убраны смена архитектуры машины, эмуляция ARM64 на x86 и загрузка по PXE - tests/test_audit_fixes.py Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019LEUXMcdZo7b87mjkUsPgg
Этот коммит содержится в:
@@ -143,6 +143,7 @@ def data_dirs(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(network_core, "NETWORK_DIR", dirs["network"])
|
||||
monkeypatch.setattr(network_core, "NFT_DIR", dirs["nft"])
|
||||
monkeypatch.setattr(network_core, "PORT_FORWARDS_FILE", dirs["network"] / "port_forwards.json")
|
||||
monkeypatch.setattr(network_core, "UFW_STATE_FILE", dirs["network"] / "ufw_rules.json")
|
||||
monkeypatch.setattr(network_core, "NAT_XML_FILE", dirs["network"] / "virtuality-nat.xml")
|
||||
monkeypatch.setattr(network_core, "NFT_FILE", dirs["nft"] / "virtuality.nft")
|
||||
monkeypatch.setattr(network_core, "enable_ip_forward", lambda: None)
|
||||
|
||||
@@ -76,6 +76,7 @@ def seed(root: Path) -> None:
|
||||
seed_backups(dirs["backups"])
|
||||
network_core.CONFIG_DIR, network_core.NETWORK_DIR, network_core.NFT_DIR = dirs["config"], dirs["network"], dirs["nft"]
|
||||
network_core.PORT_FORWARDS_FILE = dirs["network"] / "port_forwards.json"
|
||||
network_core.UFW_STATE_FILE = dirs["network"] / "ufw_rules.json"
|
||||
network_core.NAT_XML_FILE = dirs["network"] / "virtuality-nat.xml"
|
||||
network_core.NFT_FILE = dirs["nft"] / "virtuality.nft"
|
||||
network_core.enable_ip_forward = lambda: None
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
"""Regressions for the defects found by the 0.11 audit."""
|
||||
import json
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote
|
||||
|
||||
import app
|
||||
import conftest
|
||||
import network_core
|
||||
|
||||
CDROM_XML = conftest.DUMPXML.replace(
|
||||
"<disk type='file' device='disk'><target dev='vda' bus='virtio'/></disk>",
|
||||
"<disk type='file' device='disk'><target dev='vda' bus='virtio'/></disk><disk type='file' device='cdrom'><source file='/var/lib/virtuality/iso/old.iso'/><target dev='sda' bus='sata'/></disk>",
|
||||
)
|
||||
|
||||
|
||||
def recording(monkeypatch, module, overrides=()):
|
||||
calls: list[list[str]] = []
|
||||
|
||||
def run(cmd, timeout=12, **kwargs):
|
||||
calls.append(list(cmd))
|
||||
for prefix, result in overrides:
|
||||
if cmd[: len(prefix)] == list(prefix):
|
||||
return result
|
||||
return conftest.fake_run_cmd(cmd, timeout, **kwargs)
|
||||
|
||||
monkeypatch.setattr(module, "run_cmd", run)
|
||||
return calls
|
||||
|
||||
|
||||
def find(calls, *prefix):
|
||||
return [cmd for cmd in calls if cmd[: len(prefix)] == list(prefix)]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- disk image format (app.py:620)
|
||||
def test_disk_image_format_asks_qemu_img(data_dirs, monkeypatch):
|
||||
# Ubuntu cloud images are qcow2 files named *.img: the suffix must not decide the format.
|
||||
assert app.disk_image_format(Path("/tmp/noble-server-cloudimg-amd64.img")) == "qcow2"
|
||||
recording(monkeypatch, app, [(["qemu-img", "info"], conftest.fake_result(ok=False, stderr="no such file"))])
|
||||
assert app.disk_image_format(Path("/tmp/disk.img")) == "raw"
|
||||
assert app.disk_image_format(Path("/tmp/disk.qcow2")) == "qcow2"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- VM actions (app.py:1977, 1981)
|
||||
def test_vm_action_reports_virsh_error(logged_in, data_dirs, monkeypatch):
|
||||
recording(monkeypatch, app, [(["virsh", "start"], conftest.fake_result(ok=False, stderr="error: Domain is already active"))])
|
||||
response = logged_in.post("/vm/web01/start", follow_redirects=False)
|
||||
assert response.status_code == 303
|
||||
assert response.headers["location"] == "/vm/web01?error=" + quote("Машина уже запущена")
|
||||
assert "Машина уже запущена" in logged_in.get("/vm/web01?error=" + quote("Машина уже запущена")).text
|
||||
|
||||
|
||||
def test_vm_action_success_redirects_without_error(logged_in, data_dirs, monkeypatch):
|
||||
recording(monkeypatch, app)
|
||||
assert logged_in.post("/vm/web01/shutdown", follow_redirects=False).headers["location"] == "/vm/web01"
|
||||
|
||||
|
||||
def test_vm_delete_undefines_uefi_machines(logged_in, data_dirs, monkeypatch):
|
||||
calls = recording(monkeypatch, app)
|
||||
response = logged_in.post("/vm/web01/delete", follow_redirects=False)
|
||||
assert response.headers["location"].startswith("/?message=")
|
||||
assert find(calls, "virsh", "destroy", "web01")
|
||||
undefine = find(calls, "virsh", "undefine", "web01")[0]
|
||||
assert "--nvram" in undefine and "--remove-all-storage" in undefine and "--snapshots-metadata" in undefine
|
||||
|
||||
|
||||
def test_vm_delete_failure_is_reported(logged_in, data_dirs, monkeypatch):
|
||||
recording(monkeypatch, app, [(["virsh", "undefine"], conftest.fake_result(ok=False, stderr="error: Requested operation is not valid: cannot undefine domain with nvram"))])
|
||||
response = logged_in.post("/vm/web01/delete", follow_redirects=False)
|
||||
assert response.headers["location"].startswith("/vm/web01?error=")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- ISO in the CD-ROM drive (app.py:1150, 1158)
|
||||
def test_iso_mount_uses_change_media_for_existing_drive(logged_in, data_dirs, monkeypatch):
|
||||
iso = data_dirs["iso"] / "debian.iso"
|
||||
iso.write_bytes(b"iso")
|
||||
calls = recording(monkeypatch, app, [(["virsh", "dumpxml"], conftest.fake_result(CDROM_XML))])
|
||||
ok, message = app.mount_vm_iso("web01", str(iso))
|
||||
assert ok, message
|
||||
assert find(calls, "virsh", "change-media", "web01", "sda", str(iso), "--update", "--config", "--live")
|
||||
assert not find(calls, "virsh", "detach-disk") and not find(calls, "virsh", "attach-disk")
|
||||
ok, message = app.detach_vm_iso("web01")
|
||||
assert ok and find(calls, "virsh", "change-media", "web01", "sda", "--eject", "--config", "--live")
|
||||
|
||||
|
||||
def test_iso_unmount_never_guesses_targets(data_dirs, monkeypatch):
|
||||
# web01 has only a virtio data disk: nothing may be detached blindly.
|
||||
calls = recording(monkeypatch, app)
|
||||
ok, message = app.detach_vm_iso("web01")
|
||||
assert not ok and message == "Подключенный ISO не найден."
|
||||
assert not find(calls, "virsh", "detach-disk") and not find(calls, "virsh", "change-media")
|
||||
|
||||
|
||||
def test_iso_mount_adds_drive_to_config_when_missing(data_dirs, monkeypatch):
|
||||
iso = data_dirs["iso"] / "debian.iso"
|
||||
iso.write_bytes(b"iso")
|
||||
calls = recording(monkeypatch, app)
|
||||
ok, message = app.mount_vm_iso("web01", str(iso))
|
||||
assert ok and "перезапуска" in message
|
||||
attach = find(calls, "virsh", "attach-disk", "web01")[0]
|
||||
assert "--config" in attach and "--live" not in attach
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- port forwards leave no stale firewall rules (network_core.py:459)
|
||||
def test_iptables_fallback_removes_stale_rules(data_dirs, monkeypatch):
|
||||
listing = "\n".join([
|
||||
"-A FORWARD -i eth0 -o virbr1 -p tcp -d 192.168.100.51 -m tcp --dport 22 -m comment --comment virtuality-forward -j ACCEPT",
|
||||
"-A FORWARD -j SOMETHING_ELSE",
|
||||
])
|
||||
calls = recording(monkeypatch, network_core, [(["iptables", "-t", "filter", "-S", "FORWARD"], conftest.fake_result(listing))])
|
||||
network_core.apply_iptables_fallback([])
|
||||
deleted = find(calls, "iptables", "-t", "filter", "-D", "FORWARD")
|
||||
assert deleted == [["iptables", "-t", "filter", "-D", "FORWARD", "-i", "eth0", "-o", "virbr1", "-p", "tcp", "-d", "192.168.100.51", "-m", "tcp", "--dport", "22", "-m", "comment", "--comment", "virtuality-forward", "-j", "ACCEPT"]]
|
||||
checked = find(calls, "iptables", "-t", "nat", "-C", "POSTROUTING")
|
||||
assert checked and "virtuality-forward" in checked[0]
|
||||
|
||||
|
||||
def test_ufw_rules_follow_forwards(data_dirs, monkeypatch):
|
||||
old_rule = ["allow", "2222/tcp"]
|
||||
network_core.UFW_STATE_FILE.write_text(json.dumps([old_rule, ["route", "allow", "in", "on", "eth0", "out", "on", "virbr1", "to", "192.168.100.51", "port", "22", "proto", "tcp"]]))
|
||||
calls = recording(monkeypatch, network_core, [(["ufw", "status"], conftest.fake_result("Status: active"))])
|
||||
network_core.apply_ufw_route_rules([])
|
||||
assert find(calls, "ufw", "delete", "allow", "2222/tcp")
|
||||
assert find(calls, "ufw", "route", "delete", "allow", "in", "on", "eth0")
|
||||
assert json.loads(network_core.UFW_STATE_FILE.read_text()) == []
|
||||
Ссылка в новой задаче
Block a user