Skip to content

Commit

Permalink
[Rules migration][Integration test] Stats APIs (elastic#11232) (elast…
Browse files Browse the repository at this point in the history
…ic#211315)

## Summary

[Internal link](elastic/security-team#10820)
to the feature details

Part of elastic/security-team#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`)
  • Loading branch information
e40pud authored Feb 14, 2025
1 parent 89b0545 commit 6006546
Show file tree
Hide file tree
Showing 5 changed files with 268 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export interface GetRuleMigrationStatsParams {
/** Optional AbortSignal for cancelling request */
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 ({
migrationId,
signal,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default function ({ loadTestFile }: FtrProviderContext) {
describe('@ess SecuritySolution SIEM Migrations', () => {
loadTestFile(require.resolve('./create'));
loadTestFile(require.resolve('./get'));
loadTestFile(require.resolve('./stats'));
loadTestFile(require.resolve('./update'));
});
}
Original file line number Diff line number Diff line change
@@ -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,
},
})
);
});
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
*/

import type { Client } from '@elastic/elasticsearch';
import {
RuleTranslationResult,
SiemMigrationStatus,
} from '@kbn/security-solution-plugin/common/siem_migrations/constants';

import {
ElasticRule,
Expand Down Expand Up @@ -95,6 +99,58 @@ export const getMigrationRuleDocuments = (
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 (
es: Client,
rules: RuleMigrationDocument[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,44 +13,49 @@ import {
import { replaceParams } from '@kbn/openapi-common/shared';

import {
SIEM_RULE_MIGRATIONS_ALL_STATS_PATH,
SIEM_RULE_MIGRATIONS_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';
import {
CreateRuleMigrationResponse,
GetAllStatsRuleMigrationResponse,
GetRuleMigrationRequestQuery,
GetRuleMigrationResponse,
GetRuleMigrationStatsResponse,
UpdateRuleMigrationResponse,
} 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 { assertStatusCode } from './asserts';

export interface GetRuleMigrationParams {
export interface RequestParams {
/** Optional expected status code parameter */
expectStatusCode?: number;
}

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;
/** Optional expected status code parameter */
expectStatusCode?: number;
}

export interface CreateRuleMigrationParams {
export interface CreateRuleMigrationParams extends RequestParams {
/** Optional `id` of migration to add the rules to.
* The id is necessary only for batching the migration creation in multiple requests */
migrationId?: string;
/** Optional payload to send */
payload?: any;
/** Optional expected status code parameter */
expectStatusCode?: number;
}

export interface UpdateRulesParams {
/** `id` of the migration to install rules for */
migrationId: string;
export interface UpdateRulesParams extends MigrationRequestParams {
/** Optional payload to send */
payload?: any;
/** Optional expected status code parameter */
expectStatusCode?: number;
}

export const migrationRulesRouteHelpersFactory = (supertest: SuperTest.Agent) => {
Expand Down Expand Up @@ -106,5 +111,54 @@ export const migrationRulesRouteHelpersFactory = (supertest: SuperTest.Agent) =>

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;
},
};
};

0 comments on commit 6006546

Please sign in to comment.