Skip to content

Commit

Permalink
[ML] Initial API integration tests for ML jobs in spaces (#84789) (#8…
Browse files Browse the repository at this point in the history
…5164)

This PR adds initial API integration tests for the endpoints related to ML jobs in spaces.
  • Loading branch information
pheyos authored Dec 8, 2020
1 parent e07d80f commit 7367b17
Show file tree
Hide file tree
Showing 28 changed files with 2,126 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../ftr_provider_context';
import { USER } from '../../../../functional/services/ml/security_common';
import { JOB_STATE } from '../../../../../plugins/ml/common/constants/states';
import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api';

export default ({ getService }: FtrProviderContext) => {
const ml = getService('ml');
const spacesService = getService('spaces');
const supertest = getService('supertestWithoutAuth');

const jobIdSpace1 = 'fq_single_space1';
const idSpace1 = 'space1';
const idSpace2 = 'space2';

async function runRequest(jobId: string, expectedStatusCode: number, space?: string) {
const { body } = await supertest
.post(`${space ? `/s/${space}` : ''}/api/ml/anomaly_detectors/${jobId}/_close`)
.auth(
USER.ML_POWERUSER_ALL_SPACES,
ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES)
)
.set(COMMON_REQUEST_HEADERS)
.expect(expectedStatusCode);
return body;
}

