Skip to content

Commit

Permalink
fix: endpoints implementation in corda plugin
Browse files Browse the repository at this point in the history
Closes #1346

Signed-off-by: Elena Izaguirre <e.izaguirre.equiza@accenture.com>
  • Loading branch information
elenaizaguirre authored and petermetz committed Nov 4, 2021
1 parent 09ace15 commit 21a22b5
Show file tree
Hide file tree
Showing 7 changed files with 817 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ import {
IInvokeContractEndpointV1Options,
InvokeContractEndpointV1,
} from "./web-services/invoke-contract-endpoint-v1";
import {
IListFlowsEndpointV1Options,
ListFlowsEndpointV1,
} from "./web-services/list-flows-endpoint-v1";
import {
INetworkMapEndpointV1Options,
NetworkMapEndpointV1,
} from "./web-services/network-map-endpoint-v1";
import {
IDiagnoseNodeEndpointV1Options,
DiagnoseNodeEndpointV1,
} from "./web-services/diagnose-node-endpoint-v1";

export interface IPluginLedgerConnectorCordaOptions
extends ICactusPluginOptions {
Expand All @@ -41,6 +53,7 @@ export interface IPluginLedgerConnectorCordaOptions
prometheusExporter?: PrometheusExporter;
cordaStartCmd?: string;
cordaStopCmd?: string;
apiUrl?: string;
}

export class PluginLedgerConnectorCorda
Expand Down Expand Up @@ -147,14 +160,15 @@ export class PluginLedgerConnectorCorda
corDappsDir: this.options.corDappsDir,
cordaStartCmd: this.options.cordaStartCmd,
cordaStopCmd: this.options.cordaStopCmd,
apiUrl: this.options.apiUrl,
});

endpoints.push(endpoint);
}

{
const opts: IInvokeContractEndpointV1Options = {
connector: this,
apiUrl: this.options.apiUrl,
logLevel: this.options.logLevel,
};
const endpoint = new InvokeContractEndpointV1(opts);
Expand All @@ -169,6 +183,34 @@ export class PluginLedgerConnectorCorda
const endpoint = new GetPrometheusExporterMetricsEndpointV1(opts);
endpoints.push(endpoint);
}

{
const opts: IListFlowsEndpointV1Options = {
apiUrl: this.options.apiUrl,
logLevel: this.options.logLevel,
};
const endpoint = new ListFlowsEndpointV1(opts);
endpoints.push(endpoint);
}

{
const opts: INetworkMapEndpointV1Options = {
apiUrl: this.options.apiUrl,
logLevel: this.options.logLevel,
};
const endpoint = new NetworkMapEndpointV1(opts);
endpoints.push(endpoint);
}

{
const opts: IDiagnoseNodeEndpointV1Options = {
apiUrl: this.options.apiUrl,
logLevel: this.options.logLevel,
};
const endpoint = new DiagnoseNodeEndpointV1(opts);
endpoints.push(endpoint);
}

this.log.info(`Instantiated endpoints of ${pkgName}`);
return endpoints;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import fs from "fs";
import path from "path";

import { Express, Request, Response } from "express";
import temp from "temp";
import { NodeSSH, Config as SshConfig } from "node-ssh";
import { Config as SshConfig } from "node-ssh";

import {
IWebServiceEndpoint,
IExpressRequestHandler,
Configuration,
} from "@hyperledger/cactus-core-api";

