misc stuff from production
(also refactors qemu a bit)
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,27 +83,26 @@ export class QemuVM extends EventEmitter {
|
|||||||
// 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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user