diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index d77fc780f4cc2..384c6bd80ec33 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -53,7 +53,7 @@ import './management'; import './dev_tools'; import 'ui/agg_response'; import 'ui/agg_types'; -import { showAppRedirectNotification } from 'ui/notify'; +import { showAppRedirectNotification } from '../../../../plugins/kibana_legacy/public'; import 'leaflet'; import { localApplicationService } from './local_application_service'; @@ -68,4 +68,6 @@ routes.otherwise({ redirectTo: `/${config.defaultAppId || 'discover'}`, }); -uiModules.get('kibana').run(showAppRedirectNotification); +uiModules + .get('kibana') + .run($location => showAppRedirectNotification($location, npSetup.core.notifications.toasts)); diff --git a/src/legacy/core_plugins/telemetry/server/collection_manager.ts b/src/legacy/core_plugins/telemetry/server/collection_manager.ts index 0394dea343adf..715ca56e290a2 100644 --- a/src/legacy/core_plugins/telemetry/server/collection_manager.ts +++ b/src/legacy/core_plugins/telemetry/server/collection_manager.ts @@ -20,6 +20,7 @@ import { encryptTelemetry } from './collectors'; import { CallCluster } from '../../elasticsearch'; import { UsageCollectionSetup } from '../../../../plugins/usage_collection/server'; +import { ESLicense } from './telemetry_collection/get_local_license'; export type EncryptedStatsGetterConfig = { unencrypted: false } & { server: any; @@ -45,22 +46,38 @@ export interface StatsCollectionConfig { end: string | number; } +export interface BasicStatsPayload { + timestamp: string; + cluster_uuid: string; + cluster_name: string; + version: string; + cluster_stats: object; + collection?: string; + stack_stats: object; +} + export type StatsGetterConfig = UnencryptedStatsGetterConfig | EncryptedStatsGetterConfig; export type ClusterDetailsGetter = (config: StatsCollectionConfig) => Promise; -export type StatsGetter = ( +export type StatsGetter = ( + clustersDetails: ClusterDetails[], + config: StatsCollectionConfig +) => Promise; +export type LicenseGetter = ( clustersDetails: ClusterDetails[], config: StatsCollectionConfig -) => Promise; +) => Promise<{ [clusterUuid: string]: ESLicense | undefined }>; -interface CollectionConfig { +interface CollectionConfig { title: string; priority: number; esCluster: string; - statsGetter: StatsGetter; + statsGetter: StatsGetter; clusterDetailsGetter: ClusterDetailsGetter; + licenseGetter: LicenseGetter; } interface Collection { statsGetter: StatsGetter; + licenseGetter: LicenseGetter; clusterDetailsGetter: ClusterDetailsGetter; esCluster: string; title: string; @@ -70,8 +87,15 @@ export class TelemetryCollectionManager { private usageGetterMethodPriority = -1; private collections: Collection[] = []; - public setCollection = (collectionConfig: CollectionConfig) => { - const { title, priority, esCluster, statsGetter, clusterDetailsGetter } = collectionConfig; + public setCollection = (collectionConfig: CollectionConfig) => { + const { + title, + priority, + esCluster, + statsGetter, + clusterDetailsGetter, + licenseGetter, + } = collectionConfig; if (typeof priority !== 'number') { throw new Error('priority must be set.'); @@ -88,10 +112,14 @@ export class TelemetryCollectionManager { throw Error('esCluster name must be set for the getCluster method.'); } if (!clusterDetailsGetter) { - throw Error('Cluser UUIds method is not set.'); + throw Error('Cluster UUIds method is not set.'); + } + if (!licenseGetter) { + throw Error('License getter method not set.'); } this.collections.unshift({ + licenseGetter, statsGetter, clusterDetailsGetter, esCluster, @@ -141,7 +169,19 @@ export class TelemetryCollectionManager { return; } - return await collection.statsGetter(clustersDetails, statsCollectionConfig); + const [stats, licenses] = await Promise.all([ + collection.statsGetter(clustersDetails, statsCollectionConfig), + collection.licenseGetter(clustersDetails, statsCollectionConfig), + ]); + + return stats.map(stat => { + const license = licenses[stat.cluster_uuid]; + return { + ...(license ? { license } : {}), + ...stat, + collectionSource: collection.title, + }; + }); }; public getOptInStats = async (optInStatus: boolean, config: StatsGetterConfig) => { diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_info.js b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_info.ts similarity index 61% rename from src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_info.js rename to src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_info.ts index 2e4ed0b36ed26..67812457ed4ec 100644 --- a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_info.js +++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_cluster_info.ts @@ -17,14 +17,32 @@ * under the License. */ +import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; + +// This can be removed when the ES client improves the types +export interface ESClusterInfo { + cluster_uuid: string; + cluster_name: string; + version: { + number: string; + build_flavor: string; + build_type: string; + build_hash: string; + build_date: string; + build_snapshot?: boolean; + lucene_version: string; + minimum_wire_compatibility_version: string; + minimum_index_compatibility_version: string; + }; +} + /** * Get the cluster info from the connected cluster. * * This is the equivalent to GET / * * @param {function} callCluster The callWithInternalUser handler (exposed for testing) - * @return {Promise} The response from Elasticsearch. */ -export function getClusterInfo(callCluster) { - return callCluster('info'); +export function getClusterInfo(callCluster: CallCluster) { + return callCluster('info'); } diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_license.ts b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_license.ts new file mode 100644 index 0000000000000..589392ffb6095 --- /dev/null +++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_license.ts @@ -0,0 +1,90 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; +import { LicenseGetter } from '../collection_manager'; + +// From https://www.elastic.co/guide/en/elasticsearch/reference/current/get-license.html +export interface ESLicense { + status: string; + uid: string; + type: string; + issue_date: string; + issue_date_in_millis: number; + expiry_date: string; + expirty_date_in_millis: number; + max_nodes: number; + issued_to: string; + issuer: string; + start_date_in_millis: number; +} +let cachedLicense: ESLicense | undefined; + +function fetchLicense(callCluster: CallCluster, local: boolean) { + return callCluster<{ license: ESLicense }>('transport.request', { + method: 'GET', + path: '/_license', + query: { + local, + // For versions >= 7.6 and < 8.0, this flag is needed otherwise 'platinum' is returned for 'enterprise' license. + accept_enterprise: 'true', + }, + }); +} + +/** + * Get the cluster's license from the connected node. + * + * This is the equivalent of GET /_license?local=true . + * + * Like any X-Pack related API, X-Pack must installed for this to work. + */ +async function getLicenseFromLocalOrMaster(callCluster: CallCluster) { + // Fetching the local license is cheaper than getting it from the master and good enough + const { license } = await fetchLicense(callCluster, true).catch(async err => { + if (cachedLicense) { + try { + // Fallback to the master node's license info + const response = await fetchLicense(callCluster, false); + return response; + } catch (masterError) { + if (masterError.statusCode === 404) { + // If the master node does not have a license, we can assume there is no license + cachedLicense = undefined; + } else { + // Any other errors from the master node, throw and do not send any telemetry + throw err; + } + } + } + return { license: void 0 }; + }); + + if (license) { + cachedLicense = license; + } + return license; +} + +export const getLocalLicense: LicenseGetter = async (clustersDetails, { callCluster }) => { + const license = await getLicenseFromLocalOrMaster(callCluster); + + // It should be called only with 1 cluster element in the clustersDetails array, but doing reduce just in case. + return clustersDetails.reduce((acc, { clusterUuid }) => ({ ...acc, [clusterUuid]: license }), {}); +}; diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_stats.ts b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_stats.ts index 8adb6d237bee8..d99710deb1cbc 100644 --- a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_stats.ts +++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_stats.ts @@ -17,11 +17,8 @@ * under the License. */ -import { get, omit } from 'lodash'; -// @ts-ignore -import { getClusterInfo } from './get_cluster_info'; +import { getClusterInfo, ESClusterInfo } from './get_cluster_info'; import { getClusterStats } from './get_cluster_stats'; -// @ts-ignore import { getKibana, handleKibanaStats, KibanaUsageStats } from './get_kibana'; import { StatsGetter } from '../collection_manager'; @@ -33,20 +30,19 @@ import { StatsGetter } from '../collection_manager'; * @param {Object} clusterInfo Cluster info (GET /) * @param {Object} clusterStats Cluster stats (GET /_cluster/stats) * @param {Object} kibana The Kibana Usage stats - * @return {Object} A combined object containing the different responses. */ export function handleLocalStats( server: any, - clusterInfo: any, - clusterStats: any, + { cluster_name, cluster_uuid, version }: ESClusterInfo, + { _nodes, cluster_name: clusterName, ...clusterStats }: any, kibana: KibanaUsageStats ) { return { timestamp: new Date().toISOString(), - cluster_uuid: get(clusterInfo, 'cluster_uuid'), - cluster_name: get(clusterInfo, 'cluster_name'), - version: get(clusterInfo, 'version.number'), - cluster_stats: omit(clusterStats, '_nodes', 'cluster_name'), + cluster_uuid, + cluster_name, + version: version.number, + cluster_stats: clusterStats, collection: 'local', stack_stats: { kibana: handleKibanaStats(server, kibana), @@ -54,14 +50,12 @@ export function handleLocalStats( }; } +export type TelemetryLocalStats = ReturnType; + /** * Get statistics for all products joined by Elasticsearch cluster. - * - * @param {Object} server The Kibana server instance used to call ES as the internal user - * @param {function} callCluster The callWithInternalUser handler (exposed for testing) - * @return {Promise} The object containing the current Elasticsearch cluster's telemetry. */ -export const getLocalStats: StatsGetter = async (clustersDetails, config) => { +export const getLocalStats: StatsGetter = async (clustersDetails, config) => { const { server, callCluster, usageCollection } = config; return await Promise.all( diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/index.ts b/src/legacy/core_plugins/telemetry/server/telemetry_collection/index.ts index 7f228dbc5e6f6..9ac94216c21bc 100644 --- a/src/legacy/core_plugins/telemetry/server/telemetry_collection/index.ts +++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/index.ts @@ -17,7 +17,6 @@ * under the License. */ -// @ts-ignore export { getLocalStats } from './get_local_stats'; export { getClusterUuids } from './get_cluster_stats'; export { registerCollection } from './register_collection'; diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/register_collection.ts b/src/legacy/core_plugins/telemetry/server/telemetry_collection/register_collection.ts index faf8e9de79194..6580b47dba08e 100644 --- a/src/legacy/core_plugins/telemetry/server/telemetry_collection/register_collection.ts +++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/register_collection.ts @@ -39,6 +39,7 @@ import { telemetryCollectionManager } from '../collection_manager'; import { getLocalStats } from './get_local_stats'; import { getClusterUuids } from './get_cluster_stats'; +import { getLocalLicense } from './get_local_license'; export function registerCollection() { telemetryCollectionManager.setCollection({ @@ -47,5 +48,6 @@ export function registerCollection() { priority: 0, statsGetter: getLocalStats, clusterDetailsGetter: getClusterUuids, + licenseGetter: getLocalLicense, }); } diff --git a/src/legacy/ui/public/notify/index.js b/src/legacy/ui/public/notify/index.js index f7526f3b8f8fd..7ec6a394d7e88 100644 --- a/src/legacy/ui/public/notify/index.js +++ b/src/legacy/ui/public/notify/index.js @@ -19,5 +19,4 @@ export { fatalError, addFatalErrorCallback } from './fatal_error'; export { toastNotifications } from './toasts'; -export { addAppRedirectMessageToUrl, showAppRedirectNotification } from './app_redirect'; export { banners } from './banners'; diff --git a/src/legacy/ui/public/notify/app_redirect/app_redirect.test.js b/src/plugins/kibana_legacy/public/notify/app_redirect/app_redirect.test.ts similarity index 75% rename from src/legacy/ui/public/notify/app_redirect/app_redirect.test.js rename to src/plugins/kibana_legacy/public/notify/app_redirect/app_redirect.test.ts index a23aabe6ad88e..efb1393ff0b16 100644 --- a/src/legacy/ui/public/notify/app_redirect/app_redirect.test.js +++ b/src/plugins/kibana_legacy/public/notify/app_redirect/app_redirect.test.ts @@ -17,17 +17,12 @@ * under the License. */ +import { ILocationService } from 'angular'; +import { ToastsStart } from '../../../../../core/public'; import { addAppRedirectMessageToUrl, showAppRedirectNotification } from './app_redirect'; let isToastAdded = false; - -jest.mock('../toasts', () => ({ - toastNotifications: { - addDanger: () => { - isToastAdded = true; - }, - }, -})); +const toasts: ToastsStart = {} as ToastsStart; describe('addAppRedirectMessageToUrl', () => { test('adds a message to the URL', () => { @@ -39,20 +34,29 @@ describe('addAppRedirectMessageToUrl', () => { describe('showAppRedirectNotification', () => { beforeEach(() => { isToastAdded = false; + toasts.addDanger = (): any => { + isToastAdded = true; + }; }); test(`adds a toast when there's a message in the URL`, () => { - showAppRedirectNotification({ - search: () => ({ app_redirect_message: 'redirect message' }), - }); + showAppRedirectNotification( + { + search: () => ({ app_redirect_message: 'redirect message' }), + } as ILocationService, + toasts + ); expect(isToastAdded).toBe(true); }); test(`doesn't add a toast when there's no message in the URL`, () => { - showAppRedirectNotification({ - search: () => ({ app_redirect_message: '' }), - }); + showAppRedirectNotification( + { + search: () => ({ app_redirect_message: '' }), + } as ILocationService, + toasts + ); expect(isToastAdded).toBe(false); }); diff --git a/src/legacy/ui/public/notify/app_redirect/app_redirect.js b/src/plugins/kibana_legacy/public/notify/app_redirect/app_redirect.ts similarity index 82% rename from src/legacy/ui/public/notify/app_redirect/app_redirect.js rename to src/plugins/kibana_legacy/public/notify/app_redirect/app_redirect.ts index a92e5401e5e75..e79ab4b2fbc6d 100644 --- a/src/legacy/ui/public/notify/app_redirect/app_redirect.js +++ b/src/plugins/kibana_legacy/public/notify/app_redirect/app_redirect.ts @@ -17,12 +17,13 @@ * under the License. */ +import { ILocationService } from 'angular'; import { modifyUrl } from '../../../../../core/utils'; -import { toastNotifications } from '../toasts'; +import { ToastsStart } from '../../../../../core/public'; const APP_REDIRECT_MESSAGE_PARAM = 'app_redirect_message'; -export function addAppRedirectMessageToUrl(url, message) { +export function addAppRedirectMessageToUrl(url: string, message: string) { return modifyUrl(url, urlParts => { urlParts.hash = modifyUrl(urlParts.hash || '', hashParts => { hashParts.query[APP_REDIRECT_MESSAGE_PARAM] = message; @@ -32,7 +33,7 @@ export function addAppRedirectMessageToUrl(url, message) { // If an app needs to redirect, e.g. due to an expired license, it can surface a message via // the URL query params. -export function showAppRedirectNotification($location) { +export function showAppRedirectNotification($location: ILocationService, toasts: ToastsStart) { const queryString = $location.search(); if (!queryString[APP_REDIRECT_MESSAGE_PARAM]) { @@ -42,5 +43,5 @@ export function showAppRedirectNotification($location) { const message = queryString[APP_REDIRECT_MESSAGE_PARAM]; $location.search(APP_REDIRECT_MESSAGE_PARAM, null); - toastNotifications.addDanger(message); + toasts.addDanger(message); } diff --git a/src/legacy/ui/public/notify/app_redirect/index.js b/src/plugins/kibana_legacy/public/notify/app_redirect/index.ts similarity index 100% rename from src/legacy/ui/public/notify/app_redirect/index.js rename to src/plugins/kibana_legacy/public/notify/app_redirect/index.ts diff --git a/src/plugins/kibana_legacy/public/notify/index.ts b/src/plugins/kibana_legacy/public/notify/index.ts index 6aa4e36ab7227..b6f29876c2737 100644 --- a/src/plugins/kibana_legacy/public/notify/index.ts +++ b/src/plugins/kibana_legacy/public/notify/index.ts @@ -18,3 +18,4 @@ */ export * from './toasts'; export * from './lib'; +export { addAppRedirectMessageToUrl, showAppRedirectNotification } from './app_redirect'; diff --git a/src/plugins/telemetry/public/services/telemetry_service.test.ts b/src/plugins/telemetry/public/services/telemetry_service.test.ts index 0ebcd52f1423c..0a49b0ae3084e 100644 --- a/src/plugins/telemetry/public/services/telemetry_service.test.ts +++ b/src/plugins/telemetry/public/services/telemetry_service.test.ts @@ -26,9 +26,18 @@ const mockSubtract = jest.fn().mockImplementation(() => { }; }); +const mockClone = jest.fn().mockImplementation(() => { + return { + clone: mockClone, + subtract: mockSubtract, + toISOString: jest.fn(), + }; +}); + jest.mock('moment', () => { return jest.fn().mockImplementation(() => { return { + clone: mockClone, subtract: mockSubtract, toISOString: jest.fn(), }; @@ -43,6 +52,7 @@ describe('TelemetryService', () => { expect(telemetryService['http'].post).toBeCalledWith('/api/telemetry/v2/clusters/_stats', { body: JSON.stringify({ unencrypted: false, timeRange: {} }), }); + expect(mockClone).toBeCalled(); expect(mockSubtract).toBeCalledWith(20, 'minutes'); }); }); diff --git a/src/plugins/telemetry/public/services/telemetry_service.ts b/src/plugins/telemetry/public/services/telemetry_service.ts index 073886e7d1327..cb91451bd8ef4 100644 --- a/src/plugins/telemetry/public/services/telemetry_service.ts +++ b/src/plugins/telemetry/public/services/telemetry_service.ts @@ -92,7 +92,10 @@ export class TelemetryService { body: JSON.stringify({ unencrypted, timeRange: { - min: now.subtract(20, 'minutes').toISOString(), + min: now + .clone() // Need to clone it to avoid mutation (and max being the same value) + .subtract(20, 'minutes') + .toISOString(), max: now.toISOString(), }, }), diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js index 8ca023aa90cf1..e0e49fe59daf4 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -37,7 +37,7 @@ import 'plugins/kibana/dashboard/legacy'; import { npStart } from 'ui/new_platform'; import { localApplicationService } from 'plugins/kibana/local_application_service'; -import { showAppRedirectNotification } from 'ui/notify'; +import { showAppRedirectNotification } from '../../../../../src/plugins/kibana_legacy/public'; import { DashboardConstants, createDashboardEditUrl } from 'plugins/kibana/dashboard'; npStart.plugins.kibanaLegacy.dashboardConfig.turnHideWriteControlsOn(); @@ -51,7 +51,9 @@ chrome.setRootController('kibana', function() { npStart.core.chrome.navLinks.showOnly('kibana:dashboard'); }); -uiModules.get('kibana').run(showAppRedirectNotification); +uiModules + .get('kibana') + .run($location => showAppRedirectNotification($location, npStart.core.notifications.toasts)); /** * If there is a configured `kibana.defaultAppId`, and it is a dashboard ID, we'll diff --git a/x-pack/legacy/plugins/graph/public/application.ts b/x-pack/legacy/plugins/graph/public/application.ts index 80a797b7f0724..7bd18f841b478 100644 --- a/x-pack/legacy/plugins/graph/public/application.ts +++ b/x-pack/legacy/plugins/graph/public/application.ts @@ -24,7 +24,6 @@ import { configureAppAngularModule, createTopNavDirective, createTopNavHelper, - addAppRedirectMessageToUrl, } from './legacy_imports'; // @ts-ignore import { initGraphApp } from './app'; @@ -37,6 +36,7 @@ import { checkLicense } from '../../../../plugins/graph/common/check_license'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../../src/plugins/navigation/public'; import { createSavedWorkspacesLoader } from './services/persistence/saved_workspace_loader'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; +import { addAppRedirectMessageToUrl } from '../../../../../src/plugins/kibana_legacy/public'; /** * These are dependencies of the Graph app besides the base dependencies diff --git a/x-pack/legacy/plugins/graph/public/legacy_imports.ts b/x-pack/legacy/plugins/graph/public/legacy_imports.ts index ac518d34551db..84fafdb580abe 100644 --- a/x-pack/legacy/plugins/graph/public/legacy_imports.ts +++ b/x-pack/legacy/plugins/graph/public/legacy_imports.ts @@ -8,6 +8,4 @@ import 'ace'; // @ts-ignore export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; -// @ts-ignore -export { addAppRedirectMessageToUrl } from 'ui/notify'; export { configureAppAngularModule } from '../../../../../src/plugins/kibana_legacy/public'; diff --git a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts index 470642f9dd8a3..dcc7924fe171a 100644 --- a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts @@ -5,7 +5,7 @@ */ import sinon from 'sinon'; -import { addStackStats, getAllStats, handleAllStats } from './get_all_stats'; +import { getStackStats, getAllStats, handleAllStats } from './get_all_stats'; import { ESClusterStats } from './get_es_stats'; import { KibanaStats } from './get_kibana_stats'; import { ClustersHighLevelStats } from './get_high_level_stats'; @@ -223,7 +223,8 @@ describe('get_all_stats', () => { beats: {}, }); - expect(clusters).toStrictEqual(expectedClusters); + const [a, b, c] = expectedClusters; + expect(clusters).toStrictEqual([a, b, { ...c, stack_stats: {} }]); }); it('handles no clusters response', () => { @@ -233,9 +234,8 @@ describe('get_all_stats', () => { }); }); - describe('addStackStats', () => { + describe('getStackStats', () => { it('searches for clusters', () => { - const cluster = { cluster_uuid: 'a' }; const stats = { a: { count: 2, @@ -250,9 +250,7 @@ describe('get_all_stats', () => { }, }; - addStackStats(cluster as ESClusterStats, stats, 'xyz'); - - expect((cluster as any).stack_stats.xyz).toStrictEqual(stats.a); + expect(getStackStats('a', stats, 'xyz')).toStrictEqual({ xyz: stats.a }); }); }); }); diff --git a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_all_stats.ts b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_all_stats.ts index aa5e937387daf..a6ed5254dabd5 100644 --- a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_all_stats.ts +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_all_stats.ts @@ -61,38 +61,31 @@ export function handleAllStats( } ) { return clusters.map(cluster => { - // if they are using Kibana or Logstash, then add it to the cluster details under cluster.stack_stats - addStackStats(cluster, kibana, KIBANA_SYSTEM_ID); - addStackStats(cluster, logstash, LOGSTASH_SYSTEM_ID); - addStackStats(cluster, beats, BEATS_SYSTEM_ID); - mergeXPackStats(cluster, kibana, 'graph_workspace', 'graph'); // copy graph_workspace info out of kibana, merge it into stack_stats.xpack.graph + const stats = { + ...cluster, + stack_stats: { + ...cluster.stack_stats, + // if they are using Kibana or Logstash, then add it to the cluster details under cluster.stack_stats + ...getStackStats(cluster.cluster_uuid, kibana, KIBANA_SYSTEM_ID), + ...getStackStats(cluster.cluster_uuid, logstash, LOGSTASH_SYSTEM_ID), + ...getStackStats(cluster.cluster_uuid, beats, BEATS_SYSTEM_ID), + }, + }; - return cluster; + mergeXPackStats(stats, kibana, 'graph_workspace', 'graph'); // copy graph_workspace info out of kibana, merge it into stack_stats.xpack.graph + + return stats; }); } -/** - * Add product data to the {@code cluster}, only if it exists for the current {@code cluster}. - * - * @param {Object} cluster The current Elasticsearch cluster stats - * @param {Object} allProductStats Product stats, keyed by Cluster UUID - * @param {String} product The product name being added (e.g., 'kibana' or 'logstash') - */ -export function addStackStats( - cluster: ESClusterStats & { stack_stats?: { [product: string]: K } }, +export function getStackStats( + clusterUuid: string, allProductStats: T, product: string ) { - const productStats = allProductStats[cluster.cluster_uuid]; - + const productStats = allProductStats[clusterUuid]; // Don't add it if they're not using (or configured to report stats) this product for this cluster - if (productStats) { - if (!cluster.stack_stats) { - cluster.stack_stats = {}; - } - - cluster.stack_stats[product] = productStats; - } + return productStats ? { [product]: productStats } : {}; } export function mergeXPackStats( diff --git a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_es_stats.ts b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_es_stats.ts index f0ae1163d3f52..2f2fffd3f0823 100644 --- a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_es_stats.ts +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_es_stats.ts @@ -48,11 +48,6 @@ export function fetchElasticsearchStats( 'hits.hits._source.timestamp', 'hits.hits._source.cluster_name', 'hits.hits._source.version', - 'hits.hits._source.license.status', // license data only includes necessary fields to drive UI - 'hits.hits._source.license.type', - 'hits.hits._source.license.issue_date', - 'hits.hits._source.license.expiry_date', - 'hits.hits._source.license.expiry_date_in_millis', 'hits.hits._source.cluster_stats', 'hits.hits._source.stack_stats', ], @@ -79,7 +74,11 @@ export function fetchElasticsearchStats( export interface ESClusterStats { cluster_uuid: string; - type: 'cluster_stats'; + cluster_name: string; + timestamp: string; + version: string; + cluster_stats: object; + stack_stats?: object; } /** diff --git a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_licenses.test.ts b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_licenses.test.ts new file mode 100644 index 0000000000000..bb8326ce0b63a --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_licenses.test.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import sinon from 'sinon'; +import { getLicenses, handleLicenses, fetchLicenses } from './get_licenses'; + +describe('get_licenses', () => { + const callWith = sinon.stub(); + const size = 123; + const server = { + config: sinon.stub().returns({ + get: sinon + .stub() + .withArgs('xpack.monitoring.elasticsearch.index_pattern') + .returns('.monitoring-es-N-*') + .withArgs('xpack.monitoring.max_bucket_size') + .returns(size), + }), + }; + const response = { + hits: { + hits: [ + { _id: 'abc', _source: { cluster_uuid: 'abc', license: { type: 'basic' } } }, + { _id: 'xyz', _source: { cluster_uuid: 'xyz', license: { type: 'basic' } } }, + { _id: '123', _source: { cluster_uuid: '123' } }, + ], + }, + }; + const expectedClusters = response.hits.hits.map(hit => hit._source); + const clusterUuids = expectedClusters.map(cluster => ({ clusterUuid: cluster.cluster_uuid })); + const expectedLicenses = { + abc: { type: 'basic' }, + xyz: { type: 'basic' }, + '123': void 0, + }; + + describe('getLicenses', () => { + it('returns clusters', async () => { + callWith.withArgs('search').returns(Promise.resolve(response)); + + expect( + await getLicenses(clusterUuids, { server, callCluster: callWith } as any) + ).toStrictEqual(expectedLicenses); + }); + }); + + describe('fetchLicenses', () => { + it('searches for clusters', async () => { + callWith.returns(response); + + expect( + await fetchLicenses( + server, + callWith, + clusterUuids.map(({ clusterUuid }) => clusterUuid) + ) + ).toStrictEqual(response); + }); + }); + + describe('handleLicenses', () => { + // filterPath makes it easy to ignore anything unexpected because it will come back empty + it('handles unexpected response', () => { + const clusters = handleLicenses({} as any); + + expect(clusters).toStrictEqual({}); + }); + + it('handles valid response', () => { + const clusters = handleLicenses(response as any); + + expect(clusters).toStrictEqual(expectedLicenses); + }); + + it('handles no hits response', () => { + const clusters = handleLicenses({ hits: { hits: [] } } as any); + + expect(clusters).toStrictEqual({}); + }); + }); +}); diff --git a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_licenses.ts b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_licenses.ts new file mode 100644 index 0000000000000..7364227e7dc92 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_licenses.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + StatsCollectionConfig, + LicenseGetter, +} from 'src/legacy/core_plugins/telemetry/server/collection_manager'; +import { SearchResponse } from 'elasticsearch'; +import { ESLicense } from 'src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_license'; +import { INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants'; + +/** + * Get statistics for all selected Elasticsearch clusters. + */ +export const getLicenses: LicenseGetter = async (clustersDetails, { server, callCluster }) => { + const clusterUuids = clustersDetails.map(({ clusterUuid }) => clusterUuid); + const response = await fetchLicenses(server, callCluster, clusterUuids); + return handleLicenses(response); +}; + +/** + * Fetch the Elasticsearch stats. + * + * @param {Object} server The server instance + * @param {function} callCluster The callWithRequest or callWithInternalUser handler + * @param {Array} clusterUuids Cluster UUIDs to limit the request against + * + * Returns the response for the aggregations to fetch details for the product. + */ +export function fetchLicenses( + server: StatsCollectionConfig['server'], + callCluster: StatsCollectionConfig['callCluster'], + clusterUuids: string[] +) { + const config = server.config(); + const params = { + index: INDEX_PATTERN_ELASTICSEARCH, + size: config.get('monitoring.ui.max_bucket_size'), + ignoreUnavailable: true, + filterPath: ['hits.hits._source.cluster_uuid', 'hits.hits._source.license'], + body: { + query: { + bool: { + filter: [ + /* + * Note: Unlike most places, we don't care about the old _type: cluster_stats because it would NOT + * have the license in it (that used to be in the .monitoring-data-2 index in cluster_info) + */ + { term: { type: 'cluster_stats' } }, + { terms: { cluster_uuid: clusterUuids } }, + ], + }, + }, + collapse: { field: 'cluster_uuid' }, + sort: { timestamp: { order: 'desc' } }, + }, + }; + + return callCluster('search', params); +} + +export interface ESClusterStatsWithLicense { + cluster_uuid: string; + type: 'cluster_stats'; + license?: ESLicense; +} + +/** + * Extract the cluster stats for each cluster. + */ +export function handleLicenses(response: SearchResponse) { + const clusters = response.hits?.hits || []; + + return clusters.reduce( + (acc, { _source }) => ({ + ...acc, + [_source.cluster_uuid]: _source.license, + }), + {} + ); +} diff --git a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/register_monitoring_collection.ts b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/register_monitoring_collection.ts index f0fda5229cb5c..0b14eb05f796f 100644 --- a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/register_monitoring_collection.ts +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/register_monitoring_collection.ts @@ -7,6 +7,7 @@ import { telemetryCollectionManager } from '../../../../../../src/legacy/core_plugins/telemetry/server'; import { getAllStats } from './get_all_stats'; import { getClusterUuids } from './get_cluster_uuids'; +import { getLicenses } from './get_licenses'; export function registerMonitoringCollection() { telemetryCollectionManager.setCollection({ @@ -15,5 +16,6 @@ export function registerMonitoringCollection() { priority: 2, statsGetter: getAllStats, clusterDetailsGetter: getClusterUuids, + licenseGetter: getLicenses, }); } diff --git a/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/__snapshots__/get_stats_with_xpack.test.ts.snap b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/__snapshots__/get_stats_with_xpack.test.ts.snap new file mode 100644 index 0000000000000..1a70504dc9391 --- /dev/null +++ b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/__snapshots__/get_stats_with_xpack.test.ts.snap @@ -0,0 +1,118 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Telemetry Collection: Get Aggregated Stats OSS-like telemetry (no license nor X-Pack telemetry) 1`] = ` +Array [ + Object { + "cluster_name": "test", + "cluster_stats": Object {}, + "cluster_uuid": "test", + "collection": "local", + "stack_stats": Object { + "kibana": Object { + "count": 1, + "great": "googlymoogly", + "indices": 1, + "os": Object { + "platformReleases": Array [ + Object { + "count": 1, + "platformRelease": "iv", + }, + ], + "platforms": Array [ + Object { + "count": 1, + "platform": "rocky", + }, + ], + }, + "plugins": Object { + "clouds": Object { + "chances": 95, + }, + "localization": Object { + "integrities": Object {}, + "labelsCount": 0, + "locale": "en", + }, + "rain": Object { + "chances": 2, + }, + "snow": Object { + "chances": 0, + }, + "sun": Object { + "chances": 5, + }, + }, + "versions": Array [ + Object { + "count": 1, + "version": "8675309", + }, + ], + }, + }, + "version": "8.0.0", + }, +] +`; + +exports[`Telemetry Collection: Get Aggregated Stats X-Pack telemetry (license + X-Pack) 1`] = ` +Array [ + Object { + "cluster_name": "test", + "cluster_stats": Object {}, + "cluster_uuid": "test", + "collection": "local", + "stack_stats": Object { + "kibana": Object { + "count": 1, + "great": "googlymoogly", + "indices": 1, + "os": Object { + "platformReleases": Array [ + Object { + "count": 1, + "platformRelease": "iv", + }, + ], + "platforms": Array [ + Object { + "count": 1, + "platform": "rocky", + }, + ], + }, + "plugins": Object { + "clouds": Object { + "chances": 95, + }, + "localization": Object { + "integrities": Object {}, + "labelsCount": 0, + "locale": "en", + }, + "rain": Object { + "chances": 2, + }, + "snow": Object { + "chances": 0, + }, + "sun": Object { + "chances": 5, + }, + }, + "versions": Array [ + Object { + "count": 1, + "version": "8675309", + }, + ], + }, + "xpack": Object {}, + }, + "version": "8.0.0", + }, +] +`; diff --git a/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/__tests__/get_xpack.js b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/__tests__/get_xpack.js index eca130b4d7465..eb03701fd195b 100644 --- a/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/__tests__/get_xpack.js +++ b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/__tests__/get_xpack.js @@ -8,42 +8,7 @@ import expect from '@kbn/expect'; import sinon from 'sinon'; import { TIMEOUT } from '../constants'; -import { getXPackLicense, getXPackUsage, getXPack, handleXPack } from '../get_xpack'; - -function mockGetXPackLicense(callCluster, license, req) { - callCluster - .withArgs(req, 'transport.request', { - method: 'GET', - path: '/_license', - query: { - local: 'true', - accept_enterprise: 'true', - }, - }) - .returns( - license.then( - response => ({ license: response }), - () => {} // Catch error so that we don't emit UnhandledPromiseRejectionWarning for tests with invalid license - ) - ); - - callCluster - .withArgs('transport.request', { - method: 'GET', - path: '/_license', - query: { - local: 'true', - accept_enterprise: 'true', - }, - }) - // conveniently wraps the passed in license object as { license: response }, like it really is - .returns( - license.then( - response => ({ license: response }), - () => {} // Catch error so that we don't emit UnhandledPromiseRejectionWarning for tests with invalid license - ) - ); -} +import { getXPackUsage } from '../get_xpack'; function mockGetXPackUsage(callCluster, usage, req) { callCluster @@ -67,31 +32,7 @@ function mockGetXPackUsage(callCluster, usage, req) { .returns(usage); } -/** - * Mock getXPack responses. - * - * @param {Function} callCluster Sinon function mock. - * @param {Promise} license Promised license response. - * @param {Promise} usage Promised usage response. - * @param {Object} usage reqeust object. - */ -export function mockGetXPack(callCluster, license, usage, req) { - mockGetXPackLicense(callCluster, license, req); - mockGetXPackUsage(callCluster, usage, req); -} - describe('get_xpack', () => { - describe('getXPackLicense', () => { - it('uses callCluster to get /_license API', async () => { - const response = { type: 'basic' }; - const callCluster = sinon.stub(); - - mockGetXPackLicense(callCluster, Promise.resolve(response)); - - expect(await getXPackLicense(callCluster)).to.eql(response); - }); - }); - describe('getXPackUsage', () => { it('uses callCluster to get /_xpack/usage API', () => { const response = Promise.resolve({}); @@ -102,48 +43,4 @@ describe('get_xpack', () => { expect(getXPackUsage(callCluster)).to.be(response); }); }); - - describe('handleXPack', () => { - it('uses data as expected', () => { - const license = { fake: 'data' }; - const usage = { also: 'fake', nested: { object: { data: [{ field: 1 }, { field: 2 }] } } }; - - expect(handleXPack(license, usage)).to.eql({ license, stack_stats: { xpack: usage } }); - }); - }); - - describe('getXPack', () => { - it('returns the formatted response object', async () => { - const license = { fancy: 'license' }; - const xpack = { also: 'fancy' }; - - const callCluster = sinon.stub(); - - mockGetXPack(callCluster, Promise.resolve(license), Promise.resolve(xpack)); - - const data = await getXPack(callCluster); - - expect(data).to.eql({ license, xpack }); - }); - - it('returns empty object upon license failure', async () => { - const callCluster = sinon.stub(); - - mockGetXPack(callCluster, Promise.reject(new Error()), Promise.resolve({ also: 'fancy' })); - - const data = await getXPack(callCluster); - - expect(data).to.eql({}); - }); - - it('returns empty object upon usage failure', async () => { - const callCluster = sinon.stub(); - - mockGetXPack(callCluster, Promise.resolve({ fancy: 'license' }), Promise.reject(new Error())); - - const data = await getXPack(callCluster); - - expect(data).to.eql({}); - }); - }); }); diff --git a/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/constants.ts b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/constants.ts index c89fbe416a0cc..b6f1aabab95c4 100644 --- a/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/constants.ts +++ b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/constants.ts @@ -7,4 +7,5 @@ /** * The timeout used by each request, whenever a timeout can be specified. */ + export const TIMEOUT = '30s'; diff --git a/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_stats_with_xpack.test.ts b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_stats_with_xpack.test.ts new file mode 100644 index 0000000000000..b85cbd9661022 --- /dev/null +++ b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_stats_with_xpack.test.ts @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getStatsWithXpack } from './get_stats_with_xpack'; + +const kibana = { + kibana: { + great: 'googlymoogly', + versions: [{ version: '8675309', count: 1 }], + }, + kibana_stats: { + os: { + platform: 'rocky', + platformRelease: 'iv', + }, + }, + localization: { + locale: 'en', + labelsCount: 0, + integrities: {}, + }, + sun: { chances: 5 }, + clouds: { chances: 95 }, + rain: { chances: 2 }, + snow: { chances: 0 }, +}; + +const getMockServer = (getCluster = jest.fn()) => ({ + log(tags: string[], message: string) { + // eslint-disable-next-line no-console + console.log({ tags, message }); + }, + config() { + return { + get(item: string) { + switch (item) { + case 'pkg.version': + return '8675309-snapshot'; + default: + throw Error(`unexpected config.get('${item}') received.`); + } + }, + }; + }, + plugins: { + elasticsearch: { getCluster }, + }, +}); + +const mockUsageCollection = (kibanaUsage = kibana) => ({ + bulkFetch: () => kibanaUsage, + toObject: (data: any) => data, +}); + +describe('Telemetry Collection: Get Aggregated Stats', () => { + test('OSS-like telemetry (no license nor X-Pack telemetry)', async () => { + const callCluster = jest.fn(async (method: string, options: { path?: string }) => { + switch (method) { + case 'transport.request': + if (options.path === '/_license' || options.path === '/_xpack/usage') { + // eslint-disable-next-line no-throw-literal + throw { statusCode: 404 }; + } + return {}; + case 'info': + return { cluster_uuid: 'test', cluster_name: 'test', version: { number: '8.0.0' } }; + default: + return {}; + } + }); + const usageCollection = mockUsageCollection(); + const server = getMockServer(); + + const stats = await getStatsWithXpack([{ clusterUuid: '1234' }], { + callCluster, + usageCollection, + server, + } as any); + expect(stats.map(({ timestamp, ...rest }) => rest)).toMatchSnapshot(); + }); + + test('X-Pack telemetry (license + X-Pack)', async () => { + const callCluster = jest.fn(async (method: string, options: { path?: string }) => { + switch (method) { + case 'transport.request': + if (options.path === '/_license') { + return { + license: { type: 'basic' }, + }; + } + if (options.path === '/_xpack/usage') { + return {}; + } + case 'info': + return { cluster_uuid: 'test', cluster_name: 'test', version: { number: '8.0.0' } }; + default: + return {}; + } + }); + const usageCollection = mockUsageCollection(); + const server = getMockServer(); + + const stats = await getStatsWithXpack([{ clusterUuid: '1234' }], { + callCluster, + usageCollection, + server, + } as any); + expect(stats.map(({ timestamp, ...rest }) => rest)).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_stats_with_xpack.ts b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_stats_with_xpack.ts index 41076d96231c9..ea7465f66f120 100644 --- a/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_stats_with_xpack.ts +++ b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_stats_with_xpack.ts @@ -4,19 +4,33 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore -import { getXPack } from './get_xpack'; -import { getLocalStats } from '../../../../../../src/legacy/core_plugins/telemetry/server/telemetry_collection'; import { StatsGetter } from '../../../../../../src/legacy/core_plugins/telemetry/server/collection_manager'; +import { + getLocalStats, + TelemetryLocalStats, +} from '../../../../../../src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_stats'; +import { getXPackUsage } from './get_xpack'; -export const getStatsWithXpack: StatsGetter = async function(clustersDetails, config) { +export type TelemetryAggregatedStats = TelemetryLocalStats & { + stack_stats: { xpack?: object }; +}; + +export const getStatsWithXpack: StatsGetter = async function( + clustersDetails, + config +) { const { callCluster } = config; const clustersLocalStats = await getLocalStats(clustersDetails, config); - const { license, xpack } = await getXPack(callCluster); + const xpack = await getXPackUsage(callCluster).catch(() => undefined); // We want to still report something (and do not lose the license) even when this method fails. return clustersLocalStats.map(localStats => { - localStats.license = license; - localStats.stack_stats.xpack = xpack; + if (xpack) { + return { + ...localStats, + stack_stats: { ...localStats.stack_stats, xpack }, + }; + } + return localStats; }); }; diff --git a/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_xpack.js b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_xpack.js deleted file mode 100644 index aaeb890981aa1..0000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_xpack.js +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { TIMEOUT } from './constants'; - -/** - * Get the cluster stats from the connected cluster. - * - * This is the equivalent of GET /_license?local=true . - * - * Like any X-Pack related API, X-Pack must installed for this to work. - * - * @param {function} callCluster The callWithInternalUser handler (exposed for testing) - * @return {Promise} The response from Elasticsearch. - */ -export function getXPackLicense(callCluster) { - return callCluster('transport.request', { - method: 'GET', - path: '/_license', - query: { - // Fetching the local license is cheaper than getting it from the master and good enough - local: 'true', - // For versions >= 7.6 and < 8.0, this flag is needed otherwise 'platinum' is returned for 'enterprise' license. - accept_enterprise: 'true', - }, - }).then(({ license }) => license); -} - -/** - * Get the cluster stats from the connected cluster. - * - * This is the equivalent of GET /_xpack/usage?master_timeout=${TIMEOUT} - * - * Like any X-Pack related API, X-Pack must installed for this to work. - * - * @param {function} callCluster The callWithInternalUser handler (exposed for testing) - * @return {Promise} The response from Elasticsearch equivalent to GET /_cluster/stats. - */ -export function getXPackUsage(callCluster) { - return callCluster('transport.request', { - method: 'GET', - path: '/_xpack/usage', - query: { - master_timeout: TIMEOUT, - }, - }); -} - -/** - * Combine the X-Pack responses into a single response as Monitoring does already. - * - * @param {Object} license The license returned from /_license - * @param {Object} usage The usage details returned from /_xpack/usage - * @return {Object} An object containing both the license and usage. - */ -export function handleXPack(license, usage) { - return { - license, - stack_stats: { - xpack: usage, - }, - }; -} - -/** - * Combine all X-Pack requests as a singular request that is ignored upon failure. - * - * @param {function} callCluster The callWithInternalUser handler (exposed for testing) - * @return {Promise} - */ -export function getXPack(callCluster) { - return Promise.all([getXPackLicense(callCluster), getXPackUsage(callCluster)]) - .then(([license, xpack]) => { - return { - license, - xpack, - }; - }) - .catch(() => { - return {}; - }); -} diff --git a/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_xpack.ts b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_xpack.ts new file mode 100644 index 0000000000000..9b69540007e5f --- /dev/null +++ b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/get_xpack.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; +import { TIMEOUT } from './constants'; + +/** + * Get the cluster stats from the connected cluster. + * + * This is the equivalent of GET /_xpack/usage?master_timeout=${TIMEOUT} + * + * Like any X-Pack related API, X-Pack must installed for this to work. + */ +export function getXPackUsage(callCluster: CallCluster) { + return callCluster('transport.request', { + method: 'GET', + path: '/_xpack/usage', + query: { + master_timeout: TIMEOUT, + }, + }); +} diff --git a/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/register_xpack_collection.ts b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/register_xpack_collection.ts index 57faf2da90d09..04445d7bde7d7 100644 --- a/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/register_xpack_collection.ts +++ b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/register_xpack_collection.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { getLocalLicense } from '../../../../../../src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_license'; import { telemetryCollectionManager } from '../../../../../../src/legacy/core_plugins/telemetry/server'; import { getClusterUuids } from '../../../../../../src/legacy/core_plugins/telemetry/server/telemetry_collection'; import { getStatsWithXpack } from './get_stats_with_xpack'; @@ -15,5 +16,6 @@ export function registerMonitoringCollection() { priority: 1, statsGetter: getStatsWithXpack, clusterDetailsGetter: getClusterUuids, + licenseGetter: getLocalLicense, }); } diff --git a/x-pack/test/api_integration/apis/telemetry/fixtures/basiccluster.json b/x-pack/test/api_integration/apis/telemetry/fixtures/basiccluster.json index 5c3c8cfcab7a6..a0097f53ac93b 100644 --- a/x-pack/test/api_integration/apis/telemetry/fixtures/basiccluster.json +++ b/x-pack/test/api_integration/apis/telemetry/fixtures/basiccluster.json @@ -1,6 +1,7 @@ [ { "cluster_name": "docker-cluster", + "collectionSource": "monitoring", "cluster_stats": { "indices": { "completion": { @@ -161,6 +162,12 @@ }, "cluster_uuid": "ooEYzl3fSL222Y6eVm7SAA", "license": { + "uid": "79dc3adb-e85e-4cef-a985-9b74eb6c07c1", + "issue_date_in_millis": 1532383643540, + "issued_to": "docker-cluster", + "issuer": "elasticsearch", + "max_nodes": 1000, + "start_date_in_millis": -1, "issue_date": "2018-07-23T22:07:23.540Z", "status": "active", "type": "basic" diff --git a/x-pack/test/api_integration/apis/telemetry/fixtures/multicluster.json b/x-pack/test/api_integration/apis/telemetry/fixtures/multicluster.json index 2e6a75ee75972..6cc9c55157b28 100644 --- a/x-pack/test/api_integration/apis/telemetry/fixtures/multicluster.json +++ b/x-pack/test/api_integration/apis/telemetry/fixtures/multicluster.json @@ -1 +1,1001 @@ -[{"cluster_uuid":"6d-9tDFTRe-qT5GoBytdlQ","timestamp":"2017-08-15T22:10:59.952Z","cluster_name":"clustertwo","version":"7.0.0-alpha1","license":{"status":"active","type":"basic","issue_date":"2014-09-29T00:00:00.000Z","expiry_date":"2030-08-29T23:59:59.999Z","expiry_date_in_millis":1914278399999},"cluster_stats":{"timestamp":1502835059952,"status":"green","indices":{"count":1,"shards":{"total":1,"primaries":1,"replication":0,"index":{"shards":{"min":1,"max":1,"avg":1},"primaries":{"min":1,"max":1,"avg":1},"replication":{"min":0,"max":0,"avg":0}}},"docs":{"count":8,"deleted":0},"store":{"size_in_bytes":34095},"fielddata":{"memory_size_in_bytes":0,"evictions":0},"query_cache":{"memory_size_in_bytes":0,"total_count":0,"hit_count":0,"miss_count":0,"cache_size":0,"cache_count":0,"evictions":0},"completion":{"size_in_bytes":0},"segments":{"count":8,"memory_in_bytes":13852,"terms_memory_in_bytes":10412,"stored_fields_memory_in_bytes":2496,"term_vectors_memory_in_bytes":0,"norms_memory_in_bytes":256,"points_memory_in_bytes":8,"doc_values_memory_in_bytes":680,"index_writer_memory_in_bytes":0,"version_map_memory_in_bytes":0,"fixed_bit_set_memory_in_bytes":0,"max_unsafe_auto_id_timestamp":-1,"file_sizes":{}}},"nodes":{"count":{"total":1,"data":1,"coordinating_only":0,"master":1,"ingest":1},"versions":["7.0.0-alpha1"],"os":{"available_processors":4,"allocated_processors":1,"names":[{"name":"Mac OS X","count":1}],"mem":{"total_in_bytes":17179869184,"free_in_bytes":183799808,"used_in_bytes":16996069376,"free_percent":1,"used_percent":99}},"process":{"cpu":{"percent":0},"open_file_descriptors":{"min":163,"max":163,"avg":163}},"jvm":{"max_uptime_in_millis":701043,"versions":[{"vm_version":"25.121-b13","count":1,"vm_vendor":"Oracle Corporation","version":"1.8.0_121","vm_name":"Java HotSpot(TM) 64-Bit Server VM"}],"mem":{"heap_used_in_bytes":204299464,"heap_max_in_bytes":628555776},"threads":40},"fs":{"total_in_bytes":499065712640,"free_in_bytes":200665341952,"available_in_bytes":200403197952},"plugins":[{"classname":"org.elasticsearch.xpack.XPackPlugin","name":"x-pack","description":"Elasticsearch Expanded Pack Plugin","version":"7.0.0-alpha1","has_native_controller":true}],"network_types":{"transport_types":{"security4":1},"http_types":{"security4":1}}}},"stack_stats":{"xpack":{"security":{"available":false,"enabled":true,"realms":{"file":{"available":false,"enabled":false},"ldap":{"available":false,"enabled":false},"native":{"available":false,"enabled":false},"active_directory":{"available":false,"enabled":false},"pki":{"available":false,"enabled":false}},"roles":{"native":{"size":1,"dls":false,"fls":false},"file":{"size":0,"dls":false,"fls":false}},"role_mapping":{"native":{"size":0,"enabled":0}},"ssl":{"http":{"enabled":false}},"audit":{"outputs":["logfile"],"enabled":false},"ipfilter":{"http":false,"transport":false},"anonymous":{"enabled":false}},"monitoring":{"available":true,"enabled":true,"enabled_exporters":{"http":1}},"watcher":{"available":false,"enabled":true,"execution":{"actions":{"_all":{"total":0,"total_time_in_ms":0}}}},"graph":{"available":false,"enabled":true},"ml":{"available":false,"enabled":true,"jobs":{"_all":{"count":0,"detectors":{"total":0,"min":0,"avg":0,"max":0},"model_size":{"total":0,"min":0,"avg":0,"max":0}}},"datafeeds":{"_all":{"count":0}}},"logstash":{"available":false,"enabled":true},"ccr":{"auto_follow_patterns_count":0,"available":true,"follower_indices_count":0,"enabled":true}}}},{"cluster_uuid":"lOF8kofiS_2DX58o9mXJ1Q","timestamp":"2017-08-15T22:10:54.610Z","cluster_name":"monitoring-one","version":"7.0.0-alpha1","license":{"status":"active","type":"trial","issue_date":"2017-08-15T21:58:28.997Z","expiry_date":"2017-09-14T21:58:28.997Z","expiry_date_in_millis":1505426308997},"cluster_stats":{"timestamp":1502835054610,"status":"yellow","indices":{"count":8,"shards":{"total":8,"primaries":8,"replication":0,"index":{"shards":{"min":1,"max":1,"avg":1},"primaries":{"min":1,"max":1,"avg":1},"replication":{"min":0,"max":0,"avg":0}}},"docs":{"count":3997,"deleted":69},"store":{"size_in_bytes":2647163},"fielddata":{"memory_size_in_bytes":2104,"evictions":0},"query_cache":{"memory_size_in_bytes":0,"total_count":0,"hit_count":0,"miss_count":0,"cache_size":0,"cache_count":0,"evictions":0},"completion":{"size_in_bytes":0},"segments":{"count":36,"memory_in_bytes":278961,"terms_memory_in_bytes":166031,"stored_fields_memory_in_bytes":11544,"term_vectors_memory_in_bytes":0,"norms_memory_in_bytes":6784,"points_memory_in_bytes":3250,"doc_values_memory_in_bytes":91352,"index_writer_memory_in_bytes":205347,"version_map_memory_in_bytes":26362,"fixed_bit_set_memory_in_bytes":992,"max_unsafe_auto_id_timestamp":-1,"file_sizes":{}}},"nodes":{"count":{"total":1,"data":1,"coordinating_only":0,"master":1,"ingest":1},"versions":["7.0.0-alpha1"],"os":{"available_processors":4,"allocated_processors":1,"names":[{"name":"Mac OS X","count":1}],"mem":{"total_in_bytes":17179869184,"free_in_bytes":86732800,"used_in_bytes":17093136384,"free_percent":1,"used_percent":99}},"process":{"cpu":{"percent":2},"open_file_descriptors":{"min":178,"max":178,"avg":178}},"jvm":{"max_uptime_in_millis":761002,"versions":[{"vm_version":"25.121-b13","count":1,"vm_vendor":"Oracle Corporation","version":"1.8.0_121","vm_name":"Java HotSpot(TM) 64-Bit Server VM"}],"mem":{"heap_used_in_bytes":133041176,"heap_max_in_bytes":628555776},"threads":42},"fs":{"total_in_bytes":499065712640,"free_in_bytes":200665792512,"available_in_bytes":200403648512},"plugins":[{"classname":"org.elasticsearch.xpack.XPackPlugin","name":"x-pack","description":"Elasticsearch Expanded Pack Plugin","version":"7.0.0-alpha1","has_native_controller":true}],"network_types":{"transport_types":{"security4":1},"http_types":{"security4":1}}}},"stack_stats":{"xpack":{"security":{"available":true,"enabled":true,"realms":{"file":{"name":["default_file"],"available":true,"size":[0],"enabled":true,"order":[2147483647]},"ldap":{"available":true,"enabled":false},"native":{"name":["default_native"],"available":true,"size":[2],"enabled":true,"order":[2147483647]},"active_directory":{"available":true,"enabled":false},"pki":{"available":true,"enabled":false}},"roles":{"native":{"size":1,"dls":false,"fls":false},"file":{"size":0,"dls":false,"fls":false}},"role_mapping":{"native":{"size":0,"enabled":0}},"ssl":{"http":{"enabled":false}},"audit":{"outputs":["logfile"],"enabled":false},"ipfilter":{"http":false,"transport":false},"anonymous":{"enabled":false}},"monitoring":{"available":true,"enabled":true,"enabled_exporters":{"local":1}},"watcher":{"available":true,"enabled":true,"execution":{"actions":{"index":{"total":14,"total_time_in_ms":158},"_all":{"total":110,"total_time_in_ms":2245},"email":{"total":14,"total_time_in_ms":3}}}},"graph":{"available":true,"enabled":true},"ml":{"available":true,"enabled":true,"jobs":{"_all":{"count":0,"detectors":{"total":0,"min":0,"avg":0,"max":0},"model_size":{"total":0,"min":0,"avg":0,"max":0}}},"datafeeds":{"_all":{"count":0}}},"logstash":{"available":true,"enabled":true},"ccr":{"auto_follow_patterns_count":0,"available":true,"follower_indices_count":0,"enabled":true}}}},{"cluster_uuid":"TkHOX_-1TzWwbROwQJU5IA","timestamp":"2017-08-15T22:10:52.642Z","cluster_name":"clusterone","version":"7.0.0-alpha1","license":{"status":"active","type":"trial","issue_date":"2017-08-15T21:58:47.135Z","expiry_date":"2017-09-14T21:58:47.135Z","expiry_date_in_millis":1505426327135},"cluster_stats":{"timestamp":1502835052641,"status":"green","indices":{"count":5,"shards":{"total":26,"primaries":13,"replication":1,"index":{"shards":{"min":2,"max":10,"avg":5.2},"primaries":{"min":1,"max":5,"avg":2.6},"replication":{"min":1,"max":1,"avg":1}}},"docs":{"count":150,"deleted":0},"store":{"size_in_bytes":4838464},"fielddata":{"memory_size_in_bytes":0,"evictions":0},"query_cache":{"memory_size_in_bytes":0,"total_count":0,"hit_count":0,"miss_count":0,"cache_size":0,"cache_count":0,"evictions":0},"completion":{"size_in_bytes":0},"segments":{"count":76,"memory_in_bytes":1907922,"terms_memory_in_bytes":1595112,"stored_fields_memory_in_bytes":23744,"term_vectors_memory_in_bytes":0,"norms_memory_in_bytes":197184,"points_memory_in_bytes":3818,"doc_values_memory_in_bytes":88064,"index_writer_memory_in_bytes":7006184,"version_map_memory_in_bytes":260,"fixed_bit_set_memory_in_bytes":0,"max_unsafe_auto_id_timestamp":1502834982386,"file_sizes":{}}},"nodes":{"count":{"total":2,"data":2,"coordinating_only":0,"master":2,"ingest":2},"versions":["7.0.0-alpha1"],"os":{"available_processors":8,"allocated_processors":2,"names":[{"name":"Mac OS X","count":2}],"mem":{"total_in_bytes":34359738368,"free_in_bytes":332099584,"used_in_bytes":34027638784,"free_percent":1,"used_percent":99}},"process":{"cpu":{"percent":2},"open_file_descriptors":{"min":218,"max":237,"avg":227}},"jvm":{"max_uptime_in_millis":741786,"versions":[{"vm_version":"25.121-b13","count":2,"vm_vendor":"Oracle Corporation","version":"1.8.0_121","vm_name":"Java HotSpot(TM) 64-Bit Server VM"}],"mem":{"heap_used_in_bytes":465621856,"heap_max_in_bytes":1257111552},"threads":92},"fs":{"total_in_bytes":499065712640,"free_in_bytes":200666353664,"available_in_bytes":200404209664},"plugins":[{"classname":"org.elasticsearch.xpack.XPackPlugin","name":"x-pack","description":"Elasticsearch Expanded Pack Plugin","version":"7.0.0-alpha1","has_native_controller":true}],"network_types":{"transport_types":{"security4":2},"http_types":{"security4":2}}}},"stack_stats":{"xpack":{"security":{"available":true,"enabled":true,"realms":{"file":{"name":["default_file"],"available":true,"size":[0],"enabled":true,"order":[2147483647]},"ldap":{"available":true,"enabled":false},"native":{"name":["default_native"],"available":true,"size":[1],"enabled":true,"order":[2147483647]},"active_directory":{"available":true,"enabled":false},"pki":{"available":true,"enabled":false}},"roles":{"native":{"size":1,"dls":false,"fls":false},"file":{"size":0,"dls":false,"fls":false}},"role_mapping":{"native":{"size":0,"enabled":0}},"ssl":{"http":{"enabled":false}},"audit":{"outputs":["logfile"],"enabled":false},"ipfilter":{"http":false,"transport":false},"anonymous":{"enabled":false}},"monitoring":{"available":true,"enabled":true,"enabled_exporters":{"http":1}},"watcher":{"available":true,"enabled":true,"execution":{"actions":{"_all":{"total":0,"total_time_in_ms":0}}}},"graph":{"available":true,"enabled":true,"graph_workspace":{"total":0}},"ml":{"available":true,"enabled":true,"jobs":{"_all":{"count":3,"detectors":{"total":3,"min":1,"avg":1,"max":1},"model_size":{"total":0,"min":0,"avg":0,"max":0}},"opened":{"count":1,"detectors":{"total":1,"min":1,"avg":1,"max":1},"model_size":{"total":0,"min":0,"avg":0,"max":0}},"closed":{"count":2,"detectors":{"total":2,"min":1,"avg":1,"max":1},"model_size":{"total":0,"min":0,"avg":0,"max":0}}},"datafeeds":{"_all":{"count":0}}},"logstash":{"available":true,"enabled":true},"ccr":{"auto_follow_patterns_count":0,"available":true,"follower_indices_count":0,"enabled":true}},"kibana":{"count":1,"versions":[{"version":"7.0.0-alpha1","count":1}],"os":{"platforms":[],"platformReleases":[],"distros":[],"distroReleases":[]},"dashboard":{"total":0},"visualization":{"total":0},"search":{"total":0},"index_pattern":{"total":0},"graph_workspace":{"total":0},"timelion_sheet":{"total":0},"indices":1,"plugins":{}},"logstash":{"count":1,"versions":[{"version":"7.0.0-alpha1","count":1}],"os":{"platforms":[],"platformReleases":[],"distros":[],"distroReleases":[]}}}}] +[ + { + "cluster_uuid": "6d-9tDFTRe-qT5GoBytdlQ", + "collectionSource": "monitoring", + "timestamp": "2017-08-15T22:10:59.952Z", + "cluster_name": "clustertwo", + "version": "7.0.0-alpha1", + "license": { + "status": "active", + "type": "basic", + "issue_date": "2014-09-29T00:00:00.000Z", + "expiry_date": "2030-08-29T23:59:59.999Z", + "expiry_date_in_millis": 1914278399999, + "issue_date_in_millis": 1411948800000, + "issued_to": "issuedTo", + "issuer": "issuer", + "max_nodes": 1, + "uid": "893361dc-9749-4997-93cb-802e3d7fa4a8", + "hkey": null + }, + "cluster_stats": { + "timestamp": 1502835059952, + "status": "green", + "indices": { + "count": 1, + "shards": { + "total": 1, + "primaries": 1, + "replication": 0, + "index": { + "shards": { + "min": 1, + "max": 1, + "avg": 1 + }, + "primaries": { + "min": 1, + "max": 1, + "avg": 1 + }, + "replication": { + "min": 0, + "max": 0, + "avg": 0 + } + } + }, + "docs": { + "count": 8, + "deleted": 0 + }, + "store": { + "size_in_bytes": 34095 + }, + "fielddata": { + "memory_size_in_bytes": 0, + "evictions": 0 + }, + "query_cache": { + "memory_size_in_bytes": 0, + "total_count": 0, + "hit_count": 0, + "miss_count": 0, + "cache_size": 0, + "cache_count": 0, + "evictions": 0 + }, + "completion": { + "size_in_bytes": 0 + }, + "segments": { + "count": 8, + "memory_in_bytes": 13852, + "terms_memory_in_bytes": 10412, + "stored_fields_memory_in_bytes": 2496, + "term_vectors_memory_in_bytes": 0, + "norms_memory_in_bytes": 256, + "points_memory_in_bytes": 8, + "doc_values_memory_in_bytes": 680, + "index_writer_memory_in_bytes": 0, + "version_map_memory_in_bytes": 0, + "fixed_bit_set_memory_in_bytes": 0, + "max_unsafe_auto_id_timestamp": -1, + "file_sizes": {} + } + }, + "nodes": { + "count": { + "total": 1, + "data": 1, + "coordinating_only": 0, + "master": 1, + "ingest": 1 + }, + "versions": [ + "7.0.0-alpha1" + ], + "os": { + "available_processors": 4, + "allocated_processors": 1, + "names": [ + { + "name": "Mac OS X", + "count": 1 + } + ], + "mem": { + "total_in_bytes": 17179869184, + "free_in_bytes": 183799808, + "used_in_bytes": 16996069376, + "free_percent": 1, + "used_percent": 99 + } + }, + "process": { + "cpu": { + "percent": 0 + }, + "open_file_descriptors": { + "min": 163, + "max": 163, + "avg": 163 + } + }, + "jvm": { + "max_uptime_in_millis": 701043, + "versions": [ + { + "vm_version": "25.121-b13", + "count": 1, + "vm_vendor": "Oracle Corporation", + "version": "1.8.0_121", + "vm_name": "Java HotSpot(TM) 64-Bit Server VM" + } + ], + "mem": { + "heap_used_in_bytes": 204299464, + "heap_max_in_bytes": 628555776 + }, + "threads": 40 + }, + "fs": { + "total_in_bytes": 499065712640, + "free_in_bytes": 200665341952, + "available_in_bytes": 200403197952 + }, + "plugins": [ + { + "classname": "org.elasticsearch.xpack.XPackPlugin", + "name": "x-pack", + "description": "Elasticsearch Expanded Pack Plugin", + "version": "7.0.0-alpha1", + "has_native_controller": true + } + ], + "network_types": { + "transport_types": { + "security4": 1 + }, + "http_types": { + "security4": 1 + } + } + } + }, + "stack_stats": { + "xpack": { + "security": { + "available": false, + "enabled": true, + "realms": { + "file": { + "available": false, + "enabled": false + }, + "ldap": { + "available": false, + "enabled": false + }, + "native": { + "available": false, + "enabled": false + }, + "active_directory": { + "available": false, + "enabled": false + }, + "pki": { + "available": false, + "enabled": false + } + }, + "roles": { + "native": { + "size": 1, + "dls": false, + "fls": false + }, + "file": { + "size": 0, + "dls": false, + "fls": false + } + }, + "role_mapping": { + "native": { + "size": 0, + "enabled": 0 + } + }, + "ssl": { + "http": { + "enabled": false + } + }, + "audit": { + "outputs": [ + "logfile" + ], + "enabled": false + }, + "ipfilter": { + "http": false, + "transport": false + }, + "anonymous": { + "enabled": false + } + }, + "monitoring": { + "available": true, + "enabled": true, + "enabled_exporters": { + "http": 1 + } + }, + "watcher": { + "available": false, + "enabled": true, + "execution": { + "actions": { + "_all": { + "total": 0, + "total_time_in_ms": 0 + } + } + } + }, + "graph": { + "available": false, + "enabled": true + }, + "ml": { + "available": false, + "enabled": true, + "jobs": { + "_all": { + "count": 0, + "detectors": { + "total": 0, + "min": 0, + "avg": 0, + "max": 0 + }, + "model_size": { + "total": 0, + "min": 0, + "avg": 0, + "max": 0 + } + } + }, + "datafeeds": { + "_all": { + "count": 0 + } + } + }, + "logstash": { + "available": false, + "enabled": true + }, + "ccr": { + "auto_follow_patterns_count": 0, + "available": true, + "follower_indices_count": 0, + "enabled": true + } + } + } + }, + { + "cluster_uuid": "lOF8kofiS_2DX58o9mXJ1Q", + "collectionSource": "monitoring", + "timestamp": "2017-08-15T22:10:54.610Z", + "cluster_name": "monitoring-one", + "version": "7.0.0-alpha1", + "license": { + "status": "active", + "type": "trial", + "issue_date": "2017-08-15T21:58:28.997Z", + "expiry_date": "2017-09-14T21:58:28.997Z", + "expiry_date_in_millis": 1505426308997, + "issue_date_in_millis": 1502834308997, + "issued_to": "monitoring-one", + "issuer": "elasticsearch", + "max_nodes": 1000, + "start_date_in_millis": -1, + "uid": "e5f6e897-0db5-4042-ad7c-6628ddc91691", + "hkey": null + }, + "cluster_stats": { + "timestamp": 1502835054610, + "status": "yellow", + "indices": { + "count": 8, + "shards": { + "total": 8, + "primaries": 8, + "replication": 0, + "index": { + "shards": { + "min": 1, + "max": 1, + "avg": 1 + }, + "primaries": { + "min": 1, + "max": 1, + "avg": 1 + }, + "replication": { + "min": 0, + "max": 0, + "avg": 0 + } + } + }, + "docs": { + "count": 3997, + "deleted": 69 + }, + "store": { + "size_in_bytes": 2647163 + }, + "fielddata": { + "memory_size_in_bytes": 2104, + "evictions": 0 + }, + "query_cache": { + "memory_size_in_bytes": 0, + "total_count": 0, + "hit_count": 0, + "miss_count": 0, + "cache_size": 0, + "cache_count": 0, + "evictions": 0 + }, + "completion": { + "size_in_bytes": 0 + }, + "segments": { + "count": 36, + "memory_in_bytes": 278961, + "terms_memory_in_bytes": 166031, + "stored_fields_memory_in_bytes": 11544, + "term_vectors_memory_in_bytes": 0, + "norms_memory_in_bytes": 6784, + "points_memory_in_bytes": 3250, + "doc_values_memory_in_bytes": 91352, + "index_writer_memory_in_bytes": 205347, + "version_map_memory_in_bytes": 26362, + "fixed_bit_set_memory_in_bytes": 992, + "max_unsafe_auto_id_timestamp": -1, + "file_sizes": {} + } + }, + "nodes": { + "count": { + "total": 1, + "data": 1, + "coordinating_only": 0, + "master": 1, + "ingest": 1 + }, + "versions": [ + "7.0.0-alpha1" + ], + "os": { + "available_processors": 4, + "allocated_processors": 1, + "names": [ + { + "name": "Mac OS X", + "count": 1 + } + ], + "mem": { + "total_in_bytes": 17179869184, + "free_in_bytes": 86732800, + "used_in_bytes": 17093136384, + "free_percent": 1, + "used_percent": 99 + } + }, + "process": { + "cpu": { + "percent": 2 + }, + "open_file_descriptors": { + "min": 178, + "max": 178, + "avg": 178 + } + }, + "jvm": { + "max_uptime_in_millis": 761002, + "versions": [ + { + "vm_version": "25.121-b13", + "count": 1, + "vm_vendor": "Oracle Corporation", + "version": "1.8.0_121", + "vm_name": "Java HotSpot(TM) 64-Bit Server VM" + } + ], + "mem": { + "heap_used_in_bytes": 133041176, + "heap_max_in_bytes": 628555776 + }, + "threads": 42 + }, + "fs": { + "total_in_bytes": 499065712640, + "free_in_bytes": 200665792512, + "available_in_bytes": 200403648512 + }, + "plugins": [ + { + "classname": "org.elasticsearch.xpack.XPackPlugin", + "name": "x-pack", + "description": "Elasticsearch Expanded Pack Plugin", + "version": "7.0.0-alpha1", + "has_native_controller": true + } + ], + "network_types": { + "transport_types": { + "security4": 1 + }, + "http_types": { + "security4": 1 + } + } + } + }, + "stack_stats": { + "xpack": { + "security": { + "available": true, + "enabled": true, + "realms": { + "file": { + "name": [ + "default_file" + ], + "available": true, + "size": [ + 0 + ], + "enabled": true, + "order": [ + 2147483647 + ] + }, + "ldap": { + "available": true, + "enabled": false + }, + "native": { + "name": [ + "default_native" + ], + "available": true, + "size": [ + 2 + ], + "enabled": true, + "order": [ + 2147483647 + ] + }, + "active_directory": { + "available": true, + "enabled": false + }, + "pki": { + "available": true, + "enabled": false + } + }, + "roles": { + "native": { + "size": 1, + "dls": false, + "fls": false + }, + "file": { + "size": 0, + "dls": false, + "fls": false + } + }, + "role_mapping": { + "native": { + "size": 0, + "enabled": 0 + } + }, + "ssl": { + "http": { + "enabled": false + } + }, + "audit": { + "outputs": [ + "logfile" + ], + "enabled": false + }, + "ipfilter": { + "http": false, + "transport": false + }, + "anonymous": { + "enabled": false + } + }, + "monitoring": { + "available": true, + "enabled": true, + "enabled_exporters": { + "local": 1 + } + }, + "watcher": { + "available": true, + "enabled": true, + "execution": { + "actions": { + "index": { + "total": 14, + "total_time_in_ms": 158 + }, + "_all": { + "total": 110, + "total_time_in_ms": 2245 + }, + "email": { + "total": 14, + "total_time_in_ms": 3 + } + } + } + }, + "graph": { + "available": true, + "enabled": true + }, + "ml": { + "available": true, + "enabled": true, + "jobs": { + "_all": { + "count": 0, + "detectors": { + "total": 0, + "min": 0, + "avg": 0, + "max": 0 + }, + "model_size": { + "total": 0, + "min": 0, + "avg": 0, + "max": 0 + } + } + }, + "datafeeds": { + "_all": { + "count": 0 + } + } + }, + "logstash": { + "available": true, + "enabled": true + }, + "ccr": { + "auto_follow_patterns_count": 0, + "available": true, + "follower_indices_count": 0, + "enabled": true + } + } + } + }, + { + "cluster_uuid": "TkHOX_-1TzWwbROwQJU5IA", + "collectionSource": "monitoring", + "timestamp": "2017-08-15T22:10:52.642Z", + "cluster_name": "clusterone", + "version": "7.0.0-alpha1", + "license": { + "status": "active", + "type": "trial", + "issue_date": "2017-08-15T21:58:47.135Z", + "expiry_date": "2017-09-14T21:58:47.135Z", + "expiry_date_in_millis": 1505426327135, + "issue_date_in_millis": 1502834327135, + "issued_to": "clusterone", + "issuer": "elasticsearch", + "max_nodes": 1000, + "start_date_in_millis": -1, + "uid": "e5e99511-0928-41a3-97b0-ec77fa5e3b4c", + "hkey": null + }, + "cluster_stats": { + "timestamp": 1502835052641, + "status": "green", + "indices": { + "count": 5, + "shards": { + "total": 26, + "primaries": 13, + "replication": 1, + "index": { + "shards": { + "min": 2, + "max": 10, + "avg": 5.2 + }, + "primaries": { + "min": 1, + "max": 5, + "avg": 2.6 + }, + "replication": { + "min": 1, + "max": 1, + "avg": 1 + } + } + }, + "docs": { + "count": 150, + "deleted": 0 + }, + "store": { + "size_in_bytes": 4838464 + }, + "fielddata": { + "memory_size_in_bytes": 0, + "evictions": 0 + }, + "query_cache": { + "memory_size_in_bytes": 0, + "total_count": 0, + "hit_count": 0, + "miss_count": 0, + "cache_size": 0, + "cache_count": 0, + "evictions": 0 + }, + "completion": { + "size_in_bytes": 0 + }, + "segments": { + "count": 76, + "memory_in_bytes": 1907922, + "terms_memory_in_bytes": 1595112, + "stored_fields_memory_in_bytes": 23744, + "term_vectors_memory_in_bytes": 0, + "norms_memory_in_bytes": 197184, + "points_memory_in_bytes": 3818, + "doc_values_memory_in_bytes": 88064, + "index_writer_memory_in_bytes": 7006184, + "version_map_memory_in_bytes": 260, + "fixed_bit_set_memory_in_bytes": 0, + "max_unsafe_auto_id_timestamp": 1502834982386, + "file_sizes": {} + } + }, + "nodes": { + "count": { + "total": 2, + "data": 2, + "coordinating_only": 0, + "master": 2, + "ingest": 2 + }, + "versions": [ + "7.0.0-alpha1" + ], + "os": { + "available_processors": 8, + "allocated_processors": 2, + "names": [ + { + "name": "Mac OS X", + "count": 2 + } + ], + "mem": { + "total_in_bytes": 34359738368, + "free_in_bytes": 332099584, + "used_in_bytes": 34027638784, + "free_percent": 1, + "used_percent": 99 + } + }, + "process": { + "cpu": { + "percent": 2 + }, + "open_file_descriptors": { + "min": 218, + "max": 237, + "avg": 227 + } + }, + "jvm": { + "max_uptime_in_millis": 741786, + "versions": [ + { + "vm_version": "25.121-b13", + "count": 2, + "vm_vendor": "Oracle Corporation", + "version": "1.8.0_121", + "vm_name": "Java HotSpot(TM) 64-Bit Server VM" + } + ], + "mem": { + "heap_used_in_bytes": 465621856, + "heap_max_in_bytes": 1257111552 + }, + "threads": 92 + }, + "fs": { + "total_in_bytes": 499065712640, + "free_in_bytes": 200666353664, + "available_in_bytes": 200404209664 + }, + "plugins": [ + { + "classname": "org.elasticsearch.xpack.XPackPlugin", + "name": "x-pack", + "description": "Elasticsearch Expanded Pack Plugin", + "version": "7.0.0-alpha1", + "has_native_controller": true + } + ], + "network_types": { + "transport_types": { + "security4": 2 + }, + "http_types": { + "security4": 2 + } + } + } + }, + "stack_stats": { + "xpack": { + "security": { + "available": true, + "enabled": true, + "realms": { + "file": { + "name": [ + "default_file" + ], + "available": true, + "size": [ + 0 + ], + "enabled": true, + "order": [ + 2147483647 + ] + }, + "ldap": { + "available": true, + "enabled": false + }, + "native": { + "name": [ + "default_native" + ], + "available": true, + "size": [ + 1 + ], + "enabled": true, + "order": [ + 2147483647 + ] + }, + "active_directory": { + "available": true, + "enabled": false + }, + "pki": { + "available": true, + "enabled": false + } + }, + "roles": { + "native": { + "size": 1, + "dls": false, + "fls": false + }, + "file": { + "size": 0, + "dls": false, + "fls": false + } + }, + "role_mapping": { + "native": { + "size": 0, + "enabled": 0 + } + }, + "ssl": { + "http": { + "enabled": false + } + }, + "audit": { + "outputs": [ + "logfile" + ], + "enabled": false + }, + "ipfilter": { + "http": false, + "transport": false + }, + "anonymous": { + "enabled": false + } + }, + "monitoring": { + "available": true, + "enabled": true, + "enabled_exporters": { + "http": 1 + } + }, + "watcher": { + "available": true, + "enabled": true, + "execution": { + "actions": { + "_all": { + "total": 0, + "total_time_in_ms": 0 + } + } + } + }, + "graph": { + "available": true, + "enabled": true, + "graph_workspace": { + "total": 0 + } + }, + "ml": { + "available": true, + "enabled": true, + "jobs": { + "_all": { + "count": 3, + "detectors": { + "total": 3, + "min": 1, + "avg": 1, + "max": 1 + }, + "model_size": { + "total": 0, + "min": 0, + "avg": 0, + "max": 0 + } + }, + "opened": { + "count": 1, + "detectors": { + "total": 1, + "min": 1, + "avg": 1, + "max": 1 + }, + "model_size": { + "total": 0, + "min": 0, + "avg": 0, + "max": 0 + } + }, + "closed": { + "count": 2, + "detectors": { + "total": 2, + "min": 1, + "avg": 1, + "max": 1 + }, + "model_size": { + "total": 0, + "min": 0, + "avg": 0, + "max": 0 + } + } + }, + "datafeeds": { + "_all": { + "count": 0 + } + } + }, + "logstash": { + "available": true, + "enabled": true + }, + "ccr": { + "auto_follow_patterns_count": 0, + "available": true, + "follower_indices_count": 0, + "enabled": true + } + }, + "kibana": { + "count": 1, + "versions": [ + { + "version": "7.0.0-alpha1", + "count": 1 + } + ], + "os": { + "platforms": [], + "platformReleases": [], + "distros": [], + "distroReleases": [] + }, + "dashboard": { + "total": 0 + }, + "visualization": { + "total": 0 + }, + "search": { + "total": 0 + }, + "index_pattern": { + "total": 0 + }, + "graph_workspace": { + "total": 0 + }, + "timelion_sheet": { + "total": 0 + }, + "indices": 1, + "plugins": {} + }, + "logstash": { + "count": 1, + "versions": [ + { + "version": "7.0.0-alpha1", + "count": 1 + } + ], + "os": { + "platforms": [], + "platformReleases": [], + "distros": [], + "distroReleases": [] + } + } + } + } +] diff --git a/x-pack/test/functional/apps/infra/logs_source_configuration.ts b/x-pack/test/functional/apps/infra/logs_source_configuration.ts index 1dfbe3526ce40..ecad5a40ec42e 100644 --- a/x-pack/test/functional/apps/infra/logs_source_configuration.ts +++ b/x-pack/test/functional/apps/infra/logs_source_configuration.ts @@ -15,8 +15,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'infraLogs']); const retry = getService('retry'); - // FLAKY: https://github.com/elastic/kibana/issues/58059 - describe.skip('Logs Source Configuration', function() { + describe('Logs Source Configuration', function() { this.tags('smoke'); before(async () => { diff --git a/x-pack/test/functional/page_objects/security_page.js b/x-pack/test/functional/page_objects/security_page.js index 3bcba7cbd1696..5889a374e443e 100644 --- a/x-pack/test/functional/page_objects/security_page.js +++ b/x-pack/test/functional/page_objects/security_page.js @@ -30,6 +30,11 @@ export function SecurityPageProvider({ getService, getPageObjects }) { const rawDataTabLocator = 'a[id=rawdata-tab]'; await PageObjects.common.navigateToApp('login'); + + // ensure welcome screen won't be shown. This is relevant for environments which don't allow + // to use the yml setting, e.g. cloud + await browser.setLocalStorageItem('home:welcome:show', 'false'); + await testSubjects.setValue('loginUsername', username); await testSubjects.setValue('loginPassword', password); await testSubjects.click('loginSubmit'); diff --git a/x-pack/test/functional/services/infra_source_configuration_form.ts b/x-pack/test/functional/services/infra_source_configuration_form.ts index ab61d5232fa1c..dbae6f00f75a2 100644 --- a/x-pack/test/functional/services/infra_source_configuration_form.ts +++ b/x-pack/test/functional/services/infra_source_configuration_form.ts @@ -36,25 +36,37 @@ export function InfraSourceConfigurationFormProvider({ getService }: FtrProvider return await testSubjects.find('~addLogColumnPopover'); }, async addTimestampLogColumn() { - await (await this.getAddLogColumnButton()).click(); + // try to open the popover + const popover = await retry.try(async () => { + await (await this.getAddLogColumnButton()).click(); + return this.getAddLogColumnPopover(); + }); + + // try to select the timestamp field await retry.try(async () => { - await ( - await testSubjects.findDescendant( - '~addTimestampLogColumn', - await this.getAddLogColumnPopover() - ) - ).click(); + await (await testSubjects.findDescendant('~addTimestampLogColumn', popover)).click(); }); + + // wait for timestamp panel to show up + await testSubjects.findDescendant('~systemLogColumnPanel:Timestamp', await this.getForm()); }, async addFieldLogColumn(fieldName: string) { - await (await this.getAddLogColumnButton()).click(); + // try to open the popover + const popover = await retry.try(async () => { + await (await this.getAddLogColumnButton()).click(); + return this.getAddLogColumnPopover(); + }); + + // try to select the given field await retry.try(async () => { - const popover = await this.getAddLogColumnPopover(); await (await testSubjects.findDescendant('~fieldSearchInput', popover)).type(fieldName); await ( await testSubjects.findDescendant(`~addFieldLogColumn:${fieldName}`, popover) ).click(); }); + + // wait for field panel to show up + await testSubjects.findDescendant(`~fieldLogColumnPanel:${fieldName}`, await this.getForm()); }, async getLogColumnPanels(): Promise { return await testSubjects.findAllDescendant('~logColumnPanel', await this.getForm());