From 5126a7f955406c633e5e8a9c9c40da55464cc1e7 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 10 Dec 2024 13:27:36 -0700 Subject: [PATCH] Add error handling/retry logic for search source alert tests (#196443) ## Summary Resolves https://github.com/elastic/kibana/issues/193842. Adds error handling & retry logic for search source alerts that are causing failures on MKI. ### Checklist - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed (cherry picked from commit 5acba9678aef16b460bb7b578dcdf4ce10a012a4) --- .../discover/search_source_alert.ts | 86 ++++++------ .../discover/search_source_alert.ts | 124 +++++++++++------- 2 files changed, 123 insertions(+), 87 deletions(-) diff --git a/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/discover/search_source_alert.ts b/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/discover/search_source_alert.ts index 97d59de76ce4f..3ce3ea7ba12b6 100644 --- a/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/discover/search_source_alert.ts +++ b/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/discover/search_source_alert.ts @@ -45,53 +45,59 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { let connectorId: string; const createSourceIndex = () => - es.index({ - index: SOURCE_DATA_VIEW, - body: { - settings: { number_of_shards: 1 }, - mappings: { - properties: { - '@timestamp': { type: 'date' }, - message: { type: 'keyword' }, - }, + retry.try(() => + createIndex(SOURCE_DATA_VIEW, { + '@timestamp': { type: 'date' }, + message: { type: 'keyword' }, + }) + ); + + const createOutputDataIndex = () => + retry.try(() => + createIndex(OUTPUT_DATA_VIEW, { + rule_id: { type: 'text' }, + rule_name: { type: 'text' }, + alert_id: { type: 'text' }, + context_link: { type: 'text' }, + }) + ); + + async function createIndex(index: string, properties: unknown) { + try { + await es.index({ + index, + body: { + settings: { number_of_shards: 1 }, + mappings: { properties }, }, - }, - }); + }); + } catch (e) { + log.error(`Failed to create index "${index}" with error "${e.message}"`); + } + } - const generateNewDocs = async (docsNumber: number) => { + async function generateNewDocs(docsNumber: number, index = SOURCE_DATA_VIEW) { const mockMessages = Array.from({ length: docsNumber }, (_, i) => `msg-${i}`); const dateNow = new Date(); const dateToSet = new Date(dateNow); dateToSet.setMinutes(dateNow.getMinutes() - 10); - for (const message of mockMessages) { - await es.transport.request({ - path: `/${SOURCE_DATA_VIEW}/_doc`, - method: 'POST', - body: { - '@timestamp': dateToSet.toISOString(), - message, - }, - }); + try { + await Promise.all( + mockMessages.map((message) => + es.transport.request({ + path: `/${index}/_doc`, + method: 'POST', + body: { + '@timestamp': dateToSet.toISOString(), + message, + }, + }) + ) + ); + } catch (e) { + log.error(`Failed to generate new docs in "${index}" with error "${e.message}"`); } - }; - - const createOutputDataIndex = () => - es.index({ - index: OUTPUT_DATA_VIEW, - body: { - settings: { - number_of_shards: 1, - }, - mappings: { - properties: { - rule_id: { type: 'text' }, - rule_name: { type: 'text' }, - alert_id: { type: 'text' }, - context_link: { type: 'text' }, - }, - }, - }, - }); + } const deleteAlerts = (alertIds: string[]) => asyncForEach(alertIds, async (alertId: string) => { diff --git a/x-pack/test_serverless/functional/test_suites/common/discover_ml_uptime/discover/search_source_alert.ts b/x-pack/test_serverless/functional/test_suites/common/discover_ml_uptime/discover/search_source_alert.ts index d4653ca02f6f1..1007c89cb171a 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover_ml_uptime/discover/search_source_alert.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover_ml_uptime/discover/search_source_alert.ts @@ -48,53 +48,59 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { let connectorId: string; const createSourceIndex = () => - es.index({ - index: SOURCE_DATA_VIEW, - body: { - settings: { number_of_shards: 1 }, - mappings: { - properties: { - '@timestamp': { type: 'date' }, - message: { type: 'keyword' }, - }, + retry.try(() => + createIndex(SOURCE_DATA_VIEW, { + '@timestamp': { type: 'date' }, + message: { type: 'keyword' }, + }) + ); + + const createOutputDataIndex = () => + retry.try(() => + createIndex(OUTPUT_DATA_VIEW, { + rule_id: { type: 'text' }, + rule_name: { type: 'text' }, + alert_id: { type: 'text' }, + context_link: { type: 'text' }, + }) + ); + + async function createIndex(index: string, properties: unknown) { + try { + await es.index({ + index, + body: { + settings: { number_of_shards: 1 }, + mappings: { properties }, }, - }, - }); + }); + } catch (e) { + log.error(`Failed to create index "${index}" with error "${e.message}"`); + } + } - const generateNewDocs = async (docsNumber: number) => { + async function generateNewDocs(docsNumber: number, index = SOURCE_DATA_VIEW) { const mockMessages = Array.from({ length: docsNumber }, (_, i) => `msg-${i}`); const dateNow = new Date(); const dateToSet = new Date(dateNow); dateToSet.setMinutes(dateNow.getMinutes() - 10); - for (const message of mockMessages) { - await es.transport.request({ - path: `/${SOURCE_DATA_VIEW}/_doc`, - method: 'POST', - body: { - '@timestamp': dateToSet.toISOString(), - message, - }, - }); + try { + await Promise.all( + mockMessages.map((message) => + es.transport.request({ + path: `/${index}/_doc`, + method: 'POST', + body: { + '@timestamp': dateToSet.toISOString(), + message, + }, + }) + ) + ); + } catch (e) { + log.error(`Failed to generate new docs in "${index}" with error "${e.message}"`); } - }; - - const createOutputDataIndex = () => - es.index({ - index: OUTPUT_DATA_VIEW, - body: { - settings: { - number_of_shards: 1, - }, - mappings: { - properties: { - rule_id: { type: 'text' }, - rule_name: { type: 'text' }, - alert_id: { type: 'text' }, - context_link: { type: 'text' }, - }, - }, - }, - }); + } const deleteAlerts = (alertIds: string[]) => asyncForEach(alertIds, async (alertId: string) => { @@ -216,7 +222,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const openDiscoverAlertFlyout = async () => { await testSubjects.click('discoverAlertsButton'); - await testSubjects.click('discoverCreateAlertButton'); + // Different create rule buttons in serverless + if (await testSubjects.exists('discoverCreateAlertButton')) { + await testSubjects.click('discoverCreateAlertButton'); + } else { + await testSubjects.click('discoverAppMenuCustomThresholdRule'); + } }; const openManagementAlertFlyout = async () => { @@ -366,8 +377,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }; describe('Search source Alert', function () { - // see details: https://github.com/elastic/kibana/issues/193842 - this.tags(['failsOnMKI', 'skipSvlOblt']); + // Failing: https://github.com/elastic/kibana/issues/203045 + this.tags(['skipSvlOblt']); + before(async () => { await security.testUser.setRoles(['discover_alert']); await PageObjects.svlCommonPage.loginAsAdmin(); @@ -502,7 +514,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.click('thresholdPopover'); await testSubjects.setValue('alertThresholdInput0', '1'); - await testSubjects.click('saveEditedRuleButton'); + + // Different save buttons in serverless + if (await testSubjects.exists('saveEditedRuleButton')) { + await testSubjects.click('saveEditedRuleButton'); + } else { + await testSubjects.click('rulePageFooterSaveButton'); + } await PageObjects.header.waitUntilLoadingHasFinished(); await openAlertResults(RULE_NAME); @@ -652,8 +670,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); await retry.waitFor('rule name value is correct', async () => { - await testSubjects.setValue('ruleNameInput', newAlert); - const ruleName = await testSubjects.getAttribute('ruleNameInput', 'value'); + let ruleName; + // Rule name input is different in serverless + if (await testSubjects.exists('ruleNameInput')) { + await testSubjects.setValue('ruleNameInput', newAlert); + ruleName = await testSubjects.getAttribute('ruleNameInput', 'value'); + } else { + await testSubjects.setValue('ruleDetailsNameInput', newAlert); + ruleName = await testSubjects.getAttribute('ruleDetailsNameInput', 'value'); + } return ruleName === newAlert; }); @@ -677,7 +702,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await comboBox.set('ruleFormConsumerSelect', 'Stack Rules'); } - await testSubjects.click('saveRuleButton'); + // Save rule button is different in serverless + if (await testSubjects.exists('saveRuleButton')) { + await testSubjects.click('saveRuleButton'); + } else { + await testSubjects.click('rulePageFooterSaveButton'); + } await retry.waitFor('confirmation modal', async () => { return await testSubjects.exists('confirmModalConfirmButton');