From 0760bfb8701870c0991c853918ae6f981546ce6a Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Thu, 18 Feb 2021 15:34:50 -0600 Subject: [PATCH 1/4] [Fleet] Bootstrap functional test suite (#91898) --- .../components/agent_policy_section.tsx | 2 +- .../overview/components/agent_section.tsx | 2 +- .../components/datastream_section.tsx | 2 +- .../components/integration_section.tsx | 2 +- .../test/fleet_functional/apps/fleet/index.ts | 17 ++++++++ .../apps/fleet/overview_page.ts | 38 +++++++++++++++++ x-pack/test/fleet_functional/config.ts | 41 +++++++++++++++++++ .../ftr_provider_context.d.ts | 13 ++++++ .../fleet_functional/page_objects/index.ts | 14 +++++++ .../page_objects/overview_page.ts | 41 +++++++++++++++++++ .../test/fleet_functional/services/index.ts | 12 ++++++ 11 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 x-pack/test/fleet_functional/apps/fleet/index.ts create mode 100644 x-pack/test/fleet_functional/apps/fleet/overview_page.ts create mode 100644 x-pack/test/fleet_functional/config.ts create mode 100644 x-pack/test/fleet_functional/ftr_provider_context.d.ts create mode 100644 x-pack/test/fleet_functional/page_objects/index.ts create mode 100644 x-pack/test/fleet_functional/page_objects/overview_page.ts create mode 100644 x-pack/test/fleet_functional/services/index.ts diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx index 5bf1a383423b2..c3b59458abf0a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/overview/components/agent_policy_section.tsx @@ -31,7 +31,7 @@ export const OverviewPolicySection: React.FC<{ agentPolicies: AgentPolicy[] }> = }); return ( - + { const agentStatusRequest = useGetAgentStatus({}); return ( - + { } return ( - + { (item) => 'savedObject' in item && item.version > item.savedObject.attributes.version )?.length ?? 0; return ( - + { + before(async () => { + await overviewPage.navigateToOverview(); + }); + + it('should show the Integrations section', async () => { + await overviewPage.integrationsSectionExistsOrFail(); + }); + + it('should show the Agents section', async () => { + await overviewPage.agentSectionExistsOrFail(); + }); + + it('should show the Agent policies section', async () => { + await overviewPage.agentPolicySectionExistsOrFail(); + }); + + it('should show the Data streams section', async () => { + await overviewPage.datastreamSectionExistsOrFail(); + }); + }); + }); +} diff --git a/x-pack/test/fleet_functional/config.ts b/x-pack/test/fleet_functional/config.ts new file mode 100644 index 0000000000000..386f39d7ec668 --- /dev/null +++ b/x-pack/test/fleet_functional/config.ts @@ -0,0 +1,41 @@ +/* + * 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 { resolve } from 'path'; +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +import { pageObjects } from './page_objects'; +import { services } from './services'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const xpackFunctionalConfig = await readConfigFile(require.resolve('../functional/config.js')); + + return { + ...xpackFunctionalConfig.getAll(), + pageObjects, + testFiles: [resolve(__dirname, './apps/fleet')], + junit: { + reportName: 'X-Pack Fleet Functional Tests', + }, + services, + apps: { + ...xpackFunctionalConfig.get('apps'), + ['fleet']: { + pathname: '/app/fleet', + }, + }, + kbnTestServer: { + ...xpackFunctionalConfig.get('kbnTestServer'), + serverArgs: [ + ...xpackFunctionalConfig.get('kbnTestServer.serverArgs'), + '--xpack.fleet.enabled=true', + ], + }, + layout: { + fixedHeaderHeight: 200, + }, + }; +} diff --git a/x-pack/test/fleet_functional/ftr_provider_context.d.ts b/x-pack/test/fleet_functional/ftr_provider_context.d.ts new file mode 100644 index 0000000000000..ec28c00e72e47 --- /dev/null +++ b/x-pack/test/fleet_functional/ftr_provider_context.d.ts @@ -0,0 +1,13 @@ +/* + * 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 { GenericFtrProviderContext } from '@kbn/test/types/ftr'; + +import { pageObjects } from './page_objects'; +import { services } from './services'; + +export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/fleet_functional/page_objects/index.ts b/x-pack/test/fleet_functional/page_objects/index.ts new file mode 100644 index 0000000000000..2c534285146e5 --- /dev/null +++ b/x-pack/test/fleet_functional/page_objects/index.ts @@ -0,0 +1,14 @@ +/* + * 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 { pageObjects as xpackFunctionalPageObjects } from '../../functional/page_objects'; +import { OverviewPage } from './overview_page'; + +export const pageObjects = { + ...xpackFunctionalPageObjects, + overviewPage: OverviewPage, +}; diff --git a/x-pack/test/fleet_functional/page_objects/overview_page.ts b/x-pack/test/fleet_functional/page_objects/overview_page.ts new file mode 100644 index 0000000000000..ca58acd0a7b6a --- /dev/null +++ b/x-pack/test/fleet_functional/page_objects/overview_page.ts @@ -0,0 +1,41 @@ +/* + * 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 { FtrProviderContext } from '../ftr_provider_context'; +import { PLUGIN_ID } from '../../../plugins/fleet/common'; + +// NOTE: import path below should be the deep path to the actual module - else we get CI errors +import { pagePathGetters } from '../../../plugins/fleet/public/applications/fleet/constants/page_paths'; + +export function OverviewPage({ getService, getPageObjects }: FtrProviderContext) { + const pageObjects = getPageObjects(['common']); + const testSubjects = getService('testSubjects'); + + return { + async navigateToOverview() { + await pageObjects.common.navigateToApp(PLUGIN_ID, { + hash: pagePathGetters.overview(), + }); + }, + + async integrationsSectionExistsOrFail() { + await testSubjects.existOrFail('fleet-integrations-section'); + }, + + async agentPolicySectionExistsOrFail() { + await testSubjects.existOrFail('fleet-agent-policy-section'); + }, + + async agentSectionExistsOrFail() { + await testSubjects.existOrFail('fleet-agent-section'); + }, + + async datastreamSectionExistsOrFail() { + await testSubjects.existOrFail('fleet-datastream-section'); + }, + }; +} diff --git a/x-pack/test/fleet_functional/services/index.ts b/x-pack/test/fleet_functional/services/index.ts new file mode 100644 index 0000000000000..f5cfb8a32d34e --- /dev/null +++ b/x-pack/test/fleet_functional/services/index.ts @@ -0,0 +1,12 @@ +/* + * 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 { services as xPackFunctionalServices } from '../../functional/services'; + +export const services = { + ...xPackFunctionalServices, +}; From 2408d003254f2352037381fdaf5797850fed1551 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Thu, 18 Feb 2021 14:42:17 -0700 Subject: [PATCH 2/4] [data.search] Use incrementCounter for search telemetry (#91230) * [data.search] Use incrementCounter for search telemetry * Update reported type * Retry conflicts * Fix telemetry check * Use saved object migration to drop previous document * Review feedback * Fix import Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../saved_objects/migrations/to_v7_12_0.ts | 17 +++++ .../server/saved_objects/search_telemetry.ts | 4 + .../data/server/search/collectors/fetch.ts | 12 ++- .../data/server/search/collectors/register.ts | 10 ++- .../data/server/search/collectors/usage.ts | 73 +++++++++---------- 5 files changed, 70 insertions(+), 46 deletions(-) create mode 100644 src/plugins/data/server/saved_objects/migrations/to_v7_12_0.ts diff --git a/src/plugins/data/server/saved_objects/migrations/to_v7_12_0.ts b/src/plugins/data/server/saved_objects/migrations/to_v7_12_0.ts new file mode 100644 index 0000000000000..955028c0f9bf2 --- /dev/null +++ b/src/plugins/data/server/saved_objects/migrations/to_v7_12_0.ts @@ -0,0 +1,17 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { SavedObjectMigrationFn } from 'kibana/server'; + +/** + * Drop the previous document's attributes, which report `averageDuration` incorrectly. + * @param doc + */ +export const migrate712: SavedObjectMigrationFn = (doc) => { + return { ...doc, attributes: {} }; +}; diff --git a/src/plugins/data/server/saved_objects/search_telemetry.ts b/src/plugins/data/server/saved_objects/search_telemetry.ts index 24f884c85b7c5..33ad4b74f3169 100644 --- a/src/plugins/data/server/saved_objects/search_telemetry.ts +++ b/src/plugins/data/server/saved_objects/search_telemetry.ts @@ -7,6 +7,7 @@ */ import { SavedObjectsType } from 'kibana/server'; +import { migrate712 } from './migrations/to_v7_12_0'; export const searchTelemetry: SavedObjectsType = { name: 'search-telemetry', @@ -16,4 +17,7 @@ export const searchTelemetry: SavedObjectsType = { dynamic: false, properties: {}, }, + migrations: { + '7.12.0': migrate712, + }, }; diff --git a/src/plugins/data/server/search/collectors/fetch.ts b/src/plugins/data/server/search/collectors/fetch.ts index 05e5558157b3c..6dfc29e2cf2a6 100644 --- a/src/plugins/data/server/search/collectors/fetch.ts +++ b/src/plugins/data/server/search/collectors/fetch.ts @@ -11,14 +11,14 @@ import { first } from 'rxjs/operators'; import { SharedGlobalConfig } from 'kibana/server'; import { SearchResponse } from 'elasticsearch'; import { CollectorFetchContext } from 'src/plugins/usage_collection/server'; -import { Usage } from './register'; +import { CollectedUsage, ReportedUsage } from './register'; interface SearchTelemetry { - 'search-telemetry': Usage; + 'search-telemetry': CollectedUsage; } type ESResponse = SearchResponse; export function fetchProvider(config$: Observable) { - return async ({ esClient }: CollectorFetchContext): Promise => { + return async ({ esClient }: CollectorFetchContext): Promise => { const config = await config$.pipe(first()).toPromise(); const { body: esResponse } = await esClient.search( { @@ -37,6 +37,10 @@ export function fetchProvider(config$: Observable) { averageDuration: null, }; } - return esResponse.hits.hits[0]._source['search-telemetry']; + const { successCount, errorCount, totalDuration } = esResponse.hits.hits[0]._source[ + 'search-telemetry' + ]; + const averageDuration = totalDuration / successCount; + return { successCount, errorCount, averageDuration }; }; } diff --git a/src/plugins/data/server/search/collectors/register.ts b/src/plugins/data/server/search/collectors/register.ts index 2a5637d86e1bf..a370377c30eea 100644 --- a/src/plugins/data/server/search/collectors/register.ts +++ b/src/plugins/data/server/search/collectors/register.ts @@ -10,7 +10,13 @@ import { PluginInitializerContext } from 'kibana/server'; import { UsageCollectionSetup } from '../../../../usage_collection/server'; import { fetchProvider } from './fetch'; -export interface Usage { +export interface CollectedUsage { + successCount: number; + errorCount: number; + totalDuration: number; +} + +export interface ReportedUsage { successCount: number; errorCount: number; averageDuration: number | null; @@ -21,7 +27,7 @@ export async function registerUsageCollector( context: PluginInitializerContext ) { try { - const collector = usageCollection.makeUsageCollector({ + const collector = usageCollection.makeUsageCollector({ type: 'search', isReady: () => true, fetch: fetchProvider(context.config.legacy.globalConfig$), diff --git a/src/plugins/data/server/search/collectors/usage.ts b/src/plugins/data/server/search/collectors/usage.ts index c5dc2414c0e80..c9f0a5bf24944 100644 --- a/src/plugins/data/server/search/collectors/usage.ts +++ b/src/plugins/data/server/search/collectors/usage.ts @@ -6,11 +6,13 @@ * Side Public License, v 1. */ +import { once } from 'lodash'; import type { CoreSetup, Logger } from 'kibana/server'; +import { SavedObjectsErrorHelpers } from '../../../../../core/server'; import type { IEsSearchResponse } from '../../../common'; -import type { Usage } from './register'; const SAVED_OBJECT_ID = 'search-telemetry'; +const MAX_RETRY_COUNT = 3; export interface SearchUsage { trackError(): Promise; @@ -18,51 +20,42 @@ export interface SearchUsage { } export function usageProvider(core: CoreSetup): SearchUsage { - const getTracker = (eventType: keyof Usage) => { - return async (duration?: number) => { - const repository = await core - .getStartServices() - .then(([coreStart]) => coreStart.savedObjects.createInternalRepository()); + const getRepository = once(async () => { + const [coreStart] = await core.getStartServices(); + return coreStart.savedObjects.createInternalRepository(); + }); - let attributes: Usage; - let doesSavedObjectExist: boolean = true; - - try { - const response = await repository.get(SAVED_OBJECT_ID, SAVED_OBJECT_ID); - attributes = response.attributes; - } catch (e) { - doesSavedObjectExist = false; - attributes = { - successCount: 0, - errorCount: 0, - averageDuration: 0, - }; - } - - attributes[eventType]++; - - // Only track the average duration for successful requests - if (eventType === 'successCount') { - attributes.averageDuration = - ((duration ?? 0) + (attributes.averageDuration ?? 0)) / (attributes.successCount ?? 1); + const trackSuccess = async (duration: number, retryCount = 0) => { + const repository = await getRepository(); + try { + await repository.incrementCounter(SAVED_OBJECT_ID, SAVED_OBJECT_ID, [ + { fieldName: 'successCount' }, + { + fieldName: 'totalDuration', + incrementBy: duration, + }, + ]); + } catch (e) { + if (SavedObjectsErrorHelpers.isConflictError(e) && retryCount < MAX_RETRY_COUNT) { + setTimeout(() => trackSuccess(duration, retryCount + 1), 1000); } + } + }; - try { - if (doesSavedObjectExist) { - await repository.update(SAVED_OBJECT_ID, SAVED_OBJECT_ID, attributes); - } else { - await repository.create(SAVED_OBJECT_ID, attributes, { id: SAVED_OBJECT_ID }); - } - } catch (e) { - // Version conflict error, swallow + const trackError = async (retryCount = 0) => { + const repository = await getRepository(); + try { + await repository.incrementCounter(SAVED_OBJECT_ID, SAVED_OBJECT_ID, [ + { fieldName: 'errorCount' }, + ]); + } catch (e) { + if (SavedObjectsErrorHelpers.isConflictError(e) && retryCount < MAX_RETRY_COUNT) { + setTimeout(() => trackError(retryCount + 1), 1000); } - }; + } }; - return { - trackError: () => getTracker('errorCount')(), - trackSuccess: getTracker('successCount'), - }; + return { trackSuccess, trackError }; } /** From 0f804677de62e484920a7ef6ce357de2ab624aa9 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Thu, 18 Feb 2021 13:43:03 -0800 Subject: [PATCH 3/4] [Fleet] Silently swallow 404 errors when deleting ingest pipelines (#91778) * Only show transform logs when there are transforms * Silently swallow 404 errors when deleting ingest pipelines * Change to IngestManagerError --- .../epm/elasticsearch/ingest_pipeline/remove.ts | 7 ++++++- .../services/epm/elasticsearch/transform/install.ts | 10 +++++++--- .../services/epm/elasticsearch/transform/remove.ts | 4 +++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/remove.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/remove.ts index f12d68190b4ac..4acc4767de525 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/remove.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/ingest_pipeline/remove.ts @@ -8,6 +8,7 @@ import { SavedObjectsClientContract } from 'src/core/server'; import { appContextService } from '../../../'; import { CallESAsCurrentUser, ElasticsearchAssetType } from '../../../../types'; +import { IngestManagerError } from '../../../../errors'; import { getInstallation } from '../../packages/get'; import { PACKAGES_SAVED_OBJECT_TYPE, EsAssetReference } from '../../../../../common'; @@ -61,7 +62,11 @@ export async function deletePipeline(callCluster: CallESAsCurrentUser, id: strin try { await callCluster('ingest.deletePipeline', { id }); } catch (err) { - throw new Error(`error deleting pipeline ${id}`); + // Only throw if error is not a 404 error. Sometimes the pipeline is already deleted, but we have + // duplicate references to them, see https://github.com/elastic/kibana/issues/91192 + if (err.statusCode !== 404) { + throw new IngestManagerError(`error deleting pipeline ${id}: ${err}`); + } } } } diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts index 57e1090f8954b..948a9c56746f3 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/install.ts @@ -42,9 +42,13 @@ export const installTransform = async ( previousInstalledTransformEsAssets = installation.installed_es.filter( ({ type, id }) => type === ElasticsearchAssetType.transform ); - logger.info( - `Found previous transform references:\n ${JSON.stringify(previousInstalledTransformEsAssets)}` - ); + if (previousInstalledTransformEsAssets.length) { + logger.info( + `Found previous transform references:\n ${JSON.stringify( + previousInstalledTransformEsAssets + )}` + ); + } } // delete all previous transform diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.ts index b08b7cb7f1ec8..0e947e0f0b90b 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/transform/remove.ts @@ -26,7 +26,9 @@ export const deleteTransforms = async ( transformIds: string[] ) => { const logger = appContextService.getLogger(); - logger.info(`Deleting currently installed transform ids ${transformIds}`); + if (transformIds.length) { + logger.info(`Deleting currently installed transform ids ${transformIds}`); + } await Promise.all( transformIds.map(async (transformId) => { // get the index the transform From fe35e0de3b337e47bf36f5af8bdc2a00e437f9af Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Thu, 18 Feb 2021 18:45:15 -0500 Subject: [PATCH 4/4] [Fleet] Install Elastic Agent integration by default during setup (#91676) --- x-pack/plugins/fleet/common/constants/epm.ts | 1 + .../test/fleet_api_integration/apis/fleet_setup.ts | 14 ++++++++++++++ x-pack/test/fleet_api_integration/config.ts | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/common/constants/epm.ts b/x-pack/plugins/fleet/common/constants/epm.ts index b223139803257..aa17b16b3763c 100644 --- a/x-pack/plugins/fleet/common/constants/epm.ts +++ b/x-pack/plugins/fleet/common/constants/epm.ts @@ -15,6 +15,7 @@ export const FLEET_SERVER_PACKAGE = 'fleet_server'; export const requiredPackages = { System: 'system', Endpoint: 'endpoint', + ElasticAgent: 'elastic_agent', } as const; // these are currently identical. we can separate if they later diverge diff --git a/x-pack/test/fleet_api_integration/apis/fleet_setup.ts b/x-pack/test/fleet_api_integration/apis/fleet_setup.ts index 31d620cd34931..d9f55d9fa0b74 100644 --- a/x-pack/test/fleet_api_integration/apis/fleet_setup.ts +++ b/x-pack/test/fleet_api_integration/apis/fleet_setup.ts @@ -105,5 +105,19 @@ export default function (providerContext: FtrProviderContext) { transient_metadata: { enabled: true }, }); }); + + it('should install default packages', async () => { + await supertest.post(`/api/fleet/setup`).set('kbn-xsrf', 'xxxx').expect(200); + + const { body: apiResponse } = await supertest + .get(`/api/fleet/epm/packages?experimental=true`) + .expect(200); + const installedPackages = apiResponse.response + .filter((p: any) => p.status === 'installed') + .map((p: any) => p.name) + .sort(); + + expect(installedPackages).to.eql(['elastic_agent', 'endpoint', 'system']); + }); }); } diff --git a/x-pack/test/fleet_api_integration/config.ts b/x-pack/test/fleet_api_integration/config.ts index 444b8c3a68776..b4833d96c407e 100644 --- a/x-pack/test/fleet_api_integration/config.ts +++ b/x-pack/test/fleet_api_integration/config.ts @@ -15,7 +15,7 @@ import { defineDockerServersConfig } from '@kbn/test'; // example: https://beats-ci.elastic.co/blue/organizations/jenkins/Ingest-manager%2Fpackage-storage/detail/snapshot/74/pipeline/257#step-302-log-1. // It should be updated any time there is a new Docker image published for the Snapshot Distribution of the Package Registry. export const dockerImage = - 'docker.elastic.co/package-registry/distribution:5314869e2f6bc01d37b8652f7bda89248950b3a4'; + 'docker.elastic.co/package-registry/distribution:99dadb957d76b704637150d34a7219345cc0aeef'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts'));