Skip to content
5 changes: 5 additions & 0 deletions x-pack/plugins/fleet/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ import { getPackageSpecTagId } from './services/epm/kibana/assets/tag_assets';
import { FleetMetricsTask } from './services/metrics/fleet_metrics_task';
import { fetchAgentMetrics } from './services/metrics/fetch_agent_metrics';
import { registerIntegrationFieldsExtractor } from './services/register_integration_fields_extractor';
import { registerUpgradeManagedPackagePoliciesTask } from './services/setup/managed_package_policies';

export interface FleetSetupDeps {
security: SecurityPluginSetup;
Expand Down Expand Up @@ -180,6 +181,7 @@ export interface FleetAppContext {
auditLogger?: AuditLogger;
uninstallTokenService: UninstallTokenServiceInterface;
unenrollInactiveAgentsTask: UnenrollInactiveAgentsTask;
taskManagerStart?: TaskManagerStartContract;
}

export type FleetSetupContract = void;
Expand Down Expand Up @@ -596,6 +598,8 @@ export class FleetPlugin
registerRoutes(fleetAuthzRouter, config);

this.telemetryEventsSender.setup(deps.telemetry);
// Register task
registerUpgradeManagedPackagePoliciesTask(deps.taskManager);
this.bulkActionsResolver = new BulkActionsResolver(deps.taskManager, core);
this.checkDeletedFilesTask = new CheckDeletedFilesTask({
core,
Expand Down Expand Up @@ -653,6 +657,7 @@ export class FleetPlugin
messageSigningService,
uninstallTokenService,
unenrollInactiveAgentsTask: this.unenrollInactiveAgentsTask!,
taskManagerStart: plugins.taskManager,
});
licenseService.start(plugins.licensing.license$);
this.telemetryEventsSender.start(plugins.telemetry, core).catch(() => {});
Expand Down
10 changes: 7 additions & 3 deletions x-pack/plugins/fleet/server/services/app_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,12 @@ import type {
EncryptedSavedObjectsPluginSetup,
EncryptedSavedObjectsPluginStart,
} from '@kbn/encrypted-saved-objects-plugin/server';

import type { SecurityPluginStart, SecurityPluginSetup } from '@kbn/security-plugin/server';

import type { CloudSetup } from '@kbn/cloud-plugin/server';
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
import type { SavedObjectTaggingStart } from '@kbn/saved-objects-tagging-plugin/server';

import { SECURITY_EXTENSION_ID, SPACES_EXTENSION_ID } from '@kbn/core-saved-objects-server';
import type { TaskManagerStartContract } from '@kbn/task-manager-plugin/server';

import type { FleetConfigType } from '../../common/types';
import {
Expand Down Expand Up @@ -84,6 +82,7 @@ class AppContextService {
private bulkActionsResolver: BulkActionsResolver | undefined;
private messageSigningService: MessageSigningServiceInterface | undefined;
private uninstallTokenService: UninstallTokenServiceInterface | undefined;
private taskManagerStart: TaskManagerStartContract | undefined;

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

if (appContext.config$) {
this.config$ = appContext.config$;
Expand Down Expand Up @@ -282,6 +282,10 @@ class AppContextService {
return this.kibanaInstanceId;
}

public getTaskManagerStart() {
return this.taskManagerStart;
}

public addExternalCallback(type: ExternalCallback[0], callback: ExternalCallback[1]) {
if (!this.externalCallbacks.has(type)) {
this.externalCallbacks.set(type, new Set());
Expand Down
72 changes: 72 additions & 0 deletions x-pack/plugins/fleet/server/services/epm/packages/cache.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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 {
getPackageAssetsMapCache,
getPackageInfoCache,
runWithCache,
setPackageAssetsMapCache,
setPackageInfoCache,
} from './cache';

const PKG_NAME = 'test';
const PKG_VERSION = '1.0.0';

describe('EPM CacheSession', () => {
describe('outside of a cache session', () => {
it('should not cache package info', () => {
setPackageInfoCache(PKG_NAME, PKG_VERSION, {
name: 'test',
} as any);
const cache = getPackageInfoCache(PKG_NAME, PKG_VERSION);
expect(cache).toBeUndefined();
});

it('should not cache assetsMap', () => {
setPackageAssetsMapCache(PKG_NAME, PKG_VERSION, new Map());
const cache = getPackageAssetsMapCache(PKG_NAME, PKG_VERSION);
expect(cache).toBeUndefined();
});
});

describe('in of a cache session', () => {
it('should cache package info', async () => {
function setCache() {
setPackageInfoCache(PKG_NAME, PKG_VERSION, {
name: 'test',
} as any);
}
function getCache() {
const cache = getPackageInfoCache(PKG_NAME, PKG_VERSION);
expect(cache).toEqual({ name: 'test' });
}

await runWithCache(async () => {
setCache();
getCache();
});
});

it('should cache assetsMap', async () => {
function setCache() {
const map = new Map();
map.set('test.yaml', Buffer.from('name: test'));
setPackageAssetsMapCache(PKG_NAME, PKG_VERSION, map);
}
function getCache() {
const cache = getPackageAssetsMapCache(PKG_NAME, PKG_VERSION);
expect(cache).not.toBeUndefined();
expect(cache?.get('test.yaml')?.toString()).toEqual('name: test');
}

await runWithCache(async () => {
setCache();
getCache();
});
});
});
});
72 changes: 72 additions & 0 deletions x-pack/plugins/fleet/server/services/epm/packages/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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 { AsyncLocalStorage } from 'async_hooks';

import LRUCache from 'lru-cache';

import type { AssetsMap } from '../../../../common/types';

import type { PackageInfo } from '../../../../common';

const cacheStore = new AsyncLocalStorage<CacheSession>();

const PACKAGE_INFO_CACHE_SIZE = 20;
const PACKAGE_ASSETS_MAP_CACHE_SIZE = 1;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Should we make those key configurable through kibana config under fleet.internal.*

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It could be useful, but is there a risk that the user adds a number too small or too big and creates some issue? If we decide to do it we should document it very clearly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yes I think if we make this configurable it will be under an internal keyword probably that could be useful for supportability (SDH with a scenario we did not think of)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We can start without making this configurable, those size are pretty small it should not have a huge impact


class CacheSession {
private _packageInfoCache?: LRUCache<string, PackageInfo>;

private _packageAssetsMap?: LRUCache<string, AssetsMap>;

getPackageInfoCache() {
if (!this._packageInfoCache) {
this._packageInfoCache = new LRUCache<string, PackageInfo>({
max: PACKAGE_INFO_CACHE_SIZE,
});
}
return this._packageInfoCache;
}

getPackageAssetsMapCache() {
if (!this._packageAssetsMap) {
this._packageAssetsMap = new LRUCache<string, AssetsMap>({
max: PACKAGE_ASSETS_MAP_CACHE_SIZE,
});
}
return this._packageAssetsMap;
}
}

export function getPackageInfoCache(pkgName: string, pkgVersion: string) {
return cacheStore.getStore()?.getPackageInfoCache()?.get(`${pkgName}:${pkgVersion}`);
}

export function setPackageInfoCache(pkgName: string, pkgVersion: string, packageInfo: PackageInfo) {
return cacheStore.getStore()?.getPackageInfoCache()?.set(`${pkgName}:${pkgVersion}`, packageInfo);
}

export function getPackageAssetsMapCache(pkgName: string, pkgVersion: string) {
return cacheStore.getStore()?.getPackageAssetsMapCache()?.get(`${pkgName}:${pkgVersion}`);
}

export function setPackageAssetsMapCache(
pkgName: string,
pkgVersion: string,
assetsMap: AssetsMap
) {
return cacheStore
.getStore()
?.getPackageAssetsMapCache()
?.set(`${pkgName}:${pkgVersion}`, assetsMap);
}

export async function runWithCache<T = any>(cb: () => Promise<T>): Promise<T> {
const cache = new CacheSession();

return cacheStore.run(cache, cb);
}
20 changes: 19 additions & 1 deletion x-pack/plugins/fleet/server/services/epm/packages/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ import { auditLoggingService } from '../../audit_logging';
import { getFilteredSearchPackages } from '../filtered_packages';

import { createInstallableFrom } from '.';
import {
getPackageAssetsMapCache,
setPackageAssetsMapCache,
getPackageInfoCache,
setPackageInfoCache,
} from './cache';

export { getFile } from '../registry';

Expand Down Expand Up @@ -415,6 +421,10 @@ export async function getPackageInfo({
ignoreUnverified?: boolean;
prerelease?: boolean;
}): Promise<PackageInfo> {
const cacheResult = getPackageInfoCache(pkgName, pkgVersion);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Curious to get your though on that pattern for caching, it seems easier to use that passing packageInfo through all codebase

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I agree, currently we have to pass the packageInfo object around and is not the best pattern. With this we could probably replace it.

if (cacheResult) {
return cacheResult;
}
const [savedObject, latestPackage] = await Promise.all([
getInstallationObject({ savedObjectsClient, pkgName }),
Registry.fetchFindLatestPackageOrUndefined(pkgName, { prerelease }),
Expand Down Expand Up @@ -468,7 +478,10 @@ export async function getPackageInfo({
};
const updated = { ...packageInfo, ...additions };

return createInstallableFrom(updated, savedObject);
const installable = createInstallableFrom(updated, savedObject);
setPackageInfoCache(pkgName, pkgVersion, installable);

return installable;
}

export const getPackageUsageStats = async ({
Expand Down Expand Up @@ -720,6 +733,10 @@ export async function getPackageAssetsMap({
logger: Logger;
ignoreUnverified?: boolean;
}) {
const cache = getPackageAssetsMapCache(packageInfo.name, packageInfo.version);
if (cache) {
return cache;
}
const installedPackageWithAssets = await getInstalledPackageWithAssets({
savedObjectsClient,
pkgName: packageInfo.name,
Expand All @@ -736,6 +753,7 @@ export async function getPackageAssetsMap({
} else {
assetsMap = installedPackageWithAssets.assetsMap;
}
setPackageAssetsMapCache(packageInfo.name, packageInfo.version, assetsMap);

return assetsMap;
}
47 changes: 4 additions & 43 deletions x-pack/plugins/fleet/server/services/package_policies/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,55 +20,16 @@ import { outputService } from '../output';
import { appContextService } from '../app_context';

export const mapPackagePolicySavedObjectToPackagePolicy = ({
/* eslint-disable @typescript-eslint/naming-convention */
id,
version,
attributes: {
name,
description,
namespace,
enabled,
is_managed,
policy_id,
policy_ids,
output_id,
// `package` is a reserved keyword
package: packageInfo,
inputs,
vars,
elasticsearch,
agents,
revision,
secret_references,
updated_at,
updated_by,
created_at,
created_by,
/* eslint-enable @typescript-eslint/naming-convention */
},
attributes,
namespaces,
}: SavedObject<PackagePolicySOAttributes>): PackagePolicy => {
return {
id,
name,
description,
namespace,
enabled,
is_managed,
policy_id,
policy_ids,
output_id,
package: packageInfo,
inputs,
vars,
elasticsearch,
version,
agents,
revision,
secret_references,
updated_at,
updated_by,
created_at,
created_by,
spaceIds: namespaces,
...attributes,
};
};

Expand Down
8 changes: 0 additions & 8 deletions x-pack/plugins/fleet/server/services/package_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ import {
mapPackagePolicySavedObjectToPackagePolicy,
preflightCheckPackagePolicy,
} from './package_policies';
import { updateDatastreamExperimentalFeatures } from './epm/packages/update';
import type {
PackagePolicyClient,
PackagePolicyClientFetchAllItemsOptions,
Expand Down Expand Up @@ -1633,13 +1632,6 @@ class PackagePolicyClientImpl implements PackagePolicyClient {

await this.update(soClient, esClient, id, updatePackagePolicy, updateOptions);

// Persist any experimental feature opt-ins that come through the upgrade process to the Installation SO
await updateDatastreamExperimentalFeatures(
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

There is no reason for this to change during an upgrade and that codepath was extremely slow

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we have tests that cover this removal? Just making sure that we don't inadvertently break anything.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We do not, but it seems that codepath was doing nothing, it seems it was only here for supporting an UI that do not exists anymore #190613

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for checking!

soClient,
packagePolicy.package!.name,
experimentalDataStreamFeatures
);

result.push({
id,
name: packagePolicy.name,
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/fleet/server/services/preconfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import { agentPolicyService, addPackageToAgentPolicy } from './agent_policy';
import { type InputsOverride, packagePolicyService } from './package_policy';
import { preconfigurePackageInputs } from './package_policy';
import { appContextService } from './app_context';
import type { UpgradeManagedPackagePoliciesResult } from './managed_package_policies';
import type { UpgradeManagedPackagePoliciesResult } from './setup/managed_package_policies';
import { isDefaultAgentlessPolicyEnabled } from './utils/agentless';

interface PreconfigurationResult {
Expand Down
Loading