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 { GraphQLError } from 'graphql';
import type { VmDomain } from '@app/graphql/generated/api/types';
import { VmState } from '@app/graphql/generated/api/types';
const states = { const states = {
0: 'NOSTATE', 0: 'NOSTATE',
1: 'RUNNING', 1: 'RUNNING',
@@ -17,10 +17,11 @@ const states = {
/** /**
* Get vm domains. * 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 { try {
const hypervisor = await getHypervisor(); const hypervisor = await UnraidHypervisor.getInstance().getHypervisor();
if (!hypervisor) { if (!hypervisor) {
throw new GraphQLError('VMs Disabled'); throw new GraphQLError('VMs Disabled');
} }
@@ -30,9 +31,7 @@ export const getDomains =async () => {
); );
const autoStartDomainNames = await Promise.all( const autoStartDomainNames = await Promise.all(
autoStartDomains.map(async (domain) => autoStartDomains.map(async (domain) => hypervisor.domainGetName(domain))
hypervisor.domainGetName(domain)
)
); );
// Get all domains // Get all domains
@@ -53,9 +52,11 @@ export const getDomains =async () => {
}) })
); );
return resolvedDomains; return resolvedDomains;
} catch (error: unknown) { } catch (error: unknown) {
// If we hit an error expect libvirt to be offline // 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 { 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'; import { libvirtLogger } from '@app/core/log';
const uri = process.env.LIBVIRT_URI ?? 'qemu:///system'; const uri = process.env.LIBVIRT_URI ?? 'qemu:///system';
let hypervisor: Hypervisor | null;
const libvirtPid = '/var/run/libvirt/libvirtd.pid'; const libvirtPid = '/var/run/libvirt/libvirtd.pid';
const isLibvirtRunning = async (): Promise<boolean> => { const isLibvirtRunning = async (): Promise<boolean> => {
@@ -19,29 +18,40 @@ const isLibvirtRunning = async (): Promise<boolean> => {
} }
}; };
export const getHypervisor = async (): Promise<Hypervisor> => { export class UnraidHypervisor {
// Return hypervisor if it's already connected private static instance: UnraidHypervisor | null = null;
const running = await isLibvirtRunning(); private hypervisor: HypervisorType | null = null;
private constructor() {}
if (hypervisor && running) { public static getInstance(): UnraidHypervisor {
return hypervisor; if (this.instance === null) {
this.instance = new UnraidHypervisor();
}
return this.instance;
} }
if (!running) { public async getHypervisor(): Promise<HypervisorType | null> {
hypervisor = null; // Return hypervisor if it's already connected
throw new Error('Libvirt is not running'); const running = await isLibvirtRunning();
}
hypervisor = new Hypervisor({ uri }); if (this.hypervisor && running) {
await hypervisor.connectOpen().catch((error: unknown) => { return this.hypervisor;
libvirtLogger.error( }
`Failed starting VM hypervisor connection with "${
(error as Error).message
}"`
);
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 { type Domain } from '@app/core/types';
import { getHypervisor } from '@app/core/utils/vms/get-hypervisor';
export type DomainLookupType = 'id' | 'uuid' | 'name'; 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.`); 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 method = types[type];
const domain = await client[method](id); const domain = await client[method](id);
const info = await domain.getInfoAsync(); 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 { ECOSYSTEM_PATH, PM2_PATH } from '@app/consts';
import { levels, type LogLevel } from '@app/core/log'; 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 { interface StartCommandOptions {
'log-level'?: string; 'log-level'?: string;