diff --git a/src/models/Config.ts b/src/models/Config.ts index 60e15e27..e2876de1 100644 --- a/src/models/Config.ts +++ b/src/models/Config.ts @@ -8,6 +8,12 @@ export class Config { */ public nodeUri?: string + /** + * Address of Provider. + * @type {string} + */ + public providerAddress?: string + /** * Metadata Store URL. * @type {string} diff --git a/src/ocean/Assets.ts b/src/ocean/Assets.ts index d2374239..0b1da05b 100644 --- a/src/ocean/Assets.ts +++ b/src/ocean/Assets.ts @@ -1,6 +1,7 @@ import { SearchQuery } from '../metadatastore/MetadataStore' import { DDO } from '../ddo/DDO' import { Metadata } from '../ddo/interfaces/Metadata' +import { MetadataAlgorithm } from '../ddo/interfaces/MetadataAlgorithm' import { Service, ServiceAccess, @@ -29,6 +30,47 @@ export enum OrderProgressStep { TransferDataToken } + +export const ComputeJobStatus = Object.freeze({ + Started: 10, + ConfiguringVolumes: 20, + ProvisioningSuccess: 30, + DataProvisioningFailed: 31, + AlgorithmProvisioningFailed: 32, + RunningAlgorithm: 40, + FilteringResults: 50, + PublishingResult: 60, + Completed: 70, + Stopped: 80, + Deleted: 90 +}) + +export interface Output { + publishAlgorithmLog?: boolean + publishOutput?: boolean + providerAddress?: string + providerUri?: string + metadata?: Metadata + metadataUri?: string + nodeUri?: string + owner?: string + secretStoreUri?: string + whitelist?: string[] +} + +export interface ComputeJob { + owner: string + did: string + jobId: string + dateCreated: string + dateFinished: string + status: number + statusText: string + algorithmLogUrl: string + resultsUrls: string[] + resultsDid?: DID +} + /** * Assets submodule of Ocean Protocol. */ @@ -440,4 +482,64 @@ export class Assets extends Instantiable { return serviceEndpoint } + + + /** + * Start the execution of a compute job. + * @param {Account} consumerAccount The account of the consumer ordering the service. + * @param {string} did Decentralized identifer for the asset + * @param {string} algorithmDid The DID of the algorithm asset (of type `algorithm`) to run on the asset. + * @param {MetaData} algorithmMeta Metadata about the algorithm being run if `algorithm` is being used. This is ignored when `algorithmDid` is specified. + * @param {Output} output Define algorithm output publishing. Publishing the result of a compute job is turned off by default. + * @return {Promise} Returns compute job ID under status.jobId + */ + public async start( + consumerAccount: Account, + did: string, + algorithmDid?: string, + algorithmMeta?: MetadataAlgorithm, + output?: Output + ): Promise { + output = this.checkOutput(consumerAccount, output) + if (did) { + const computeJobsList = await this.ocean.provider.compute( + 'post', + did, + consumerAccount, + algorithmDid, + algorithmMeta, + undefined, + output + ) + return computeJobsList[0] as ComputeJob + } else return null + } + + /** + * Check the output object and add default properties if needed + * @param {Account} consumerAccount The account of the consumer ordering the service. + * @param {Output} output Output section used for publishing the result. + * @return {Promise} Returns output object + */ + private checkOutput(consumerAccount: Account, output?: Output): Output { + const isDefault = + !output || (!output.publishAlgorithmLog && !output.publishOutput) + + if (isDefault) { + return { + publishAlgorithmLog: false, + publishOutput: false + } + } + + return { + publishAlgorithmLog: output.publishAlgorithmLog, + publishOutput: output.publishOutput, + providerAddress: output.providerAddress || this.config.providerAddress, + providerUri: output.providerUri || this.config.providerUri, + metadataUri: output.metadataUri || this.config.metadataStoreUri, + nodeUri: output.nodeUri || this.config.nodeUri, + owner: output.owner || consumerAccount.getId() + } + } } diff --git a/src/provider/Provider.ts b/src/provider/Provider.ts index 7829e0b5..72fa6bc8 100644 --- a/src/provider/Provider.ts +++ b/src/provider/Provider.ts @@ -2,6 +2,8 @@ import Account from '../ocean/Account' import { noZeroX } from '../utils' import { Instantiable, InstantiableConfig } from '../Instantiable.abstract' import { File } from '../ddo/interfaces/File' +import { ComputeJob, Output } from '../ocean/Assets' +import { MetadataAlgorithm } from '../ddo/interfaces/MetadataAlgorithm' const apiPath = '/api/v1/services' @@ -131,6 +133,79 @@ export class Provider extends Instantiable { return destination } + public async compute( + method: string, + did: string, + consumerAccount: Account, + algorithmDid?: string, + algorithmMeta?: MetadataAlgorithm, + jobId?: string, + output?: Output + ): Promise { + const address = consumerAccount.getId() + + let signatureMessage = address + signatureMessage += jobId || '' + signatureMessage += (did && `${noZeroX(did)}`) || '' + const signature = await this.createHashSignature( + consumerAccount, + signatureMessage + ) + + // construct Brizo URL + let url = this.getComputeEndpoint() + url += `?signature=${signature}` + url += `&consumerAddress=${address}` + url += `&did=${noZeroX(did)}` + url += (algorithmDid && `&algorithmDid=${algorithmDid}`) || '' + url += + (algorithmMeta && + `&algorithmMeta=${encodeURIComponent(JSON.stringify(algorithmMeta))}`) || + '' + url += (output && `&output=${JSON.stringify(output)}`) || '' + url += (jobId && `&jobId=${jobId}`) || '' + + // switch fetch method + let fetch + + switch (method) { + case 'post': + fetch = this.ocean.utils.fetch.post(url, '') + break + case 'put': + fetch = this.ocean.utils.fetch.put(url, '') + break + case 'delete': + fetch = this.ocean.utils.fetch.delete(url) + break + default: + fetch = this.ocean.utils.fetch.get(url) + break + } + + const result = await fetch + .then((response: any) => { + if (response.ok) { + return response.json() + } + + this.logger.error( + 'Compute job failed:', + response.status, + response.statusText + ) + + return null + }) + .catch((error: Error) => { + this.logger.error('Error with compute job') + this.logger.error(error.message) + throw error + }) + + return result + } + public async getVersionInfo() { return (await this.ocean.utils.fetch.get(this.url)).json() }