mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
## Summary [Internal link](https://github.com/elastic/security-team/issues/10820) to the feature details Part of https://github.com/elastic/security-team/issues/11232 This PR covers SIEM Migrations Stats APIs: * Retrieves the stats for the specific migration: (route: `GET /internal/siem_migrations/rules/{migration_id}/stat`) * Retrieves the stats for all the existing migrations, aggregated by `migration_id`: (route: `GET /internal/siem_migrations/rules/stats`) * Retrieves the translation stats for the migration: (route: `GET /internal/siem_migrations/rules/{migration_id}/translation_stats`)
This commit is contained in:
parent
89b05458ad
commit
6006546dc4
5 changed files with 270 additions and 14 deletions
|
@ -52,7 +52,7 @@ export interface GetRuleMigrationStatsParams {
|
||||||
/** Optional AbortSignal for cancelling request */
|
/** Optional AbortSignal for cancelling request */
|
||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
}
|
}
|
||||||
/** Retrieves the stats for all the existing migrations, aggregated by `migration_id`. */
|
/** Retrieves the stats for the specific migration. */
|
||||||
export const getRuleMigrationStats = async ({
|
export const getRuleMigrationStats = async ({
|
||||||
migrationId,
|
migrationId,
|
||||||
signal,
|
signal,
|
||||||
|
|
|
@ -10,6 +10,7 @@ export default function ({ loadTestFile }: FtrProviderContext) {
|
||||||
describe('@ess SecuritySolution SIEM Migrations', () => {
|
describe('@ess SecuritySolution SIEM Migrations', () => {
|
||||||
loadTestFile(require.resolve('./create'));
|
loadTestFile(require.resolve('./create'));
|
||||||
loadTestFile(require.resolve('./get'));
|
loadTestFile(require.resolve('./get'));
|
||||||
|
loadTestFile(require.resolve('./stats'));
|
||||||
loadTestFile(require.resolve('./update'));
|
loadTestFile(require.resolve('./update'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
/*
|
||||||
|
* 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 'expect';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import {
|
||||||
|
createMigrationRules,
|
||||||
|
deleteAllMigrationRules,
|
||||||
|
getMigrationRuleDocuments,
|
||||||
|
migrationRulesRouteHelpersFactory,
|
||||||
|
statsOverrideCallbackFactory,
|
||||||
|
} from '../../utils';
|
||||||
|
import { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||||
|
|
||||||
|
export default ({ getService }: FtrProviderContext) => {
|
||||||
|
const es = getService('es');
|
||||||
|
const supertest = getService('supertest');
|
||||||
|
const migrationRulesRoutes = migrationRulesRouteHelpersFactory(supertest);
|
||||||
|
|
||||||
|
describe('@ess @serverless @serverlessQA Stats API', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await deleteAllMigrationRules(es);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return stats for the specific migration', async () => {
|
||||||
|
const migrationId = uuidv4();
|
||||||
|
|
||||||
|
const failed = 3;
|
||||||
|
const pending = 5;
|
||||||
|
const processing = 7;
|
||||||
|
const completed = 10;
|
||||||
|
const total = failed + pending + processing + completed;
|
||||||
|
const overrideCallback = statsOverrideCallbackFactory({
|
||||||
|
migrationId,
|
||||||
|
failed,
|
||||||
|
pending,
|
||||||
|
processing,
|
||||||
|
completed, // 4 - full, 5 - partial, 1 - untranslated
|
||||||
|
fullyTranslated: 4,
|
||||||
|
partiallyTranslated: 5,
|
||||||
|
});
|
||||||
|
const migrationRuleDocuments = getMigrationRuleDocuments(total, overrideCallback);
|
||||||
|
await createMigrationRules(es, migrationRuleDocuments);
|
||||||
|
|
||||||
|
const response = await migrationRulesRoutes.stats({ migrationId });
|
||||||
|
expect(response.body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
status: 'stopped',
|
||||||
|
id: migrationId,
|
||||||
|
rules: {
|
||||||
|
total,
|
||||||
|
pending,
|
||||||
|
processing,
|
||||||
|
completed,
|
||||||
|
failed,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return stats for the existing migrations', async () => {
|
||||||
|
const migrationId1 = uuidv4();
|
||||||
|
const migrationId2 = uuidv4();
|
||||||
|
|
||||||
|
const overrideCallback1 = statsOverrideCallbackFactory({
|
||||||
|
migrationId: migrationId1,
|
||||||
|
failed: 2,
|
||||||
|
pending: 4,
|
||||||
|
processing: 3,
|
||||||
|
completed: 33,
|
||||||
|
fullyTranslated: 10,
|
||||||
|
partiallyTranslated: 10,
|
||||||
|
});
|
||||||
|
const migrationRuleDocuments1 = getMigrationRuleDocuments(42, overrideCallback1);
|
||||||
|
const overrideCallback2 = statsOverrideCallbackFactory({
|
||||||
|
migrationId: migrationId2,
|
||||||
|
failed: 7,
|
||||||
|
pending: 2,
|
||||||
|
processing: 5,
|
||||||
|
completed: 14,
|
||||||
|
fullyTranslated: 3,
|
||||||
|
partiallyTranslated: 5,
|
||||||
|
});
|
||||||
|
const migrationRuleDocuments2 = getMigrationRuleDocuments(28, overrideCallback2);
|
||||||
|
await createMigrationRules(es, [...migrationRuleDocuments1, ...migrationRuleDocuments2]);
|
||||||
|
|
||||||
|
const response = await migrationRulesRoutes.statsAll({});
|
||||||
|
const expectedStats = expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
status: 'stopped',
|
||||||
|
id: migrationId1,
|
||||||
|
rules: { total: 42, pending: 4, processing: 3, completed: 33, failed: 2 },
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
status: 'stopped',
|
||||||
|
id: migrationId2,
|
||||||
|
rules: { total: 28, pending: 2, processing: 5, completed: 14, failed: 7 },
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
expect(response.body).toEqual(expectedStats);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return translation stats for the specific migration', async () => {
|
||||||
|
const migrationId = uuidv4();
|
||||||
|
|
||||||
|
const failed = 3;
|
||||||
|
const pending = 5;
|
||||||
|
const processing = 7;
|
||||||
|
const completed = 10;
|
||||||
|
const total = failed + pending + processing + completed;
|
||||||
|
const overrideCallback = statsOverrideCallbackFactory({
|
||||||
|
migrationId,
|
||||||
|
failed,
|
||||||
|
pending,
|
||||||
|
processing,
|
||||||
|
completed, // 4 - full, 5 - partial, 1 - untranslated
|
||||||
|
fullyTranslated: 4,
|
||||||
|
partiallyTranslated: 5,
|
||||||
|
});
|
||||||
|
const migrationRuleDocuments = getMigrationRuleDocuments(total, overrideCallback);
|
||||||
|
await createMigrationRules(es, migrationRuleDocuments);
|
||||||
|
|
||||||
|
const response = await migrationRulesRoutes.translationStats({ migrationId });
|
||||||
|
expect(response.body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: migrationId,
|
||||||
|
rules: {
|
||||||
|
total,
|
||||||
|
success: {
|
||||||
|
total: completed,
|
||||||
|
result: { full: 4, partial: 5, untranslatable: 1 },
|
||||||
|
installable: 4,
|
||||||
|
prebuilt: 0,
|
||||||
|
},
|
||||||
|
failed,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
|
@ -6,6 +6,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Client } from '@elastic/elasticsearch';
|
import type { Client } from '@elastic/elasticsearch';
|
||||||
|
import {
|
||||||
|
RuleTranslationResult,
|
||||||
|
SiemMigrationStatus,
|
||||||
|
} from '@kbn/security-solution-plugin/common/siem_migrations/constants';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ElasticRule,
|
ElasticRule,
|
||||||
|
@ -95,6 +99,58 @@ export const getMigrationRuleDocuments = (
|
||||||
return docs;
|
return docs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const statsOverrideCallbackFactory = ({
|
||||||
|
migrationId,
|
||||||
|
failed,
|
||||||
|
pending,
|
||||||
|
processing,
|
||||||
|
completed,
|
||||||
|
fullyTranslated,
|
||||||
|
partiallyTranslated,
|
||||||
|
}: {
|
||||||
|
migrationId: string;
|
||||||
|
failed: number;
|
||||||
|
pending: number;
|
||||||
|
processing: number;
|
||||||
|
completed: number;
|
||||||
|
fullyTranslated: number;
|
||||||
|
partiallyTranslated: number;
|
||||||
|
}) => {
|
||||||
|
const overrideCallback = (index: number): Partial<RuleMigrationDocument> => {
|
||||||
|
let translationResult;
|
||||||
|
let status = SiemMigrationStatus.PENDING;
|
||||||
|
|
||||||
|
const pendingEndIndex = failed + pending;
|
||||||
|
const processingEndIndex = failed + pending + processing;
|
||||||
|
const completedEndIndex = failed + pending + processing + completed;
|
||||||
|
if (index < failed) {
|
||||||
|
status = SiemMigrationStatus.FAILED;
|
||||||
|
} else if (index < pendingEndIndex) {
|
||||||
|
status = SiemMigrationStatus.PENDING;
|
||||||
|
} else if (index < processingEndIndex) {
|
||||||
|
status = SiemMigrationStatus.PROCESSING;
|
||||||
|
} else if (index < completedEndIndex) {
|
||||||
|
status = SiemMigrationStatus.COMPLETED;
|
||||||
|
const fullyTranslatedEndIndex = completedEndIndex - completed + fullyTranslated;
|
||||||
|
const partiallyTranslatedEndIndex =
|
||||||
|
completedEndIndex - completed + fullyTranslated + partiallyTranslated;
|
||||||
|
if (index < fullyTranslatedEndIndex) {
|
||||||
|
translationResult = RuleTranslationResult.FULL;
|
||||||
|
} else if (index < partiallyTranslatedEndIndex) {
|
||||||
|
translationResult = RuleTranslationResult.PARTIAL;
|
||||||
|
} else {
|
||||||
|
translationResult = RuleTranslationResult.UNTRANSLATABLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
migration_id: migrationId,
|
||||||
|
translation_result: translationResult,
|
||||||
|
status,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return overrideCallback;
|
||||||
|
};
|
||||||
|
|
||||||
export const createMigrationRules = async (
|
export const createMigrationRules = async (
|
||||||
es: Client,
|
es: Client,
|
||||||
rules: RuleMigrationDocument[]
|
rules: RuleMigrationDocument[]
|
||||||
|
|
|
@ -13,44 +13,49 @@ import {
|
||||||
import { replaceParams } from '@kbn/openapi-common/shared';
|
import { replaceParams } from '@kbn/openapi-common/shared';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
SIEM_RULE_MIGRATIONS_ALL_STATS_PATH,
|
||||||
SIEM_RULE_MIGRATIONS_PATH,
|
SIEM_RULE_MIGRATIONS_PATH,
|
||||||
SIEM_RULE_MIGRATION_PATH,
|
SIEM_RULE_MIGRATION_PATH,
|
||||||
|
SIEM_RULE_MIGRATION_STATS_PATH,
|
||||||
|
SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH,
|
||||||
} from '@kbn/security-solution-plugin/common/siem_migrations/constants';
|
} from '@kbn/security-solution-plugin/common/siem_migrations/constants';
|
||||||
import {
|
import {
|
||||||
CreateRuleMigrationResponse,
|
CreateRuleMigrationResponse,
|
||||||
|
GetAllStatsRuleMigrationResponse,
|
||||||
GetRuleMigrationRequestQuery,
|
GetRuleMigrationRequestQuery,
|
||||||
GetRuleMigrationResponse,
|
GetRuleMigrationResponse,
|
||||||
|
GetRuleMigrationStatsResponse,
|
||||||
UpdateRuleMigrationResponse,
|
UpdateRuleMigrationResponse,
|
||||||
} from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rule_migration.gen';
|
} from '@kbn/security-solution-plugin/common/siem_migrations/model/api/rules/rule_migration.gen';
|
||||||
import { API_VERSIONS } from '@kbn/security-solution-plugin/common/constants';
|
import { API_VERSIONS } from '@kbn/security-solution-plugin/common/constants';
|
||||||
import { assertStatusCode } from './asserts';
|
import { assertStatusCode } from './asserts';
|
||||||
|
|
||||||
export interface GetRuleMigrationParams {
|
export interface RequestParams {
|
||||||
/** `id` of the migration to get rules documents for */
|
|
||||||
migrationId: string;
|
|
||||||
/** Optional query parameters */
|
|
||||||
queryParams?: GetRuleMigrationRequestQuery;
|
|
||||||
/** Optional expected status code parameter */
|
/** Optional expected status code parameter */
|
||||||
expectStatusCode?: number;
|
expectStatusCode?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateRuleMigrationParams {
|
export interface MigrationRequestParams extends RequestParams {
|
||||||
|
/** `id` of the migration to get rules documents for */
|
||||||
|
migrationId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetRuleMigrationParams extends MigrationRequestParams {
|
||||||
|
/** Optional query parameters */
|
||||||
|
queryParams?: GetRuleMigrationRequestQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateRuleMigrationParams extends RequestParams {
|
||||||
/** Optional `id` of migration to add the rules to.
|
/** Optional `id` of migration to add the rules to.
|
||||||
* The id is necessary only for batching the migration creation in multiple requests */
|
* The id is necessary only for batching the migration creation in multiple requests */
|
||||||
migrationId?: string;
|
migrationId?: string;
|
||||||
/** Optional payload to send */
|
/** Optional payload to send */
|
||||||
payload?: any;
|
payload?: any;
|
||||||
/** Optional expected status code parameter */
|
|
||||||
expectStatusCode?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateRulesParams {
|
export interface UpdateRulesParams extends MigrationRequestParams {
|
||||||
/** `id` of the migration to install rules for */
|
|
||||||
migrationId: string;
|
|
||||||
/** Optional payload to send */
|
/** Optional payload to send */
|
||||||
payload?: any;
|
payload?: any;
|
||||||
/** Optional expected status code parameter */
|
|
||||||
expectStatusCode?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const migrationRulesRouteHelpersFactory = (supertest: SuperTest.Agent) => {
|
export const migrationRulesRouteHelpersFactory = (supertest: SuperTest.Agent) => {
|
||||||
|
@ -106,5 +111,54 @@ export const migrationRulesRouteHelpersFactory = (supertest: SuperTest.Agent) =>
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
stats: async ({
|
||||||
|
migrationId,
|
||||||
|
expectStatusCode = 200,
|
||||||
|
}: MigrationRequestParams): Promise<{ body: GetRuleMigrationStatsResponse }> => {
|
||||||
|
const response = await supertest
|
||||||
|
.get(replaceParams(SIEM_RULE_MIGRATION_STATS_PATH, { migration_id: migrationId }))
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.set(ELASTIC_HTTP_VERSION_HEADER, API_VERSIONS.internal.v1)
|
||||||
|
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
|
||||||
|
.send();
|
||||||
|
|
||||||
|
assertStatusCode(expectStatusCode, response);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
statsAll: async ({
|
||||||
|
expectStatusCode = 200,
|
||||||
|
}: RequestParams): Promise<{ body: GetAllStatsRuleMigrationResponse }> => {
|
||||||
|
const response = await supertest
|
||||||
|
.get(SIEM_RULE_MIGRATIONS_ALL_STATS_PATH)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.set(ELASTIC_HTTP_VERSION_HEADER, API_VERSIONS.internal.v1)
|
||||||
|
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
|
||||||
|
.send();
|
||||||
|
|
||||||
|
assertStatusCode(expectStatusCode, response);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
translationStats: async ({
|
||||||
|
migrationId,
|
||||||
|
expectStatusCode = 200,
|
||||||
|
}: MigrationRequestParams): Promise<{ body: GetRuleMigrationStatsResponse }> => {
|
||||||
|
const response = await supertest
|
||||||
|
.get(
|
||||||
|
replaceParams(SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH, { migration_id: migrationId })
|
||||||
|
)
|
||||||
|
.set('kbn-xsrf', 'true')
|
||||||
|
.set(ELASTIC_HTTP_VERSION_HEADER, API_VERSIONS.internal.v1)
|
||||||
|
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
|
||||||
|
.send();
|
||||||
|
|
||||||
|
assertStatusCode(expectStatusCode, response);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue