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

Worktimes Support #9

Merged
merged 5 commits into from
Mar 25, 2024
Merged
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
3,839 changes: 1,529 additions & 2,310 deletions package-lock.json

Large diffs are not rendered by default.

38 changes: 16 additions & 22 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,31 +1,25 @@
{
"dependencies": {
"client-oauth2": "^4.3.3",
"cors": "^2.8.5",
"dcp-client": "^4.3.5",
"dcp-util": "^2.2.28",
"dcp-worker": "^3.2.34",
"dotenv": "^16.3.1",
"dotenv-expand": "^10.0.0",
"ejs": "^3.1.9",
"express": "^4.18.2",
"express-openapi-validator": "^5.0.6",
"dcp-client": "^4.4.0",
"dotenv": "^16.4.5",
"dotenv-expand": "^11.0.6",
"express": "^4.19.1",
"express-openapi-validator": "^5.1.6",
"express-validator": "^7.0.1",
"jsonwebtoken": "^9.0.1",
"kvin": "^1.2.14",
"sqlite3": "^5.1.6",
"typescript": "^5.1.6"
"sqlite3": "^5.1.7"
},
"devDependencies": {
"@babel/eslint-parser": "7.12.13",
"@babel/plugin-syntax-top-level-await": "7.10.4",
"@distributive/eslint-config": "1.1.0",
"@distributive/eslint-plugin": "1.0.1",
"eslint": "^8.53.0",
"eslint-plugin-jsdoc": "46.5.0",
"jsdoc": "^3.6.10",
"jsdoc-tsimport-plugin": "1.0.5",
"param-case": "3.0.3",
"prompts": "^2.4.1"
"@babel/eslint-parser": "^7.24.1",
"@babel/plugin-syntax-top-level-await": "^7.14.5",
"@distributive/eslint-config": "^2.1.2",
"@distributive/eslint-plugin": "^1.0.2",
"eslint": "^8.57.0",
"eslint-plugin-jsdoc": "^48.2.1",
"jsdoc": "^4.0.2",
"jsdoc-tsimport-plugin": "^1.0.5",
"param-case": "^4.0.0",
"prompts": "^2.4.2"
}
}
31 changes: 22 additions & 9 deletions spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ info:
version: 1.0.0
title: Restful DCP API Documentation
servers:
- url: https://dcp-rest.diana.distributive.network/api/v0/
- url: http://localhost:1234/api/v0/
paths:
/job:
post:
summary: Job - Deploy
description: Deploys a job to the DCP scheduler for computation. Only required fields are <work> and <account>. Currently supported languages are JavaScript and TypeScript, but more are coming soon!
security:
- bearerAuth: []
description: Deploys a job to the DCP scheduler for computation. Only required fields are <work> and <account>. Currently supported Worktimes are map-basic and pyodide, but more are coming soon!
requestBody:
required: true
content:
Expand All @@ -21,23 +20,37 @@ paths:
work:
type: object
properties:
language:
type: string
enum: ['JavaScript', 'TypeScript']
runtime:
type: object
description: The Worktime specified for the job to execute in (for example Pyodide)
properties:
name:
type: string
version:
type: string
custom:
type: boolean
required:
- name
function:
type: string
required:
- language
- runtime
- function
account:
type: object
properties:
address:
type: string
description: Oauth-Identity flow-> Portal Payment Account Address
label:
type: string
description: Oauth-Identity flow-> Portal Payment Account Name
json:
type: string
description: Non Oauth-Identity flow-> An arbitrary payment account keystore
password:
type: string
required:
- address
slices:
oneOf:
- type: array
Expand Down
57 changes: 30 additions & 27 deletions src/dcp.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,6 @@ const protocol = require('dcp/protocol');
const wallet = require('dcp/wallet');
const dcpConfig = require('dcp/dcp-config');

// unlock bank account
async function unlockBankAccount(bankAccounts, address, password)
{
// try to find the account in the list of bankAccounts
for (const i in bankAccounts)
{
if (wallet.Address(bankAccounts[i].address).eq(address))
{
const ks = await new wallet.Keystore(bankAccounts[i]);
await ks.unlock(password, 1000, true);
return ks;
}
}

return null;
}

// get the accounts associated with the portal user
async function getBankAccounts(request)
{
Expand All @@ -44,6 +27,9 @@ async function getBankAccounts(request)
});
}

if (bankAccounts.length === 0)
throw new HttpError(`No payment accounts associated with identity ${idKs.address}`);

return bankAccounts;
}

Expand All @@ -59,23 +45,40 @@ async function getBankAccountKeystores(idKs)
return payload;
}


