Skip to content

Commit

Permalink
[Fleet] Telemetry for space awareness (#206493)
Browse files Browse the repository at this point in the history
  • Loading branch information
nchaulet authored Jan 14, 2025
1 parent 9618e42 commit 9c01db9
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import _ from 'lodash';
import { OUTPUT_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT } from '../../common';
import type { OutputSOAttributes, AgentPolicy } from '../types';
import { getAgentPolicySavedObjectType } from '../services/agent_policy';
import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server';

export interface AgentPoliciesUsage {
count: number;
output_types: string[];
count_with_global_data_tags: number;
count_with_non_default_space: number;
avg_number_global_data_tags_per_policy?: number;
}

Expand All @@ -33,17 +35,27 @@ export const getAgentPoliciesUsage = async (
const outputsById = _.keyBy(outputs, 'id');

const agentPolicySavedObjectType = await getAgentPolicySavedObjectType();
const { saved_objects: agentPolicies, total: totalAgentPolicies } =
await soClient.find<AgentPolicy>({
type: agentPolicySavedObjectType,
page: 1,
perPage: SO_SEARCH_LIMIT,
});
const { saved_objects: agentPolicies, total: totalAgentPolicies } = await soClient.find<
Pick<AgentPolicy, 'data_output_id' | 'monitoring_output_id' | 'global_data_tags'>
>({
type: agentPolicySavedObjectType,
page: 1,
perPage: SO_SEARCH_LIMIT,
namespaces: ['*'],
fields: ['monitoring_output_id', 'data_output_id', 'global_data_tags'],
});

let countWithNonDefaultSpace = 0;
const uniqueOutputIds = new Set<string>();
agentPolicies.forEach((agentPolicy) => {
uniqueOutputIds.add(agentPolicy.attributes.monitoring_output_id || defaultOutputId);
uniqueOutputIds.add(agentPolicy.attributes.data_output_id || defaultOutputId);
if (
(agentPolicy.namespaces?.length ?? 0) > 0 &&
agentPolicy.namespaces?.some((namespace) => namespace !== DEFAULT_NAMESPACE_STRING)
) {
countWithNonDefaultSpace++;
}
uniqueOutputIds.add(agentPolicy.attributes?.monitoring_output_id || defaultOutputId);
uniqueOutputIds.add(agentPolicy.attributes?.data_output_id || defaultOutputId);
});

const uniqueOutputTypes = new Set(
Expand All @@ -56,10 +68,10 @@ export const getAgentPoliciesUsage = async (

const [policiesWithGlobalDataTag, totalNumberOfGlobalDataTagFields] = agentPolicies.reduce(
([policiesNumber, fieldsNumber], agentPolicy) => {
if (agentPolicy.attributes.global_data_tags?.length ?? 0 > 0) {
if (agentPolicy.attributes?.global_data_tags?.length ?? 0 > 0) {
return [
policiesNumber + 1,
fieldsNumber + (agentPolicy.attributes.global_data_tags?.length ?? 0),
fieldsNumber + (agentPolicy.attributes?.global_data_tags?.length ?? 0),
];
}
return [policiesNumber, fieldsNumber];
Expand All @@ -70,6 +82,7 @@ export const getAgentPoliciesUsage = async (
return {
count: totalAgentPolicies,
output_types: Array.from(uniqueOutputTypes),
count_with_non_default_space: countWithNonDefaultSpace,
count_with_global_data_tags: policiesWithGlobalDataTag,
avg_number_global_data_tags_per_policy:
policiesWithGlobalDataTag > 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ export interface Usage {

export interface FleetUsage extends Usage, AgentData {
fleet_server_config: { policies: Array<{ input_config: any }> };
agent_policies: { count: number; output_types: string[] };
agent_policies: {
count: number;
output_types: string[];
count_with_global_data_tags: number;
count_with_non_default_space: number;
};
agent_logs_panics_last_hour: AgentPanicLogsData['agent_logs_panics_last_hour'];
agent_logs_top_errors?: string[];
fleet_server_logs_top_errors?: string[];
Expand All @@ -55,6 +60,7 @@ export const fetchFleetUsage = async (
if (!soClient || !esClient) {
return;
}

const usage = {
agents_enabled: getIsAgentsEnabled(config),
agents: await getAgentUsage(soClient, esClient),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,7 @@ describe('fleet usage telemetry', () => {
count: 3,
output_types: expect.arrayContaining(['elasticsearch', 'logstash', 'third_type']),
count_with_global_data_tags: 2,
count_with_non_default_space: 0,
avg_number_global_data_tags_per_policy: 2,
},
agent_logs_panics_last_hour: [
Expand Down
8 changes: 6 additions & 2 deletions x-pack/platform/plugins/shared/fleet/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ import {
import {
fetchAgentsUsage,
fetchFleetUsage,
type FleetUsage,
registerFleetUsageCollector,
} from './collectors/register';
import { FleetArtifactsClient } from './services/artifacts';
Expand Down Expand Up @@ -198,6 +199,7 @@ export interface FleetAppContext {
unenrollInactiveAgentsTask: UnenrollInactiveAgentsTask;
deleteUnenrolledAgentsTask: DeleteUnenrolledAgentsTask;
taskManagerStart?: TaskManagerStartContract;
fetchUsage?: (abortController: AbortController) => Promise<FleetUsage | undefined>;
}

export type FleetSetupContract = void;
Expand Down Expand Up @@ -301,6 +303,7 @@ export class FleetPlugin
private packageService?: PackageService;
private packagePolicyService?: PackagePolicyService;
private policyWatcher?: PolicyWatcher;
private fetchUsage?: (abortController: AbortController) => Promise<FleetUsage | undefined>;

constructor(private readonly initializerContext: PluginInitializerContext) {
this.config$ = this.initializerContext.config.create<FleetConfigType>();
Expand Down Expand Up @@ -603,9 +606,9 @@ export class FleetPlugin

// Register usage collection
registerFleetUsageCollector(core, config, deps.usageCollection);
const fetch = async (abortController: AbortController) =>
this.fetchUsage = async (abortController: AbortController) =>
await fetchFleetUsage(core, config, abortController);
this.fleetUsageSender = new FleetUsageSender(deps.taskManager, core, fetch);
this.fleetUsageSender = new FleetUsageSender(deps.taskManager, core, this.fetchUsage);
registerFleetUsageLogger(deps.taskManager, async () => fetchAgentsUsage(core, config));

const fetchAgents = async (abortController: AbortController) =>
Expand Down Expand Up @@ -694,6 +697,7 @@ export class FleetPlugin
unenrollInactiveAgentsTask: this.unenrollInactiveAgentsTask!,
deleteUnenrolledAgentsTask: this.deleteUnenrolledAgentsTask!,
taskManagerStart: plugins.taskManager,
fetchUsage: this.fetchUsage,
});
licenseService.start(plugins.licensing.license$);
this.telemetryEventsSender.start(plugins.telemetry, core).catch(() => {});
Expand Down
35 changes: 34 additions & 1 deletion x-pack/platform/plugins/shared/fleet/server/routes/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,27 @@ export const GenerateServiceTokenResponseSchema = schema.object({

export const registerRoutes = (router: FleetAuthzRouter, config: FleetConfigType) => {
const experimentalFeatures = parseExperimentalConfigValue(config.enableExperimental);

router.versioned
.get({
path: '/internal/fleet/telemetry/usage',
access: 'internal',
security: {
authz: {
requiredPrivileges: [
FLEET_API_PRIVILEGES.AGENTS.ALL,
FLEET_API_PRIVILEGES.AGENT_POLICIES.ALL,
FLEET_API_PRIVILEGES.SETTINGS.ALL,
],
},
},
})
.addVersion(
{
version: API_VERSIONS.internal.v1,
validate: {},
},
getTelemetryUsageHandler
);
if (experimentalFeatures.useSpaceAwareness) {
router.versioned
.post({
Expand Down Expand Up @@ -288,3 +308,16 @@ export const registerRoutes = (router: FleetAuthzRouter, config: FleetConfigType
generateServiceTokenHandler
);
};
const getTelemetryUsageHandler: FleetRequestHandler = async (context, request, response) => {
const fetchUsage = appContextService.getFetchUsage();
if (!fetchUsage) {
throw new Error('Fetch usage is not initialized.');
}
const usage = await fetchUsage(new AbortController());

return response.ok({
body: {
usage,
},
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import type { MessageSigningServiceInterface } from '..';

import type { BulkActionsResolver } from './agents/bulk_actions_resolver';
import { type UninstallTokenServiceInterface } from './security/uninstall_token_service';
import type { FleetUsage } from '../collectors/register';

class AppContextService {
private encryptedSavedObjects: EncryptedSavedObjectsClient | undefined;
Expand Down Expand Up @@ -80,6 +81,7 @@ class AppContextService {
private messageSigningService: MessageSigningServiceInterface | undefined;
private uninstallTokenService: UninstallTokenServiceInterface | undefined;
private taskManagerStart: TaskManagerStartContract | undefined;
private fetchUsage?: (abortController: AbortController) => Promise<FleetUsage | undefined>;

public start(appContext: FleetAppContext) {
this.data = appContext.data;
Expand All @@ -105,6 +107,7 @@ class AppContextService {
this.messageSigningService = appContext.messageSigningService;
this.uninstallTokenService = appContext.uninstallTokenService;
this.taskManagerStart = appContext.taskManagerStart;
this.fetchUsage = appContext.fetchUsage;

if (appContext.config$) {
this.config$ = appContext.config$;
Expand Down Expand Up @@ -344,6 +347,10 @@ class AppContextService {
public getUninstallTokenService() {
return this.uninstallTokenService;
}

public getFetchUsage() {
return this.fetchUsage;
}
}

export const appContextService = new AppContextService();
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,12 @@ export const fleetUsagesSchema: RootSchema<any> = {
description: 'Number of agent policies using global data tags',
},
},
count_with_non_default_space: {
type: 'long',
_meta: {
description: 'Number of agent policies using another space than the default one',
},
},
avg_number_global_data_tags_per_policy: {
type: 'long',
_meta: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
GetUninstallTokensMetadataResponse,
} from '@kbn/fleet-plugin/common/types/rest_spec/uninstall_token';
import { SimplifiedPackagePolicy } from '@kbn/fleet-plugin/common/services/simplified_package_policy_helper';
import { type FleetUsage } from '@kbn/fleet-plugin/server/collectors/register';
import { testUsers } from '../test_users';

export class SpaceTestApiClient {
Expand Down Expand Up @@ -375,6 +376,16 @@ export class SpaceTestApiClient {

return res;
}
// Fleet Usage
async getFleetUsage(spaceId?: string): Promise<{ usage: FleetUsage }> {
const { body: res } = await this.supertest
.get(`${this.getBaseUrl(spaceId)}/internal/fleet/telemetry/usage`)
.set('kbn-xsrf', 'xxxx')
.set('elastic-api-version', '1')
.expect(200);

return res;
}
// Space Settings
async getSpaceSettings(spaceId?: string): Promise<GetSpaceSettingsResponse> {
const { body: res } = await this.supertest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ export default function loadTests({ loadTestFile }) {
loadTestFile(require.resolve('./actions'));
loadTestFile(require.resolve('./change_space_agent_policies'));
loadTestFile(require.resolve('./space_awareness_migration'));
loadTestFile(require.resolve('./telemetry'));
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
import { SpaceTestApiClient } from './api_helper';

export default function (providerContext: FtrProviderContext) {
const { getService } = providerContext;
const supertest = getService('supertest');
const kibanaServer = getService('kibanaServer');
const spaces = getService('spaces');
let TEST_SPACE_1: string;

const apiClient = new SpaceTestApiClient(supertest);

describe('space_telemetry', function () {
before(async () => {
TEST_SPACE_1 = spaces.getDefaultTestSpace();
await kibanaServer.savedObjects.cleanStandardList();
await kibanaServer.savedObjects.cleanStandardList({
space: TEST_SPACE_1,
});
await spaces.createTestSpace(TEST_SPACE_1);
await apiClient.postEnableSpaceAwareness();
await Promise.all([
apiClient.createAgentPolicy(),
apiClient.createAgentPolicy(),
apiClient.createAgentPolicy(TEST_SPACE_1),
apiClient.createAgentPolicy(TEST_SPACE_1),
apiClient.createAgentPolicy(TEST_SPACE_1),
]);
});

after(async () => {
await kibanaServer.savedObjects.cleanStandardList();
await kibanaServer.savedObjects.cleanStandardList({
space: TEST_SPACE_1,
});
});

it('return correct fleet usage', async () => {
const res = await apiClient.getFleetUsage();
expect(res.usage.agent_policies.count).to.eql(5);
expect(res.usage.agent_policies.count_with_non_default_space).to.eql(3);
});
});
}

0 comments on commit 9c01db9

Please sign in to comment.