Skip to content

Commit

Permalink
[APM] Inject agent config directly into APM Fleet policies (elastic#9…
Browse files Browse the repository at this point in the history
  • Loading branch information
ogupte committed May 4, 2021
1 parent ad9f1c3 commit 3f646d8
Show file tree
Hide file tree
Showing 12 changed files with 521 additions and 22 deletions.
10 changes: 10 additions & 0 deletions x-pack/plugins/apm/common/alert_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export enum AlertType {
TransactionErrorRate = 'apm.transaction_error_rate',
TransactionDuration = 'apm.transaction_duration',
TransactionDurationAnomaly = 'apm.transaction_duration_anomaly',
AgentConfigFleetSync = 'apm.agent_config_fleet_sync',
}

export const THRESHOLD_MET_GROUP_ID = 'threshold_met';
Expand Down Expand Up @@ -72,6 +73,15 @@ export const ALERT_TYPES_CONFIG: Record<
minimumLicenseRequired: 'basic',
producer: 'apm',
},
[AlertType.AgentConfigFleetSync]: {
name: i18n.translate('xpack.apm.agentConfigFleetSyncAlert.name', {
defaultMessage: 'Central agent configuration updates',
}),
actionGroups: [THRESHOLD_MET_GROUP],
defaultActionGroupId: THRESHOLD_MET_GROUP_ID,
minimumLicenseRequired: 'basic',
producer: 'apm',
},
};

export const ANOMALY_ALERT_SEVERITY_TYPES = [
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/apm/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"security",
"ml",
"home",
"maps"
"maps",
"fleet"
],
"server": true,
"ui": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,24 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { isEmpty } from 'lodash';
import React from 'react';
import React, { useState } from 'react';
import { useLocation } from 'react-router-dom';
import { callApmApi } from '../../../../services/rest/createCallApmApi';
import { useTrackPageview } from '../../../../../../observability/public';
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
import { useFetcher } from '../../../../hooks/use_fetcher';
import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher';
import { createAgentConfigurationHref } from '../../../shared/Links/apm/agentConfigurationLinks';
import { AgentConfigurationList } from './List';

const INITIAL_DATA = { configurations: [] };

async function enableFleetSync() {
return await callApmApi({
endpoint: 'POST /api/apm/settings/agent-configuration/fleet_sync',
signal: null,
});
}

