Client blueprint, service incapsulation (#940)

This commit is contained in:
Dima 2020-08-20 20:28:32 +03:00 committed by GitHub
parent cc493ea8d4
commit 2277a3584c
12 changed files with 182 additions and 81 deletions

31
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "fluence",
"version": "0.5.3",
"version": "0.7.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -134,6 +134,12 @@
"source-map": "^0.6.1"
}
},
"@types/uuid": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz",
"integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==",
"dev": true
},
"@types/webpack": {
"version": "4.41.12",
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.12.tgz",
@ -5957,6 +5963,14 @@
"faye-websocket": "^0.10.0",
"uuid": "^3.4.0",
"websocket-driver": "0.6.5"
},
"dependencies": {
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
}
}
},
"sockjs-client": {
@ -6769,10 +6783,9 @@
"dev": true
},
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz",
"integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ=="
},
"v8-compile-cache": {
"version": "2.0.3",
@ -7873,6 +7886,14 @@
"requires": {
"ansi-colors": "^3.0.0",
"uuid": "^3.3.2"
},
"dependencies": {
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
}
}
},
"webpack-sources": {

View File

@ -1,6 +1,6 @@
{
"name": "fluence",
"version": "0.7.3",
"version": "0.7.6",
"description": "the browser js-libp2p client for the Fluence network",
"main": "./dist/fluence.js",
"typings": "./dist/fluence.d.ts",
@ -26,11 +26,13 @@
"libp2p-secio": "0.12.5",
"libp2p-websockets": "0.13.6",
"peer-id": "0.13.12",
"peer-info": "0.17.5"
"peer-info": "0.17.5",
"uuid": "8.3.0"
},
"devDependencies": {
"@types/base64-js": "1.2.5",
"@types/bs58": "4.0.1",
"@types/uuid": "8.3.0",
"@types/chai": "4.2.11",
"@types/mocha": "7.0.2",
"assert": "2.0.0",

35
src/blueprint.ts Normal file
View File

@ -0,0 +1,35 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface Blueprint {
dependencies: string[],
id: string,
name: string
}
export function checkBlueprint(b: any): b is Blueprint {
if (!b.id) throw new Error(`There is no 'id' field in Blueprint struct: ${JSON.stringify(b)}`)
if (b.dependencies) {
b.dependencies.forEach((dep: any) => {
if ((typeof dep) !== 'string') {
throw Error(`'dependencies' should be an array of strings: ${JSON.stringify(b)}`)
}
});
}
return true;
}

View File

@ -17,7 +17,7 @@
import * as PeerInfo from "peer-info";
import * as PeerId from "peer-id";
import Multiaddr from "multiaddr"
import {FluenceClient} from "./fluence_client";
import {FluenceClient} from "./fluenceClient";
export default class Fluence {

View File

@ -22,21 +22,22 @@ import {
ProtocolType,
addressToString
} from "./address";
import {callToString, FunctionCall, genUUID, makeFunctionCall,} from "./function_call";
import {callToString, FunctionCall, genUUID, makeFunctionCall,} from "./functionCall";
import * as PeerId from "peer-id";
import {Services} from "./services";
import {LocalServices} from "./localServices";
import Multiaddr from "multiaddr"
import {Subscriptions} from "./subscriptions";
import * as PeerInfo from "peer-info";
import {FluenceConnection} from "./fluence_connection";
import {FluenceConnection} from "./fluenceConnection";
import {checkInterface, Interface} from "./Interface";
import {Service} from "./service";
import {Blueprint, checkBlueprint} from "./blueprint";
/**
* @param target receiver
* @param args message in the call
* @param moduleId module name
* @param fname function name
* @param context list of modules to use with the request
* @param name common field for debug purposes
* @param msgId hash that will be added to replyTo address
*/
@ -46,7 +47,6 @@ interface Call {
moduleId?: string,
fname?: string,
msgId?: string,
context?: string[],
name?: string
}
@ -57,7 +57,7 @@ export class FluenceClient {
private connection: FluenceConnection;
private services: Services = new Services();
private services: LocalServices = new LocalServices();
private subscriptions: Subscriptions = new Subscriptions();
@ -105,30 +105,13 @@ export class FluenceClient {
return (args: any, target: Address) => target.hash && target.hash === msgId;
}
/**
* Send call and wait a response.
*
* @param target receiver
* @param args message in the call
* @param moduleId module name
* @param fname functin name
*/
async sendCallWaitResponse(target: Address, args: any, moduleId?: string, fname?: string): Promise<any> {
let msgId = genUUID();
let predicate = this.getPredicate(msgId);
await this.sendCall({target: target, args: args, moduleId: moduleId, fname: fname, msgId: msgId});
return this.waitResponse(predicate, false);
}
/**
* Send call and forget.
*
*/
async sendCall(call: Call) {
if (this.connection && this.connection.isConnected()) {
await this.connection.sendFunctionCall(call.target, call.args, call.moduleId, call.fname, call.msgId, call.context, call.name);
await this.connection.sendFunctionCall(call.target, call.args, call.moduleId, call.fname, call.msgId, call.name);
} else {
throw Error("client is not connected")
}
@ -158,10 +141,9 @@ export class FluenceClient {
* @param addr node address
* @param args message to the service
* @param fname function name
* @param context
* @param name debug info
*/
async callPeer(moduleId: string, args: any, fname?: string, addr?: string, context?: string[], name?: string): Promise<any> {
async callPeer(moduleId: string, args: any, fname?: string, addr?: string, name?: string): Promise<any> {
let msgId = genUUID();
let predicate = this.getPredicate(msgId);
@ -172,7 +154,7 @@ export class FluenceClient {
address = createPeerAddress(this.nodePeerIdStr);
}
await this.sendCall({target: address, args: args, moduleId: moduleId, fname: fname, msgId: msgId, context: context, name: name})
await this.sendCall({target: address, args: args, moduleId: moduleId, fname: fname, msgId: msgId, name: name})
return await this.waitResponse(predicate, false);
}
@ -186,6 +168,10 @@ export class FluenceClient {
return await this.waitResponse(predicate, false);
}
getService(peerId: string, serviceId: string): Service {
return new Service(this, peerId, serviceId);
}
/**
* Handle incoming call.
* If FunctionCall returns - we should send it as a response.
@ -280,17 +266,32 @@ export class FluenceClient {
/**
* Sends a call to create a service on remote node.
*/
async createService(peerId: string, context: string[]): Promise<string> {
let resp = await this.callPeer("create", {}, undefined, peerId, context);
async createService(peerId: string, blueprint: string): Promise<string> {
let resp = await this.callPeer("create", {blueprint_id: blueprint}, undefined, peerId);
if (resp.result && resp.result.service_id) {
return resp.result.service_id
if (resp && resp.service_id) {
return resp.service_id
} else {
console.error("Unknown response type on `createService`: ", resp)
throw new Error("Unknown response type on `createService`");
}
}
async addBlueprint(peerId: string, name: string, dependencies: string[]): Promise<string> {
let id = genUUID();
let blueprint = {
name: name,
id: id,
dependencies: dependencies
};
let msg_id = genUUID();
await this.callPeer("add_blueprint", {msg_id, blueprint}, undefined, peerId);
return id;
}
async getInterface(serviceId: string, peerId?: string): Promise<Interface> {
let resp;
resp = await this.callPeer("get_interface", {service_id: serviceId}, undefined, peerId)
@ -303,13 +304,27 @@ export class FluenceClient {
}
}
async getActiveInterfaces(peerId?: string): Promise<Interface[]> {
let resp;
if (peerId) {
resp = await this.sendCallWaitResponse(createPeerAddress(peerId), {}, "get_active_interfaces");
async getAvailableBlueprints(peerId?: string): Promise<Blueprint[]> {
let resp = await this.callPeer("get_available_blueprints", {}, undefined);
let blueprints = resp.available_blueprints;
if (blueprints && blueprints instanceof Array) {
return blueprints.map((b: any) => {
if (checkBlueprint(b)) {
return b;
} else {
throw new Error("Unexpected");
}
});
} else {
resp = await this.callPeer("get_active_interfaces", {}, undefined, peerId);
throw new Error("Unexpected. 'get_active_interfaces' should return an array of interfaces.");
}
}
async getActiveInterfaces(peerId?: string): Promise<Interface[]> {
let resp = await this.callPeer("get_active_interfaces", {}, undefined);
let interfaces = resp.active_interfaces;
if (interfaces && interfaces instanceof Array) {
return interfaces.map((i: any) => {
@ -320,18 +335,12 @@ export class FluenceClient {
}
});
} else {
throw new Error("Unexpected");
throw new Error("Unexpected. 'get_active_interfaces' should return an array pf interfaces.");
}
}
async getAvailableModules(peerId?: string): Promise<string[]> {
let resp;
if (peerId) {
resp = await this.sendCallWaitResponse(createPeerAddress(peerId), {}, "get_available_modules");
} else {
resp = await this.callPeer("get_available_modules", {}, undefined, peerId);
}
let resp = await this.callPeer("get_available_modules", {}, undefined, peerId);
return resp.available_modules;
}
@ -357,12 +366,7 @@ export class FluenceClient {
preopened_files: preopened_files
}
}
let resp;
if (peerId) {
resp = await this.sendCallWaitResponse(createPeerAddress(peerId), {bytes: bytes, config: config}, "add_module");
} else {
resp = await this.callPeer("add_module", {bytes: bytes, config: config}, undefined, peerId);
}
let resp = await this.callPeer("add_module", {bytes: bytes, config: config}, undefined, peerId);
return resp.available_modules;
}

View File

@ -22,7 +22,7 @@ import {
makeFunctionCall,
makeProvideMessage,
parseFunctionCall
} from "./function_call";
} from "./functionCall";
import * as PeerId from "peer-id";
import * as PeerInfo from "peer-info";
import Websockets from "libp2p-websockets";
@ -162,13 +162,13 @@ export class FluenceConnection {
/**
* Send FunctionCall to the connected node.
*/
async sendFunctionCall(target: Address, args: any, moduleId?: string, fname?: string, msgId?: string, context?: string[], name?: string) {
async sendFunctionCall(target: Address, args: any, moduleId?: string, fname?: string, msgId?: string, name?: string) {
this.checkConnectedOrThrow();
let replyTo;
if (msgId) replyTo = this.makeReplyTo(msgId);
let call = makeFunctionCall(genUUID(), target, this.sender, args, moduleId, fname, replyTo, context, name);
let call = makeFunctionCall(genUUID(), target, this.sender, args, moduleId, fname, replyTo, name);
await this.sendCall(call);
}

View File

@ -15,11 +15,9 @@
*/
import {
createPeerAddress,
createRelayAddress,
Address, addressToString, parseAddress
} from "./address";
import * as PeerId from "peer-id";
import { v4 as uuidv4 } from 'uuid';
export interface FunctionCall {
uuid: string,
@ -29,7 +27,6 @@ export interface FunctionCall {
"module"?: string,
fname?: string,
arguments: any,
context?: string[],
name?: string,
action: "FunctionCall"
}
@ -47,7 +44,7 @@ export function callToString(call: FunctionCall) {
return JSON.stringify(obj)
}
export function makeFunctionCall(uuid: string, target: Address, sender: Address, args: object, moduleId?: string, fname?: string, replyTo?: Address, context?: string[], name?: string): FunctionCall {
export function makeFunctionCall(uuid: string, target: Address, sender: Address, args: object, moduleId?: string, fname?: string, replyTo?: Address, name?: string): FunctionCall {
return {
uuid: uuid,
@ -57,7 +54,6 @@ export function makeFunctionCall(uuid: string, target: Address, sender: Address,
"module": moduleId,
fname: fname,
arguments: args,
context: context,
name: name,
action: "FunctionCall"
}
@ -83,7 +79,6 @@ export function parseFunctionCall(str: string): FunctionCall {
reply_to: replyTo,
sender: sender,
arguments: json.arguments,
context: json.context,
"module": json.module,
fname: json.fname,
name: json.name,
@ -92,15 +87,14 @@ export function parseFunctionCall(str: string): FunctionCall {
}
export function genUUID() {
let date = new Date();
return date.toISOString()
return uuidv4();
}
/**
* Message to provide new name.
*/
export async function makeProvideMessage(name: string, target: Address, sender: Address): Promise<FunctionCall> {
return makeFunctionCall(genUUID(), target, sender, {name: name, address: addressToString(sender)}, "provide", undefined, sender, undefined, "provide service_id");
return makeFunctionCall(genUUID(), target, sender, {name: name, address: addressToString(sender)}, "provide", undefined, sender, "provide service_id");
}
// TODO uncomment when this will be implemented in Fluence network

View File

@ -14,9 +14,9 @@
* limitations under the License.
*/
import {FunctionCall} from "./function_call";
import {FunctionCall} from "./functionCall";
export class Services {
export class LocalServices {
private services: Map<string, (req: FunctionCall) => void> = new Map();

39
src/service.ts Normal file
View File

@ -0,0 +1,39 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {FluenceClient} from "./fluenceClient";
export class Service {
private readonly client: FluenceClient;
private readonly serviceId: string;
private readonly peerId: string;
constructor(client: FluenceClient, peerId: string, serviceId: string) {
this.client = client;
this.serviceId = serviceId;
this.peerId = peerId;
}
/**
*
* @param moduleId wich module in service to call
* @param args parameters to call service
* @param fname function name if existed
*/
async call(moduleId: string, args: any, fname?: string): Promise<any> {
return this.client.callService(this.peerId, this.serviceId, moduleId, args, fname);
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import {FunctionCall} from "./function_call";
import {FunctionCall} from "./functionCall";
import {Address} from "./address";
export class Subscriptions {

View File

@ -10,7 +10,7 @@ import {expect} from 'chai';
import 'mocha';
import {encode} from "bs58"
import * as PeerId from "peer-id";
import {callToString, genUUID, makeFunctionCall, parseFunctionCall} from "../function_call";
import {callToString, genUUID, makeFunctionCall, parseFunctionCall} from "../functionCall";
import Fluence from "../fluence";
import {certificateFromString, certificateToString, issue} from "../trust/certificate";
import {TrustGraph} from "../trust/trust_graph";
@ -70,7 +70,6 @@ describe("Typescript usage suite", () => {
"mm",
"fff",
addr,
undefined,
"2444"
);
@ -197,10 +196,16 @@ export async function testUploadWasm() {
let peerId1 = "12D3KooWPnLxnY71JDxvB3zbjKu9k1BCYNthGZw6iGrLYsR1RnWM"
let serviceId = await cl1.createService(peerId1, [moduleName]);
let blueprintId = await cl1.addBlueprint(peerId1, "some test blueprint", [moduleName])
let blueprints = await cl1.getAvailableBlueprints(peerId1)
console.log(blueprints);
let serviceId = await cl1.createService(peerId1, blueprintId);
let service = cl1.getService(peerId1, serviceId);
let argName = genUUID();
let resp = await cl1.callService(peerId1, serviceId, moduleName, {name: argName}, "greeting")
let resp = await service.call(moduleName, {name: argName}, "greeting")
expect(resp.result).to.be.equal(`Hi, ${argName}`)
}
@ -215,9 +220,10 @@ export async function testServicesAndInterfaces() {
let peerId1 = "12D3KooWPnLxnY71JDxvB3zbjKu9k1BCYNthGZw6iGrLYsR1RnWM"
let serviceId = await cl2.createService(peerId1, ["ipfs_node.wasm"]);
let blueprintId = await cl1.addBlueprint(peerId1, "some test blueprint", ["ipfs_node"])
let serviceId = await cl2.createService(peerId1, blueprintId);
let resp = await cl2.callService(peerId1, serviceId, "ipfs_node.wasm", {}, "get_address")
let resp = await cl2.callService(peerId1, serviceId, "ipfs_node", {}, "get_address")
console.log(resp)
let interfaces = await cl1.getActiveInterfaces();

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import {FluenceClient} from "../fluence_client";
import {FluenceClient} from "../fluenceClient";
import {Certificate, certificateFromString, certificateToString} from "./certificate";
// The client to interact with the Fluence trust graph API