mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Usage Collection] Usage collection add saved objects client to collector fetch context (#80554)
This commit is contained in:
parent
66b2976656
commit
9afd63f56d
22 changed files with 193 additions and 70 deletions
|
@ -20,6 +20,7 @@ import { EnvironmentMode } from '@kbn/config';
|
|||
import { ErrorToastOptions } from 'src/core/public/notifications';
|
||||
import { ExpressionAstFunction } from 'src/plugins/expressions/common';
|
||||
import { ExpressionsServerSetup } from 'src/plugins/expressions/server';
|
||||
import { ISavedObjectsRepository } from 'kibana/server';
|
||||
import { ISearchOptions as ISearchOptions_2 } from 'src/plugins/data/public';
|
||||
import { ISearchSource } from 'src/plugins/data/public';
|
||||
import { KibanaRequest } from 'src/core/server';
|
||||
|
|
|
@ -35,6 +35,7 @@ import {
|
|||
Logger,
|
||||
IClusterClient,
|
||||
UiSettingsServiceStart,
|
||||
SavedObjectsServiceStart,
|
||||
} from '../../../core/server';
|
||||
import { registerRoutes } from './routes';
|
||||
import { registerCollection } from './telemetry_collection';
|
||||
|
@ -88,6 +89,7 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
|
|||
private readonly oldUiSettingsHandled$ = new AsyncSubject();
|
||||
private savedObjectsClient?: ISavedObjectsRepository;
|
||||
private elasticsearchClient?: IClusterClient;
|
||||
private savedObjectsService?: SavedObjectsServiceStart;
|
||||
|
||||
constructor(initializerContext: PluginInitializerContext<TelemetryConfigType>) {
|
||||
this.logger = initializerContext.logger.get();
|
||||
|
@ -110,7 +112,8 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
|
|||
registerCollection(
|
||||
telemetryCollectionManager,
|
||||
elasticsearch.legacy.client,
|
||||
() => this.elasticsearchClient
|
||||
() => this.elasticsearchClient,
|
||||
() => this.savedObjectsService
|
||||
);
|
||||
const router = http.createRouter();
|
||||
|
||||
|
@ -139,6 +142,7 @@ export class TelemetryPlugin implements Plugin<TelemetryPluginSetup, TelemetryPl
|
|||
const savedObjectsInternalRepository = savedObjects.createInternalRepository();
|
||||
this.savedObjectsClient = savedObjectsInternalRepository;
|
||||
this.elasticsearchClient = elasticsearch.client;
|
||||
this.savedObjectsService = savedObjects;
|
||||
|
||||
// Not catching nor awaiting these promises because they should never reject
|
||||
this.handleOldUiSettings(uiSettings);
|
||||
|
|
|
@ -19,7 +19,11 @@
|
|||
|
||||
import { omit } from 'lodash';
|
||||
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
|
||||
import { LegacyAPICaller } from 'kibana/server';
|
||||
import {
|
||||
ISavedObjectsRepository,
|
||||
LegacyAPICaller,
|
||||
SavedObjectsClientContract,
|
||||
} from 'kibana/server';
|
||||
import { StatsCollectionContext } from 'src/plugins/telemetry_collection_manager/server';
|
||||
import { ElasticsearchClient } from 'src/core/server';
|
||||
|
||||
|
@ -84,8 +88,9 @@ export function handleKibanaStats(
|
|||
export async function getKibana(
|
||||
usageCollection: UsageCollectionSetup,
|
||||
callWithInternalUser: LegacyAPICaller,
|
||||
asInternalUser: ElasticsearchClient
|
||||
asInternalUser: ElasticsearchClient,
|
||||
soClient: SavedObjectsClientContract | ISavedObjectsRepository
|
||||
): Promise<KibanaUsageStats> {
|
||||
const usage = await usageCollection.bulkFetch(callWithInternalUser, asInternalUser);
|
||||
const usage = await usageCollection.bulkFetch(callWithInternalUser, asInternalUser, soClient);
|
||||
return usageCollection.toObject(usage);
|
||||
}
|
||||
|
|
|
@ -20,7 +20,10 @@
|
|||
import { merge, omit } from 'lodash';
|
||||
|
||||
import { getLocalStats, handleLocalStats } from './get_local_stats';
|
||||
import { usageCollectionPluginMock } from '../../../usage_collection/server/mocks';
|
||||
import {
|
||||
usageCollectionPluginMock,
|
||||
createCollectorFetchContextMock,
|
||||
} from '../../../usage_collection/server/mocks';
|
||||
import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks';
|
||||
|
||||
function mockUsageCollection(kibanaUsage = {}) {
|
||||
|
@ -79,6 +82,16 @@ function mockGetLocalStats(clusterInfo: any, clusterStats: any) {
|
|||
return esClient;
|
||||
}
|
||||
|
||||
function mockStatsCollectionConfig(clusterInfo: any, clusterStats: any, kibana: {}) {
|
||||
return {
|
||||
...createCollectorFetchContextMock(),
|
||||
esClient: mockGetLocalStats(clusterInfo, clusterStats),
|
||||
usageCollection: mockUsageCollection(kibana),
|
||||
start: '',
|
||||
end: '',
|
||||
};
|
||||
}
|
||||
|
||||
describe('get_local_stats', () => {
|
||||
const clusterUuid = 'abc123';
|
||||
const clusterName = 'my-cool-cluster';
|
||||
|
@ -224,12 +237,10 @@ describe('get_local_stats', () => {
|
|||
|
||||
describe('getLocalStats', () => {
|
||||
it('returns expected object with kibana data', async () => {
|
||||
const callCluster = jest.fn();
|
||||
const usageCollection = mockUsageCollection(kibana);
|
||||
const esClient = mockGetLocalStats(clusterInfo, clusterStats);
|
||||
const statsCollectionConfig = mockStatsCollectionConfig(clusterInfo, clusterStats, kibana);
|
||||
const response = await getLocalStats(
|
||||
[{ clusterUuid: 'abc123' }],
|
||||
{ callCluster, usageCollection, esClient, start: '', end: '' },
|
||||
{ ...statsCollectionConfig },
|
||||
context
|
||||
);
|
||||
const result = response[0];
|
||||
|
@ -244,14 +255,8 @@ describe('get_local_stats', () => {
|
|||
});
|
||||
|
||||
it('returns an empty array when no cluster uuid is provided', async () => {
|
||||
const callCluster = jest.fn();
|
||||
const usageCollection = mockUsageCollection(kibana);
|
||||
const esClient = mockGetLocalStats(clusterInfo, clusterStats);
|
||||
const response = await getLocalStats(
|
||||
[],
|
||||
{ callCluster, usageCollection, esClient, start: '', end: '' },
|
||||
context
|
||||
);
|
||||
const statsCollectionConfig = mockStatsCollectionConfig(clusterInfo, clusterStats, kibana);
|
||||
const response = await getLocalStats([], { ...statsCollectionConfig }, context);
|
||||
expect(response).toBeDefined();
|
||||
expect(response.length).toEqual(0);
|
||||
});
|
||||
|
|
|
@ -68,10 +68,10 @@ export type TelemetryLocalStats = ReturnType<typeof handleLocalStats>;
|
|||
*/
|
||||
export const getLocalStats: StatsGetter<{}, TelemetryLocalStats> = async (
|
||||
clustersDetails, // array of cluster uuid's
|
||||
config, // contains the new esClient already scoped contains usageCollection, callCluster, esClient, start, end
|
||||
config, // contains the new esClient already scoped contains usageCollection, callCluster, esClient, start, end and the saved objects client scoped to the request or the internal repository
|
||||
context // StatsCollectionContext contains logger and version (string)
|
||||
) => {
|
||||
const { callCluster, usageCollection, esClient } = config;
|
||||
const { callCluster, usageCollection, esClient, soClient } = config;
|
||||
|
||||
return await Promise.all(
|
||||
clustersDetails.map(async (clustersDetail) => {
|
||||
|
@ -79,7 +79,7 @@ export const getLocalStats: StatsGetter<{}, TelemetryLocalStats> = async (
|
|||
getClusterInfo(esClient), // cluster info
|
||||
getClusterStats(esClient), // cluster stats (not to be confused with cluster _state_)
|
||||
getNodesUsage(esClient), // nodes_usage info
|
||||
getKibana(usageCollection, callCluster, esClient),
|
||||
getKibana(usageCollection, callCluster, esClient, soClient),
|
||||
getDataTelemetry(esClient),
|
||||
]);
|
||||
return handleLocalStats(
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { ILegacyClusterClient } from 'kibana/server';
|
||||
import { ILegacyClusterClient, SavedObjectsServiceStart } from 'kibana/server';
|
||||
import { TelemetryCollectionManagerPluginSetup } from 'src/plugins/telemetry_collection_manager/server';
|
||||
import { IClusterClient } from '../../../../../src/core/server';
|
||||
import { getLocalStats } from './get_local_stats';
|
||||
|
@ -46,11 +46,13 @@ import { getLocalLicense } from './get_local_license';
|
|||
export function registerCollection(
|
||||
telemetryCollectionManager: TelemetryCollectionManagerPluginSetup,
|
||||
esCluster: ILegacyClusterClient,
|
||||
esClientGetter: () => IClusterClient | undefined
|
||||
esClientGetter: () => IClusterClient | undefined,
|
||||
soServiceGetter: () => SavedObjectsServiceStart | undefined
|
||||
) {
|
||||
telemetryCollectionManager.setCollection({
|
||||
esCluster,
|
||||
esClientGetter,
|
||||
soServiceGetter,
|
||||
title: 'local',
|
||||
priority: 0,
|
||||
statsGetter: getLocalStats,
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
Plugin,
|
||||
Logger,
|
||||
IClusterClient,
|
||||
SavedObjectsServiceStart,
|
||||
} from '../../../core/server';
|
||||
|
||||
import {
|
||||
|
@ -90,6 +91,7 @@ export class TelemetryCollectionManagerPlugin
|
|||
priority,
|
||||
esCluster,
|
||||
esClientGetter,
|
||||
soServiceGetter,
|
||||
statsGetter,
|
||||
clusterDetailsGetter,
|
||||
licenseGetter,
|
||||
|
@ -112,6 +114,9 @@ export class TelemetryCollectionManagerPlugin
|
|||
if (!esClientGetter) {
|
||||
throw Error('esClientGetter method not set.');
|
||||
}
|
||||
if (!soServiceGetter) {
|
||||
throw Error('soServiceGetter method not set.');
|
||||
}
|
||||
if (!clusterDetailsGetter) {
|
||||
throw Error('Cluster UUIds method is not set.');
|
||||
}
|
||||
|
@ -126,6 +131,7 @@ export class TelemetryCollectionManagerPlugin
|
|||
esCluster,
|
||||
title,
|
||||
esClientGetter,
|
||||
soServiceGetter,
|
||||
});
|
||||
this.usageGetterMethodPriority = priority;
|
||||
}
|
||||
|
@ -135,6 +141,7 @@ export class TelemetryCollectionManagerPlugin
|
|||
config: StatsGetterConfig,
|
||||
collection: Collection,
|
||||
collectionEsClient: IClusterClient,
|
||||
collectionSoService: SavedObjectsServiceStart,
|
||||
usageCollection: UsageCollectionSetup
|
||||
): StatsCollectionConfig {
|
||||
const { start, end, request } = config;
|
||||
|
@ -146,7 +153,11 @@ export class TelemetryCollectionManagerPlugin
|
|||
const esClient = config.unencrypted
|
||||
? collectionEsClient.asScoped(config.request).asCurrentUser
|
||||
: collectionEsClient.asInternalUser;
|
||||
return { callCluster, start, end, usageCollection, esClient };
|
||||
// Scope the saved objects client appropriately and pass to the stats collection config
|
||||
const soClient = config.unencrypted
|
||||
? collectionSoService.getScopedClient(config.request)
|
||||
: collectionSoService.createInternalRepository();
|
||||
return { callCluster, start, end, usageCollection, esClient, soClient };
|
||||
}
|
||||
|
||||
private async getOptInStats(optInStatus: boolean, config: StatsGetterConfig) {
|
||||
|
@ -156,11 +167,13 @@ export class TelemetryCollectionManagerPlugin
|
|||
for (const collection of this.collections) {
|
||||
// first fetch the client and make sure it's not undefined.
|
||||
const collectionEsClient = collection.esClientGetter();
|
||||
if (collectionEsClient !== undefined) {
|
||||
const collectionSoService = collection.soServiceGetter();
|
||||
if (collectionEsClient !== undefined && collectionSoService !== undefined) {
|
||||
const statsCollectionConfig = this.getStatsCollectionConfig(
|
||||
config,
|
||||
collection,
|
||||
collectionEsClient,
|
||||
collectionSoService,
|
||||
this.usageCollection
|
||||
);
|
||||
|
||||
|
@ -215,11 +228,13 @@ export class TelemetryCollectionManagerPlugin
|
|||
}
|
||||
for (const collection of this.collections) {
|
||||
const collectionEsClient = collection.esClientGetter();
|
||||
if (collectionEsClient !== undefined) {
|
||||
const collectionSavedObjectsService = collection.soServiceGetter();
|
||||
if (collectionEsClient !== undefined && collectionSavedObjectsService !== undefined) {
|
||||
const statsCollectionConfig = this.getStatsCollectionConfig(
|
||||
config,
|
||||
collection,
|
||||
collectionEsClient,
|
||||
collectionSavedObjectsService,
|
||||
this.usageCollection
|
||||
);
|
||||
try {
|
||||
|
|
|
@ -23,6 +23,9 @@ import {
|
|||
KibanaRequest,
|
||||
ILegacyClusterClient,
|
||||
IClusterClient,
|
||||
SavedObjectsServiceStart,
|
||||
SavedObjectsClientContract,
|
||||
ISavedObjectsRepository,
|
||||
} from 'kibana/server';
|
||||
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
|
||||
import { ElasticsearchClient } from '../../../../src/core/server';
|
||||
|
@ -77,6 +80,7 @@ export interface StatsCollectionConfig {
|
|||
start: string | number;
|
||||
end: string | number;
|
||||
esClient: ElasticsearchClient;
|
||||
soClient: SavedObjectsClientContract | ISavedObjectsRepository;
|
||||
}
|
||||
|
||||
export interface BasicStatsPayload {
|
||||
|
@ -141,6 +145,7 @@ export interface CollectionConfig<
|
|||
priority: number;
|
||||
esCluster: ILegacyClusterClient;
|
||||
esClientGetter: () => IClusterClient | undefined; // --> by now we know that the client getter will return the IClusterClient but we assure that through a code check
|
||||
soServiceGetter: () => SavedObjectsServiceStart | undefined; // --> by now we know that the service getter will return the SavedObjectsServiceStart but we assure that through a code check
|
||||
statsGetter: StatsGetter<CustomContext, T>;
|
||||
clusterDetailsGetter: ClusterDetailsGetter<CustomContext>;
|
||||
licenseGetter: LicenseGetter<CustomContext>;
|
||||
|
@ -157,5 +162,6 @@ export interface Collection<
|
|||
clusterDetailsGetter: ClusterDetailsGetter<CustomContext>;
|
||||
esCluster: ILegacyClusterClient;
|
||||
esClientGetter: () => IClusterClient | undefined; // the collection could still return undefined for the es client getter.
|
||||
soServiceGetter: () => SavedObjectsServiceStart | undefined; // the collection could still return undefined for the Saved Objects Service getter.
|
||||
title: string;
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ All you need to provide is a `type` for organizing your fields, `schema` field t
|
|||
},
|
||||
fetch: async (collectorFetchContext: CollectorFetchContext) => {
|
||||
|
||||
// query ES and get some data
|
||||
// query ES or saved objects and get some data
|
||||
// summarize the data into a model
|
||||
// return the modeled object that includes whatever you want to track
|
||||
|
||||
|
@ -85,9 +85,11 @@ Some background:
|
|||
|
||||
- `MY_USAGE_TYPE` can be any string. It usually matches the plugin name. As a safety mechanism, we double check there are no duplicates at the moment of registering the collector.
|
||||
- The `fetch` method needs to support multiple contexts in which it is called. For example, when stats are pulled from a Kibana Metricbeat module, the Beat calls Kibana's stats API to invoke usage collection.
|
||||
In this case, the `fetch` method is called as a result of an HTTP API request and `callCluster` wraps `callWithRequest` or `esClient` wraps `asCurrentUser`, where the request headers are expected to have read privilege on the entire `.kibana' index.
|
||||
In this case, the `fetch` method is called as a result of an HTTP API request and `callCluster` wraps `callWithRequest` or `esClient` wraps `asCurrentUser`, where the request headers are expected to have read privilege on the entire `.kibana' index. The `fetch` method also exposes the saved objects client that will have the correct scope when the collectors' `fetch` method is called.
|
||||
|
||||
Note: there will be many cases where you won't need to use the `callCluster` (or `esClient`) function that gets passed in to your `fetch` method at all. Your feature might have an accumulating value in server memory, or read something from the OS, or use other clients like a custom SavedObjects client. In that case it's up to the plugin to initialize those clients like the example below:
|
||||
Note: there will be many cases where you won't need to use the `callCluster`, `esClient` or `soClient` function that gets passed in to your `fetch` method at all. Your feature might have an accumulating value in server memory, or read something from the OS.
|
||||
|
||||
In the case of using a custom SavedObjects client, it is up to the plugin to initialize the client to save the data and it is strongly recommended to scope that client to the `kibana_system` user.
|
||||
|
||||
```ts
|
||||
// server/plugin.ts
|
||||
|
@ -98,7 +100,7 @@ class Plugin {
|
|||
private savedObjectsRepository?: ISavedObjectsRepository;
|
||||
|
||||
public setup(core: CoreSetup, plugins: { usageCollection?: UsageCollectionSetup }) {
|
||||
registerMyPluginUsageCollector(() => this.savedObjectsRepository, plugins.usageCollection);
|
||||
registerMyPluginUsageCollector(plugins.usageCollection);
|
||||
}
|
||||
|
||||
public start(core: CoreStart) {
|
||||
|
|
|
@ -17,7 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { Logger, LegacyAPICaller, ElasticsearchClient } from 'kibana/server';
|
||||
import {
|
||||
Logger,
|
||||
LegacyAPICaller,
|
||||
ElasticsearchClient,
|
||||
ISavedObjectsRepository,
|
||||
SavedObjectsClientContract,
|
||||
} from 'kibana/server';
|
||||
|
||||
export type CollectorFormatForBulkUpload<T, U> = (result: T) => { type: string; payload: U };
|
||||
|
||||
|
@ -56,7 +62,14 @@ export interface CollectorFetchContext {
|
|||
* - When building the telemetry data payload to report to the remote cluster, the requests are scoped to the `kibana` internal user
|
||||
*/
|
||||
esClient: ElasticsearchClient;
|
||||
/**
|
||||
* Request-scoped Saved Objects client:
|
||||
* - When users are requesting a sample of data, it is scoped to their role to avoid exposing data they should't read
|
||||
* - When building the telemetry data payload to report to the remote cluster, the requests are scoped to the `kibana` internal user
|
||||
*/
|
||||
soClient: SavedObjectsClientContract | ISavedObjectsRepository;
|
||||
}
|
||||
|
||||
export interface CollectorOptions<T = unknown, U = T> {
|
||||
type: string;
|
||||
init?: Function;
|
||||
|
|
|
@ -21,7 +21,11 @@ import { noop } from 'lodash';
|
|||
import { Collector } from './collector';
|
||||
import { CollectorSet } from './collector_set';
|
||||
import { UsageCollector } from './usage_collector';
|
||||
import { elasticsearchServiceMock, loggingSystemMock } from '../../../../core/server/mocks';
|
||||
import {
|
||||
elasticsearchServiceMock,
|
||||
loggingSystemMock,
|
||||
savedObjectsRepositoryMock,
|
||||
} from '../../../../core/server/mocks';
|
||||
|
||||
const logger = loggingSystemMock.createLogger();
|
||||
|
||||
|
@ -40,9 +44,9 @@ describe('CollectorSet', () => {
|
|||
loggerSpies.debug.mockRestore();
|
||||
loggerSpies.warn.mockRestore();
|
||||
});
|
||||
|
||||
const mockCallCluster = jest.fn().mockResolvedValue({ passTest: 1000 });
|
||||
const mockEsClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
const mockSoClient = savedObjectsRepositoryMock.create();
|
||||
|
||||
it('should throw an error if non-Collector type of object is registered', () => {
|
||||
const collectors = new CollectorSet({ logger });
|
||||
|
@ -88,7 +92,7 @@ describe('CollectorSet', () => {
|
|||
})
|
||||
);
|
||||
|
||||
const result = await collectors.bulkFetch(mockCallCluster, mockEsClient);
|
||||
const result = await collectors.bulkFetch(mockCallCluster, mockEsClient, mockSoClient);
|
||||
expect(loggerSpies.debug).toHaveBeenCalledTimes(1);
|
||||
expect(loggerSpies.debug).toHaveBeenCalledWith(
|
||||
'Fetching data from MY_TEST_COLLECTOR collector'
|
||||
|
@ -113,7 +117,7 @@ describe('CollectorSet', () => {
|
|||
|
||||
let result;
|
||||
try {
|
||||
result = await collectors.bulkFetch(mockCallCluster, mockEsClient);
|
||||
result = await collectors.bulkFetch(mockCallCluster, mockEsClient, mockSoClient);
|
||||
} catch (err) {
|
||||
// Do nothing
|
||||
}
|
||||
|
@ -131,7 +135,7 @@ describe('CollectorSet', () => {
|
|||
})
|
||||
);
|
||||
|
||||
const result = await collectors.bulkFetch(mockCallCluster, mockEsClient);
|
||||
const result = await collectors.bulkFetch(mockCallCluster, mockEsClient, mockSoClient);
|
||||
expect(result).toStrictEqual([
|
||||
{
|
||||
type: 'MY_TEST_COLLECTOR',
|
||||
|
@ -149,7 +153,7 @@ describe('CollectorSet', () => {
|
|||
} as any)
|
||||
);
|
||||
|
||||
const result = await collectors.bulkFetch(mockCallCluster, mockEsClient);
|
||||
const result = await collectors.bulkFetch(mockCallCluster, mockEsClient, mockSoClient);
|
||||
expect(result).toStrictEqual([
|
||||
{
|
||||
type: 'MY_TEST_COLLECTOR',
|
||||
|
@ -172,7 +176,7 @@ describe('CollectorSet', () => {
|
|||
})
|
||||
);
|
||||
|
||||
const result = await collectors.bulkFetch(mockCallCluster, mockEsClient);
|
||||
const result = await collectors.bulkFetch(mockCallCluster, mockEsClient, mockSoClient);
|
||||
expect(result).toStrictEqual([
|
||||
{
|
||||
type: 'MY_TEST_COLLECTOR',
|
||||
|
|
|
@ -18,7 +18,13 @@
|
|||
*/
|
||||
|
||||
import { snakeCase } from 'lodash';
|
||||
import { Logger, LegacyAPICaller, ElasticsearchClient } from 'kibana/server';
|
||||
import {
|
||||
Logger,
|
||||
LegacyAPICaller,
|
||||
ElasticsearchClient,
|
||||
ISavedObjectsRepository,
|
||||
SavedObjectsClientContract,
|
||||
} from 'kibana/server';
|
||||
import { Collector, CollectorOptions } from './collector';
|
||||
import { UsageCollector } from './usage_collector';
|
||||
|
||||
|
@ -125,6 +131,7 @@ export class CollectorSet {
|
|||
public bulkFetch = async (
|
||||
callCluster: LegacyAPICaller,
|
||||
esClient: ElasticsearchClient,
|
||||
soClient: SavedObjectsClientContract | ISavedObjectsRepository,
|
||||
collectors: Map<string, Collector<any, any>> = this.collectors
|
||||
) => {
|
||||
const responses = await Promise.all(
|
||||
|
@ -133,7 +140,7 @@ export class CollectorSet {
|
|||
try {
|
||||
return {
|
||||
type: collector.type,
|
||||
result: await collector.fetch({ callCluster, esClient }),
|
||||
result: await collector.fetch({ callCluster, esClient, soClient }),
|
||||
};
|
||||
} catch (err) {
|
||||
this.logger.warn(err);
|
||||
|
@ -155,9 +162,18 @@ export class CollectorSet {
|
|||
return this.makeCollectorSetFromArray(filtered);
|
||||
};
|
||||
|
||||
public bulkFetchUsage = async (callCluster: LegacyAPICaller, esClient: ElasticsearchClient) => {
|
||||
public bulkFetchUsage = async (
|
||||
callCluster: LegacyAPICaller,
|
||||
esClient: ElasticsearchClient,
|
||||
savedObjectsClient: SavedObjectsClientContract | ISavedObjectsRepository
|
||||
) => {
|
||||
const usageCollectors = this.getFilteredCollectorSet((c) => c instanceof UsageCollector);
|
||||
return await this.bulkFetch(callCluster, esClient, usageCollectors.collectors);
|
||||
return await this.bulkFetch(
|
||||
callCluster,
|
||||
esClient,
|
||||
savedObjectsClient,
|
||||
usageCollectors.collectors
|
||||
);
|
||||
};
|
||||
|
||||
// convert an array of fetched stats results into key/object
|
||||
|
|
|
@ -26,8 +26,10 @@ import { first } from 'rxjs/operators';
|
|||
import {
|
||||
ElasticsearchClient,
|
||||
IRouter,
|
||||
ISavedObjectsRepository,
|
||||
LegacyAPICaller,
|
||||
MetricsServiceSetup,
|
||||
SavedObjectsClientContract,
|
||||
ServiceStatus,
|
||||
ServiceStatusLevels,
|
||||
} from '../../../../../core/server';
|
||||
|
@ -64,9 +66,10 @@ export function registerStatsRoute({
|
|||
}) {
|
||||
const getUsage = async (
|
||||
callCluster: LegacyAPICaller,
|
||||
esClient: ElasticsearchClient
|
||||
esClient: ElasticsearchClient,
|
||||
savedObjectsClient: SavedObjectsClientContract | ISavedObjectsRepository
|
||||
): Promise<any> => {
|
||||
const usage = await collectorSet.bulkFetchUsage(callCluster, esClient);
|
||||
const usage = await collectorSet.bulkFetchUsage(callCluster, esClient, savedObjectsClient);
|
||||
return collectorSet.toObject(usage);
|
||||
};
|
||||
|
||||
|
@ -101,6 +104,7 @@ export function registerStatsRoute({
|
|||
if (isExtended) {
|
||||
const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
const savedObjectsClient = context.core.savedObjects.client;
|
||||
|
||||
if (shouldGetUsage) {
|
||||
const collectorsReady = await collectorSet.areAllCollectorsReady();
|
||||
|
@ -109,7 +113,9 @@ export function registerStatsRoute({
|
|||
}
|
||||
}
|
||||
|
||||
const usagePromise = shouldGetUsage ? getUsage(callCluster, esClient) : Promise.resolve({});
|
||||
const usagePromise = shouldGetUsage
|
||||
? getUsage(callCluster, esClient, savedObjectsClient)
|
||||
: Promise.resolve({});
|
||||
const [usage, clusterUuid] = await Promise.all([usagePromise, getClusterUuid(callCluster)]);
|
||||
|
||||
let modifiedUsage = usage;
|
||||
|
|
|
@ -17,7 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { elasticsearchServiceMock } from '../../../../src/core/server/mocks';
|
||||
import {
|
||||
elasticsearchServiceMock,
|
||||
savedObjectsRepositoryMock,
|
||||
} from '../../../../src/core/server/mocks';
|
||||
|
||||
import { CollectorOptions } from './collector/collector';
|
||||
import { UsageCollectionSetup, CollectorFetchContext } from './index';
|
||||
|
@ -52,6 +55,7 @@ export function createCollectorFetchContextMock(): jest.Mocked<CollectorFetchCon
|
|||
const collectorFetchClientsMock: jest.Mocked<CollectorFetchContext> = {
|
||||
callCluster: elasticsearchServiceMock.createLegacyClusterClient().callAsInternalUser,
|
||||
esClient: elasticsearchServiceMock.createClusterClient().asInternalUser,
|
||||
soClient: savedObjectsRepositoryMock.create(),
|
||||
};
|
||||
return collectorFetchClientsMock;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
CustomHttpResponseOptions,
|
||||
ResponseError,
|
||||
IClusterClient,
|
||||
SavedObjectsServiceStart,
|
||||
} from 'kibana/server';
|
||||
import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server';
|
||||
import {
|
||||
|
@ -76,6 +77,7 @@ export class Plugin {
|
|||
private legacyShimDependencies = {} as LegacyShimDependencies;
|
||||
private bulkUploader: IBulkUploader = {} as IBulkUploader;
|
||||
private telemetryElasticsearchClient: IClusterClient | undefined;
|
||||
private telemetrySavedObjectsService: SavedObjectsServiceStart | undefined;
|
||||
|
||||
constructor(initializerContext: PluginInitializerContext) {
|
||||
this.initializerContext = initializerContext;
|
||||
|
@ -145,14 +147,15 @@ export class Plugin {
|
|||
|
||||
// Initialize telemetry
|
||||
if (plugins.telemetryCollectionManager) {
|
||||
registerMonitoringCollection(
|
||||
plugins.telemetryCollectionManager,
|
||||
this.cluster,
|
||||
() => this.telemetryElasticsearchClient,
|
||||
{
|
||||
registerMonitoringCollection({
|
||||
telemetryCollectionManager: plugins.telemetryCollectionManager,
|
||||
esCluster: this.cluster,
|
||||
esClientGetter: () => this.telemetryElasticsearchClient,
|
||||
soServiceGetter: () => this.telemetrySavedObjectsService,
|
||||
customContext: {
|
||||
maxBucketSize: config.ui.max_bucket_size,
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Register collector objects for stats to show up in the APIs
|
||||
|
@ -249,12 +252,15 @@ export class Plugin {
|
|||
};
|
||||
}
|
||||
|
||||
start({ elasticsearch }: CoreStart) {
|
||||
start({ elasticsearch, savedObjects }: CoreStart) {
|
||||
// TODO: For the telemetry plugin to work, we need to provide the new ES client.
|
||||
// The new client should be inititalized with a similar config to `this.cluster` but, since we're not using
|
||||
// the new client in Monitoring Telemetry collection yet, setting the local client allos progress for now.
|
||||
// the new client in Monitoring Telemetry collection yet, setting the local client allows progress for now.
|
||||
// The usage collector `fetch` method has been refactored to accept a `collectorFetchContext` object,
|
||||
// exposing both es clients and the saved objects client.
|
||||
// We will update the client in a follow up PR.
|
||||
this.telemetryElasticsearchClient = elasticsearch.client;
|
||||
this.telemetrySavedObjectsService = savedObjects;
|
||||
}
|
||||
|
||||
stop() {
|
||||
|
|
|
@ -16,6 +16,7 @@ describe('get_all_stats', () => {
|
|||
const end = 1;
|
||||
const callCluster = sinon.stub();
|
||||
const esClient = sinon.stub();
|
||||
const soClient = sinon.stub();
|
||||
|
||||
const esClusters = [
|
||||
{ cluster_uuid: 'a' },
|
||||
|
@ -178,6 +179,7 @@ describe('get_all_stats', () => {
|
|||
{
|
||||
callCluster: callCluster as any,
|
||||
esClient: esClient as any,
|
||||
soClient: soClient as any,
|
||||
usageCollection: {} as any,
|
||||
start,
|
||||
end,
|
||||
|
@ -204,6 +206,7 @@ describe('get_all_stats', () => {
|
|||
{
|
||||
callCluster: callCluster as any,
|
||||
esClient: esClient as any,
|
||||
soClient: soClient as any,
|
||||
usageCollection: {} as any,
|
||||
start,
|
||||
end,
|
||||
|
|
|
@ -28,7 +28,7 @@ export interface CustomContext {
|
|||
*/
|
||||
export const getAllStats: StatsGetter<CustomContext> = async (
|
||||
clustersDetails,
|
||||
{ callCluster, start, end, esClient },
|
||||
{ callCluster, start, end, esClient, soClient },
|
||||
{ maxBucketSize }
|
||||
) => {
|
||||
const clusterUuids = clustersDetails.map((clusterDetails) => clusterDetails.clusterUuid);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import sinon from 'sinon';
|
||||
import { elasticsearchServiceMock } from 'src/core/server/mocks';
|
||||
import { elasticsearchServiceMock, savedObjectsRepositoryMock } from 'src/core/server/mocks';
|
||||
import {
|
||||
getClusterUuids,
|
||||
fetchClusterUuids,
|
||||
|
@ -15,6 +15,7 @@ import {
|
|||
describe('get_cluster_uuids', () => {
|
||||
const callCluster = sinon.stub();
|
||||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
const soClient = savedObjectsRepositoryMock.create();
|
||||
const response = {
|
||||
aggregations: {
|
||||
cluster_uuids: {
|
||||
|
@ -32,9 +33,12 @@ describe('get_cluster_uuids', () => {
|
|||
it('returns cluster UUIDs', async () => {
|
||||
callCluster.withArgs('search').returns(Promise.resolve(response));
|
||||
expect(
|
||||
await getClusterUuids({ callCluster, esClient, start, end, usageCollection: {} as any }, {
|
||||
maxBucketSize: 1,
|
||||
} as any)
|
||||
await getClusterUuids(
|
||||
{ callCluster, esClient, soClient, start, end, usageCollection: {} as any },
|
||||
{
|
||||
maxBucketSize: 1,
|
||||
} as any
|
||||
)
|
||||
).toStrictEqual(expectedUuids);
|
||||
});
|
||||
});
|
||||
|
@ -43,9 +47,12 @@ describe('get_cluster_uuids', () => {
|
|||
it('searches for clusters', async () => {
|
||||
callCluster.returns(Promise.resolve(response));
|
||||
expect(
|
||||
await fetchClusterUuids({ callCluster, esClient, start, end, usageCollection: {} as any }, {
|
||||
maxBucketSize: 1,
|
||||
} as any)
|
||||
await fetchClusterUuids(
|
||||
{ callCluster, esClient, soClient, start, end, usageCollection: {} as any },
|
||||
{
|
||||
maxBucketSize: 1,
|
||||
} as any
|
||||
)
|
||||
).toStrictEqual(response);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,21 +4,33 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ILegacyCustomClusterClient, IClusterClient } from 'kibana/server';
|
||||
import {
|
||||
ILegacyCustomClusterClient,
|
||||
IClusterClient,
|
||||
SavedObjectsServiceStart,
|
||||
} from 'kibana/server';
|
||||
import { TelemetryCollectionManagerPluginSetup } from 'src/plugins/telemetry_collection_manager/server';
|
||||
import { getAllStats, CustomContext } from './get_all_stats';
|
||||
import { getClusterUuids } from './get_cluster_uuids';
|
||||
import { getLicenses } from './get_licenses';
|
||||
|
||||
export function registerMonitoringCollection(
|
||||
telemetryCollectionManager: TelemetryCollectionManagerPluginSetup,
|
||||
esCluster: ILegacyCustomClusterClient,
|
||||
esClientGetter: () => IClusterClient | undefined,
|
||||
customContext: CustomContext
|
||||
) {
|
||||
export function registerMonitoringCollection({
|
||||
telemetryCollectionManager,
|
||||
esCluster,
|
||||
esClientGetter,
|
||||
soServiceGetter,
|
||||
customContext,
|
||||
}: {
|
||||
telemetryCollectionManager: TelemetryCollectionManagerPluginSetup;
|
||||
esCluster: ILegacyCustomClusterClient;
|
||||
esClientGetter: () => IClusterClient | undefined;
|
||||
soServiceGetter: () => SavedObjectsServiceStart | undefined;
|
||||
customContext: CustomContext;
|
||||
}) {
|
||||
telemetryCollectionManager.setCollection({
|
||||
esCluster,
|
||||
esClientGetter,
|
||||
soServiceGetter,
|
||||
title: 'monitoring',
|
||||
priority: 2,
|
||||
statsGetter: getAllStats,
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
CoreStart,
|
||||
Plugin,
|
||||
IClusterClient,
|
||||
SavedObjectsServiceStart,
|
||||
} from 'kibana/server';
|
||||
import { TelemetryCollectionManagerPluginSetup } from 'src/plugins/telemetry_collection_manager/server';
|
||||
import { getClusterUuids, getLocalLicense } from '../../../../src/plugins/telemetry/server';
|
||||
|
@ -21,12 +22,14 @@ interface TelemetryCollectionXpackDepsSetup {
|
|||
|
||||
export class TelemetryCollectionXpackPlugin implements Plugin {
|
||||
private elasticsearchClient?: IClusterClient;
|
||||
private savedObjectsService?: SavedObjectsServiceStart;
|
||||
constructor(initializerContext: PluginInitializerContext) {}
|
||||
|
||||
public setup(core: CoreSetup, { telemetryCollectionManager }: TelemetryCollectionXpackDepsSetup) {
|
||||
telemetryCollectionManager.setCollection({
|
||||
esCluster: core.elasticsearch.legacy.client,
|
||||
esClientGetter: () => this.elasticsearchClient,
|
||||
soServiceGetter: () => this.savedObjectsService,
|
||||
title: 'local_xpack',
|
||||
priority: 1,
|
||||
statsGetter: getStatsWithXpack,
|
||||
|
@ -37,5 +40,6 @@ export class TelemetryCollectionXpackPlugin implements Plugin {
|
|||
|
||||
public start(core: CoreStart) {
|
||||
this.elasticsearchClient = core.elasticsearch.client;
|
||||
this.savedObjectsService = core.savedObjects;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,11 @@ import {
|
|||
ServiceStatus,
|
||||
ServiceStatusLevels,
|
||||
} from '../../../../../src/core/server';
|
||||
import { contextServiceMock, elasticsearchServiceMock } from '../../../../../src/core/server/mocks';
|
||||
import {
|
||||
contextServiceMock,
|
||||
elasticsearchServiceMock,
|
||||
savedObjectsServiceMock,
|
||||
} from '../../../../../src/core/server/mocks';
|
||||
import { createHttpServer } from '../../../../../src/core/server/test_utils';
|
||||
import { registerSettingsRoute } from './settings';
|
||||
|
||||
|
@ -42,6 +46,9 @@ describe('/api/settings', () => {
|
|||
asCurrentUser: elasticsearchServiceMock.createScopedClusterClient().asCurrentUser,
|
||||
},
|
||||
},
|
||||
savedObjects: {
|
||||
client: savedObjectsServiceMock.create(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -45,6 +45,7 @@ export function registerSettingsRoute({
|
|||
const collectorFetchContext = {
|
||||
callCluster: callAsCurrentUser,
|
||||
esClient: context.core.elasticsearch.client.asCurrentUser,
|
||||
soClient: context.core.savedObjects.client,
|
||||
};
|
||||
|
||||
const settingsCollector = usageCollection.getCollectorByType(KIBANA_SETTINGS_TYPE) as
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue