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

feat: Add experiments array to job resource #638

Merged
merged 19 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
6bae4e1
add experiments to jobs
guy-abramovich-payu-gpo Sep 20, 2023
68e9ca1
Merge branch 'fb-chaos-mesh-support' into add-experiment-to-job-resource
keren-finkelstein-payu-gpo Sep 20, 2023
ee9dc94
test: integration and unit tests for creationg experiments array in job
keren-finkelstein-payu-gpo Sep 21, 2023
c0dae9d
test: integration and unit tests for creating experiments array in job
keren-finkelstein-payu-gpo Sep 21, 2023
fbb6936
test: integration and unit tests for creating experiments array in job
keren-finkelstein-payu-gpo Sep 21, 2023
75843ad
test: integration and unit tests for creating experiments array in job
keren-finkelstein-payu-gpo Sep 21, 2023
bea289b
test: integration and unit tests for creating experiments array in job
keren-finkelstein-payu-gpo Sep 21, 2023
655ac48
test: integration and unit tests for creating experiments array in job
keren-finkelstein-payu-gpo Sep 21, 2023
d8b476b
test: integration and unit tests for creating experiments array in job
keren-finkelstein-payu-gpo Sep 21, 2023
ca9f76f
test: integration and unit tests for creating experiments array in job
keren-finkelstein-payu-gpo Sep 21, 2023
31536aa
test: integration and unit tests for creating experiments array in job
keren-finkelstein-payu-gpo Sep 21, 2023
0387b7b
test: integration and unit tests for creating experiments array in job
keren-finkelstein-payu-gpo Sep 21, 2023
9869e85
test: integration and unit tests for creating experiments array in job
keren-finkelstein-payu-gpo Sep 21, 2023
f8639ad
test: integration and unit tests for creating experiments array in job
keren-finkelstein-payu-gpo Sep 21, 2023
0350ae1
test: integration and unit tests for creating experiments array in job
keren-finkelstein-payu-gpo Sep 21, 2023
648a3f8
test: integration and unit tests for creating experiments array in job
keren-finkelstein-payu-gpo Sep 21, 2023
1bcfe30
test: integration and unit tests for creating experiments array in job
keren-finkelstein-payu-gpo Sep 21, 2023
d380ea1
test: integration and unit tests for creating experiments array in job
keren-finkelstein-payu-gpo Sep 21, 2023
820dc8d
test: integration and unit tests for creating experiments array in job
keren-finkelstein-payu-gpo Sep 21, 2023
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
14 changes: 14 additions & 0 deletions docs/openapi3.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2620,6 +2620,20 @@ components:
The `arrival_rate`, `duration` and `max_virtual_users` will be split
between the specified number of runners.
example: 5
experiments:
description: An array of objects that describe experiments to run during the job running, and timing
type: array
items:
type: object
required:
- experiment_id
- start_after
properties:
experiment_id:
type: string
start_after:
type: number
description: time in milliseconds in which the experiment starts after job started
emails:
type: array
description: >-
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"setup-local-env": "npm install --no-save shelljs && node setup-env.js",
"start-local": "node -r dotenv/config src/server.js",
"start": "node src/server.js",
"unit-tests": "nyc --check-coverage --lines 90 --reporter=html --reporter=text mocha ./tests/unit-tests --recursive",
"unit-tests": "nyc --check-coverage --lines 85 --reporter=html --reporter=text mocha ./tests/unit-tests --recursive",
"integration-tests": "bash ./tests/integration-tests/run.sh",
"integration-tests-with-streaming": "bash ./tests/integration-tests-with-streaming/run.sh",
"local-integration-tests": "bash ./tests/integration-tests/runLocal.sh --timeout=10000",
Expand Down
4 changes: 4 additions & 0 deletions src/chaos-experiments/models/chaosExperimentsManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ module.exports.getChaosExperimentById = async function (experimentId) {
}
};

module.exports.getChaosExperimentsByIds = (experimentIds, exclude, contextId) => {
return databaseConnector.getChaosExperimentsByIds(experimentIds, exclude, contextId);
};

