Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/lxd-dev #2599

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,19 +104,21 @@
"@apollo/client": "3.11.8",
"@apollo/server": "4.11.0",
"@ideafast/idgen": "0.2.1",
"@simplewebauthn/browser": "^10.0.0",
"@simplewebauthn/server": "^10.0.1",
"@nivo/calendar": "0.87.0",
"@nivo/bar": "0.87.0",
"@nivo/calendar": "0.87.0",
"@nivo/core": "0.87.0",
"@nivo/line": "0.87.0",
"@nivo/pie": "0.87.0",
"@nivo/treemap": "0.87.0",
"@simplewebauthn/browser": "^10.0.0",
"@simplewebauthn/server": "^10.0.1",
"@swc/helpers": "0.5.13",
"@tanstack/react-query": "4.36.1",
"@trpc/client": "10.45.2",
"@trpc/react-query": "10.45.2",
"@trpc/server": "10.45.2",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
"JSONStream": "1.3.5",
"antd": "5.21.4",
"antd-img-crop": "4.23.0",
Expand Down Expand Up @@ -191,4 +193,4 @@
"**/jest-util": "^29",
"**/pretty-format": "^29"
}
}
}
4 changes: 3 additions & 1 deletion packages/itmat-apis/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ export * from './trpc/trpc';
export * from './trpc/tRPCRouter';
export * from './trpc/userProcedure';
export * from './trpc/middleware';
export * from './trpc/webauthnProcedure';
export * from './trpc/webauthnProcedure';
export * from './trpc/instanceProcedure';
export * from './trpc/lxdProcedure';
160 changes: 160 additions & 0 deletions packages/itmat-apis/src/trpc/instanceProcedure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { z } from 'zod';
import { TRPCBaseProcedure, TRPCRouter } from './trpc';
import { InstanceCore } from '@itmat-broker/itmat-cores';
import { enumAppType, enumInstanceStatus, LXDInstanceTypeEnum, CoreError, enumCoreErrors, enumOpeType, enumUserTypes} from '@itmat-broker/itmat-types';

