feat: hypervisor async imports

This commit is contained in:
Eli Bosley
2025-01-27 13:20:15 -05:00
parent 32f9c50227
commit f1e53831c8
4 changed files with 49 additions and 38 deletions

View File

@@ -1,8 +1,8 @@
import { ConnectListAllDomainsFlags } from '@vmngr/libvirt';
import { getHypervisor } from '@app/core/utils/vms/get-hypervisor';
import { VmState, type VmDomain } from '@app/graphql/generated/api/types';
import { GraphQLError } from 'graphql';
import type { VmDomain } from '@app/graphql/generated/api/types';
import { VmState } from '@app/graphql/generated/api/types';
const states = {
0: 'NOSTATE',
1: 'RUNNING',
@@ -17,10 +17,11 @@ const states = {
/**
* Get vm domains.
*/
export const getDomains =async () => {
export const getDomains = async () => {
const { ConnectListAllDomainsFlags } = await import('@vmngr/libvirt');
const { UnraidHypervisor } = await import('@app/core/utils/vms/get-hypervisor');
try {
const hypervisor = await getHypervisor();
const hypervisor = await UnraidHypervisor.getInstance().getHypervisor();
if (!hypervisor) {
throw new GraphQLError('VMs Disabled');
}
@@ -30,9 +31,7 @@ export const getDomains =async () => {
);
const autoStartDomainNames = await Promise.all(
autoStartDomains.map(async (domain) =>
hypervisor.domainGetName(domain)
)
autoStartDomains.map(async (domain) => hypervisor.domainGetName(domain))
);
// Get all domains
@@ -53,9 +52,11 @@ export const getDomains =async () => {
})
);
return resolvedDomains;
return resolvedDomains;
} catch (error: unknown) {
// If we hit an error expect libvirt to be offline
throw new GraphQLError(`Failed to fetch domains with error: ${error instanceof Error ? error.message : 'Unknown Error'}`);
throw new GraphQLError(
`Failed to fetch domains with error: ${error instanceof Error ? error.message : 'Unknown Error'}`
);
}
};

View File

@@ -1,13 +1,12 @@
import { access } from 'fs/promises';
import { constants } from 'fs';
import { access } from 'fs/promises';
import type { Hypervisor as HypervisorType } from '@vmngr/libvirt';
import { Hypervisor } from '@vmngr/libvirt';
import { libvirtLogger } from '@app/core/log';
const uri = process.env.LIBVIRT_URI ?? 'qemu:///system';
let hypervisor: Hypervisor | null;
const libvirtPid = '/var/run/libvirt/libvirtd.pid';
const isLibvirtRunning = async (): Promise<boolean> => {
@@ -19,29 +18,40 @@ const isLibvirtRunning = async (): Promise<boolean> => {
}
};
export const getHypervisor = async (): Promise<Hypervisor> => {
// Return hypervisor if it's already connected
const running = await isLibvirtRunning();
export class UnraidHypervisor {
private static instance: UnraidHypervisor | null = null;
private hypervisor: HypervisorType | null = null;
private constructor() {}
if (hypervisor && running) {
return hypervisor;
public static getInstance(): UnraidHypervisor {
if (this.instance === null) {
this.instance = new UnraidHypervisor();
}
return this.instance;
}
if (!running) {
hypervisor = null;
throw new Error('Libvirt is not running');
}
public async getHypervisor(): Promise<HypervisorType | null> {
// Return hypervisor if it's already connected
const running = await isLibvirtRunning();
hypervisor = new Hypervisor({ uri });
await hypervisor.connectOpen().catch((error: unknown) => {
libvirtLogger.error(
`Failed starting VM hypervisor connection with "${
(error as Error).message
}"`
);
if (this.hypervisor && running) {
return this.hypervisor;
}
throw error;
});
if (!running) {
this.hypervisor = null;
throw new Error('Libvirt is not running');
}
const { Hypervisor } = await import('@vmngr/libvirt');
this.hypervisor = new Hypervisor({ uri });
await this.hypervisor.connectOpen().catch((error: unknown) => {
libvirtLogger.error(
`Failed starting VM hypervisor connection with "${(error as Error).message}"`
);
return hypervisor;
};
throw error;
});
return this.hypervisor;
}
}

View File

@@ -1,5 +1,4 @@
import { type Domain } from '@app/core/types';
import { getHypervisor } from '@app/core/utils/vms/get-hypervisor';
export type DomainLookupType = 'id' | 'uuid' | 'name';
@@ -21,7 +20,8 @@ export const parseDomain = async (type: DomainLookupType, id: string): Promise<D
throw new Error(`Type must be one of [${Object.keys(types).join(', ')}], ${type} given.`);
}
const client = await getHypervisor();
const { UnraidHypervisor } = await import('@app/core/utils/vms/get-hypervisor');
const client = await UnraidHypervisor.getInstance().getHypervisor();
const method = types[type];
const domain = await client[method](id);
const info = await domain.getInfoAsync();

View File

@@ -3,7 +3,7 @@ import { Command, CommandRunner, Option } from 'nest-commander';
import { ECOSYSTEM_PATH, PM2_PATH } from '@app/consts';
import { levels, type LogLevel } from '@app/core/log';
import { LogService } from '@app/unraid-api/cli/log.service';
import type { LogService } from '@app/unraid-api/cli/log.service';
interface StartCommandOptions {
'log-level'?: string;