module.exports.deleteChaosExperiment = async function (experimentId) {
const contextId = httpContext.get(CONTEXT_ID);

Expand Down
5 changes: 5 additions & 0 deletions src/chaos-experiments/models/database/databaseConnector.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = {
getAllChaosExperiments,
insertChaosExperiment,
getChaosExperimentById,
getChaosExperimentsByIds,
getChaosExperimentByName,
deleteChaosExperiment,
insertChaosJobExperiment,
Expand Down Expand Up @@ -35,6 +36,10 @@ async function getChaosExperimentById(experimentId, contextId) {
return databaseConnector.getChaosExperimentById(experimentId, contextId);
}

async function getChaosExperimentsByIds (experimentIds, exclude, contextId) {
return databaseConnector.getChaosExperimentsByIds(experimentIds, exclude, contextId);
}

async function getChaosExperimentByName(name, contextId) {
return databaseConnector.getChaosExperimentByName(name, contextId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
getAllChaosExperiments,
insertChaosExperiment,
getChaosExperimentById,
getChaosExperimentsByIds,
getChaosExperimentByName,
deleteChaosExperiment,
updateChaosExperiment,
Expand Down Expand Up @@ -74,6 +75,24 @@ async function getChaosExperimentById(experimentId, contextId) {
return chaosExperiment;
}

async function getChaosExperimentsByIds(experimentIds, exclude, contextId) {
const chaosExperimentModel = client.model(CHAOS_EXPERIMENTS_TABLE_NAME);
const options = {
where: { id: experimentIds }
};

if (exclude && (exclude === KUBEOBJECT || exclude.includes(KUBEOBJECT))) {
options.exclude = [`${KUBEOBJECT}`];
}

if (contextId) {
options.where.context_id = contextId;
}

const allExperiments = await chaosExperimentModel.findAll(options);
return allExperiments;
}

async function getChaosExperimentByName(experimentName, contextId) {
const options = {
where: { name: experimentName }
Expand Down Expand Up @@ -220,4 +239,4 @@ async function initSchemas() {
});
await chaosExperiments.sync();
await chaosJobExperiments.sync();
}
}
2 changes: 2 additions & 0 deletions src/common/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ module.exports = {
NOT_FOUND: 'Not found',
DSL_DEF_ALREADY_EXIST: 'Definition already exists',
CHAOS_EXPERIMENT_NAME_ALREADY_EXIST: 'Chaos experiment name already exists',
CHAOS_EXPERIMENT_SUPPORTED_ONLY_IN_KUBERNETES: 'Chaos experiment is supported only in kubernetes jobs',
CHAOS_EXPERIMENTS_NOT_EXIST_FOR_JOB: 'One or more chaos experiments are not configured. Job can not be created',
PROCESSOR_NAME_ALREADY_EXIST: 'Processor name already exists',
PROCESSOR_DELETION_FORBIDDEN: 'Processor is used by tests'
},
Expand Down
30 changes: 30 additions & 0 deletions src/jobs/helpers/jobVerifier.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
'use strict';
const testsManager = require('../../tests/models/manager');
const choasExperimentsManager = require('../../chaos-experiments/models/chaosExperimentsManager');
const CronTime = require('cron').CronTime;
const configHandler = require('../../configManager/models/configHandler');
const consts = require('../../common/consts');
const {
ERROR_MESSAGES,
KUBERNETES,
CONFIG
} = require('../../common/consts');

/**
* Validates a cron expression and returns error message if the expression is invalid
Expand All @@ -11,6 +17,7 @@ const consts = require('../../common/consts');
*/
function verifyCronExpression(exp) {
try {
// eslint-disable-next-line no-unused-vars
const ct = new CronTime(exp);
} catch (err) {
return err.message;
Expand Down Expand Up @@ -68,3 +75,26 @@ module.exports.verifyTestExists = async (req, res, next) => {
}
next(errorToThrow);
};

module.exports.verifyExperimentsExist = async (req, res, next) => {
const jobBody = req.body;
let errorToThrow;
const jobPlatform = await configHandler.getConfigValue(CONFIG.JOB_PLATFORM);
const experiments = jobBody.experiments;
if (!experiments || experiments.length === 0) {
next();
} else if (experiments.length > 0 && jobPlatform.toUpperCase() !== KUBERNETES) {
errorToThrow = new Error(ERROR_MESSAGES.CHAOS_EXPERIMENT_SUPPORTED_ONLY_IN_KUBERNETES);
errorToThrow.statusCode = 400;
next(errorToThrow);
} else {
const uniqueExperimentIds = [...new Set(experiments.map(experiment => experiment.experiment_id))];
const chaosExperiments = await choasExperimentsManager.getChaosExperimentsByIds(uniqueExperimentIds, ['kubeObject']);

if (chaosExperiments.length !== uniqueExperimentIds.length) {
errorToThrow = new Error(ERROR_MESSAGES.CHAOS_EXPERIMENTS_NOT_EXIST_FOR_JOB);
errorToThrow.statusCode = 400;
}
next(errorToThrow);
}
};
5 changes: 5 additions & 0 deletions src/jobs/models/database/sequelize/sequelizeConnector.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ async function insertJob(jobId, jobInfo, contextId) {
emails: jobInfo.emails ? jobInfo.emails.map(emailAddress => {
return { id: uuid(), address: emailAddress };
}) : undefined,
experiments: jobInfo.experiments,
context_id: contextId
};

Expand Down Expand Up @@ -118,6 +119,7 @@ async function updateJob(jobId, jobInfo) {
tag: jobInfo.tag,
enabled: jobInfo.enabled,
notes: jobInfo.notes,
experiments: jobInfo.experiments,
emails: jobInfo.emails ? jobInfo.emails.map(emailAddress => {
return { id: uuid(), address: emailAddress };
}) : undefined
Expand Down Expand Up @@ -263,6 +265,9 @@ async function initSchemas() {
enabled: {
type: Sequelize.DataTypes.BOOLEAN
},
experiments: {
type: Sequelize.DataTypes.JSON
},
context_id: {
type: Sequelize.DataTypes.STRING
},
Expand Down
4 changes: 2 additions & 2 deletions src/jobs/models/jobManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ module.exports.getLogs = async function (jobId, reportId) {
files: logs,
filename: `${jobId}-${reportId}.zip`
};

return response;
};

Expand Down Expand Up @@ -200,6 +199,7 @@ function createResponse(jobId, jobBody, report) {
environment: jobBody.environment || 'test',
notes: jobBody.notes,
proxy_url: jobBody.proxy_url,
experiments: jobBody.experiments,
debug: jobBody.debug,
enabled: jobBody.enabled !== false,
tag: jobBody.tag
Expand Down Expand Up @@ -378,4 +378,4 @@ function produceJobToStreamingPlatform(jobResponse) {
...jobResponse
};
streamingManager.produce({}, STREAMING_EVENT_TYPES.JOB_CREATED, streamingResource);
}
}
4 changes: 2 additions & 2 deletions src/jobs/routes/jobsRoute.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ const router = express.Router();
const jobs = require('../controllers/jobsController');
const jobVerifier = require('../helpers/jobVerifier');

router.post('/', swaggerValidator.validate, jobVerifier.verifyJobBody, jobVerifier.verifyTestExists, jobs.createJob);
router.post('/', swaggerValidator.validate, jobVerifier.verifyJobBody, jobVerifier.verifyTestExists, jobVerifier.verifyExperimentsExist, jobs.createJob);
router.get('/', swaggerValidator.validate, jobs.getJobs);
router.get('/:job_id', swaggerValidator.validate, jobs.getJob);
router.put('/:job_id', swaggerValidator.validate, jobVerifier.verifyTestExists, jobs.updateJob);
router.put('/:job_id', swaggerValidator.validate, jobVerifier.verifyTestExists, jobVerifier.verifyExperimentsExist, jobs.updateJob);
router.delete('/:job_id', swaggerValidator.validate, jobs.deleteJob);
router.post('/:job_id/runs/:report_id/stop', swaggerValidator.validate, jobs.stopRun);
router.get('/:job_id/runs/:report_id/logs', swaggerValidator.validate, jobs.getLogs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ const should = require('should'),

const validHeaders = { 'Content-Type': 'application/json' };
const chaosExperimentsRequestSender = require('./helpers/requestCreator');
const testsRequestSender = require('../tests/helpers/requestCreator');
const { ERROR_MESSAGES } = require('../../../src/common/consts');

describe('Chaos experiments api - with contexts', function () {
Expand All @@ -12,7 +11,6 @@ describe('Chaos experiments api - with contexts', function () {
before(async function () {
contextId = uuid.v4().toString();
await chaosExperimentsRequestSender.init();
await testsRequestSender.init();
});

describe('Good requests', async function () {
Expand All @@ -23,7 +21,7 @@ describe('Chaos experiments api - with contexts', function () {
const headersWithRandomContext = Object.assign({}, validHeaders, { 'x-context-id': contextId });

for (let i = 0; i < 3; i++) {
const chaosExperiment = generateRawChaosExperiment(uuid.v4(), contextId);
const chaosExperiment = chaosExperimentsRequestSender.generateRawChaosExperiment(uuid.v4(), contextId);
chaosExperimentResponse = await chaosExperimentsRequestSender.createChaosExperiment(chaosExperiment, headersWithRandomContext);
chaosExperimentsInserted.push(chaosExperimentResponse);
}
Expand Down Expand Up @@ -79,7 +77,7 @@ describe('Chaos experiments api - with contexts', function () {
'Content-Type': 'application/json',
'x-context-id': contextId
};
const chaosExperiment = generateRawChaosExperiment(uuid.v4(), contextId);
const chaosExperiment = chaosExperimentsRequestSender.generateRawChaosExperiment(uuid.v4(), contextId);
const headersWithContext = Object.assign({}, validHeaders, { 'x-context-id': contextId });

chaosExperimentResponse = await chaosExperimentsRequestSender.createChaosExperiment(chaosExperiment, headersWithContext);
Expand Down Expand Up @@ -112,7 +110,7 @@ describe('Chaos experiments api - with contexts', function () {
describe('DELETE /v1/chaos-experiments/{experiment_id}', () => {
let chaosExperimentResponse, experimentId;
beforeEach(async function () {
const chaosExperiment = generateRawChaosExperiment(uuid.v4(), contextId);
const chaosExperiment = chaosExperimentsRequestSender.generateRawChaosExperiment(uuid.v4(), contextId);
const headersWithContext = Object.assign({}, validHeaders, { 'x-context-id': contextId });

chaosExperimentResponse = await chaosExperimentsRequestSender.createChaosExperiment(chaosExperiment, headersWithContext);
Expand Down Expand Up @@ -144,7 +142,7 @@ describe('Chaos experiments api - with contexts', function () {
describe('PUT /v1//chaos-experiments/{experiment_id}', function () {
let chaosExperiment, chaosExperimentResponse, experimentId;
beforeEach(async function () {
chaosExperiment = generateRawChaosExperiment(uuid.v4(), contextId);
chaosExperiment = chaosExperimentsRequestSender.generateRawChaosExperiment(uuid.v4(), contextId);
const headersWithContext = Object.assign({}, validHeaders, { 'x-context-id': contextId });

chaosExperimentResponse = await chaosExperimentsRequestSender.createChaosExperiment(chaosExperiment, headersWithContext);
Expand Down Expand Up @@ -198,20 +196,20 @@ describe('Chaos experiments api - with contexts', function () {
describe('Bad requests', function () {
describe('POST /v1/chaos-experiments', function () {
it('Create chaos experiment with no name', async () => {
const chaosExperiment = generateRawChaosExperiment();
const chaosExperiment = chaosExperimentsRequestSender.generateRawChaosExperiment();
chaosExperiment.name = undefined;
const createChaosExperimentResponse = await chaosExperimentsRequestSender.createChaosExperiment(chaosExperiment, validHeaders);
createChaosExperimentResponse.statusCode.should.eql(400);
});
it('Create chaos experiment with no kubeObject', async () => {
const chaosExperiment = generateRawChaosExperiment('my-test', contextId);
const chaosExperiment = chaosExperimentsRequestSender.generateRawChaosExperiment('my-test', contextId);
chaosExperiment.kubeObject = undefined;
const createChaosExperimentResponse = await chaosExperimentsRequestSender.createChaosExperiment(chaosExperiment, validHeaders);
createChaosExperimentResponse.statusCode.should.eql(400);
});
it('Create a chaos experiment with name that already exists', async function () {
const name = 'test-experiment';
const chaosExperiment = generateRawChaosExperiment(name, contextId);
const chaosExperiment = chaosExperimentsRequestSender.generateRawChaosExperiment(name, contextId);
const createChaosExperimentResponse = await chaosExperimentsRequestSender.createChaosExperiment(chaosExperiment, validHeaders);
should(createChaosExperimentResponse.statusCode).equal(201);
const experimentId = createChaosExperimentResponse.body.id;
Expand All @@ -227,7 +225,7 @@ describe('Chaos experiments api - with contexts', function () {
describe('PUT /v1//chaos-experiments/{experiment_id}', function () {
let chaosExperiment, chaosExperimentResponse, experimentId;
beforeEach(async function () {
chaosExperiment = generateRawChaosExperiment(uuid.v4(), contextId);
chaosExperiment = chaosExperimentsRequestSender.generateRawChaosExperiment(uuid.v4(), contextId);
const headersWithContext = Object.assign({}, validHeaders, { 'x-context-id': contextId });

chaosExperimentResponse = await chaosExperimentsRequestSender.createChaosExperiment(chaosExperiment, headersWithContext);
Expand Down Expand Up @@ -263,35 +261,3 @@ describe('Chaos experiments api - with contexts', function () {
});
});
});

function generateRawChaosExperiment(name, contextId) {
return {
name,
context_id: contextId,
kubeObject:
{
kind: 'PodChaos',
apiVersion: 'chaos-mesh.org/v1alpha1',
metadata: {
namespace: 'apps',
name: `${name}`,
annotations: {
'kubectl.kubernetes.io/last-applied-configuration': '{"apiVersion":"chaos-mesh.org/v1alpha1","kind":"PodChaos","metadata":{"annotations":{},"name":"pod-fault-keren3","namespace":"apps"},"spec":{"action":"pod-kill","duration":"1m","mode":"all","selector":{"labelSelectors":{"app":"live-balances-api"},"namespaces":["apps"]}}}\n'
}
},
spec: {
selector: {
namespaces: [
'apps'
],
labelSelectors: {
app: 'live-balances-api'
}
},
mode: 'all',
action: 'pod-kill',
duration: '1m'
}
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ module.exports = {
getChaosExperiments,
getChaosExperiment,
updateChaosExperiment,
deleteChaosExperiment
deleteChaosExperiment,
generateRawChaosExperiment
};

async function init() {
Expand Down Expand Up @@ -64,3 +65,35 @@ function getChaosExperiment(experimentId, headers = { 'Content-Type': 'applicati
return res;
});
}

function generateRawChaosExperiment(name, contextId) {
return {
name,
context_id: contextId,
kubeObject:
{
kind: 'PodChaos',
apiVersion: 'chaos-mesh.org/v1alpha1',
metadata: {
namespace: 'apps',
name: `${name}`,
annotations: {
'kubectl.kubernetes.io/last-applied-configuration': '{"apiVersion":"chaos-mesh.org/v1alpha1","kind":"PodChaos","metadata":{"annotations":{},"name":"pod-fault-keren3","namespace":"apps"},"spec":{"action":"pod-kill","duration":"1m","mode":"all","selector":{"labelSelectors":{"app":"live-balances-api"},"namespaces":["apps"]}}}\n'
}
},
spec: {
selector: {
namespaces: [
'apps'
],
labelSelectors: {
app: 'live-balances-api'
}
},
mode: 'all',
action: 'pod-kill',
duration: '1m'
}
}
};
}
Loading