Skip to content

Commit

Permalink
feat: added experimental volume support for golem-js
Browse files Browse the repository at this point in the history
  • Loading branch information
grisha87 committed Nov 18, 2024
1 parent 9e07885 commit 634d2b0
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 10 deletions.
35 changes: 34 additions & 1 deletion src/activity/exe-unit/exe-unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,17 @@ import { Agreement, ProviderInfo } from "../../market";
import { TcpProxy } from "../../network/tcp-proxy";
import { ExecutionOptions, ExeScriptExecutor } from "../exe-script-executor";
import { lastValueFrom, tap, toArray } from "rxjs";
import { DeployArgs } from "../script/command";

export type LifecycleFunction = (exe: ExeUnit) => Promise<void>;

export type VolumeSpec = {
/** Size of the volume to mount */
sizeGib: number;
/** Location of the volume */
path: string;
};

export interface ExeUnitOptions {
activityDeployingTimeout?: number;
storageProvider?: StorageProvider;
Expand All @@ -38,6 +46,7 @@ export interface ExeUnitOptions {
teardown?: LifecycleFunction;
executionOptions?: ExecutionOptions;
signalOrTimeout?: number | AbortSignal;
volumes?: Record<string, VolumeSpec>;
}

export interface CommandOptions {
Expand Down Expand Up @@ -158,7 +167,13 @@ export class ExeUnit {
private async deployActivity() {
try {
const executionMetadata = await this.executor.execute(
new Script([new Deploy(this.networkNode?.getNetworkConfig?.()), new Start()]).getExeScriptRequest(),
new Script([
new Deploy({
...this.networkNode?.getNetworkDeploymentArg?.(),
...this.getVolumeDeploymentArg(),
}),
new Start(),
]).getExeScriptRequest(),
);
const result$ = this.executor.getResultsObservable(executionMetadata);
// if any result is an error, throw an error
Expand Down Expand Up @@ -422,4 +437,22 @@ export class ExeUnit {

return allResults[0];
}

private getVolumeDeploymentArg(): Pick<DeployArgs, "volumes"> {
if (!this.options?.volumes) {
return {};
}

const argument: Required<Pick<DeployArgs, "volumes">> = {
volumes: {},
};

for (const [, volumeSpec] of Object.entries(this.options.volumes)) {
argument.volumes[volumeSpec.path] = {
storage: { size: `${volumeSpec.sizeGib}g`, errors: "panic" },
};
}

return argument;
}
}
26 changes: 25 additions & 1 deletion src/activity/script/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,32 @@ export class Command<T = unknown> {
}
}

export type DeployArgs = {
net?: DeployNetworkArgs[];
volumes?: DeployVolumesArgs;
};

type DeployNetworkArgs = {
id: string;
ip: string;
mask: string;
gateway?: string;
nodes: { [ip: string]: string };
nodeIp: string;
};

type DeployVolumesArgs = {
[path: string]: {
storage: {
/** @example 7000m */
size: string;
errors?: "panic";
};
};
};

export class Deploy extends Command {
constructor(args?: Record<string, unknown>) {
constructor(args?: DeployArgs) {
super("deploy", args);
}
}
Expand Down
54 changes: 49 additions & 5 deletions src/golem-network/golem-network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { DataTransferProtocol } from "../shared/types";
import { NetworkApiAdapter } from "../shared/yagna/adapters/network-api-adapter";
import { IProposalRepository } from "../market/proposal";
import { Subscription } from "rxjs";
import { GolemConfigError } from "../shared/error/golem-error";
import { GolemConfigError, GolemUserError } from "../shared/error/golem-error";

/**
* Instance of an object or a factory function that you can call `new` on.
Expand Down Expand Up @@ -158,13 +158,27 @@ export interface OneOfOptions {
signalOrTimeout?: number | AbortSignal;
setup?: ExeUnitOptions["setup"];
teardown?: ExeUnitOptions["teardown"];

/**
* Define additional volumes ot be mounted when the activity is deployed
*
* @experimental The Provider has to run yagna 0.17.x or newer and offer `vm` runtime 0.5.x or newer
*/
volumes?: ExeUnitOptions["volumes"];
}

export interface ManyOfOptions {
order: MarketOrderSpec;
poolSize: PoolSize;
setup?: ExeUnitOptions["setup"];
teardown?: ExeUnitOptions["teardown"];

/**
* Define additional volumes ot be mounted when the activity is deployed
*
* @experimental The Provider has to run yagna 0.17.x or newer and offer `vm` runtime 0.5.x or newer
*/
volumes?: ExeUnitOptions["volumes"];
}

/**
Expand Down Expand Up @@ -423,7 +437,12 @@ export class GolemNetwork {
* @param options.setup - an optional function that is called as soon as the exe unit is ready
* @param options.teardown - an optional function that is called before the exe unit is destroyed
*/
async oneOf({ order, setup, teardown, signalOrTimeout }: OneOfOptions): Promise<ResourceRental> {
async oneOf({ order, setup, teardown, signalOrTimeout, volumes }: OneOfOptions): Promise<ResourceRental> {
this.validateSettings({
order,
volumes,
});

const { signal, cleanup: cleanupAbortSignals } = anyAbortSignal(
createAbortSignalFromTimeout(signalOrTimeout),
this.abortController.signal,
Expand Down Expand Up @@ -458,6 +477,7 @@ export class GolemNetwork {
.releaseAllocation(allocation)
.catch((err) => this.logger.error("Error while releasing allocation", err));
};

try {
const proposalPool = new DraftOfferProposalPool({
logger: this.logger,
Expand Down Expand Up @@ -492,7 +512,7 @@ export class GolemNetwork {
payment: order.payment,
activity: order.activity,
networkNode,
exeUnit: { setup, teardown },
exeUnit: { setup, teardown, volumes },
});

// We managed to create the activity, no need to look for more agreement candidates
Expand Down Expand Up @@ -551,7 +571,12 @@ export class GolemNetwork {
* @param options.setup - an optional function that is called as soon as the exe unit is ready
* @param options.teardown - an optional function that is called before the exe unit is destroyed
*/
public async manyOf({ poolSize, order, setup, teardown }: ManyOfOptions): Promise<ResourceRentalPool> {
public async manyOf({ poolSize, order, setup, teardown, volumes }: ManyOfOptions): Promise<ResourceRentalPool> {
this.validateSettings({
order,
volumes,
});

const signal = this.abortController.signal;
let allocation: Allocation | undefined = undefined;
let resourceRentalPool: ResourceRentalPool | undefined = undefined;
Expand Down Expand Up @@ -605,7 +630,7 @@ export class GolemNetwork {
resourceRentalOptions: {
activity: order.activity,
payment: order.payment,
exeUnit: { setup, teardown },
exeUnit: { setup, teardown, volumes },
},
agreementOptions: {
expirationSec: rentSeconds,
Expand Down Expand Up @@ -668,4 +693,23 @@ export class GolemNetwork {
return new NullStorageProvider();
}
}

/**
* A helper method used to check if the user provided settings and settings are reasonable
* @param settings
* @private
*/
private validateSettings(settings: { volumes?: ExeUnitOptions["volumes"]; order: MarketOrderSpec }) {
// Rule: If user specifies volumes and the min storage size, then the min storage has to be at least of the largest volume size
if (settings.volumes && settings.order.demand.workload?.minStorageGib !== undefined) {
const largestVolumeSizeGib = Math.max(...Object.values(settings.volumes).map((spec) => spec.sizeGib));
if (settings.order.demand.workload.minStorageGib < largestVolumeSizeGib) {
throw new GolemUserError("Your minStorageGib requirement is below your expected largest volume size.");
}
}
// Rule: Require minStorageGib settings for volume users to ensure that they will get suitable providers from the market
if (settings.volumes && settings.order.demand.workload?.minStorageGib === undefined) {
throw new GolemUserError("You have specified volumes but did not specify a minStorageGib requirement.");
}
}
}
5 changes: 3 additions & 2 deletions src/network/node.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NetworkInfo } from "./network";
import { DeployArgs } from "../activity/script/command";

/**
* Describes a node in a VPN, mapping a Golem node id to an IP address
Expand All @@ -13,10 +14,10 @@ export class NetworkNode {

/**
* Generate a dictionary of arguments that are required for the appropriate
*`Deploy` command of an exescript in order to pass the network configuration to the runtime
*`Deploy` command of an exe-script in order to pass the network configuration to the runtime
* on the provider's end.
*/
getNetworkConfig() {
getNetworkDeploymentArg(): Pick<DeployArgs, "net"> {
return {
net: [
{
Expand Down
2 changes: 1 addition & 1 deletion src/resource-rental/resource-rental.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export interface ResourceRentalEvents {
}

export interface ResourceRentalOptions {
exeUnit?: Pick<ExeUnitOptions, "setup" | "teardown" | "activityDeployingTimeout">;
exeUnit?: Pick<ExeUnitOptions, "setup" | "teardown" | "activityDeployingTimeout" | "volumes">;
activity?: ExecutionOptions;
payment?: Partial<PaymentProcessOptions>;
networkNode?: NetworkNode;
Expand Down

0 comments on commit 634d2b0

Please sign in to comment.