[8.16] [Entity Store] [FTR Tests] Fix flakiness + poll for engine started on setup (#196564) (#197053)

# Backport

This will backport the following commits from `main` to `8.16`:
- [[Entity Store] [FTR Tests] Fix flakiness + poll for engine started on
setup (#196564)](https://github.com/elastic/kibana/pull/196564)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Mark
Hopkin","email":"mark.hopkin@elastic.co"},"sourceCommit":{"committedDate":"2024-10-21T11:31:00Z","message":"[Entity
Store] [FTR Tests] Fix flakiness + poll for engine started on setup
(#196564)\n\n## Summary\r\n\r\nCloses
https://github.com/elastic/kibana/issues/196546\r\nCloses
https://github.com/elastic/kibana/issues/196526\r\n\r\nUnskips flaky
entity store tests after fixes. \r\n\r\nEntity store tests were not
polling for the engine to be started before\r\nasserting the assets were
present.\r\n\r\nI have also added some retries to the asset checks as
some assets are\r\nnot immediately queryable after
creation.","sha":"0e1b2a3663d5a277247c74ed0957c00cffd4b0a0","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:
SecuritySolution","Team:Entity
Analytics","v8.16.0","backport:version","v8.17.0"],"number":196564,"url":"https://github.com/elastic/kibana/pull/196564","mergeCommit":{"message":"[Entity
Store] [FTR Tests] Fix flakiness + poll for engine started on setup
(#196564)\n\n## Summary\r\n\r\nCloses
https://github.com/elastic/kibana/issues/196546\r\nCloses
https://github.com/elastic/kibana/issues/196526\r\n\r\nUnskips flaky
entity store tests after fixes. \r\n\r\nEntity store tests were not
polling for the engine to be started before\r\nasserting the assets were
present.\r\n\r\nI have also added some retries to the asset checks as
some assets are\r\nnot immediately queryable after
creation.","sha":"0e1b2a3663d5a277247c74ed0957c00cffd4b0a0"}},"sourceBranch":"main","suggestedTargetBranches":["8.16","8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/196564","number":196564,"mergeCommit":{"message":"[Entity
Store] [FTR Tests] Fix flakiness + poll for engine started on setup
(#196564)\n\n## Summary\r\n\r\nCloses
https://github.com/elastic/kibana/issues/196546\r\nCloses
https://github.com/elastic/kibana/issues/196526\r\n\r\nUnskips flaky
entity store tests after fixes. \r\n\r\nEntity store tests were not
polling for the engine to be started before\r\nasserting the assets were
present.\r\n\r\nI have also added some retries to the asset checks as
some assets are\r\nnot immediately queryable after
creation.","sha":"0e1b2a3663d5a277247c74ed0957c00cffd4b0a0"}},{"branch":"8.16","label":"v8.16.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.x","label":"v8.17.0","labelRegex":"^v8.17.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->
This commit is contained in:
Mark Hopkin 2024-10-23 12:51:26 +01:00 committed by GitHub
parent 4ee3734018
commit 6095d94a99
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 153 additions and 85 deletions

View file

@ -72,10 +72,36 @@ export const executeFieldRetentionEnrichPolicy = async ({
export const deleteFieldRetentionEnrichPolicy = async ({
unitedDefinition,
esClient,
logger,
attempts = 5,
delayMs = 2000,
}: {
esClient: ElasticsearchClient;
unitedDefinition: DefinitionMetadata;
esClient: ElasticsearchClient;
logger: Logger;
attempts?: number;
delayMs?: number;
}) => {
const name = getFieldRetentionEnrichPolicyName(unitedDefinition);
return esClient.enrich.deletePolicy({ name }, { ignore: [404] });
let currentAttempt = 1;
while (currentAttempt <= attempts) {
try {
await esClient.enrich.deletePolicy({ name }, { ignore: [404] });
return;
} catch (e) {
// a 429 status code indicates that the enrich policy is being executed
if (currentAttempt === attempts || e.statusCode !== 429) {
logger.error(
`Error deleting enrich policy ${name}: ${e.message} after ${currentAttempt} attempts`
);
throw e;
}
logger.info(
`Enrich policy ${name} is being executed, waiting for it to finish before deleting`
);
await new Promise((resolve) => setTimeout(resolve, delayMs));
currentAttempt++;
}
}
};

View file

@ -244,7 +244,7 @@ export class EntityStoreDataClient {
logger,
taskManager,
});
logger.info(`Entity store initialized`);
logger.info(`Entity store initialized for ${entityType}`);
return updated;
} catch (err) {
@ -360,6 +360,7 @@ export class EntityStoreDataClient {
await deleteFieldRetentionEnrichPolicy({
unitedDefinition,
esClient,
logger,
});
if (deleteData) {
@ -460,7 +461,7 @@ export class EntityStoreDataClient {
originalStatus === ENGINE_STATUS.UPDATING
) {
throw new Error(
`Error updating entity store: There is an changes already in progress for engine ${id}`
`Error updating entity store: There are changes already in progress for engine ${id}`
);
}

View file

@ -14,9 +14,7 @@ export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const utils = EntityStoreUtils(getService);
// Failing: See https://github.com/elastic/kibana/issues/196526
describe.skip('@ess @skipInServerlessMKI Entity Store Engine APIs', () => {
describe('@ess @skipInServerlessMKI Entity Store Engine APIs', () => {
const dataView = dataViewRouteHelpersFactory(supertest);
before(async () => {
@ -34,22 +32,19 @@ export default ({ getService }: FtrProviderContext) => {
});
it('should have installed the expected user resources', async () => {
await utils.initEntityEngineForEntityType('user');
await utils.initEntityEngineForEntityTypesAndWait(['user']);
await utils.expectEngineAssetsExist('user');
});
it('should have installed the expected host resources', async () => {
await utils.initEntityEngineForEntityType('host');
await utils.initEntityEngineForEntityTypesAndWait(['host']);
await utils.expectEngineAssetsExist('host');
});
});
describe('get and list', () => {
before(async () => {
await Promise.all([
utils.initEntityEngineForEntityType('host'),
utils.initEntityEngineForEntityType('user'),
]);
await utils.initEntityEngineForEntityTypesAndWait(['host', 'user']);
});
after(async () => {
@ -119,7 +114,7 @@ export default ({ getService }: FtrProviderContext) => {
describe('start and stop', () => {
before(async () => {
await utils.initEntityEngineForEntityType('host');
await utils.initEntityEngineForEntityTypesAndWait(['host']);
});
after(async () => {
@ -161,7 +156,7 @@ export default ({ getService }: FtrProviderContext) => {
describe('delete', () => {
it('should delete the host entity engine', async () => {
await utils.initEntityEngineForEntityType('host');
await utils.initEntityEngineForEntityTypesAndWait(['host']);
await api
.deleteEntityEngine({
@ -174,7 +169,7 @@ export default ({ getService }: FtrProviderContext) => {
});
it('should delete the user entity engine', async () => {
await utils.initEntityEngineForEntityType('user');
await utils.initEntityEngineForEntityTypesAndWait(['user']);
await api
.deleteEntityEngine({
@ -189,7 +184,7 @@ export default ({ getService }: FtrProviderContext) => {
describe('apply_dataview_indices', () => {
before(async () => {
await utils.initEntityEngineForEntityType('host');
await utils.initEntityEngineForEntityTypesAndWait(['host']);
});
after(async () => {

View file

@ -18,8 +18,7 @@ export default ({ getService }: FtrProviderContextWithSpaces) => {
const supertest = getService('supertest');
const utils = EntityStoreUtils(getService, namespace);
// Failing: See https://github.com/elastic/kibana/issues/196546
describe.skip('@ess Entity Store Engine APIs in non-default space', () => {
describe('@ess Entity Store Engine APIs in non-default space', () => {
const dataView = dataViewRouteHelpersFactory(supertest, namespace);
before(async () => {
@ -43,22 +42,19 @@ export default ({ getService }: FtrProviderContextWithSpaces) => {
});
it('should have installed the expected user resources', async () => {
await utils.initEntityEngineForEntityType('user');
await utils.initEntityEngineForEntityTypesAndWait(['user']);
await utils.expectEngineAssetsExist('user');
});
it('should have installed the expected host resources', async () => {
await utils.initEntityEngineForEntityType('host');
await utils.initEntityEngineForEntityTypesAndWait(['host']);
await utils.expectEngineAssetsExist('host');
});
});
describe('get and list', () => {
before(async () => {
await Promise.all([
utils.initEntityEngineForEntityType('host'),
utils.initEntityEngineForEntityType('user'),
]);
await utils.initEntityEngineForEntityTypesAndWait(['host', 'user']);
});
after(async () => {
@ -134,7 +130,7 @@ export default ({ getService }: FtrProviderContextWithSpaces) => {
describe('start and stop', () => {
before(async () => {
await utils.initEntityEngineForEntityType('host');
await utils.initEntityEngineForEntityTypesAndWait(['host']);
});
after(async () => {
@ -188,7 +184,7 @@ export default ({ getService }: FtrProviderContextWithSpaces) => {
describe('delete', () => {
it('should delete the host entity engine', async () => {
await utils.initEntityEngineForEntityType('host');
await utils.initEntityEngineForEntityTypesAndWait(['host']);
await api
.deleteEntityEngine(
@ -204,7 +200,7 @@ export default ({ getService }: FtrProviderContextWithSpaces) => {
});
it('should delete the user entity engine', async () => {
await utils.initEntityEngineForEntityType('user');
await utils.initEntityEngineForEntityTypesAndWait(['user']);
await api
.deleteEntityEngine(

View file

@ -9,6 +9,8 @@ import { FtrProviderContext } from '@kbn/ftr-common-functional-services';
export const elasticAssetCheckerFactory = (getService: FtrProviderContext['getService']) => {
const es = getService('es');
const retry = getService('retry');
const log = getService('log');
const expectTransformExists = async (transformId: string) => {
return expectTransformStatus(transformId, true);
@ -18,45 +20,43 @@ export const elasticAssetCheckerFactory = (getService: FtrProviderContext['getSe
return expectTransformStatus(transformId, false);
};
const expectTransformStatus = async (
transformId: string,
exists: boolean,
attempts: number = 5,
delayMs: number = 2000
) => {
let currentAttempt = 1;
while (currentAttempt <= attempts) {
try {
await es.transform.getTransform({ transform_id: transformId });
if (!exists) {
throw new Error(`Expected transform ${transformId} to not exist, but it does`);
const expectTransformStatus = async (transformId: string, exists: boolean) => {
await retry.waitForWithTimeout(
`transform ${transformId} to ${exists ? 'exist' : 'not exist'}`,
10_000,
async () => {
try {
await es.transform.getTransform({ transform_id: transformId });
return exists;
} catch (e) {
log.debug(`Transform ${transformId} not found: ${e}`);
return !exists;
}
return; // Transform exists, exit the loop
} catch (e) {
if (currentAttempt === attempts) {
if (exists) {
throw new Error(`Expected transform ${transformId} to exist, but it does not: ${e}`);
} else {
return; // Transform does not exist, exit the loop
}
}
await new Promise((resolve) => setTimeout(resolve, delayMs));
currentAttempt++;
}
}
);
};
const expectEnrichPolicyStatus = async (policyId: string, exists: boolean) => {
try {
await es.enrich.getPolicy({ name: policyId });
if (!exists) {
throw new Error(`Expected enrich policy ${policyId} to not exist, but it does`);
await retry.waitForWithTimeout(
`enrich policy ${policyId} to ${exists ? 'exist' : 'not exist'}`,
20_000,
async () => {
try {
const res = await es.enrich.getPolicy({ name: policyId });
const policy = res.policies?.[0];
if (policy) {
log.debug(`Enrich policy ${policyId} found: ${JSON.stringify(res)}`);
return exists;
} else {
log.debug(`Enrich policy ${policyId} not found: ${JSON.stringify(res)}`);
return !exists;
}
} catch (e) {
log.debug(`Enrich policy ${policyId} not found: ${e}`);
return !exists;
}
}
} catch (e) {
if (exists) {
throw new Error(`Expected enrich policy ${policyId} to exist, but it does not: ${e}`);
}
}
);
};
const expectEnrichPolicyExists = async (policyId: string) =>
@ -66,18 +66,19 @@ export const elasticAssetCheckerFactory = (getService: FtrProviderContext['getSe
expectEnrichPolicyStatus(policyId, false);
const expectComponentTemplatStatus = async (templateName: string, exists: boolean) => {
try {
await es.cluster.getComponentTemplate({ name: templateName });
if (!exists) {
throw new Error(`Expected component template ${templateName} to not exist, but it does`);
await retry.waitForWithTimeout(
`component template ${templateName} to ${exists ? 'exist' : 'not exist'}`,
10_000,
async () => {
try {
await es.cluster.getComponentTemplate({ name: templateName });
return exists; // Component template exists
} catch (e) {
log.debug(`Component template ${templateName} not found: ${e}`);
return !exists; // Component template does not exist
}
}
} catch (e) {
if (exists) {
throw new Error(
`Expected component template ${templateName} to exist, but it does not: ${e}`
);
}
}
);
};
const expectComponentTemplateExists = async (templateName: string) =>
@ -87,16 +88,19 @@ export const elasticAssetCheckerFactory = (getService: FtrProviderContext['getSe
expectComponentTemplatStatus(templateName, false);
const expectIngestPipelineStatus = async (pipelineId: string, exists: boolean) => {
try {
await es.ingest.getPipeline({ id: pipelineId });
if (!exists) {
throw new Error(`Expected ingest pipeline ${pipelineId} to not exist, but it does`);
await retry.waitForWithTimeout(
`ingest pipeline ${pipelineId} to ${exists ? 'exist' : 'not exist'}`,
10_000,
async () => {
try {
await es.ingest.getPipeline({ id: pipelineId });
return exists; // Ingest pipeline exists
} catch (e) {
log.debug(`Ingest pipeline ${pipelineId} not found: ${e}`);
return !exists; // Ingest pipeline does not exist
}
}
} catch (e) {
if (exists) {
throw new Error(`Expected ingest pipeline ${pipelineId} to exist, but it does not: ${e}`);
}
}
);
};
const expectIngestPipelineExists = async (pipelineId: string) =>
@ -105,6 +109,25 @@ export const elasticAssetCheckerFactory = (getService: FtrProviderContext['getSe
const expectIngestPipelineNotFound = async (pipelineId: string) =>
expectIngestPipelineStatus(pipelineId, false);
const expectIndexStatus = async (indexName: string, exists: boolean) => {
try {
await es.indices.get({ index: indexName });
if (!exists) {
throw new Error(`Expected index ${indexName} to not exist, but it does`);
}
} catch (e) {
if (exists) {
throw new Error(`Expected index ${indexName} to exist, but it does not: ${e}`);
}
}
};
const expectEntitiesIndexExists = async (entityType: string, namespace: string) =>
expectIndexStatus(`.entities.v1.latest.security_${entityType}_${namespace}`, true);
const expectEntitiesIndexNotFound = async (entityType: string, namespace: string) =>
expectIndexStatus(`.entities.v1.latest.security_${entityType}_${namespace}`, false);
return {
expectComponentTemplateExists,
expectComponentTemplateNotFound,
@ -112,6 +135,8 @@ export const elasticAssetCheckerFactory = (getService: FtrProviderContext['getSe
expectEnrichPolicyNotFound,
expectIngestPipelineExists,
expectIngestPipelineNotFound,
expectEntitiesIndexExists,
expectEntitiesIndexNotFound,
expectTransformExists,
expectTransformNotFound,
};

View file

@ -17,6 +17,7 @@ export const EntityStoreUtils = (
const api = getService('securitySolutionApi');
const es = getService('es');
const log = getService('log');
const retry = getService('retry');
const {
expectTransformExists,
expectTransformNotFound,
@ -26,6 +27,8 @@ export const EntityStoreUtils = (
expectComponentTemplateNotFound,
expectIngestPipelineExists,
expectIngestPipelineNotFound,
expectEntitiesIndexExists,
expectEntitiesIndexNotFound,
} = elasticAssetCheckerFactory(getService);
log.debug(`EntityStoreUtils namespace: ${namespace}`);
@ -48,7 +51,7 @@ export const EntityStoreUtils = (
}
};
const initEntityEngineForEntityType = async (entityType: EntityType) => {
const _initEntityEngineForEntityType = async (entityType: EntityType) => {
log.info(
`Initializing engine for entity type ${entityType} in namespace ${namespace || 'default'}`
);
@ -68,6 +71,22 @@ export const EntityStoreUtils = (
expect(res.status).to.eql(200);
};
const initEntityEngineForEntityTypesAndWait = async (entityTypes: EntityType[]) => {
await Promise.all(entityTypes.map((entityType) => _initEntityEngineForEntityType(entityType)));
await retry.waitForWithTimeout(
`Engines to start for entity types: ${entityTypes.join(', ')}`,
60_000,
async () => {
const { body } = await api.listEntityEngines(namespace).expect(200);
if (body.engines.every((engine: any) => engine.status === 'started')) {
return true;
}
return false;
}
);
};
const expectTransformStatus = async (
transformId: string,
exists: boolean,
@ -98,21 +117,27 @@ export const EntityStoreUtils = (
const expectEngineAssetsExist = async (entityType: EntityType) => {
await expectTransformExists(`entities-v1-latest-security_${entityType}_${namespace}`);
await expectEnrichPolicyExists(`entity_store_field_retention_${entityType}_${namespace}_v1`);
await expectEnrichPolicyExists(
`entity_store_field_retention_${entityType}_${namespace}_v1.0.0`
);
await expectComponentTemplateExists(`security_${entityType}_${namespace}-latest@platform`);
await expectIngestPipelineExists(`security_${entityType}_${namespace}-latest@platform`);
await expectEntitiesIndexExists(entityType, namespace);
};
const expectEngineAssetsDoNotExist = async (entityType: EntityType) => {
await expectTransformNotFound(`entities-v1-latest-security_${entityType}_${namespace}`);
await expectEnrichPolicyNotFound(`entity_store_field_retention_${entityType}_${namespace}_v1`);
await expectEnrichPolicyNotFound(
`entity_store_field_retention_${entityType}_${namespace}_v1.0.0`
);
await expectComponentTemplateNotFound(`security_${entityType}_${namespace}-latest@platform`);
await expectIngestPipelineNotFound(`security_${entityType}_${namespace}-latest@platform`);
await expectEntitiesIndexNotFound(entityType, namespace);
};
return {
cleanEngines,
initEntityEngineForEntityType,
initEntityEngineForEntityTypesAndWait,
expectTransformStatus,
expectEngineAssetsExist,
expectEngineAssetsDoNotExist,