-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ML] Data Frame Analytic: adds api integration tests for _start and _…
…stop endpoints (#92532) * add start and stop endpoint tests * add start and stop within spaces endpoint tests * move start and start_spaces tests to separate files * wip: move stop and stop_spaces to separate files * use slow running job config so job is still running when stop request happens * check started job state is actually started * check job is stopped after stopping * add debug logs * ensure jobs are created/started before stopping attempt * remove unnecessary debug logs
- Loading branch information
1 parent
bf8417e
commit ccc70b4
Showing
7 changed files
with
463 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
136 changes: 136 additions & 0 deletions
136
x-pack/test/api_integration/apis/ml/data_frame_analytics/start.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import expect from '@kbn/expect'; | ||
import { FtrProviderContext } from '../../../ftr_provider_context'; | ||
import { USER } from '../../../../functional/services/ml/security_common'; | ||
import { DataFrameAnalyticsConfig } from '../../../../../plugins/ml/public/application/data_frame_analytics/common'; | ||
import { DeepPartial } from '../../../../../plugins/ml/common/types/common'; | ||
import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; | ||
import { DATA_FRAME_TASK_STATE } from '../../../../../plugins/ml/common/constants/data_frame_analytics'; | ||
|
||
export default ({ getService }: FtrProviderContext) => { | ||
const esArchiver = getService('esArchiver'); | ||
const supertest = getService('supertestWithoutAuth'); | ||
const ml = getService('ml'); | ||
|
||
const jobId = `bm_${Date.now()}`; | ||
const generateDestinationIndex = (analyticsId: string) => `user-${analyticsId}`; | ||
const commonJobConfig = { | ||
source: { | ||
index: ['ft_bank_marketing'], | ||
query: { | ||
match_all: {}, | ||
}, | ||
}, | ||
analysis: { | ||
classification: { | ||
dependent_variable: 'y', | ||
training_percent: 20, | ||
}, | ||
}, | ||
analyzed_fields: { | ||
includes: [], | ||
excludes: [], | ||
}, | ||
model_memory_limit: '60mb', | ||
allow_lazy_start: false, // default value | ||
max_num_threads: 1, // default value | ||
}; | ||
const destinationIndex = generateDestinationIndex(`${jobId}_0`); | ||
|
||
const testJobConfigs: Array<DeepPartial<DataFrameAnalyticsConfig>> = [ | ||
{ | ||
id: `${jobId}_0`, | ||
description: 'Test start for analytics', | ||
dest: { | ||
index: destinationIndex, | ||
results_field: 'ml', | ||
}, | ||
...commonJobConfig, | ||
}, | ||
]; | ||
|
||
async function createJobs(mockJobConfigs: Array<DeepPartial<DataFrameAnalyticsConfig>>) { | ||
for (const jobConfig of mockJobConfigs) { | ||
await ml.api.createDataFrameAnalyticsJob(jobConfig as DataFrameAnalyticsConfig); | ||
} | ||
} | ||
|
||
describe('POST data_frame/analytics/{analyticsId}/_start', () => { | ||
before(async () => { | ||
await esArchiver.loadIfNeeded('ml/bm_classification'); | ||
await ml.testResources.setKibanaTimeZoneToUTC(); | ||
await createJobs(testJobConfigs); | ||
}); | ||
|
||
after(async () => { | ||
await ml.api.deleteDataFrameAnalyticsJobES(`${jobId}_0`); | ||
await ml.api.cleanMlIndices(); | ||
await ml.api.deleteIndices(destinationIndex); | ||
}); | ||
|
||
describe('StartDataFrameAnalyticsJob', () => { | ||
it('should start analytics job for specified id if job exists', async () => { | ||
const analyticsId = `${jobId}_0`; | ||
|
||
const { body } = await supertest | ||
.post(`/api/ml/data_frame/analytics/${analyticsId}/_start`) | ||
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) | ||
.set(COMMON_REQUEST_HEADERS) | ||
.expect(200); | ||
|
||
expect(body).not.to.be(undefined); | ||
expect(body.acknowledged).to.be(true); | ||
expect(body.node).not.to.be(''); | ||
|
||
await ml.api.waitForAnalyticsState(analyticsId, DATA_FRAME_TASK_STATE.STARTED); | ||
await ml.api.assertIndicesExist(destinationIndex); | ||
}); | ||
|
||
it('should show 404 error if job does not exist', async () => { | ||
const id = `${jobId}_invalid`; | ||
const message = `No known job with id '${id}'`; | ||
|
||
const { body } = await supertest | ||
.post(`/api/ml/data_frame/analytics/${id}/_start`) | ||
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) | ||
.set(COMMON_REQUEST_HEADERS) | ||
.expect(404); | ||
|
||
expect(body.error).to.eql('Not Found'); | ||
expect(body.message).to.eql(message); | ||
}); | ||
|
||
it('should not allow to start analytics job for unauthorized user', async () => { | ||
const analyticsId = `${jobId}_0`; | ||
|
||
const { body } = await supertest | ||
.post(`/api/ml/data_frame/analytics/${analyticsId}/_start`) | ||
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) | ||
.set(COMMON_REQUEST_HEADERS) | ||
.expect(403); | ||
|
||
expect(body.error).to.eql('Forbidden'); | ||
expect(body.message).to.eql('Forbidden'); | ||
}); | ||
|
||
it('should not allow to start analytics job for user with view only permission', async () => { | ||
const analyticsId = `${jobId}_0`; | ||
|
||
const { body } = await supertest | ||
.post(`/api/ml/data_frame/analytics/${analyticsId}/_start`) | ||
.auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) | ||
.set(COMMON_REQUEST_HEADERS) | ||
.expect(403); | ||
|
||
expect(body.error).to.eql('Forbidden'); | ||
expect(body.message).to.eql('Forbidden'); | ||
}); | ||
}); | ||
}); | ||
}; |
94 changes: 94 additions & 0 deletions
94
x-pack/test/api_integration/apis/ml/data_frame_analytics/start_spaces.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import expect from '@kbn/expect'; | ||
|
||
import { FtrProviderContext } from '../../../ftr_provider_context'; | ||
import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; | ||
import { USER } from '../../../../functional/services/ml/security_common'; | ||
import { DATA_FRAME_TASK_STATE } from '../../../../../plugins/ml/common/constants/data_frame_analytics'; | ||
|
||
export default ({ getService }: FtrProviderContext) => { | ||
const esArchiver = getService('esArchiver'); | ||
const ml = getService('ml'); | ||
const spacesService = getService('spaces'); | ||
const supertest = getService('supertestWithoutAuth'); | ||
|
||
const jobIdSpace1 = 'ihp_od_space1'; | ||
const jobIdSpace2 = 'ihp_od_space2'; | ||
const idSpace1 = 'space1'; | ||
const idSpace2 = 'space2'; | ||
|
||
const initialModelMemoryLimit = '17mb'; | ||
|
||
async function runStartRequest(jobId: string, space: string, expectedStatusCode: number) { | ||
const { body } = await supertest | ||
.post(`/s/${space}/api/ml/data_frame/analytics/${jobId}/_start`) | ||
.auth( | ||
USER.ML_POWERUSER_ALL_SPACES, | ||
ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER_ALL_SPACES) | ||
) | ||
.set(COMMON_REQUEST_HEADERS) | ||
.expect(expectedStatusCode); | ||
|
||
return body; | ||
} | ||
|
||
let space1JobDestIndex: string; | ||
let space2JobDestIndex: string; | ||
|
||
describe('POST data_frame/analytics/{analyticsId}/_start with spaces', function () { | ||
before(async () => { | ||
await esArchiver.loadIfNeeded('ml/ihp_outlier'); | ||
await spacesService.create({ id: idSpace1, name: 'space_one', disabledFeatures: [] }); | ||
await spacesService.create({ id: idSpace2, name: 'space_two', disabledFeatures: [] }); | ||
|
||
const jobConfigSpace1 = ml.commonConfig.getDFAIhpOutlierDetectionJobConfig(jobIdSpace1); | ||
await ml.api.createDataFrameAnalyticsJob( | ||
{ ...jobConfigSpace1, model_memory_limit: initialModelMemoryLimit }, | ||
idSpace1 | ||
); | ||
|
||
const jobConfigSpace2 = ml.commonConfig.getDFAIhpOutlierDetectionJobConfig(jobIdSpace2); | ||
await ml.api.createDataFrameAnalyticsJob( | ||
{ ...jobConfigSpace2, model_memory_limit: initialModelMemoryLimit }, | ||
idSpace2 | ||
); | ||
|
||
space1JobDestIndex = jobConfigSpace1.dest.index; | ||
space2JobDestIndex = jobConfigSpace2.dest.index; | ||
|
||
await ml.testResources.setKibanaTimeZoneToUTC(); | ||
}); | ||
|
||
after(async () => { | ||
await ml.api.deleteDataFrameAnalyticsJobES(jobIdSpace1); | ||
await ml.api.deleteDataFrameAnalyticsJobES(jobIdSpace2); | ||
await spacesService.delete(idSpace1); | ||
await spacesService.delete(idSpace2); | ||
await ml.api.cleanMlIndices(); | ||
await ml.api.deleteIndices(space1JobDestIndex); | ||
await ml.api.deleteIndices(space2JobDestIndex); | ||
await ml.testResources.cleanMLSavedObjects(); | ||
}); | ||
|
||
it('should start job from same space', async () => { | ||
const body = await runStartRequest(jobIdSpace1, idSpace1, 200); | ||
expect(body).to.have.property('acknowledged', true); | ||
|
||
await ml.api.waitForAnalyticsState(jobIdSpace1, DATA_FRAME_TASK_STATE.STARTED); | ||
await ml.api.assertIndicesExist(space1JobDestIndex); | ||
}); | ||
|
||
it('should fail to start job from different space', async () => { | ||
const body = await runStartRequest(jobIdSpace2, idSpace1, 404); | ||
expect(body.error).to.eql('Not Found'); | ||
|
||
await ml.api.waitForAnalyticsState(jobIdSpace1, DATA_FRAME_TASK_STATE.STOPPED); | ||
}); | ||
}); | ||
}; |
91 changes: 91 additions & 0 deletions
91
x-pack/test/api_integration/apis/ml/data_frame_analytics/stop.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
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 { DATA_FRAME_TASK_STATE } from '../../../../../plugins/ml/common/constants/data_frame_analytics'; | ||
|
||
export default ({ getService }: FtrProviderContext) => { | ||
const esArchiver = getService('esArchiver'); | ||
const supertest = getService('supertestWithoutAuth'); | ||
const ml = getService('ml'); | ||
|
||
const jobId = `bm_${Date.now()}`; | ||
const analyticsId = `${jobId}_1`; | ||
let destinationIndex: string; | ||
|
||
describe('POST data_frame/analytics/{analyticsId}/_stop', () => { | ||
before(async () => { | ||
await esArchiver.loadIfNeeded('ml/bm_classification'); | ||
await ml.testResources.setKibanaTimeZoneToUTC(); | ||
// job config with high training percent so it takes longer to run | ||
const slowRunningConfig = ml.commonConfig.getDFABmClassificationJobConfig(analyticsId); | ||
destinationIndex = slowRunningConfig.dest.index; | ||
|
||
await ml.api.createDataFrameAnalyticsJob(slowRunningConfig); | ||
await ml.api.runDFAJob(analyticsId); | ||
}); | ||
|
||
after(async () => { | ||
await ml.api.deleteDataFrameAnalyticsJobES(analyticsId); | ||
await ml.api.cleanMlIndices(); | ||
await ml.api.deleteIndices(destinationIndex); | ||
}); | ||
|
||
describe('StopsDataFrameAnalyticsJob', () => { | ||
it('should stop analytics job for specified id when job exists', async () => { | ||
const { body } = await supertest | ||
.post(`/api/ml/data_frame/analytics/${analyticsId}/_stop`) | ||
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) | ||
.set(COMMON_REQUEST_HEADERS) | ||
.expect(200); | ||
|
||
expect(body).not.to.be(undefined); | ||
expect(body.stopped).to.be(true); | ||
await ml.api.waitForAnalyticsState(analyticsId, DATA_FRAME_TASK_STATE.STOPPED, 5000); | ||
}); | ||
|
||
it('should show 404 error if job does not exist', async () => { | ||
const id = `${jobId}_invalid`; | ||
const message = `No known job with id '${id}'`; | ||
|
||
const { body } = await supertest | ||
.post(`/api/ml/data_frame/analytics/${id}/_stop`) | ||
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) | ||
.set(COMMON_REQUEST_HEADERS) | ||
.expect(404); | ||
|
||
expect(body.error).to.eql('Not Found'); | ||
expect(body.message).to.eql(message); | ||
}); | ||
|
||
it('should not allow to stop analytics job for unauthorized user', async () => { | ||
const { body } = await supertest | ||
.post(`/api/ml/data_frame/analytics/${analyticsId}/_stop`) | ||
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) | ||
.set(COMMON_REQUEST_HEADERS) | ||
.expect(403); | ||
|
||
expect(body.error).to.eql('Forbidden'); | ||
expect(body.message).to.eql('Forbidden'); | ||
}); | ||
|
||
it('should not allow to stop analytics job for user with view only permission', async () => { | ||
const { body } = await supertest | ||
.post(`/api/ml/data_frame/analytics/${analyticsId}/_stop`) | ||
.auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) | ||
.set(COMMON_REQUEST_HEADERS) | ||
.expect(403); | ||
|
||
expect(body.error).to.eql('Forbidden'); | ||
expect(body.message).to.eql('Forbidden'); | ||
}); | ||
}); | ||
}); | ||
}; |
Oops, something went wrong.