export class InstanceRouter {
baseProcedure: TRPCBaseProcedure;
router: TRPCRouter;
instanceCore: InstanceCore;

constructor(baseProcedure: TRPCBaseProcedure, router: TRPCRouter, instanceCore: InstanceCore) {
this.baseProcedure = baseProcedure;
this.router = router;
this.instanceCore = instanceCore;
}

_router() {
return this.router({
/**
* Create an instance
*/
createInstance: this.baseProcedure.input(
z.object({
name: z.string(),
type: z.nativeEnum(LXDInstanceTypeEnum),
appType: z.nativeEnum(enumAppType),
lifeSpan: z.number(),
cpuLimit: z.number().optional(),
memoryLimit: z.string().optional(),
diskLimit: z.string().optional()
})
).mutation(async ({ input, ctx }) => {

if (!ctx.req.user || !ctx.req.user.id) {
throw new CoreError(
enumCoreErrors.NOT_LOGGED_IN,
'User must be authenticated.');
}
const userId = ctx.req.user.id;

// Check if requested resources exceed the user's quota
await this.instanceCore.checkQuotaBeforeCreation(userId, input.cpuLimit ?? 0, input.memoryLimit ?? '0', input.diskLimit ?? '0', 1);


return await this.instanceCore.createInstance(
userId,
ctx.req.user.username,
input.name,
input.type,
input.appType,
input.lifeSpan,
input.cpuLimit,
input.memoryLimit,
input.diskLimit
);
}),

/**
* Start or stop an instance
*/
startStopInstance: this.baseProcedure.input(
z.object({
instanceId: z.string(),
action: z.enum([enumOpeType.START, enumOpeType.STOP])
})
).mutation(async ({ input, ctx }) => {
if (!ctx.req.user || !ctx.req.user.id) {
throw new CoreError(enumCoreErrors.NOT_LOGGED_IN, 'User must be authenticated.');
}
const userId = ctx.req.user.id;

return await this.instanceCore.startStopInstance(userId, input.instanceId, input.action);
}),

/**
* Restart an instance with a new lifespan
*/
restartInstance: this.baseProcedure.input(
z.object({
instanceId: z.string(),
lifeSpan: z.number()
})
).mutation(async ({ input, ctx }) => {
if (!ctx.req.user || !ctx.req.user.id) {
throw new CoreError(enumCoreErrors.NOT_LOGGED_IN, 'User must be authenticated.');
}
const userId = ctx.req.user.id;

return await this.instanceCore.restartInstance(userId, input.instanceId, input.lifeSpan);
}),

/**
* Get all instances for an user
*/
getInstances: this.baseProcedure.query(async ({ ctx }) => {
const user = ctx.req.user;
if (!user) {
throw new CoreError(
enumCoreErrors.NO_PERMISSION_ERROR,
'Insufficient permissions.'
);
}
return await this.instanceCore.getInstances(user.id);
}),

/**
* Edit an instance
*/
editInstance: this.baseProcedure.input(
z.object({
instanceId: z.string().optional(),
instanceName: z.string().optional(),
// set the update to the type LxdConfiguration
updates: z.object({
name: z.string().optional(),
type: z.nativeEnum(LXDInstanceTypeEnum).optional(),
appType: z.nativeEnum(enumAppType).optional(),
lifeSpan: z.number().optional(),
project: z.string().optional(),
status: z.nativeEnum(enumInstanceStatus).optional(),
cpuLimit: z.number().optional(),
memoryLimit: z.string().optional()
}).passthrough()
})
).mutation(async ({ input, ctx }) => {
if (!ctx.req.user || !ctx.req.user.id) {
throw new CoreError(enumCoreErrors.NOT_LOGGED_IN, 'User must be authenticated.');
}

return await this.instanceCore.editInstance(ctx.req.user, input.instanceId, input.instanceName, input.updates);
}),

/**
* Delete an instance
*/
deleteInstance: this.baseProcedure.input(
z.object({
instanceId: z.string()
})
).mutation(async ({ input, ctx }) => {
const user = ctx.req.user;
const instance = await this.instanceCore.getInstanceById(input.instanceId);
if (user.type !== enumUserTypes.ADMIN || user.id !== instance.userId) {
throw new CoreError(
enumCoreErrors.NO_PERMISSION_ERROR,
'Insufficient permissions.'
);
}

return await this.instanceCore.deleteInstance(user.id, input.instanceId);
}),
getQuotaAndFlavors: this.baseProcedure.query(async ({ ctx }) => {
if (!ctx.req.user || !ctx.req.user.id) {
throw new CoreError(enumCoreErrors.NOT_LOGGED_IN, 'User must be authenticated.');
}
return await this.instanceCore.getQuotaAndFlavors(ctx.req.user);
})
});
}
}
149 changes: 149 additions & 0 deletions packages/itmat-apis/src/trpc/lxdProcedure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { z } from 'zod';
import { TRPCBaseProcedure, TRPCRouter } from './trpc';
import {LxdManager} from '@itmat-broker/itmat-cores';
import { CoreError, enumCoreErrors , LXDInstanceTypeEnum} from '@itmat-broker/itmat-types';

