misc stuff from production

(also refactors qemu a bit)
This commit is contained in:
modeco80
2024-06-19 23:30:29 -04:00
parent 97de887518
commit 39521a4b1d
4 changed files with 69 additions and 34 deletions

View File

@@ -6,7 +6,7 @@ import * as guac from '@cvmts/guac-rs';
import CircularBuffer from 'mnemonist/circular-buffer.js'; import CircularBuffer from 'mnemonist/circular-buffer.js';
import Queue from 'mnemonist/queue.js'; import Queue from 'mnemonist/queue.js';
import { createHash } from 'crypto'; import { createHash } from 'crypto';
import { QemuVM, QemuVmDefinition } from '@cvmts/qemu'; import { VMState, QemuVM, QemuVmDefinition } from '@cvmts/qemu';
import { IPDataManager } from './IPData.js'; import { IPDataManager } from './IPData.js';
import { readFileSync } from 'node:fs'; import { readFileSync } from 'node:fs';
import path from 'node:path'; import path from 'node:path';
@@ -21,6 +21,10 @@ const __dirname = import.meta.dirname;
const kCVMTSAssetsRoot = path.resolve(__dirname, '../../assets'); const kCVMTSAssetsRoot = path.resolve(__dirname, '../../assets');
const kRestartTimeout = 5000;
type ChatHistory = { type ChatHistory = {
user: string; user: string;
msg: string; msg: string;
@@ -110,6 +114,19 @@ export default class CollabVMServer {
this.VM = vm; this.VM = vm;
// hack but whatever (TODO: less rickity)
if(config.vm.type == "qemu") {
(vm as QemuVM).on('statechange', (newState: VMState) => {
if(newState == VMState.Stopped) {
this.logger.Info("stopped ?");
setTimeout(async () => {
this.logger.Info("restarting VM");
await this.VM.Start();
}, kRestartTimeout)
}
});
}
// authentication manager // authentication manager
this.auth = auth; this.auth = auth;
} }

View File

@@ -20,7 +20,6 @@ function GetRawSharpOptions(size: Size): sharp.CreateRaw {
export default async (opts: any) => { export default async (opts: any) => {
try { try {
console.log(opts)
let out = await sharp(opts.buffer, { raw: GetRawSharpOptions(opts.size) }) let out = await sharp(opts.buffer, { raw: GetRawSharpOptions(opts.size) })
.resize(kThumbnailSize.width, kThumbnailSize.height, { fit: 'fill' }) .resize(kThumbnailSize.width, kThumbnailSize.height, { fit: 'fill' })
.jpeg({ .jpeg({

View File

@@ -1,7 +1,7 @@
{ {
"name": "@cvmts/qemu", "name": "@cvmts/qemu",
"version": "1.0.0", "version": "1.0.0",
"description": "QEMU runtime for crusttest backend", "description": "A simple and easy to use QEMU supervision runtime",
"exports": "./dist/index.js", "exports": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"type": "module", "type": "module",

View File

@@ -28,7 +28,6 @@ const kVmTmpPathBase = `/tmp`;
/// the VM is forcefully stopped. /// the VM is forcefully stopped.
const kMaxFailCount = 5; const kMaxFailCount = 5;
export class QemuVM extends EventEmitter { export class QemuVM extends EventEmitter {
private state = VMState.Stopped; private state = VMState.Stopped;
@@ -69,7 +68,6 @@ export class QemuVM extends EventEmitter {
this.addedAdditionalArguments = true; this.addedAdditionalArguments = true;
} }
this.VMLog().Info(`Starting QEMU with command \"${cmd}\"`);
await this.StartQemu(cmd); await this.StartQemu(cmd);
} }
@@ -84,28 +82,27 @@ export class QemuVM extends EventEmitter {
async Stop() { async Stop() {
// This is called in certain lifecycle places where we can't safely assert state yet // This is called in certain lifecycle places where we can't safely assert state yet
//this.AssertState(VMState.Started, 'cannot use QemuVM#Stop on a non-started VM'); //this.AssertState(VMState.Started, 'cannot use QemuVM#Stop on a non-started VM');
// Start indicating we're stopping, so we don't // Indicate we're stopping, so we don't
// erroneously start trying to restart everything // erroneously start trying to restart everything
// we're going to tear down in this function call. // we're going to tear down.
this.SetState(VMState.Stopping); this.SetState(VMState.Stopping);
// Kill the QEMU process and QMP/display connections if they are running. // Kill the QEMU process and QMP/display connections if they are running.
await this.DisconnectQmp(); await this.DisconnectQmp();
this.DisconnectDisplay(); await this.DisconnectDisplay();
await this.StopQemu(); await this.StopQemu();
} }
async Reset() { async Reset() {
this.AssertState(VMState.Started, 'cannot use QemuVM#Reset on a non-started VM'); //this.AssertState(VMState.Started, 'cannot use QemuVM#Reset on a non-started VM');
// let code know the VM is going to reset // let code know the VM is going to reset
// N.B: In the crusttest world, a reset simply amounts to a
// mean cold reboot of the qemu process basically
this.emit('reset'); this.emit('reset');
await this.Stop();
await Shared.Sleep(500); // Do magic.
await this.Start(); await this.StopQemu();
} }
async QmpCommand(command: string, args: any | null): Promise<any> { async QmpCommand(command: string, args: any | null): Promise<any> {
@@ -152,6 +149,12 @@ export class QemuVM extends EventEmitter {
private SetState(state: VMState) { private SetState(state: VMState) {
this.state = state; this.state = state;
this.emit('statechange', this.state); this.emit('statechange', this.state);
// reset some state when starting the vm back up
// to avoid potentional issues.
if(this.state == VMState.Starting) {
this.qmpFailCount = 0;
}
} }
private GetQmpPath() { private GetQmpPath() {
@@ -167,27 +170,44 @@ export class QemuVM extends EventEmitter {
this.SetState(VMState.Starting); this.SetState(VMState.Starting);
this.VMLog().Info(`Starting QEMU with command \"${split}\"`);
// Start QEMU // Start QEMU
this.qemuProcess = execaCommand(split); this.qemuProcess = execaCommand(split);
this.qemuProcess.on('spawn', async () => { this.qemuProcess.on('spawn', async () => {
self.VMLog().Info("QEMU started");
self.qemuRunning = true; self.qemuRunning = true;
await Shared.Sleep(500); await Shared.Sleep(500);
await self.ConnectQmp(); await self.ConnectQmp();
}); });
this.qemuProcess.on('exit', async (code) => { this.qemuProcess.on('exit', async (code) => {
self.VMLog().Info("QEMU process exited");
self.qemuRunning = false; self.qemuRunning = false;
// this should be being done anways but it's very clearly not sometimes so
// fuck it, let's just force it here
try {
await unlink(this.GetVncPath());
} catch(_) {}
try {
await unlink(this.GetQmpPath());
} catch(_) {}
// ? // ?
if (self.qmpConnected) { if (self.qmpConnected) {
await self.DisconnectQmp(); await self.DisconnectQmp();
} }
self.DisconnectDisplay(); await self.DisconnectDisplay();
if (self.state != VMState.Stopping) { if (self.state != VMState.Stopping) {
if (code == 0) { if (code == 0) {
// Wait a bit and restart QEMU.
await Shared.Sleep(500); await Shared.Sleep(500);
await self.StartQemu(split); await self.StartQemu(split);
} else { } else {
@@ -195,6 +215,7 @@ export class QemuVM extends EventEmitter {
await self.Stop(); await self.Stop();
} }
} else { } else {
// Indicate we have stopped.
this.SetState(VMState.Stopped); this.SetState(VMState.Stopped);
} }
}); });
@@ -210,26 +231,28 @@ export class QemuVM extends EventEmitter {
if (!this.qmpConnected) { if (!this.qmpConnected) {
self.qmpInstance = new QmpClient(); self.qmpInstance = new QmpClient();
let onQmpError = async (err: Error|undefined) => { let onQmpError = async () => {
self.qmpConnected = false; if(self.qmpConnected) {
self.qmpConnected = false;
// If we aren't stopping, then we do actually need to care QMP disconnected // If we aren't stopping, then we should care QMP disconnected
if (self.state != VMState.Stopping) { if (self.state != VMState.Stopping) {
//if(err !== undefined) // This doesn't show anything useful or maybe I'm just stupid idk if (self.qmpFailCount++ < kMaxFailCount) {
// self.VMLog().Error(`Error: ${err!}`) self.VMLog().Error(`Failed to connect to QMP ${self.qmpFailCount} times.`);
if (self.qmpFailCount++ < kMaxFailCount) { await Shared.Sleep(500);
self.VMLog().Error(`Failed to connect to QMP ${self.qmpFailCount} times.`); await self.ConnectQmp();
await Shared.Sleep(500); } else {
await self.ConnectQmp(); self.VMLog().Error(`Reached max retries, giving up.`);
} else { await self.Stop();
self.VMLog().Error(`Reached max retries, giving up.`); }
await self.Stop();
} }
} }
}; };
self.qmpInstance.on('close', onQmpError); self.qmpInstance.on('close', onQmpError);
self.qmpInstance.on('error', onQmpError); self.qmpInstance.on('error', (e: Error) => {
self.VMLog().Error("QMP Error: {0}", e.message);
});
self.qmpInstance.on('event', async (ev) => { self.qmpInstance.on('event', async (ev) => {
switch (ev.event) { switch (ev.event) {
@@ -269,10 +292,6 @@ export class QemuVM extends EventEmitter {
try { try {
this.display?.Disconnect(); this.display?.Disconnect();
//this.display = null; // disassociate with that display object. //this.display = null; // disassociate with that display object.
await unlink(this.GetVncPath());
// qemu *should* do this on its own but it really doesn't like doing so sometimes
await unlink(this.GetQmpPath());
} catch (err) { } catch (err) {
// oh well lol // oh well lol
} }