Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fleet] Add fleet server telemetry #101400

Merged
merged 7 commits into from
Jun 8, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions x-pack/plugins/fleet/server/collectors/agent_collectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
* 2.0.
*/

import type { SavedObjectsClient } from 'kibana/server';
import type { ElasticsearchClient } from 'kibana/server';
import type { SavedObjectsClient, ElasticsearchClient } from 'kibana/server';

import type { FleetConfigType } from '../../common/types';
import * as AgentService from '../services/agents';
Expand Down
73 changes: 73 additions & 0 deletions x-pack/plugins/fleet/server/collectors/fleet_server_collector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* 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 type { SavedObjectsClient, ElasticsearchClient } from 'kibana/server';

import { packagePolicyService, settingsService } from '../services';
import { getAgentStatusForAgentPolicy } from '../services/agents';
import { isFleetServerSetup } from '../services/fleet_server';

const DEFAULT_USAGE = {
total_all_statuses: 0,
healthy: 0,
num_host_urls: 0,
};

export interface FleetServerUsage {
healthy: number;
total_all_statuses: number;
num_host_urls: number;
}

export const getFleetServerUsage = async (
soClient?: SavedObjectsClient,
esClient?: ElasticsearchClient
): Promise<any> => {
if (!soClient || !esClient || !(await isFleetServerSetup())) {
return DEFAULT_USAGE;
}

const numHostsUrls =
(await settingsService.getSettings(soClient)).fleet_server_hosts?.length ?? 0;

// Find all policies with Fleet server than query agent status

let hasMore = true;
const policyIds = new Set<string>();
let page = 1;
while (hasMore) {
const res = await packagePolicyService.list(soClient, {
page: page++,
perPage: 20,
kuery: 'ingest-package-policies.package.name:fleet_server',
});

for (const item of res.items) {
policyIds.add(item.policy_id);
}

if (res.items.length === 0) {
hasMore = false;
}
}

if (policyIds.size === 0) {
return DEFAULT_USAGE;
}

const { total, inactive, online } = await getAgentStatusForAgentPolicy(
soClient,
esClient,
undefined,
[...policyIds].map((policyId) => `(policy_id:"${policyId}")`).join(' or ')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need the [...policyIds]? .map won't mutate the array, so this could maybe be policyIds.map directly w/o the copy.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

policyIds is not an array it's a set (I want uniq values) so I think we need the spread operator here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I understand. Array.from might convey the intent more explicitly in that case: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from

);

return {
total_all_statuses: total + inactive,
healthy: online,
num_host_urls: numHostsUrls,
};
};
25 changes: 25 additions & 0 deletions x-pack/plugins/fleet/server/collectors/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ import type { AgentUsage } from './agent_collectors';
import { getInternalClients } from './helpers';
import { getPackageUsage } from './package_collectors';
import type { PackageUsage } from './package_collectors';
import { getFleetServerUsage } from './fleet_server_collector';
import type { FleetServerUsage } from './fleet_server_collector';

interface Usage {
agents_enabled: boolean;
agents: AgentUsage;
packages: PackageUsage[];
fleet_server: FleetServerUsage;
}