export function AgentConfigurations() {
const { refetch, data = INITIAL_DATA, status } = useFetcher(
(callApmApi) =>
Expand All @@ -38,8 +46,37 @@ export function AgentConfigurations() {
useTrackPageview({ app: 'apm', path: 'agent_configuration' });
useTrackPageview({ app: 'apm', path: 'agent_configuration', delay: 15000 });

const {
data: packagePolicyInput,
status: packagePolicyInputStatus,
error: packagePolicyError,
refetch: refetchPackagePolicyInput,
} = useFetcher(
(callApmApi) =>
callApmApi({
endpoint:
'GET /api/apm/settings/agent-configuration/fleet-sync/package-policy-input',
}),
[],
{ preservePreviousData: false, showToastOnError: false }
);

const hasConfigurations = !isEmpty(data.configurations);

const isFleetSyncLoading =
packagePolicyInputStatus !== FETCH_STATUS.FAILURE &&
packagePolicyInputStatus !== FETCH_STATUS.SUCCESS;
const isFleetSyncUnavailable = packagePolicyError?.response?.status === 503;
const isFleetSyncEnabled = Boolean(
packagePolicyInputStatus === FETCH_STATUS.SUCCESS &&
packagePolicyInput?.agent_config.value &&
packagePolicyInput?.alert
);

const [isFleetSyncEnableLoading, setIsFleetSyncEnableLoading] = useState(
false
);

return (
<>
<EuiTitle size="l">
Expand All @@ -62,13 +99,38 @@ export function AgentConfigurations() {
<EuiTitle size="s">
<h2>
{i18n.translate(
'xpack.apm.agentConfig.configurationsPanelTitle',
'xpack.apm.agentConfig.configurationsPanel.title',
{ defaultMessage: 'Configurations' }
)}
</h2>
</EuiTitle>
</EuiFlexItem>

{!isFleetSyncLoading && !isFleetSyncUnavailable && (
<EuiFlexItem>
<div>
<EuiButton
disabled={isFleetSyncEnabled}
onClick={async () => {
setIsFleetSyncEnableLoading(true);
await enableFleetSync();
refetchPackagePolicyInput();
setIsFleetSyncEnableLoading(false);
}}
isLoading={isFleetSyncEnableLoading}
>
{isFleetSyncEnabled
? i18n.translate(
'xpack.apm.agentConfig.configurationsPanel.fleetSyncingEnabledLabel',
{ defaultMessage: 'Syncing with fleet policy' }
)
: i18n.translate(
'xpack.apm.agentConfig.configurationsPanel.enableFleetSyncButtonLabel',
{ defaultMessage: 'Sync with fleet policy' }
)}
</EuiButton>
</div>
</EuiFlexItem>
)}
{hasConfigurations ? <CreateConfigurationButton /> : null}
</EuiFlexGroup>

Expand Down Expand Up @@ -98,7 +160,7 @@ function CreateConfigurationButton() {
content={
!canSave &&
i18n.translate(
'xpack.apm.agentConfig.configurationsPanelTitle.noPermissionTooltipLabel',
'xpack.apm.agentConfig.configurationsPanel.title.noPermissionTooltipLabel',
{
defaultMessage:
"Your user role doesn't have permissions to create agent configurations",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* 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 { schema } from '@kbn/config-schema';
import { take } from 'rxjs/operators';
import { AlertType, ALERT_TYPES_CONFIG } from '../../../common/alert_types';
import { getApmIndices } from '../settings/apm_indices/get_apm_indices';
import { alertingEsClient } from './alerting_es_client';
import { RegisterRuleDependencies } from './register_apm_alerts';
import { createAPMLifecycleRuleType } from './create_apm_lifecycle_rule_type';
import { convertConfigSettingsToString } from '../settings/agent_configuration/convert_settings_to_string';
import { AgentConfiguration } from '../../../common/agent_configuration/configuration_types';
import { syncAgentConfigsToApmPackagePolicies } from '../fleet/sync_agent_configs_to_apm_package_policies';

const alertTypeConfig = ALERT_TYPES_CONFIG[AlertType.AgentConfigFleetSync];

export function registerAgentConfigFleetSyncAlertType({
registry,
config$,
getFleetPluginStart,
}: RegisterRuleDependencies) {
registry.registerType(
createAPMLifecycleRuleType({
id: AlertType.AgentConfigFleetSync,
name: alertTypeConfig.name,
actionGroups: alertTypeConfig.actionGroups,
defaultActionGroupId: alertTypeConfig.defaultActionGroupId,
validate: {
params: schema.any(),
},
actionVariables: {
context: [],
},
producer: 'apm',
minimumLicenseRequired: 'basic',
executor: async ({ services, state }) => {
const config = await config$.pipe(take(1)).toPromise();
const indices = await getApmIndices({
config,
savedObjectsClient: services.savedObjectsClient,
});

const searchParams = {
index: indices['apmAgentConfigurationIndex'],
body: {
size: 1,
query: {
match_all: {},
},
sort: { '@timestamp': 'desc' as const },
},
};

const response = await alertingEsClient({
scopedClusterClient: services.scopedClusterClient,
params: searchParams,
});
if (response.hits.total.value === 0) {
return {};
}

const { ['@timestamp']: lastTimestamp } = response.hits.hits[0]
._source as { '@timestamp': number };
// @ts-ignore
if (lastTimestamp > state.lastTimestamp) {
const fleetPluginStart = await getFleetPluginStart();
if (fleetPluginStart) {
services.logger.info(
`New agent configurations detected. Updating fleet policy...`
);
const configurationsSearchResponse = await alertingEsClient({
scopedClusterClient: services.scopedClusterClient,
params: {
index: indices['apmAgentConfigurationIndex'],
body: { size: 200, query: { match_all: {} } },
},
});
const agentConfigurations = configurationsSearchResponse.hits.hits
// @ts-ignore
.map(convertConfigSettingsToString)
.map((hit) => hit._source) as AgentConfiguration[];
await syncAgentConfigsToApmPackagePolicies({
fleetPluginStart,
savedObjectsClient: services.savedObjectsClient,
esClient: services.scopedClusterClient.asCurrentUser,
agentConfigurations,
});
services.logger.info(`Policy updated.`);
}
}
return { lastTimestamp };
},
})
);
}
4 changes: 4 additions & 0 deletions x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,21 @@ import { APMConfig } from '../..';
import { MlPluginSetup } from '../../../../ml/server';
import { registerTransactionErrorRateAlertType } from './register_transaction_error_rate_alert_type';
import { APMRuleRegistry } from '../../plugin';
import { registerAgentConfigFleetSyncAlertType } from './register_agent_config_fleet_sync_alert_type';
import { APMPluginStartDependencies } from '../../types';

export interface RegisterRuleDependencies {
registry: APMRuleRegistry;
ml?: MlPluginSetup;
config$: Observable<APMConfig>;
logger: Logger;
getFleetPluginStart: () => Promise<APMPluginStartDependencies['fleet']>;
}

export function registerApmAlerts(dependencies: RegisterRuleDependencies) {
registerTransactionDurationAlertType(dependencies);
registerTransactionDurationAnomalyAlertType(dependencies);
registerErrorCountAlertType(dependencies);
registerTransactionErrorRateAlertType(dependencies);
registerAgentConfigFleetSyncAlertType(dependencies);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 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 { AlertsClient } from 'x-pack/plugins/alerting/server';
import { APMPluginStartDependencies } from '../../types';

export async function createApmAgentFleetSyncAlert({
alertsClient,
}: {
alertsClient: AlertsClient;
}) {
return alertsClient?.create({
data: {
enabled: true,
name: 'APM - Agent config fleet sync',
tags: ['apm'],
alertTypeId: 'apm.agent_config_fleet_sync',
consumer: 'apm',
schedule: { interval: '1m' },
actions: [],
params: {},
throttle: null,
notifyWhen: null,
},
});
}
export async function getApmAgentFleetSyncAlert({
alertsClient,
}: {
alertsClient: AlertsClient;
}) {
const { total, data } = (await alertsClient?.find({
options: {
filter: `alert.attributes.alertTypeId: apm.agent_config_fleet_sync`,
},
})) ?? { total: 0, data: [] };

return total === 0 ? null : data[0];
}

export async function runTaskForApmAgentFleetSyncAlert({
alertsClient,
taskManagerPluginStart,
}: {
alertsClient: AlertsClient;
taskManagerPluginStart: APMPluginStartDependencies['taskManager'];
}) {
const alert = await getApmAgentFleetSyncAlert({ alertsClient });
if (!alert) {
return;
}
const { scheduledTaskId } = alert;
if (!scheduledTaskId) {
return;
}
await taskManagerPluginStart?.runNow(scheduledTaskId);
}
Loading

0 comments on commit 3f646d8

Please sign in to comment.