describe('POST anomaly_detectors _close with spaces', () => {
before(async () => {
await spacesService.create({ id: idSpace1, name: 'space_one', disabledFeatures: [] });
await spacesService.create({ id: idSpace2, name: 'space_two', disabledFeatures: [] });

await ml.testResources.setKibanaTimeZoneToUTC();
});

beforeEach(async () => {
const jobConfig = ml.commonConfig.getADFqSingleMetricJobConfig(jobIdSpace1);
await ml.api.createAnomalyDetectionJob(jobConfig, idSpace1);
await ml.api.openAnomalyDetectionJob(jobIdSpace1);
});

afterEach(async () => {
await ml.api.closeAnomalyDetectionJob(jobIdSpace1);
await ml.api.cleanMlIndices();
await ml.testResources.cleanMLSavedObjects();
});

after(async () => {
await spacesService.delete(idSpace1);
await spacesService.delete(idSpace2);
});

it('should close job from same space', async () => {
const body = await runRequest(jobIdSpace1, 200, idSpace1);
expect(body).to.have.property('closed').eql(true, 'Job closing should be acknowledged');
await ml.api.waitForJobState(jobIdSpace1, JOB_STATE.CLOSED);
});

it('should fail to close job from different space', async () => {
await runRequest(jobIdSpace1, 404, idSpace2);
await ml.api.waitForJobState(jobIdSpace1, JOB_STATE.OPENED);
});
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { FtrProviderContext } from '../../../ftr_provider_context';
import { USER } from '../../../../functional/services/ml/security_common';
import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api';

export default ({ getService }: FtrProviderContext) => {
const ml = getService('ml');
const spacesService = getService('spaces');
const supertest = getService('supertestWithoutAuth');

const jobIdSpace1 = 'fq_single_space1';
const idSpace1 = 'space1';
const idSpace2 = 'space2';

describe('PUT anomaly_detectors with spaces', () => {
before(async () => {
await spacesService.create({ id: idSpace1, name: 'space_one', disabledFeatures: [] });
await spacesService.create({ id: idSpace2, name: 'space_two', disabledFeatures: [] });

await ml.testResources.setKibanaTimeZoneToUTC();
});

after(async () => {
await spacesService.delete(idSpace1);
await spacesService.delete(idSpace2);
await ml.api.cleanMlIndices();
await ml.testResources.cleanMLSavedObjects();
});

it('should create a job in the current space', async () => {
const jobConfig = ml.commonConfig.getADFqSingleMetricJobConfig(jobIdSpace1);

await ml.testExecution.logTestStep('should create job');
await supertest
.put(`/s/${idSpace1}/api/ml/anomaly_detectors/${jobIdSpace1}`)
.auth(
USER.ML_POWERUSER_ALL_SPACES,
ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES)
)
.set(COMMON_REQUEST_HEADERS)
.send(jobConfig)
.expect(200);

await ml.testExecution.logTestStep(`job should be in space '${idSpace1}' only`);
await ml.api.assertJobSpaces(jobIdSpace1, 'anomaly-detector', [idSpace1]);
});
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { FtrProviderContext } from '../../../ftr_provider_context';
import { USER } from '../../../../functional/services/ml/security_common';
import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api';

export default ({ getService }: FtrProviderContext) => {
const ml = getService('ml');
const spacesService = getService('spaces');
const supertest = getService('supertestWithoutAuth');

const jobIdSpace1 = 'fq_single_space1';
const idSpace1 = 'space1';
const idSpace2 = 'space2';

async function runRequest(jobId: string, expectedStatusCode: number, space?: string) {
const { body } = await supertest
.delete(`${space ? `/s/${space}` : ''}/api/ml/anomaly_detectors/${jobId}`)
.auth(
USER.ML_POWERUSER_ALL_SPACES,
ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES)
)
.set(COMMON_REQUEST_HEADERS)
.expect(expectedStatusCode);
return body;
}

describe('DELETE anomaly_detectors with spaces', () => {
before(async () => {
await spacesService.create({ id: idSpace1, name: 'space_one', disabledFeatures: [] });
await spacesService.create({ id: idSpace2, name: 'space_two', disabledFeatures: [] });

await ml.testResources.setKibanaTimeZoneToUTC();
});

beforeEach(async () => {
const jobConfig = ml.commonConfig.getADFqSingleMetricJobConfig(jobIdSpace1);
await ml.api.createAnomalyDetectionJob(jobConfig, idSpace1);
});

afterEach(async () => {
await ml.api.cleanMlIndices();
await ml.testResources.cleanMLSavedObjects();
});

after(async () => {
await spacesService.delete(idSpace1);
await spacesService.delete(idSpace2);
});

it('should delete job from same space', async () => {
await runRequest(jobIdSpace1, 200, idSpace1);
await ml.api.waitForAnomalyDetectionJobNotToExist(jobIdSpace1);
});

it('should fail to delete job from different space', async () => {
await runRequest(jobIdSpace1, 404, idSpace2);
await ml.api.waitForAnomalyDetectionJobToExist(jobIdSpace1);
});
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../ftr_provider_context';
import { USER } from '../../../../functional/services/ml/security_common';
import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api';

export default ({ getService }: FtrProviderContext) => {
const ml = getService('ml');
const spacesService = getService('spaces');
const supertest = getService('supertestWithoutAuth');

const jobIdSpace1 = 'fq_single_space1';
const jobIdWildcardSpace1 = 'fq_single_space1*';
const jobGroupSpace1 = 'space1_group';
const jobGroupWildcardSpace1 = 'space1_group*';
const idSpace1 = 'space1';
const idSpace2 = 'space2';

async function runRequest(jobOrGroup: string, expectedStatusCode: number, space?: string) {
const { body } = await supertest
.get(`${space ? `/s/${space}` : ''}/api/ml/anomaly_detectors/${jobOrGroup}`)
.auth(
USER.ML_VIEWER_ALL_SPACES,
ml.securityCommon.getPasswordForUser(USER.ML_VIEWER_ALL_SPACES)
)
.set(COMMON_REQUEST_HEADERS)
.expect(expectedStatusCode);

return body;
}

describe('GET anomaly_detectors with spaces', () => {
before(async () => {
await spacesService.create({ id: idSpace1, name: 'space_one', disabledFeatures: [] });
await spacesService.create({ id: idSpace2, name: 'space_two', disabledFeatures: [] });

const jobConfig = ml.commonConfig.getADFqSingleMetricJobConfig(jobIdSpace1);
await ml.api.createAnomalyDetectionJob({ ...jobConfig, groups: [jobGroupSpace1] }, idSpace1);

await ml.testResources.setKibanaTimeZoneToUTC();
});

after(async () => {
await spacesService.delete(idSpace1);
await spacesService.delete(idSpace2);
await ml.api.cleanMlIndices();
await ml.testResources.cleanMLSavedObjects();
});

it('should fail with non-existing job', async () => {
await runRequest('non-existing-job', 404);
});

it('should return empty list with non-existing job wildcard', async () => {
const body = await runRequest('non-existing-job*', 200);

expect(body.count).to.eql(0, `response count should be 0 (got ${body.count})`);
expect(body.jobs.length).to.eql(
0,
`response jobs list should be empty (got ${JSON.stringify(body.jobs)})`
);
});

it('should fail with job from different space', async () => {
await runRequest(jobIdSpace1, 404, idSpace2);
});

it('should return empty list with job wildcard from different space', async () => {
const body = await runRequest(jobIdWildcardSpace1, 200, idSpace2);

expect(body.count).to.eql(0, `response count should be 0 (got ${body.count})`);
expect(body.jobs.length).to.eql(
0,
`response jobs list should be empty (got ${JSON.stringify(body.jobs)})`
);
});

it('should return job by group from same space', async () => {
const body = await runRequest(jobGroupSpace1, 200, idSpace1);

expect(body.count).to.eql(1, `response count should be 1 (got ${body.count})`);
expect(body.jobs.length).to.eql(
1,
`response jobs list should have one element (got ${JSON.stringify(body.jobs)})`
);
expect(body.jobs[0].job_id).to.eql(
jobIdSpace1,
`response job id should be ${jobIdSpace1} (got ${body.jobs[0].job_id})`
);
});

it('should return job by group wildcard from same space', async () => {
const body = await runRequest(jobGroupWildcardSpace1, 200, idSpace1);

expect(body.count).to.eql(1, `response count should be 1 (got ${body.count})`);
expect(body.jobs.length).to.eql(
1,
`response jobs list should have one element (got ${JSON.stringify(body.jobs)})`
);
expect(body.jobs[0].job_id).to.eql(
jobIdSpace1,
`response job id should be ${jobIdSpace1} (got ${body.jobs[0].job_id})`
);
});

it('should fail with group from different space', async () => {
await runRequest(jobGroupSpace1, 404, idSpace2);
});

it('should return empty list with group wildcard from different space', async () => {
const body = await runRequest(jobGroupWildcardSpace1, 200, idSpace2);

expect(body.count).to.eql(0, `response count should be 0 (got ${body.count})`);
expect(body.jobs.length).to.eql(
0,
`response jobs list should be empty (got ${JSON.stringify(body.jobs)})`
);
});
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,10 @@ export default function ({ loadTestFile }: FtrProviderContext) {
describe('anomaly detectors', function () {
loadTestFile(require.resolve('./create'));
loadTestFile(require.resolve('./get'));
loadTestFile(require.resolve('./get_with_spaces'));
loadTestFile(require.resolve('./open_with_spaces'));
loadTestFile(require.resolve('./close_with_spaces'));
loadTestFile(require.resolve('./delete_with_spaces'));
loadTestFile(require.resolve('./create_with_spaces'));
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../ftr_provider_context';
import { USER } from '../../../../functional/services/ml/security_common';
import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api';
import { JOB_STATE } from '../../../../../plugins/ml/common/constants/states';

export default ({ getService }: FtrProviderContext) => {
const ml = getService('ml');
const spacesService = getService('spaces');
const supertest = getService('supertestWithoutAuth');

const jobIdSpace1 = 'fq_single_space1';
const idSpace1 = 'space1';
const idSpace2 = 'space2';

async function runRequest(jobId: string, expectedStatusCode: number, space?: string) {
const { body } = await supertest
.post(`${space ? `/s/${space}` : ''}/api/ml/anomaly_detectors/${jobId}/_open`)
.auth(
USER.ML_POWERUSER_ALL_SPACES,
ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES)
)
.set(COMMON_REQUEST_HEADERS)
.expect(expectedStatusCode);
return body;
}

describe('POST anomaly_detectors _open with spaces', () => {
before(async () => {
await spacesService.create({ id: idSpace1, name: 'space_one', disabledFeatures: [] });
await spacesService.create({ id: idSpace2, name: 'space_two', disabledFeatures: [] });

await ml.testResources.setKibanaTimeZoneToUTC();
});

beforeEach(async () => {
const jobConfig = ml.commonConfig.getADFqSingleMetricJobConfig(jobIdSpace1);
await ml.api.createAnomalyDetectionJob(jobConfig, idSpace1);
});

afterEach(async () => {
await ml.api.closeAnomalyDetectionJob(jobIdSpace1);
await ml.api.cleanMlIndices();
await ml.testResources.cleanMLSavedObjects();
});

after(async () => {
await spacesService.delete(idSpace1);
await spacesService.delete(idSpace2);
});

it('should open job from same space', async () => {
const body = await runRequest(jobIdSpace1, 200, idSpace1);
expect(body).to.have.property('opened').eql(true, 'Job opening should be acknowledged');
await ml.api.waitForJobState(jobIdSpace1, JOB_STATE.OPENED);
});

it('should fail to open job from different space', async () => {
await runRequest(jobIdSpace1, 404, idSpace2);
await ml.api.waitForJobState(jobIdSpace1, JOB_STATE.CLOSED);
});
});
};
Loading

0 comments on commit 7367b17

Please sign in to comment.