export class LXDRouter {
baseProcedure: TRPCBaseProcedure;
router: TRPCRouter;
lxdManager: LxdManager;


constructor(baseProcedure: TRPCBaseProcedure, router: TRPCRouter, lxdManager: LxdManager) {
this.baseProcedure = baseProcedure;
this.router = router;
this.lxdManager = lxdManager;
}

_router() {
return this.router({
getResources: this.baseProcedure.query(async ({ctx }) => {
if (!ctx.req?.user) {
throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.');
}
return await this.lxdManager.getResources();
}),

getInstances: this.baseProcedure.query(async ({ctx }) => {
if (!ctx.req?.user) {
throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.');
}
return await this.lxdManager.getInstances();
}),

getInstanceState: this.baseProcedure.input(z.object({
container: z.string(),
project: z.string()
})).query(async ({ input, ctx }) => {
if (!ctx.req?.user) {
throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.');
}
return await this.lxdManager.getInstanceState(input.container, input.project);
}),

getOperations: this.baseProcedure.input(z.object({})).query(async ({ ctx }) => {
if (!ctx.req?.user) {
throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.');
}
return await this.lxdManager.getOperations();
}),

getOperationStatus: this.baseProcedure.input(z.object({
operationId: z.string()
})).query(async ({ input, ctx }) => {
if (!ctx.req?.user) {
throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.');
}
return await this.lxdManager.getOperationStatus(`/1.0/operations/${input.operationId}`);
}),

getInstanceConsole: this.baseProcedure.input(z.object({
container: z.string(),
options: z.object({
height: z.number(),
width: z.number(),
type: z.string()
})
})).mutation(async ({ input, ctx }) => {
if (!ctx.req?.user) {
throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.');
}
return await this.lxdManager.getInstanceConsole(input.container, input.options);
}),

getInstanceConsoleLog: this.baseProcedure.input(z.object({
container: z.string()
})).mutation(async ({ input, ctx }) => {
if (!ctx.req?.user) {
throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.');
}
return await this.lxdManager.getInstanceConsoleLog(input.container);
}),

createInstance: this.baseProcedure.input(z.object({
// set the config to LxdConfiguration
name: z.string(),
architecture: z.literal('x86_64'),
config: z.object({
'limits.cpu': z.string(),
'limits.memory': z.string(),
'user.username': z.string(),
'user.user-data': z.string()
}),
source: z.object({
type: z.string(),
alias: z.string()
}),
profiles: z.array(z.string()),
// use LXDInstanceTypeEnum
type: z.nativeEnum(LXDInstanceTypeEnum),
project: z.string()
})).mutation(async ({ input, ctx }) => {
if (!ctx.req?.user) {
throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.');
}
return await this.lxdManager.createInstance({
name: input.name,
architecture: input.architecture,
config: input.config,
source: input.source,
profiles: input.profiles,
type: input.type
}
, input.project);
}),

updateInstance: this.baseProcedure.input(z.object({
instanceName: z.string(),
payload: z.any(),
project: z.string()
})).mutation(async ({ input, ctx }) => {
if (!ctx.req?.user) {
throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.');
}
return await this.lxdManager.updateInstance(input.instanceName, input.payload, input.project);
}),

startStopInstance: this.baseProcedure.input(z.object({
instanceName: z.string(),
action: z.enum(['start', 'stop']),
project: z.string()
})).mutation(async ({ input, ctx }) => {
if (!ctx.req?.user) {
throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.');
}
return await this.lxdManager.startStopInstance(input.instanceName, input.action, input.project);
}),

deleteInstance: this.baseProcedure.input(z.object({
instanceName: z.string(),
project: z.string()
})).mutation(async ({ input, ctx }) => {
if (!ctx.req?.user) {
throw new CoreError(enumCoreErrors.AUTHENTICATION_ERROR, 'User must be authenticated.');
}
return await this.lxdManager.deleteInstance(input.instanceName, input.project);
})
});
}
}
13 changes: 11 additions & 2 deletions packages/itmat-apis/src/trpc/tRPCRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { StudyRouter } from './studyProcedure';
import { TRPCRouter } from './trpc';
import { UserRouter } from './userProcedure';
import { WebAuthnRouter } from './webauthnProcedure';
import { InstanceRouter } from './instanceProcedure';
import { LXDRouter } from './lxdProcedure';

export class TRPCAggRouter {
router: TRPCRouter;
Expand All @@ -22,7 +24,10 @@ export class TRPCAggRouter {
domainRouter: DomainRouter;
organisationRouter: OrganisationRouter;
webAuthnRouter: WebAuthnRouter;
constructor(router: TRPCRouter, userRouter: UserRouter, driveRouter: DriveRouter, studyRouter: StudyRouter, dataRouter: DataRouter, roleRouter: RoleRouter, configRouter: ConfigRouter, logRouter: LogRouter, domainRouter: DomainRouter, organisationRouter: OrganisationRouter, webAuthnRouter: WebAuthnRouter) {
instanceRouter: InstanceRouter;
lxdRouter: LXDRouter;

constructor(router: TRPCRouter, userRouter: UserRouter, driveRouter: DriveRouter, studyRouter: StudyRouter, dataRouter: DataRouter, roleRouter: RoleRouter, configRouter: ConfigRouter, logRouter: LogRouter, domainRouter: DomainRouter, organisationRouter: OrganisationRouter, webAuthnRouter: WebAuthnRouter, instanceRouter: InstanceRouter, lxdRouter: LXDRouter) {
this.router = router;
this.userRouter = userRouter;
this.driveRouter = driveRouter;
Expand All @@ -34,6 +39,8 @@ export class TRPCAggRouter {
this.domainRouter = domainRouter;
this.organisationRouter = organisationRouter;
this.webAuthnRouter = webAuthnRouter;
this.instanceRouter = instanceRouter;
this.lxdRouter = lxdRouter;
}

_routers() {
Expand All @@ -47,7 +54,9 @@ export class TRPCAggRouter {
log: this.logRouter._router(),
domain: this.domainRouter._router(),
organisation: this.organisationRouter._router(),
webauthn: this.webAuthnRouter._router()
webauthn: this.webAuthnRouter._router(),
instance: this.instanceRouter._router(),
lxd: this.lxdRouter._router()
});
}
}
Expand Down
Loading
Loading