[8.9] [Profiling] Remove profiling from apm-server in apm package policy (#162798) (#162960)

# Backport

This will backport the following commits from `main` to `8.9`:
- [[Profiling] Remove profiling from apm-server in apm package policy
(#162798)](https://github.com/elastic/kibana/pull/162798)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Cauê
Marcondes","email":"55978943+cauemarcondes@users.noreply.github.com"},"sourceCommit":{"committedDate":"2023-08-02T10:50:34Z","message":"[Profiling]
Remove profiling from apm-server in apm package policy (#162798)\n\nThis
PR does:\r\n\r\n- Verify if the APM package policy contains profiling
instructions\r\n- When it does, it show the set up instructions.\r\n-
When the setup profiling button is clicked, updates the apm
package\r\npolicy removing the profiling
instructions.\r\n\r\n---------\r\n\r\nCo-authored-by: Francesco Gualazzi
<inge4pres@users.noreply.github.com>","sha":"2fd6af9063ecd0b7719573dbd4571531911cc1b7","branchLabelMapping":{"^v8.10.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["backport","release_note:skip","ci:build-cloud-image","v8.10.0","v8.9.1"],"number":162798,"url":"https://github.com/elastic/kibana/pull/162798","mergeCommit":{"message":"[Profiling]
Remove profiling from apm-server in apm package policy (#162798)\n\nThis
PR does:\r\n\r\n- Verify if the APM package policy contains profiling
instructions\r\n- When it does, it show the set up instructions.\r\n-
When the setup profiling button is clicked, updates the apm
package\r\npolicy removing the profiling
instructions.\r\n\r\n---------\r\n\r\nCo-authored-by: Francesco Gualazzi
<inge4pres@users.noreply.github.com>","sha":"2fd6af9063ecd0b7719573dbd4571531911cc1b7"}},"sourceBranch":"main","suggestedTargetBranches":["8.9"],"targetPullRequestStates":[{"branch":"main","label":"v8.10.0","labelRegex":"^v8.10.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/162798","number":162798,"mergeCommit":{"message":"[Profiling]
Remove profiling from apm-server in apm package policy (#162798)\n\nThis
PR does:\r\n\r\n- Verify if the APM package policy contains profiling
instructions\r\n- When it does, it show the set up instructions.\r\n-
When the setup profiling button is clicked, updates the apm
package\r\npolicy removing the profiling
instructions.\r\n\r\n---------\r\n\r\nCo-authored-by: Francesco Gualazzi
<inge4pres@users.noreply.github.com>","sha":"2fd6af9063ecd0b7719573dbd4571531911cc1b7"}},{"branch":"8.9","label":"v8.9.1","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Cauê Marcondes <55978943+cauemarcondes@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2023-08-02 08:04:11 -04:00 committed by GitHub
parent a1bc028122
commit d941804915
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 278 additions and 131 deletions

View file

@ -6,55 +6,27 @@
*/
import {
areResourcesSetup,
areResourcesSetupForAdmin,
areResourcesSetupForViewer,
createDefaultSetupState,
mergePartialSetupStates,
PartialSetupState,
} from './setup';
function createCloudState(available: boolean): PartialSetupState {
return {
cloud: {
available,
},
};
}
function createDataState(available: boolean): PartialSetupState {
return {
data: {
available,
},
};
}
function createPermissionState(configured: boolean): PartialSetupState {
return {
permissions: {
configured,
},
};
}
function createCollectorPolicyState(installed: boolean): PartialSetupState {
return {
policies: {
collector: {
installed,
},
},
};
}
function createSymbolizerPolicyState(installed: boolean): PartialSetupState {
return {
policies: {
symbolizer: {
installed,
},
},
};
}
const createCloudState = (available: boolean): PartialSetupState => ({ cloud: { available } });
const createDataState = (available: boolean): PartialSetupState => ({ data: { available } });
const createPermissionState = (configured: boolean): PartialSetupState => ({
permissions: { configured },
});
const createCollectorPolicyState = (installed: boolean): PartialSetupState => ({
policies: { collector: { installed } },
});
const createSymbolizerPolicyState = (installed: boolean): PartialSetupState => ({
policies: { symbolizer: { installed } },
});
const createProfilingInApmPolicyState = (profilingEnabled: boolean): PartialSetupState => ({
policies: { apm: { profilingEnabled } },
});
function createResourceState({
enabled,
@ -82,50 +54,124 @@ function createSettingsState(configured: boolean): PartialSetupState {
}
describe('Merging partial state operations', () => {
const defaultSetupState = createDefaultSetupState();
describe('Merge states', () => {
const defaultSetupState = createDefaultSetupState();
test('partial states with missing key', () => {
const mergedState = mergePartialSetupStates(defaultSetupState, [
createCloudState(true),
createDataState(true),
]);
it('returns partial states with missing key', () => {
const mergedState = mergePartialSetupStates(defaultSetupState, [
createCloudState(true),
createDataState(true),
]);
expect(mergedState.cloud.available).toEqual(true);
expect(mergedState.cloud.required).toEqual(true);
expect(mergedState.data.available).toEqual(true);
expect(mergedState.cloud.available).toEqual(true);
expect(mergedState.cloud.required).toEqual(true);
expect(mergedState.data.available).toEqual(true);
});
it('should deeply nested partial states with overlap', () => {
const mergedState = mergePartialSetupStates(defaultSetupState, [
createCollectorPolicyState(true),
createSymbolizerPolicyState(true),
]);
expect(mergedState.policies.collector.installed).toEqual(true);
expect(mergedState.policies.symbolizer.installed).toEqual(true);
});
});
describe('For admin users', () => {
const defaultSetupState = createDefaultSetupState();
it('returns false when permission is not configured', () => {
const mergedState = mergePartialSetupStates(defaultSetupState, [
createResourceState({ enabled: true, created: true }),
createSettingsState(true),
createPermissionState(false),
]);
expect(areResourcesSetupForAdmin(mergedState)).toBeFalsy();
});
it('returns false when resource management is not enabled', () => {
const mergedState = mergePartialSetupStates(defaultSetupState, [
createResourceState({ enabled: false, created: true }),
createSettingsState(true),
createPermissionState(true),
]);
expect(areResourcesSetupForAdmin(mergedState)).toBeFalsy();
});
it('returns false when resources are not created', () => {
const mergedState = mergePartialSetupStates(defaultSetupState, [
createResourceState({ enabled: true, created: false }),
createSettingsState(true),
createPermissionState(true),
]);
expect(areResourcesSetupForAdmin(mergedState)).toBeFalsy();
});
it('returns false when settings are not configured', () => {
const mergedState = mergePartialSetupStates(defaultSetupState, [
createResourceState({ enabled: true, created: true }),
createSettingsState(false),
createPermissionState(true),
]);
expect(areResourcesSetupForAdmin(mergedState)).toBeFalsy();
});
it('returns true when all checks are valid', () => {
const mergedState = mergePartialSetupStates(defaultSetupState, [
createResourceState({ enabled: true, created: true }),
createSettingsState(true),
createPermissionState(true),
]);
expect(areResourcesSetupForAdmin(mergedState)).toBeTruthy();
});
});
test('deeply nested partial states with overlap', () => {
const mergedState = mergePartialSetupStates(defaultSetupState, [
createCollectorPolicyState(true),
createSymbolizerPolicyState(true),
]);
describe('For viewer users', () => {
const defaultSetupState = createDefaultSetupState();
expect(mergedState.policies.collector.installed).toEqual(true);
expect(mergedState.policies.symbolizer.installed).toEqual(true);
});
it('returns false when collector is not installed', () => {
const mergedState = mergePartialSetupStates(defaultSetupState, [
createCollectorPolicyState(false),
createSymbolizerPolicyState(true),
createProfilingInApmPolicyState(false),
]);
test('check resource status with failed partial states', () => {
const mergedState = mergePartialSetupStates(defaultSetupState, [
createCollectorPolicyState(true),
createSymbolizerPolicyState(true),
createPermissionState(false),
createResourceState({ enabled: true, created: true }),
createSettingsState(true),
]);
expect(areResourcesSetupForViewer(mergedState)).toBeFalsy();
});
expect(areResourcesSetup(mergedState)).toEqual(false);
});
it('returns false when symbolizer is not installed', () => {
const mergedState = mergePartialSetupStates(defaultSetupState, [
createCollectorPolicyState(true),
createSymbolizerPolicyState(false),
createProfilingInApmPolicyState(false),
]);
test('check resource status with all successful partial states', () => {
const mergedState = mergePartialSetupStates(defaultSetupState, [
createCollectorPolicyState(true),
createSymbolizerPolicyState(true),
createPermissionState(true),
createResourceState({ enabled: true, created: true }),
createSettingsState(true),
]);
expect(areResourcesSetupForViewer(mergedState)).toBeFalsy();
});
expect(areResourcesSetup(mergedState)).toEqual(true);
it('returns false when profiling is configured in APM policy', () => {
const mergedState = mergePartialSetupStates(defaultSetupState, [
createCollectorPolicyState(true),
createSymbolizerPolicyState(true),
createProfilingInApmPolicyState(true),
]);
expect(areResourcesSetupForViewer(mergedState)).toBeFalsy();
});
it('returns true when all checks are valid', () => {
const mergedState = mergePartialSetupStates(defaultSetupState, [
createCollectorPolicyState(true),
createSymbolizerPolicyState(true),
createProfilingInApmPolicyState(false),
]);
expect(areResourcesSetupForViewer(mergedState)).toBeTruthy();
});
});
});

View file

@ -26,6 +26,9 @@ export interface SetupState {
symbolizer: {
installed: boolean;
};
apm: {
profilingEnabled: boolean;
};
};
resource_management: {
enabled: boolean;
@ -59,6 +62,9 @@ export function createDefaultSetupState(): SetupState {
symbolizer: {
installed: false,
},
apm: {
profilingEnabled: false,
},
},
resource_management: {
enabled: false,
@ -72,13 +78,19 @@ export function createDefaultSetupState(): SetupState {
};
}
export function areResourcesSetup(state: SetupState): boolean {
export function areResourcesSetupForViewer(state: SetupState): boolean {
return (
state.policies.collector.installed &&
state.policies.symbolizer.installed &&
!state.policies.apm.profilingEnabled
);
}
export function areResourcesSetupForAdmin(state: SetupState): boolean {
return (
state.resource_management.enabled &&
state.resources.created &&
state.permissions.configured &&
state.policies.collector.installed &&
state.policies.symbolizer.installed &&
state.settings.configured
);
}

View file

@ -57,17 +57,6 @@ export function CheckSetup({ children }: { children: React.ReactElement }) {
);
}
const displaySetupScreen =
(status === AsyncStatus.Settled && data?.has_setup !== true) || !!error;
const displayAddDataInstructions =
status === AsyncStatus.Settled && data?.has_setup === true && data?.has_data === false;
const displayUi =
// Display UI if there's data or if the user is opening the add data instruction page.
// does not use profiling router because that breaks as at this point the route might not have all required params
data?.has_data === true || history.location.pathname === '/add-data-instructions';
const displayLoadingScreen = status !== AsyncStatus.Settled;
if (displayLoadingScreen) {
@ -89,18 +78,8 @@ export function CheckSetup({ children }: { children: React.ReactElement }) {
);
}
if (displayUi) {
return children;
}
if (displayAddDataInstructions) {
// when there's no data redirect the user to the add data instructions page
router.push('/add-data-instructions', {
path: {},
query: { selectedTab: NoDataTabs.Kubernetes },
});
return null;
}
const displaySetupScreen =
(status === AsyncStatus.Settled && data?.has_setup !== true) || !!error;
if (displaySetupScreen) {
return (
@ -119,7 +98,7 @@ export function CheckSetup({ children }: { children: React.ReactElement }) {
<EuiText>
{i18n.translate('xpack.profiling.noDataConfig.action.title', {
defaultMessage: `Universal Profiling provides fleet-wide, whole-system, continuous profiling with zero instrumentation.
Understand what lines of code are consuming compute resources, at all times, and across your entire infrastructure.`,
Understand what lines of code are consuming compute resources, at all times, and across your entire infrastructure.`,
})}
</EuiText>
<EuiCallOut
@ -225,5 +204,26 @@ export function CheckSetup({ children }: { children: React.ReactElement }) {
);
}
const displayAddDataInstructions =
status === AsyncStatus.Settled && data?.has_setup === true && data?.has_data === false;
const displayUi =
// Display UI if there's data or if the user is opening the add data instruction page.
// does not use profiling router because that breaks as at this point the route might not have all required params
data?.has_data === true || history.location.pathname === '/add-data-instructions';
if (displayUi) {
return children;
}
if (displayAddDataInstructions) {
// when there's no data redirect the user to the add data instructions page
router.push('/add-data-instructions', {
path: {},
query: { selectedTab: NoDataTabs.Kubernetes },
});
return null;
}
throw new Error('Invalid state');
}

View file

@ -5,12 +5,14 @@
* 2.0.
*/
import { fetchFindLatestPackageOrThrow } from '@kbn/fleet-plugin/server/services/epm/registry';
import { SavedObjectsClientContract } from '@kbn/core/server';
import { PackagePolicyClient } from '@kbn/fleet-plugin/server';
import { ProfilingSetupOptions } from './types';
import { PartialSetupState } from '../../../common/setup';
import { fetchFindLatestPackageOrThrow } from '@kbn/fleet-plugin/server/services/epm/registry';
import { omit } from 'lodash';
import { PackageInputType } from '../..';
import { PartialSetupState } from '../../../common/setup';
import { ELASTIC_CLOUD_APM_POLICY, getApmPolicy } from './get_apm_policy';
import { ProfilingSetupOptions } from './types';
const CLOUD_AGENT_POLICY_ID = 'policy-elastic-agent-on-cloud';
const COLLECTOR_PACKAGE_POLICY_NAME = 'elastic-universal-profiling-collector';
@ -182,3 +184,35 @@ export async function createSymbolizerPackagePolicy({
force: true,
});
}
export async function validateProfilingInApmPackagePolicy({
soClient,
packagePolicyClient,
}: ProfilingSetupOptions): Promise<PartialSetupState> {
const apmPolicy = await getApmPolicy({ packagePolicyClient, soClient });
return {
policies: {
apm: {
profilingEnabled: !!(
apmPolicy && apmPolicy?.inputs[0].config?.['apm-server'].value?.profiling
),
},
},
};
}
export async function removeProfilingFromApmPackagePolicy({
client,
soClient,
packagePolicyClient,
}: ProfilingSetupOptions) {
const apmPackagePolicy = await getApmPolicy({ packagePolicyClient, soClient });
if (!apmPackagePolicy) {
throw new Error(`Could not find APM package policy`);
}
const esClient = client.getEsClient();
// remove profiling from apm-server config
const newPackagePolicy = omit(apmPackagePolicy, "inputs[0].config['apm-server'].value.profiling");
await packagePolicyClient.update(soClient, esClient, ELASTIC_CLOUD_APM_POLICY, newPackagePolicy);
}

View file

@ -0,0 +1,21 @@
/*
* 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 { SavedObjectsClientContract } from '@kbn/core/server';
import { PackagePolicyClient } from '@kbn/fleet-plugin/server';
export const ELASTIC_CLOUD_APM_POLICY = 'elastic-cloud-apm';
export async function getApmPolicy({
packagePolicyClient,
soClient,
}: {
packagePolicyClient: PackagePolicyClient;
soClient: SavedObjectsClientContract;
}) {
return packagePolicyClient.get(soClient, ELASTIC_CLOUD_APM_POLICY);
}

View file

@ -5,14 +5,17 @@
* 2.0.
*/
import { PartialSetupState } from '../../../common/setup';
import { ProfilingSetupOptions } from './types';
export async function hasProfilingData({ client }: ProfilingSetupOptions): Promise<boolean> {
export async function hasProfilingData({
client,
}: ProfilingSetupOptions): Promise<PartialSetupState> {
const hasProfilingDataResponse = await client.search('has_any_profiling_data', {
index: 'profiling*',
size: 0,
track_total_hits: 1,
terminate_after: 1,
});
return hasProfilingDataResponse.hits.total.value > 0;
return { data: { available: hasProfilingDataResponse.hits.total.value > 0 } };
}

View file

@ -7,7 +7,13 @@
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
import { RouteRegisterParameters } from '.';
import { getClient } from './compat';
import { getRoutePaths } from '../../common';
import {
areResourcesSetupForAdmin,
areResourcesSetupForViewer,
createDefaultSetupState,
mergePartialSetupStates,
} from '../../common/setup';
import {
enableResourceManagement,
setMaximumBuckets,
@ -17,7 +23,9 @@ import {
import {
createCollectorPackagePolicy,
createSymbolizerPackagePolicy,
removeProfilingFromApmPackagePolicy,
validateCollectorPackagePolicy,
validateProfilingInApmPackagePolicy,
validateSymbolizerPackagePolicy,
} from '../lib/setup/fleet_policies';
import { getSetupInstructions } from '../lib/setup/get_setup_instructions';
@ -25,12 +33,7 @@ import { hasProfilingData } from '../lib/setup/has_profiling_data';
import { setSecurityRole, validateSecurityRole } from '../lib/setup/security_role';
import { ProfilingSetupOptions } from '../lib/setup/types';
import { handleRouteHandlerError } from '../utils/handle_route_error_handler';
import { getRoutePaths } from '../../common';
import {
areResourcesSetup,
createDefaultSetupState,
mergePartialSetupStates,
} from '../../common/setup';
import { getClient } from './compat';
export function registerSetupRoute({
router,
@ -86,32 +89,56 @@ export function registerSetupRoute({
});
}
state.data.available = await hasProfilingData({
...setupOptions,
client: clientWithProfilingAuth,
});
if (state.data.available) {
const verifyFunctionsForViewer = [
validateCollectorPackagePolicy,
validateSymbolizerPackagePolicy,
validateProfilingInApmPackagePolicy,
];
const partialStatesForViewer = await Promise.all([
...verifyFunctionsForViewer.map((fn) => fn(setupOptions)),
hasProfilingData({
...setupOptions,
client: clientWithProfilingAuth,
}),
]);
const mergedStateForViewer = mergePartialSetupStates(state, partialStatesForViewer);
/*
* We need to split the verification steps
* because of users with viewer privileges
* cannot get the cluster settings
*/
if (
areResourcesSetupForViewer(mergedStateForViewer) &&
mergedStateForViewer.data.available
) {
return response.ok({
body: {
has_setup: true,
has_data: state.data.available,
has_data: mergedStateForViewer.data.available,
},
});
}
const verifyFunctions = [
validateCollectorPackagePolicy,
/**
* Performe advanced verification in case the first step failed.
*/
const verifyFunctionsForAdmin = [
validateMaximumBuckets,
validateResourceManagement,
validateSecurityRole,
validateSymbolizerPackagePolicy,
];
const partialStates = await Promise.all(verifyFunctions.map((fn) => fn(setupOptions)));
const mergedState = mergePartialSetupStates(state, partialStates);
const partialStatesForAdmin = await Promise.all(
verifyFunctionsForAdmin.map((fn) => fn(setupOptions))
);
const mergedState = mergePartialSetupStates(mergedStateForViewer, partialStatesForAdmin);
return response.ok({
body: {
has_setup: areResourcesSetup(mergedState),
has_setup: areResourcesSetupForAdmin(mergedState),
has_data: mergedState.data.available,
},
});
@ -173,6 +200,7 @@ export function registerSetupRoute({
validateResourceManagement,
validateSecurityRole,
validateSymbolizerPackagePolicy,
validateProfilingInApmPackagePolicy,
].map((fn) => fn(setupOptions))
);
const mergedState = mergePartialSetupStates(state, partialStates);
@ -180,6 +208,9 @@ export function registerSetupRoute({
const executeFunctions = [
...(mergedState.policies.collector.installed ? [] : [createCollectorPackagePolicy]),
...(mergedState.policies.symbolizer.installed ? [] : [createSymbolizerPackagePolicy]),
...(mergedState.policies.apm.profilingEnabled
? [removeProfilingFromApmPackagePolicy]
: []),
...(mergedState.resource_management.enabled ? [] : [enableResourceManagement]),
...(mergedState.permissions.configured ? [] : [setSecurityRole]),
...(mergedState.settings.configured ? [] : [setMaximumBuckets]),