Skip to content

Commit

Permalink
[OGUI-1344] Add DELETE endpoint with environment controller and servi…
Browse files Browse the repository at this point in the history
…ce (#2206)

* adds `DELETE` endpoint for environment destroy operations
* adds controller and service needed for the endpoint
  • Loading branch information
graduta authored Nov 21, 2023
1 parent e54a523 commit 6eed5a1
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 1 deletion.
1 change: 1 addition & 0 deletions Control/lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ module.exports.setup = (http, ws) => {
http.get('/environment/:id/:source?', coreMiddleware, envCtrl.getEnvironmentHandler.bind(envCtrl), {public: true});
http.post('/environment/auto', coreMiddleware, envCtrl.newAutoEnvironmentHandler.bind(envCtrl));
http.put('/environment/:id', coreMiddleware, envCtrl.transitionEnvironmentHandler.bind(envCtrl));
http.delete('/environment/:id', coreMiddleware, envCtrl.destroyEnvironmentHandler.bind(envCtrl));

http.get('/core/environments', coreMiddleware, (req, res) => envCache.get(req, res), {public: true});
http.post('/core/environments/configuration/save', (req, res) => apricotService.saveCoreEnvConfig(req, res));
Expand Down
20 changes: 20 additions & 0 deletions Control/lib/controllers/Environment.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,26 @@ class EnvironmentController {
}
}

/**
* API - DELETE endpoint for destroying an environment
* @param {Request} req - HTTP Request object which expects an `id` as mandatory parameter
* @param {Response} res - HTTP Response object with result of the transition of the environment
* @returns {void}
*/
async destroyEnvironmentHandler(req, res) {
const {id} = req.params ?? {};
const {keepTasks = false, allowInRunningState = false, force = false} = req.body ?? {};
if (!id) {
updateExpressResponseFromNativeError(res, new InvalidInputError('Missing environment ID parameter'));
}
try {
const response = await this._envService.destroyEnvironment(id, {keepTasks, allowInRunningState, force});
res.status(200).json(response);
} catch (error) {
updateExpressResponseFromNativeError(res, error);
}
}

/**
* API - POST endpoint for deploying a new environment based on a given configuration name
* @param {Request} req - HTTP Request object
Expand Down
15 changes: 15 additions & 0 deletions Control/lib/services/Environment.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@ class EnvironmentService {
}
}

/**
* Given an environment ID and optional parameters, use the gRPC client to send a request to destroy an environment
* @param {String} id - environment id as defined by AliECS Core
* @param {{keepTasks: Boolean, allowInRunningState: Boolean, force: Boolean}} - options for destroying the environment
* @return {Promise.<{String}, Error>} - if operation was a success or not
*/
async destroyEnvironment(id, {keepTasks = false, allowInRunningState = false, force = false} = {}) {
try {
await this._coreGrpc.DestroyEnvironment({id, keepTasks, allowInRunningState, force});
return {id};
} catch (grpcError) {
throw grpcErrorToNativeError(grpcError);
}
}

/**
* Given the workflowTemplate and variables configuration, it will generate a unique string and send all to AliECS to create a
* new auto transitioning environment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,16 @@ describe('EnvironmentController test suite', () => {
const transitionEnvironmentStub = sinon.stub();
transitionEnvironmentStub.withArgs(ENVIRONMENT_ID_FAILED_TO_RETRIEVE, 'START_ACTIVITY').rejects(new Error(`Cannot transition environment`));
transitionEnvironmentStub.withArgs(ENVIRONMENT_VALID, 'START_ACTIVITY').resolves({id: ENVIRONMENT_VALID, state: 'RUNNING', currentRunNumber: 1});

const destroyEnvironmentStub = sinon.stub();
destroyEnvironmentStub.withArgs(ENVIRONMENT_ID_FAILED_TO_RETRIEVE).rejects(new Error(`Cannot destroy environment`));
destroyEnvironmentStub.withArgs(ENVIRONMENT_VALID).resolves({id: ENVIRONMENT_VALID});


const envService = {
getEnvironment: getEnvironmentStub,
transitionEnvironment: transitionEnvironmentStub
transitionEnvironment: transitionEnvironmentStub,
destroyEnvironment: destroyEnvironmentStub
};
const envCtrl = new EnvironmentController(envService);
let res;
Expand Down Expand Up @@ -119,4 +125,31 @@ describe('EnvironmentController test suite', () => {
assert.ok(res.json.calledWith({id: ENVIRONMENT_VALID, state: 'RUNNING', currentRunNumber: 1}));
});
});

describe(`'destroyEnvironmentHandler' test suite`, async () => {
beforeEach(() => {
res = {
status: sinon.stub().returnsThis(),
json: sinon.stub()
};
});

it('should return error due to missing id when attempting to destroy environment', async () => {
await envCtrl.destroyEnvironmentHandler({params: {id: null}, body: {type: null}}, res);
assert.ok(res.status.calledWith(400));
assert.ok(res.json.calledWith({message: `Missing environment ID parameter`}));
});

it('should return error due to destroy environment issue', async () => {
await envCtrl.destroyEnvironmentHandler({params: {id: ENVIRONMENT_ID_FAILED_TO_RETRIEVE}, body: {}}, res);
assert.ok(res.status.calledWith(500));
assert.ok(res.json.calledWith({message: `Cannot destroy environment`}));
});

it('should successfully return environment following destroy action', async () => {
await envCtrl.destroyEnvironmentHandler({params: {id: ENVIRONMENT_VALID}, body: {}}, res);
assert.ok(res.status.calledWith(200));
assert.ok(res.json.calledWith({id: ENVIRONMENT_VALID}));
});
});
});
18 changes: 18 additions & 0 deletions Control/test/lib/services/mocha-environment.service.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,16 @@ describe('EnvironmentService test suite', () => {
ControlEnvironmentStub.withArgs({id: ENVIRONMENT_ID_FAILED_TO_RETRIEVE, type: 'START_ACTIVITY'}).rejects({code: 5, details: 'Environment not found'});
ControlEnvironmentStub.withArgs({id: ENVIRONMENT_VALID, type: 'START_ACTIVITY'}).resolves({id: ENVIRONMENT_VALID, state: 'RUNNING', currentRunNumber: 1});

const DestroyEnvironmentStub = sinon.stub();
DestroyEnvironmentStub.withArgs({id: ENVIRONMENT_ID_FAILED_TO_RETRIEVE, keepTasks: false, allowInRunningState: false, force: false}).rejects({code: 5, details: 'Environment not found'});
DestroyEnvironmentStub.withArgs({id: ENVIRONMENT_VALID, keepTasks: true, allowInRunningState: false, force: false}).resolves({id: ENVIRONMENT_VALID});
DestroyEnvironmentStub.rejects({code: 1, details: 'Wrong arguments, using default stub reject'});

const envService = new EnvironmentService(
{
GetEnvironment: GetEnvironmentStub,
ControlEnvironment: ControlEnvironmentStub,
DestroyEnvironment: DestroyEnvironmentStub,
}, {detectors: [], includedDetectors: []}
);

Expand All @@ -59,9 +65,21 @@ describe('EnvironmentService test suite', () => {
it('should throw gRPC type of error due to issue', async () => {
await assert.rejects(envService.transitionEnvironment(ENVIRONMENT_ID_FAILED_TO_RETRIEVE, 'START_ACTIVITY'), new NotFoundError('Environment not found'));
});

it('should successfully return environment transition results', async () => {
const environmentTransitioned = await envService.transitionEnvironment(ENVIRONMENT_VALID, 'START_ACTIVITY');
assert.deepStrictEqual(environmentTransitioned, {id: ENVIRONMENT_VALID, state: 'RUNNING', currentRunNumber: 1})
});
});

describe(`'destroyEnvironment' test suite`, async () => {
it('should throw gRPC type of error due to issue encountered when trying to destroy environment and have default values set', async () => {
await assert.rejects(envService.destroyEnvironment(ENVIRONMENT_ID_FAILED_TO_RETRIEVE), new NotFoundError('Environment not found'));
});

it('should successfully return environment id if successfully destroyed', async () => {
const environmentTransitioned = await envService.destroyEnvironment(ENVIRONMENT_VALID, {keepTasks: true});
assert.deepStrictEqual(environmentTransitioned, {id: ENVIRONMENT_VALID})
});
});
});

0 comments on commit 6eed5a1

Please sign in to comment.