From 2ea04cf6719816f0373881bc1dd74157424de0c4 Mon Sep 17 00:00:00 2001 From: Matias Date: Mon, 13 May 2019 21:06:57 -0300 Subject: [PATCH] Adding simulator restart --- server/package-lock.json | 5 + server/package.json | 4 +- server/src/errors/index.js | 2 +- server/src/kubernetes/createClone.js | 20 +-- server/src/kubernetes/removeClone.js | 13 +- .../src/schema/mutations/AddCloneMutation.js | 4 +- .../schema/mutations/RemoveCloneMutation.js | 2 +- server/src/schema/mutations/RunSimulation.js | 137 ++++++++++++++++++ .../mutations/UpdateExecutionSummary.js | 4 +- server/src/schema/mutations/index.js | 4 +- server/src/schema/types/input/Simulation.js | 20 +++ server/src/schema/types/input/index.js | 4 +- 12 files changed, 193 insertions(+), 26 deletions(-) create mode 100644 server/src/schema/mutations/RunSimulation.js create mode 100644 server/src/schema/types/input/Simulation.js diff --git a/server/package-lock.json b/server/package-lock.json index a132b650b..4643a98c6 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -3474,6 +3474,11 @@ "yallist": "^2.1.2" } }, + "luxon": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.13.2.tgz", + "integrity": "sha512-U7i2AE+/VWeB8PZZkIeEcxJCZvBA8LegCHufaIFYx3qRQdw2UJw3fuaL/Fqi9Q+2MeFYu+gYqIzr5hWOvAMHBQ==" + }, "make-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", diff --git a/server/package.json b/server/package.json index f32361060..ff7477df1 100644 --- a/server/package.json +++ b/server/package.json @@ -11,7 +11,8 @@ "main": "src/index.js", "scripts": { "start": "nodemon --exec babel-node --presets env,stage-2 src/index.js", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "debug": "nodemon --exec babel-node --source-maps --presets env,stage-2 --inspect src/index.js 9229" }, "devDependencies": { "babel-cli": "6.26.0", @@ -27,6 +28,7 @@ "express-graphql": "0.6.12", "graphql": "14.0.0", "kubernetes-client": "6.5.1", + "luxon": "^1.13.2", "mongoose": "5.2.12", "winston": "3.1.0" } diff --git a/server/src/errors/index.js b/server/src/errors/index.js index 0a2f6ad14..9fe13402e 100644 --- a/server/src/errors/index.js +++ b/server/src/errors/index.js @@ -6,7 +6,7 @@ export class AuthentificationError extends Error { } } -export class AutorizationError extends Error { +export class AuthorizationError extends Error { code = 401 message = 'You are not authorized to perform this action.' constructor(){ diff --git a/server/src/kubernetes/createClone.js b/server/src/kubernetes/createClone.js index 9cc216645..9a9098e10 100644 --- a/server/src/kubernetes/createClone.js +++ b/server/src/kubernetes/createClone.js @@ -114,12 +114,8 @@ const createClone = async (clone) => { }) env.push({ - 'name': 'INITIAL_BALANCE_ASSET_A', - 'value': clone.balanceAssetA.toString() - }) - env.push({ - 'name': 'INITIAL_BALANCE_ASSET_B', - 'value': clone.balanceAssetB.toString() + 'name': 'TIME_PERIOD', + 'value': clone.timePeriod.toString() }) logger.debug('createClone Trading Configuration.') @@ -130,13 +126,17 @@ const createClone = async (clone) => { }) env.push({ - 'name': 'TIME_PERIOD', - 'value': clone.timePeriod + 'name': 'DATA_SET', + 'value': datasetNames().get(clone.timePeriod) }) env.push({ - 'name': 'DATA_SET', - 'value': datasetNames().get(clone.timePeriod) + 'name': 'INITIAL_BALANCE_ASSET_A', + 'value': clone.balanceAssetA.toString() + }) + env.push({ + 'name': 'INITIAL_BALANCE_ASSET_B', + 'value': clone.balanceAssetB.toString() }) if (clone.mode === BACKTEST) { diff --git a/server/src/kubernetes/removeClone.js b/server/src/kubernetes/removeClone.js index 54bb0c3c5..a65d4d6c1 100644 --- a/server/src/kubernetes/removeClone.js +++ b/server/src/kubernetes/removeClone.js @@ -1,26 +1,25 @@ import logger from '../config/logger' -import { KubernateError } from '../errors' import { Client, config } from 'kubernetes-client' -const removeClone = async (clone) => { +const removeClone = async (cloneId) => { try { - logger.debug('removeClone %s', clone.id) + logger.debug('removeClone %s', cloneId) const client = new Client({config: config.fromKubeconfig(), version: '1.9'}) let query = { 'qs': { - 'labelSelector': 'job-name=' + clone.id + 'labelSelector': 'job-name=' + cloneId } } - await client.apis.batch.v1.namespaces('default').jobs(clone.id).delete() + await client.apis.batch.v1.namespaces('default').jobs(cloneId).delete() const pod = await client.api.v1.namespaces('default').pods.get(query) await client.api.v1.namespaces('default').pods(pod.body.items[0].metadata.name).delete() - logger.debug('removeClone %s on kubernates succesful', clone.id) + logger.debug('removeClone %s on kubernates succesful', cloneId) } catch (err) { - logger.warn('removeClone %s on kubernates failed, the job was not found on the server.', clone.id) + logger.warn('removeClone %s on kubernates failed, the job was not found on the server.', cloneId) } } export default removeClone diff --git a/server/src/schema/mutations/AddCloneMutation.js b/server/src/schema/mutations/AddCloneMutation.js index 4d7377a0b..c4882df4e 100644 --- a/server/src/schema/mutations/AddCloneMutation.js +++ b/server/src/schema/mutations/AddCloneMutation.js @@ -10,7 +10,7 @@ import { AuthentificationError, WrongArgumentsError, OperationsError, - AutorizationError + AuthorizationError } from '../../errors' import logger from '../../config/logger' import createKubernetesClone from '../../kubernetes/createClone' @@ -84,7 +84,7 @@ const resolve = async (parent, { clone }, context) => { if (response.data.data.keyVault_AuthorizeClone === clone.keyId) { await createKubernetesClone(clone) } else { - throw new AutorizationError() + throw new AuthorizationError() } } else { await createKubernetesClone(clone) diff --git a/server/src/schema/mutations/RemoveCloneMutation.js b/server/src/schema/mutations/RemoveCloneMutation.js index 5e4fa9ed0..ad5e230c6 100644 --- a/server/src/schema/mutations/RemoveCloneMutation.js +++ b/server/src/schema/mutations/RemoveCloneMutation.js @@ -40,7 +40,7 @@ const resolve = async (parent, { id }, context) => { } logger.debug('removeClone -> Removing Clone from Kubernates.') - await removeKuberneteClone(clone) + await removeKuberneteClone(clone.id) const query = { _id: id, diff --git a/server/src/schema/mutations/RunSimulation.js b/server/src/schema/mutations/RunSimulation.js new file mode 100644 index 000000000..1dface7e8 --- /dev/null +++ b/server/src/schema/mutations/RunSimulation.js @@ -0,0 +1,137 @@ +import { GraphQLString } from 'graphql' +import { SimulationInputType } from '../types/input' +import removeKuberneteClone from '../../kubernetes/removeClone' +import { Clone } from '../../models' +import { Indicator } from '../../enums/BotTypes' +import { DateTime } from 'luxon' +import teamQuery from '../../graphQLCalls/teamQuery' +import { + AuthentificationError, + OperationsError +} from '../../errors' +import logger from '../../config/logger' +import createKubernetesClone from '../../kubernetes/createClone' + +const args = { + simulation: { type: SimulationInputType } +} + +const resolve = async (parent, { simulation }, context) => { + logger.debug('runSimulation -> Entering Fuction.') + + if (!context.userId) { + throw new AuthentificationError() + } + + try { + // Find all running clones + let clones = await Clone.find({ + authId: context.userId, + active: true + }).sort({ createDatetime: -1 }) + + // Find all user bots + let allUserBotsResponse = await teamQuery(context.authorization, null) + let allUserBots = allUserBotsResponse.data.data.teams_FbByOwner.edges + let simulatorBot + + // Identify clones to stop, if any + for (var j = 0; j < allUserBots.length; j++) { + let bot = allUserBots[j].node + if (bot.kind === Indicator && bot.slug.substring(0, 10) === "simulator-") { + simulatorBot = bot // TODO One simulator per user + for (var i = 0; i < clones.length; i++) { + if (clones[i].botId === bot.id) { + if ((simulation.timePeriodDailyArray !== undefined && clones[i].processName === "Multi-Period-Daily") || + (simulation.timePeriodMarketArray !== undefined && clones[i].processName === "Multi-Period-Market") || + (simulation.timePeriodDailyArray === undefined && simulation.timePeriodMarketArray === undefined)) { + + logger.debug('runSimulation -> Stop running clones.') + + await removeKuberneteClone(clones[i]._id) + logger.debug('runSimulation -> Clone Removed from Kubernetes: %s.', clones[i]._id) + + let clone = await Clone.findOneAndUpdate({ _id: clones[i]._id, authId: context.userId }, { active: false }, { new: true }) + + if (!clone) throw new Error('Clone Not Found.') + logger.debug('runSimulation -> Clone Removed from the DB.') + } + } + } + } + } + + if (simulation.beginDatetime === undefined) { + simulation.beginDatetime = DateTime.utc().minus({ days: 7 }).startOf('day') + } + + if (simulation.resumeExecution === undefined) { + simulation.resumeExecution = false + } + + if (simulatorBot !== undefined) { + if (simulation.timePeriodDailyArray !== undefined && simulation.timePeriodDailyArray.length > 0) { + simulation.timePeriod = simulation.timePeriodDailyArray + await createSimulatorClone(simulation, "Multi-Period-Daily", context.userId, simulatorBot) + } + + if (simulation.timePeriodMarketArray !== undefined && simulation.timePeriodMarketArray.length > 0) { + simulation.timePeriod = simulation.timePeriodMarketArray + await createSimulatorClone(simulation, "Multi-Period-Market", context.userId, simulatorBot) + } + + if (simulation.timePeriodDailyArray === undefined && simulation.timePeriodMarketArray === undefined) { + simulation.timePeriod = ["15-min", "10-min", "05-min"] + await createSimulatorClone(simulation, "Multi-Period-Daily", context.userId, simulatorBot) + + simulation.timePeriod = ["24-hs", "12-hs", "08-hs", "06-hs", "04-hs", "03-hs", "02-hs", "01-hs"] + await createSimulatorClone(simulation, "Multi-Period-Market", context.userId, simulatorBot) + } + + return "Simulation Clones Recreated." + } else { + return "The user doesn't have any simulator bot." + } + } catch (err) { + logger.error('runSimulation -> Error: %s', err.stack) + throw new OperationsError('There has been an error creating the clones.') + } +} + +async function createSimulatorClone(simulation, processName, userId, simulatorBot) { + let clone = { + teamId: simulatorBot.team.id, + botId: simulatorBot.id, + mode: "noTime", + kind: "Indicator", + botSlug: simulatorBot.slug, + resumeExecution: simulation.resumeExecution, + exchangeName: "Poloniex", // TODO pending enable multiple exchange + beginDatetime: simulation.beginDatetime, + runAsTeam: true, + processName: processName, + createDatetime: new Date().valueOf() / 1000 | 0, + active: true, + authId: userId, + timePeriod: simulation.timePeriod + } + + let newClone = new Clone(clone) + clone.id = newClone._id + + logger.debug('runSimulation -> Creating %s clone on the Database.', processName) + await newClone.save() + await createKubernetesClone(clone) + + logger.debug('runSimulation -> %s clone created.', processName) +} + +const RunSimulationMutation = { + runSimulation: { + type: GraphQLString, + args, + resolve + } +} + +export default RunSimulationMutation diff --git a/server/src/schema/mutations/UpdateExecutionSummary.js b/server/src/schema/mutations/UpdateExecutionSummary.js index 876f2b07d..282e8cf26 100644 --- a/server/src/schema/mutations/UpdateExecutionSummary.js +++ b/server/src/schema/mutations/UpdateExecutionSummary.js @@ -5,7 +5,7 @@ import { GraphQLInt, GraphQLFloat } from 'graphql' -import { OperationsError } from '../../errors' +import { OperationsError, AuthorizationError, AuthentificationError } from '../../errors' import { Clone } from '../../models' import logger from '../../config/logger' import { isDefined } from '../../config/utils' @@ -34,7 +34,7 @@ const resolve = async (parent, if (!(context.userId === process.env.AACLOUD_ID)) { logger.debug('User %s is not authorized to manage team %s.', authId, team.slug) - throw new AutorizationError() + throw new AuthorizationError() } try { diff --git a/server/src/schema/mutations/index.js b/server/src/schema/mutations/index.js index 74c316ea8..2b85823a6 100644 --- a/server/src/schema/mutations/index.js +++ b/server/src/schema/mutations/index.js @@ -2,13 +2,15 @@ import { GraphQLObjectType } from 'graphql' import AddCloneMutation from './AddCloneMutation' import RemoveCloneMutation from './RemoveCloneMutation' import UpdateExecutionSummary from './UpdateExecutionSummary' +import RunSimulation from './RunSimulation' const Mutation = new GraphQLObjectType({ name: 'Mutation', fields: Object.assign( AddCloneMutation, RemoveCloneMutation, - UpdateExecutionSummary + UpdateExecutionSummary, + RunSimulation ) }) diff --git a/server/src/schema/types/input/Simulation.js b/server/src/schema/types/input/Simulation.js new file mode 100644 index 000000000..26ef2a11f --- /dev/null +++ b/server/src/schema/types/input/Simulation.js @@ -0,0 +1,20 @@ +import { + GraphQLString, + GraphQLInt, + GraphQLInputObjectType, + GraphQLNonNull, + GraphQLList, + GraphQLBoolean +} from 'graphql' + +const Type = new GraphQLInputObjectType({ + name: 'SimulationInput', + fields: () => ({ + beginDatetime: { type: new GraphQLNonNull(GraphQLInt) }, + timePeriodDailyArray: { type: new GraphQLList(new GraphQLNonNull(GraphQLString)) }, + timePeriodMarketArray: { type: new GraphQLList(new GraphQLNonNull(GraphQLString)) }, + resumeExecution: { type: GraphQLBoolean } + }) +}) + +export default Type diff --git a/server/src/schema/types/input/index.js b/server/src/schema/types/input/index.js index 5c39e7316..08e467348 100644 --- a/server/src/schema/types/input/index.js +++ b/server/src/schema/types/input/index.js @@ -1,5 +1,7 @@ import CloneInputType from './Clone' +import SimulationInputType from './Simulation' export { - CloneInputType + CloneInputType, + SimulationInputType }