import {
Expand All @@ -26,6 +23,7 @@ import {
import { IEndpointAuthzOptions } from "@hyperledger/cactus-core-api";

import {
DefaultApi,
DeployContractJarsSuccessV1Response,
DeployContractJarsV1Request,
} from "../generated/openapi/typescript-axios/api";
Expand All @@ -39,6 +37,7 @@ export interface IDeployContractEndpointOptions {
cordaStartCmd?: string;
cordaStopCmd?: string;
authorizationOptionsProvider?: AuthorizationOptionsProvider;
apiUrl?: string;
}

const K_DEFAULT_AUTHORIZATION_OPTIONS: IEndpointAuthzOptions = {
Expand All @@ -51,6 +50,7 @@ export class DeployContractJarsEndpoint implements IWebServiceEndpoint {

private readonly log: Logger;
private readonly authorizationOptionsProvider: AuthorizationOptionsProvider;
private readonly apiUrl?: string;

public get className(): string {
return DeployContractJarsEndpoint.CLASS_NAME;
Expand All @@ -71,6 +71,7 @@ export class DeployContractJarsEndpoint implements IWebServiceEndpoint {
AuthorizationOptionsProvider.of(K_DEFAULT_AUTHORIZATION_OPTIONS, level);

this.log.debug(`Instantiated ${this.className} OK`);
this.apiUrl = options.apiUrl;
}

getAuthorizationOptionsProvider(): IAsyncProvider<IEndpointAuthzOptions> {
Expand Down Expand Up @@ -126,7 +127,8 @@ export class DeployContractJarsEndpoint implements IWebServiceEndpoint {
this.log.debug(`${verb} ${thePath} handleRequest()`);

try {
const body = await this.doDeploy(req.body);
if (this.apiUrl === undefined) throw "apiUrl option is necessary";
const body = await this.callInternalContainer(req.body);
res.status(200);
res.json(body);
} catch (ex) {
Expand All @@ -140,75 +142,12 @@ export class DeployContractJarsEndpoint implements IWebServiceEndpoint {
}
}

private async doDeploy(
reqBody: DeployContractJarsV1Request,
async callInternalContainer(
req: DeployContractJarsV1Request,
): Promise<DeployContractJarsSuccessV1Response> {
const fnTag = `${this.className}#doDeploy()`;
this.log.debug(`ENTER doDeploy()`);

if (!Array.isArray(reqBody.jarFiles)) {
throw new TypeError(`${fnTag} expected req.files to be an array`);
}

const { sshConfigAdminShell, corDappsDir: cordappDir } = this.options;
const ssh = new NodeSSH();
try {
const resBody: DeployContractJarsSuccessV1Response = {
deployedJarFiles: [],
};

temp.track();
const prefix = `hyperledger-cactus-${this.className}`;
const tmpDirPath = temp.mkdirSync(prefix);

await ssh.connect(sshConfigAdminShell);

await this.stopCordaNode(ssh);

for (const aJarFile of reqBody.jarFiles) {
const localFilePath = path.join(tmpDirPath, aJarFile.filename);
const remoteFilePath = path.join(cordappDir, aJarFile.filename);

fs.writeFileSync(localFilePath, aJarFile.contentBase64, "base64");

this.log.debug(`SCP from/to %o => %o`, localFilePath, remoteFilePath);
await ssh.putFile(localFilePath, remoteFilePath);
this.log.debug(`SCP OK %o`, remoteFilePath);
}

await this.startCordaNode(ssh);

fs.rmdirSync(tmpDirPath, { recursive: true });

this.log.debug(`EXIT doDeploy()`);
return resBody;
} finally {
ssh.dispose();
temp.cleanup();
}
}

private async stopCordaNode(ssh: NodeSSH): Promise<void> {
const fnTag = `${this.className}#stopCordaNode()`;
Checks.truthy(ssh.isConnected, `${fnTag} ssh.isConnected`);
const cmd = this.options.cordaStopCmd || "sudo systemctl stop corda";
try {
const response = await ssh.execCommand(cmd);
this.log.debug(`${fnTag} stopped Corda node OK `, response);
} catch (ex) {
this.log.error(`${fnTag} stopping of Corda node failed`, ex);
}
}

private async startCordaNode(ssh: NodeSSH): Promise<void> {
const fnTag = `${this.className}#startCordaNode()`;
Checks.truthy(ssh.isConnected, `${fnTag} ssh.isConnected`);
const cmd = this.options.cordaStartCmd || "sudo systemctl start corda";
try {
const response = await ssh.execCommand(cmd);
this.log.debug(`${fnTag} started Corda node OK `, response);
} catch (ex) {
this.log.error(`${fnTag} starting of Corda node failed`, ex);
}
const apiConfig = new Configuration({ basePath: this.apiUrl });
const apiClient = new DefaultApi(apiConfig);
const res = await apiClient.deployContractJarsV1(req);
return res.data;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { Express, Request, Response } from "express";

import {
Logger,
LoggerProvider,
LogLevelDesc,
Checks,
IAsyncProvider,
} from "@hyperledger/cactus-common";

import {
IWebServiceEndpoint,
IExpressRequestHandler,
IEndpointAuthzOptions,
Configuration,
} from "@hyperledger/cactus-core-api";

import OAS from "../../json/openapi.json";

import { registerWebServiceEndpoint } from "@hyperledger/cactus-core";
import {
DefaultApi,
DiagnoseNodeV1Request,
DiagnoseNodeV1Response,
} from "../generated/openapi/typescript-axios";

export interface IDiagnoseNodeEndpointV1Options {
logLevel?: LogLevelDesc;
apiUrl?: string;
}

export class DiagnoseNodeEndpointV1 implements IWebServiceEndpoint {
private readonly log: Logger;
private readonly apiUrl?: string;

constructor(public readonly opts: IDiagnoseNodeEndpointV1Options) {
const fnTag = "NetworkMapEndpointV1#constructor()";

Checks.truthy(opts, `${fnTag} options`);

this.log = LoggerProvider.getOrCreate({
label: "diagnose-node-endpoint-v1",
level: opts.logLevel || "INFO",
});

this.apiUrl = opts.apiUrl;
}

getAuthorizationOptionsProvider(): IAsyncProvider<IEndpointAuthzOptions> {
// TODO: make this an injectable dependency in the constructor
return {
get: async () => ({
isProtected: true,
requiredRoles: [],
}),
};
}

public getExpressRequestHandler(): IExpressRequestHandler {
return this.handleRequest.bind(this);
}

public get oasPath(): typeof OAS.paths["/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-corda/diagnose-node"] {
return OAS.paths[
"/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-corda/diagnose-node"
];
}

public getPath(): string {
return this.oasPath.post["x-hyperledger-cactus"].http.path;
}

public getVerbLowerCase(): string {
return this.oasPath.post["x-hyperledger-cactus"].http.verbLowerCase;
}

public getOperationId(): string {
return this.oasPath.post.operationId;
}

public async registerExpress(
expressApp: Express,
): Promise<IWebServiceEndpoint> {
await registerWebServiceEndpoint(expressApp, this);
return this;
}

async handleRequest(req: Request, res: Response): Promise<void> {
const fnTag = "DiagnoseNodeEndpointV1#handleRequest()";
const verbUpper = this.getVerbLowerCase().toUpperCase();
this.log.debug(`${verbUpper} ${this.getPath()}`);

try {
if (this.apiUrl === undefined) throw "apiUrl option is necessary";
const resBody = await this.callInternalContainer(req.body);
res.status(200);
res.send(resBody);
} catch (ex) {
this.log.error(`${fnTag} failed to serve request`, ex);
res.status(500);
res.statusMessage = ex.message;
res.json({ error: ex.stack });
}
}

async callInternalContainer(
req: DiagnoseNodeV1Request,
): Promise<DiagnoseNodeV1Response> {
const apiConfig = new Configuration({ basePath: this.apiUrl });
const apiClient = new DefaultApi(apiConfig);
const res = await apiClient.diagnoseNodeV1(req);
return res.data;
}
}
Loading

0 comments on commit 21a22b5

Please sign in to comment.