功能:完善通用云管工作台、审计与诊断能力

This commit is contained in:
songyiming committed 2026-11-11 18:46:16 +08:00
1 parent 5ac692724d
commit 9f05aa20ea
225 files changed
+14266 -141

No files matched your search

+50
View File
@@ -0,0 +1,50 @@
/* Fake NVIDIA entrypoints for exercising the real preload adapter's ABI.
* No GPU or proprietary library is used. Compile with vendor/NvFBC.h.
*/
#include "NvFBC.h"
extern void *dlsym(void *, const char *);
static int test_case, captured_cursor, output_id, sampling;
static NVFBCSTATUS NVFBCAPI mock_capture(const NVFBC_SESSION_HANDLE handle,
NVFBC_CREATE_CAPTURE_SESSION_PARAMS *p) {
(void)handle;
captured_cursor = p->bWithCursor;
output_id = p->dwOutputId;
sampling = p->dwSamplingRateMs;
return test_case == 6 ? NVFBC_ERR_INTERNAL : NVFBC_SUCCESS;
}
static NVFBCSTATUS NVFBCAPI mock_status(const NVFBC_SESSION_HANDLE handle,
NVFBC_GET_STATUS_PARAMS *p) {
(void)handle;
p->bIsCapturePossible = test_case == 4 ? NVFBC_FALSE : NVFBC_TRUE;
p->screenSize = (NVFBC_SIZE){test_case == 3 ? 1920 : 2560, 1440};
p->bXRandRAvailable = NVFBC_TRUE;
p->dwOutputNum = test_case == 5 ? 2 : 0;
return NVFBC_SUCCESS;
}
NVFBCSTATUS NVFBCAPI NvFBCCreateInstance(NVFBC_API_FUNCTION_LIST *f) {
f->nvFBCCreateCaptureSession = mock_capture;
f->nvFBCGetStatus = mock_status;
return NVFBC_SUCCESS;
}
int run_test(int which, int leased, int headless) {
test_case = which;
PNVFBCCREATEINSTANCE create = (PNVFBCCREATEINSTANCE)dlsym((void *)0, "NvFBCCreateInstance");
if (!create) return 1;
NVFBC_API_FUNCTION_LIST f = {.dwVersion=NVFBC_VERSION};
if (create(&f) != NVFBC_SUCCESS) return 2;
NVFBC_CREATE_CAPTURE_SESSION_PARAMS p = {
.dwVersion = which == 7 ? 0 : NVFBC_CREATE_CAPTURE_SESSION_PARAMS_VER,
.bWithCursor = NVFBC_TRUE, .dwOutputId = 73, .dwSamplingRateMs=16
};
NVFBCSTATUS status = f.nvFBCCreateCaptureSession(1, &p);
if (status != (which == 6 ? NVFBC_ERR_INTERNAL : NVFBC_SUCCESS)) return 3;
if (captured_cursor != (leased ? NVFBC_FALSE : NVFBC_TRUE)) return 4;
if (p.bWithCursor != NVFBC_TRUE || p.dwOutputId != 73 || output_id != 73 || sampling != 16) return 5;
NVFBC_GET_STATUS_PARAMS s = {.dwVersion=which == 8 ? 0 : NVFBC_GET_STATUS_PARAMS_VER};
if (f.nvFBCGetStatus(1, &s) != NVFBC_SUCCESS) return 6;
int enumerate_root = headless && which != 3 && which != 4 && which != 5;
if (enumerate_root) {
if (s.dwOutputNum != 1 || s.bXRandRAvailable || s.outputs[0].trackedBox.w != 2560 || s.outputs[0].trackedBox.h != 1440) return 7;
} else if (s.dwOutputNum != (which == 5 ? 2U : 0U) || !s.bXRandRAvailable) return 8;
return 0;
}
+44
View File
@@ -0,0 +1,44 @@
"""Run against built shared objects on Linux, including abort-on-ABI-change cases.
Usage: python3 run_nvfbc_adapter.py /absolute/adapter.so /absolute/stub.so
"""
import ctypes
import os
from pathlib import Path
import resource
import signal
import subprocess
import sys
import tempfile
if len(sys.argv) > 3:
resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
library = ctypes.CDLL(sys.argv[2], mode=ctypes.RTLD_GLOBAL)
library.run_test.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int]
library.run_test.restype = ctypes.c_int
raise SystemExit(library.run_test(*(int(v) for v in sys.argv[3:])))
adapter, stub = [str(Path(p).resolve(strict=True)) for p in sys.argv[1:]]
with tempfile.TemporaryDirectory() as directory:
for case, leased, headless in [(0, 0, 0), (1, 1, 0), (2, 1, 1),
(2, 0, 1), (3, 1, 1), (4, 1, 1),
(5, 1, 1), (6, 1, 1), (7, 1, 1), (8, 1, 1)]:
env = dict(os.environ, LD_PRELOAD=adapter,
VDI_CURSOR_RECEIPT=str(Path(directory) / 'receipt'))
env.pop('VDI_CURSOR_LEASE', None)
env.pop('VDI_NVFBC_HEADLESS', None)
if leased:
env['VDI_CURSOR_LEASE'] = 'c' * 32
if headless:
env['VDI_NVFBC_HEADLESS'] = '1'
result = subprocess.run([sys.executable, __file__, adapter, stub,
str(case), str(leased), str(headless)], env=env)
expected = -signal.SIGABRT if case in (7, 8) else 0
assert result.returncode == expected, (case, leased, headless, result.returncode)
if leased or headless:
receipt = (Path(directory) / 'receipt').read_text()
if case == 6:
assert receipt == 'nvfbc-ready-v1\n', receipt
elif case not in (7, 8):
assert receipt == 'nvfbc-v1\n', receipt
print('PASS', case, 'lease', leased, 'headless', headless, flush=True)
+106
View File
@@ -0,0 +1,106 @@
"""Linux host regressions: reject incomplete capture manifests and stale readiness."""
import importlib.util
import hashlib
import io
import json
import os
from pathlib import Path
import sys
import tempfile
import types
import unittest
from unittest.mock import patch
@unittest.skipUnless(sys.platform.startswith('linux'), 'host requires Linux flock')
class HostLeaseTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
configured_source = os.environ.get('VDI_NATIVE_CURSOR_HOST')
source = Path(configured_source) if configured_source else (
Path(__file__).resolve().parents[2] / 'terminal/native-cursor/host.py')
spec = importlib.util.spec_from_file_location('cursor_host', source)
cls.host = importlib.util.module_from_spec(spec)
spec.loader.exec_module(cls.host)
def test_display_restart_cannot_reuse_previous_ready_or_receipt(self):
h = self.host
lease = 'a' * 32
with tempfile.TemporaryDirectory() as directory:
run = Path(directory)
(run / 'ready').write_text('b' * 32)
(run / 'receipt').write_text('nvfbc-v1\n')
calls = []
def systemctl(*args):
calls.append(args)
if args == ('start', h.GUARD):
self.assertFalse((run / 'ready').exists())
self.assertFalse((run / 'receipt').exists())
self.assertEqual((run / 'lease').read_text(), lease)
(run / 'ready').write_text(lease)
return ''
cursor = types.SimpleNamespace(size=(2560, 1440), read=lambda: {})
hello = json.dumps(dict(version=1, lease=lease, size=[2560, 1440]))
stdin = types.SimpleNamespace(buffer=io.BytesIO((hello + '\nstop\n').encode()))
stdout = io.StringIO()
with patch.object(h, 'RUN', run), patch.object(h, 'systemctl', systemctl), \
patch.object(h, 'XCursor', lambda: cursor), patch.object(h.sys, 'stdin', stdin), \
patch.object(h.sys, 'stdout', stdout), \
patch.object(h.select, 'select', lambda *args: ([stdin], [], [])):
h.stream()
self.assertEqual(json.loads(stdout.getvalue())['lease'], lease)
self.assertEqual(calls, [('stop', h.GUARD), ('start', h.GUARD), ('stop', h.GUARD)])
self.assertFalse((run / 'lease').exists())
def test_incomplete_manifest_rejected_before_stopping_capture(self):
h = self.host
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
(root / 'lease').write_text('a' * 32)
(root / 'manifest.json').write_text(json.dumps({'sunshine': '0' * 64}))
with patch.object(h, 'RUN', root), patch.object(h, 'ROOT', root), \
patch.object(h, 'systemctl') as systemctl:
with self.assertRaisesRegex(RuntimeError, 'incomplete pinned capture manifest'):
h.guard()
systemctl.assert_not_called()
def test_software_sprite_guard_stops_capture_before_unhide_on_failure(self):
h = self.host
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
(root / 'lease').write_text('a' * 32)
manifest = {}
for name in ('sunshine', 'capture-no-cursor.so'):
(root / name).write_bytes(name.encode())
manifest[name] = hashlib.sha256(name.encode()).hexdigest()
(root / 'manifest.json').write_text(json.dumps(manifest))
(root / 'hide-server-cursor').touch()
events = []
class CursorOwner:
def __enter__(self):
return self
def hide_server_cursor(self):
events.append('hide')
def __exit__(self, *_):
events.append('restore')
def capture(lease):
events.append('capture')
raise RuntimeError('producer failed')
with patch.object(h, 'ROOT', root), patch.object(h, 'RUN', root), \
patch.object(h, 'XCursor', CursorOwner), \
patch.object(h, 'guard_capture', capture), \
patch.object(h, 'systemctl', lambda *args: events.append(args)):
with self.assertRaisesRegex(RuntimeError, 'producer failed'):
h.guard()
self.assertEqual(events, ['hide', 'capture', ('stop', h.SERVICE), 'restore'])
if __name__ == '__main__':
unittest.main()
+70
View File
@@ -0,0 +1,70 @@
import importlib.util
import json
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
spec = importlib.util.spec_from_file_location(
'gpu_hook', Path(__file__).resolve().parents[1] / 'pve-shared-gpu-hook.py')
hook = importlib.util.module_from_spec(spec)
spec.loader.exec_module(hook)
class SharedGPUHookTests(unittest.TestCase):
def setUp(self):
self.directory = tempfile.TemporaryDirectory()
self.addCleanup(self.directory.cleanup)
root = Path(self.directory.name)
(root / 'boot').write_text('test-boot')
for name, value in [('STATE_DIR', root), ('BOOT_ID', root / 'boot')]:
p = patch.object(hook, name, value)
p.start()
self.addCleanup(p.stop)
def test_running_guest_or_busy_device_never_resets(self):
for failing_guard in ('stopped', 'unused_gpu'):
with self.subTest(guard=failing_guard), patch.object(hook, 'stopped'), \
patch.object(hook, 'unused_gpu'), patch.object(hook, 'reenumerate') as reset:
getattr(hook, failing_guard).side_effect = RuntimeError('busy')
with self.assertRaises(RuntimeError):
hook.handle(102, 'pre-start')
reset.assert_not_called()
self.assertFalse((hook.STATE_DIR / 'reservation.json').exists())
def test_second_start_blocked_until_first_guest_stops(self):
with patch.object(hook, 'stopped'), patch.object(hook, 'unused_gpu'), \
patch.object(hook, 'reenumerate') as reset:
hook.handle(102, 'pre-start')
with self.assertRaises(RuntimeError):
hook.handle(100, 'pre-start')
self.assertEqual(reset.call_count, 1)
hook.handle(100, 'post-stop')
self.assertTrue((hook.STATE_DIR / 'reservation.json').exists())
hook.handle(102, 'post-start')
hook.handle(102, 'post-stop')
hook.handle(100, 'pre-start')
self.assertEqual(reset.call_count, 2)
def test_failed_reset_stays_reserved_and_is_not_replayed(self):
with patch.object(hook, 'stopped'), patch.object(hook, 'unused_gpu'), \
patch.object(hook, 'reenumerate', side_effect=RuntimeError('device missing')) as reset:
with self.assertRaises(RuntimeError):
hook.handle(102, 'pre-start')
hook.handle(102, 'post-stop')
state = json.loads((hook.STATE_DIR / 'reservation.json').read_text())
self.assertEqual(state['phase'], 'failed')
with self.assertRaises(RuntimeError):
hook.handle(102, 'pre-start')
self.assertEqual(reset.call_count, 1)
def test_post_start_cannot_claim_another_guest_reservation(self):
with patch.object(hook, 'stopped'), patch.object(hook, 'unused_gpu'), \
patch.object(hook, 'reenumerate'):
hook.handle(102, 'pre-start')
with self.assertRaises(RuntimeError):
hook.handle(100, 'post-start')
if __name__ == '__main__':
unittest.main()