async function deployJobDCP(req)
{
var bankKeystore;
const options = req.body;

const bankAddress = req.body.account.address;
const bankPassword = req.body.account.password;
const account = req.body.account;

const idKeystore = await req.authorizedIdentity;
wallet.addId(idKeystore);

// unlock and set the banka ccount
const accounts = await getBankAccountKeystores(idKeystore);
const bankKs = await unlockBankAccount(accounts, bankAddress, bankPassword);
if (bankKs === null)
throw new HttpError(`DCP bank account ${bankAddress} not found`);
options.bankKs = bankKs;
// if the bank account json is passed in its entirety, don't use oauth
if (req.body.account.json)
bankKeystore = await new wallet.Keystore(req.body.account.json);
else
{
const portalBankAccounts = await getBankAccountKeystores(idKeystore);

function finderCmp(ks)
{
if (account.address)
return wallet.Address(ks.address).eq(address);
else if (account.label)
return ks.label === account.label || ks.label === account.name;
else
throw new HttpError('No payment account information provided');
}

bankKeystore = portalBankAccounts.find(finderCmp);
}

await bankKeystore.unlock(account.password, 1000, true);

if (!bankKeystore)
throw new HttpError(`Cannot find a matching payment account with identity ${idKeystore.address}`);
options.bankKs = bankKeystore;

const jobSpec = new JobSpec(options);
return await jobSpec.deploy();
Expand Down
30 changes: 19 additions & 11 deletions src/dcp/job.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
const kvin = require('kvin');

const HttpError = require('../error').HttpError;
const workFunctionTransformer = require('./work-function');
const worktimes = require('./worktime-setup');
const webhooks = require('../webhooks/lib');

const compute = require('dcp/compute');
Expand Down Expand Up @@ -40,10 +40,12 @@ class JobSpec
*/
constructor(options)
{
// validate work function and transform it if required
const work = workFunctionTransformer.setup(options.work);
this.workFunction = work.workFunction;
const additionalRequires = work.requires || [];
// shape the job into something which matches the worktime
const workSpec = worktimes.setup(options); // needs options.work and options.args
this.workFunction = workSpec.function;
const jobArgs = workSpec.args;
const jobPackages = (workSpec.packages || []).concat(options.packages || []);
const jobWorktime = workSpec.worktime;

// use the this.#jobRef variable to store a reference to job
const job = compute.for(options.slices || [], this.workFunction, options.args);
Expand All @@ -53,24 +55,30 @@ class JobSpec
job.public = options.public || { name: 'Restfully helping make the world smarter' };
job.autoClose = false; // all jobs deployed by dcp-rest are open by default.

if (jobWorktime)
{
if (jobWorktime.name) job.worktime = jobWorktime.name;
if (jobWorktime.name) job.worktimeVersion = jobWorktime.version;
if (jobWorktime.name) job.customWorktime = jobWorktime.custom;
}

// webhook: TODO drop this functionality later
if (options.webHookUrls)
{
throw new HttpError("WebHooks are no longer supported in dcp-rest");
/*
// TODO check if webHookUrls is empty
this.appIdPromise = webhooks.setJobWebhookServers(options.webHookUrls);
this.appidPromise.then((appId) => {
return webhooks.getDcpRDSUrl(appId);
}).then((jobUrl) => {
job.setResultStorage(new URL(jobUrl), {});
});
*/
}

// add requirements to the job TODO: clean this up
let jobRequires = options.packages || [];
jobRequires = jobRequires.concat(additionalRequires);

if ((options.packages && options.packages.length > 0) || (additionalRequires && additionalRequires.length > 0))
job.requires(jobRequires);
if (jobPackages.length > 0)
job.requires(jobPackages);

// set slice payment offer
if (options.slicePaymentOffer === 'MARKET_VALUE')
Expand Down
100 changes: 0 additions & 100 deletions src/dcp/work-function.js

This file was deleted.

26 changes: 26 additions & 0 deletions src/dcp/worktime-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Initialize arguments, workfunction, and packages if needed for any
* worktime special casing / helper stuff.
*
* I'm leaving this empty for now so the onus is on the user to set
* the right parameters for their specific worktime... That's probably
* the best appraoch to take anyways...
*/
function setupWorkForWorktime(options)
{
const workSpec = {
args: options.args,
function: options.work.function,
worktime: options.work.runtime,
packages: [],
};

// explicitly use 'map-basic' by default...
if (!workSpec.worktime || !workSpec.worktime.name)
workSpec.worktime = { name: 'map-basic' };

return workSpec;
}

exports.setup = setupWorkForWorktime;

Loading
Loading