export function registerFleetUsageCollector(
Expand All @@ -43,6 +46,7 @@ export function registerFleetUsageCollector(
return {
agents_enabled: getIsAgentsEnabled(config),
agents: await getAgentUsage(config, soClient, esClient),
fleet_server: await getFleetServerUsage(soClient, esClient),
packages: await getPackageUsage(soClient),
};
},
Expand Down Expand Up @@ -80,6 +84,27 @@ export function registerFleetUsageCollector(
},
},
},
fleet_server: {
total_all_statuses: {
type: 'long',
_meta: {
description:
'The total number of Fleet Server agents in any state, both enrolled and inactive.',
},
},
healthy: {
type: 'long',
_meta: {
description: 'The total number of enrolled Fleet Server agents in a healthy state.',
},
},
num_host_urls: {
type: 'long',
_meta: {
description: 'The number of Fleet Server hosts configured in Fleet settings.',
},
},
},
packages: {
type: 'array',
items: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2055,6 +2055,28 @@
}
}
},
"fleet_server": {
"properties": {
"total_all_statuses": {
"type": "long",
"_meta": {
"description": "The total number of Fleet Server agents in any state, both enrolled and inactive."
}
},
"healthy": {
"type": "long",
"_meta": {
"description": "The total number of enrolled Fleet Server agents in a healthy state."
}
},
"num_host_urls": {
"type": "long",
"_meta": {
"description": "The number of Fleet Server hosts configured in Fleet settings."
}
}
}
},
"packages": {
"type": "array",
"items": {
Expand Down
122 changes: 122 additions & 0 deletions x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* 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 { skipIfNoDockerRegistry } from '../helpers';
import { setupFleetAndAgents } from './agents/services';

export default function (providerContext: FtrProviderContext) {
const { getService } = providerContext;
const supertest = getService('supertest');
const es = getService('es');
const esArchiver = getService('esArchiver');

let agentCount = 0;
async function generateAgent(status: string, policyId: string) {
let data: any = {};

switch (status) {
case 'unhealthy':
data = { last_checkin_status: 'error' };
break;
case 'offline':
data = { last_checkin: '2017-06-07T18:59:04.498Z' };
break;
default:
data = { last_checkin: new Date().toISOString() };
}

await es.index({
index: '.fleet-agents',
body: {
id: `agent-${++agentCount}`,
active: true,
last_checkin: new Date().toISOString(),
policy_id: policyId,
policy_revision: 1,
...data,
},
refresh: 'wait_for',
});
}

describe('fleet_telemetry', () => {
skipIfNoDockerRegistry(providerContext);
before(async () => {
await esArchiver.load('empty_kibana');
await esArchiver.load('fleet/empty_fleet_server');
});

setupFleetAndAgents(providerContext);

after(async () => {
await esArchiver.unload('empty_kibana');
await esArchiver.unload('fleet/empty_fleet_server');
});

before(async () => {
// Get FleetServer policy id
const { body: apiResponse } = await supertest.get(`/api/fleet/agent_policies`).expect(200);
const defaultFleetServerPolicy = apiResponse.items.find(
(item: any) => item.is_default_fleet_server
);

const defaultServerPolicy = apiResponse.items.find((item: any) => item.is_default);

if (!defaultFleetServerPolicy) {
throw new Error('No default Fleet server policy');
}

if (!defaultServerPolicy) {
throw new Error('No default policy');
}

await supertest
.put(`/api/fleet/settings`)
.set('kbn-xsrf', 'xxxx')
.send({ fleet_server_hosts: ['https://test1.fr', 'https://test2.fr'] })
.expect(200);

// Default Fleet Server
await generateAgent('healthy', defaultFleetServerPolicy.id);
await generateAgent('healthy', defaultFleetServerPolicy.id);
await generateAgent('unhealthy', defaultFleetServerPolicy.id);

// Default policy
await generateAgent('healthy', defaultServerPolicy.id);
await generateAgent('offline', defaultServerPolicy.id);
await generateAgent('unhealthy', defaultServerPolicy.id);
});

it('should return the correct telemetry values for fleet', async () => {
const {
body: [apiResponse],
} = await supertest
.post(`/api/telemetry/v2/clusters/_stats`)
.set('kbn-xsrf', 'xxxx')
.send({
unencrypted: true,
})
.expect(200);

expect(apiResponse.stack_stats.kibana.plugins.fleet.agents).eql({
total_enrolled: 6,
healthy: 3,
unhealthy: 2,
offline: 1,
total_all_statuses: 6,
});

expect(apiResponse.stack_stats.kibana.plugins.fleet.fleet_server).eql({
total_all_statuses: 3,
healthy: 2,
num_host_urls: 2,
});
});
});
}
3 changes: 3 additions & 0 deletions x-pack/test/fleet_api_integration/apis/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,8 @@ export default function ({ loadTestFile }) {

// Outputs
loadTestFile(require.resolve('./outputs'));

// Telemetry
loadTestFile(require.resolve('./fleet_telemetry'));
});
}