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] Package policy upgrade telemetry with sender #115180

Merged
merged 39 commits into from
Nov 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
def871d
draft of upgrade usage collector
juliaElastic Oct 15, 2021
d17c611
Merge branch 'elastic:master' into upgrade-telemetry
juliaElastic Oct 18, 2021
4957690
telemetry sender service
juliaElastic Oct 18, 2021
1d2deff
fixed tests and types
juliaElastic Oct 18, 2021
719be2d
cleanup
juliaElastic Oct 18, 2021
0434d46
type fix
juliaElastic Oct 18, 2021
d4b01a3
Merge branch 'elastic:master' into upgrade-telemetry
juliaElastic Oct 18, 2021
0e8fa09
removed collector
juliaElastic Oct 19, 2021
d6d11df
Merge branch 'master' into upgrade-telemetry
juliaElastic Oct 19, 2021
316ff8b
made required field message generic, added test
juliaElastic Oct 19, 2021
8ff9797
Merge branch 'upgrade-telemetry' of https://github.com/juliaElastic/k…
juliaElastic Oct 19, 2021
217ef55
cleanup
juliaElastic Oct 19, 2021
d0620df
cleanup
juliaElastic Oct 19, 2021
03d39ad
cleanup
juliaElastic Oct 19, 2021
1fe58f1
removed v3-dev as outdated
juliaElastic Oct 19, 2021
9f18ad3
removed conditional from telemetry url creation
juliaElastic Oct 19, 2021
a3b1011
supporting multiple channels in sender
juliaElastic Oct 20, 2021
caef79a
fix types
juliaElastic Oct 20, 2021
ae3b673
refactor
juliaElastic Oct 20, 2021
0b8072e
Merge branch 'master' into upgrade-telemetry
kibanamachine Oct 20, 2021
3798547
using json content type
juliaElastic Oct 20, 2021
b210f64
fix test
juliaElastic Oct 20, 2021
0181b6a
Merge branch 'master' into upgrade-telemetry
kibanamachine Oct 21, 2021
786b63f
simplified telemetry url
juliaElastic Oct 21, 2021
ec3b977
fixed type
juliaElastic Oct 21, 2021
b0bf43b
added back ndjson
juliaElastic Oct 23, 2021
1572d89
Merge branch 'master' into upgrade-telemetry
juliaElastic Oct 23, 2021
7c7a8d8
Merge branch 'master' into upgrade-telemetry
kibanamachine Oct 23, 2021
c77d7f6
moved telemetry to update, added dryrun
juliaElastic Oct 25, 2021
ee2c916
fix types
juliaElastic Oct 25, 2021
5914a61
Merge branch 'master' into upgrade-telemetry
juliaElastic Oct 26, 2021
74b6c1f
fix prettier
juliaElastic Oct 26, 2021
cb591ad
updated after review
juliaElastic Oct 27, 2021
bfc09a0
Merge branch 'master' into upgrade-telemetry
kibanamachine Oct 27, 2021
b4a89d9
fix imports
juliaElastic Oct 28, 2021
f0a8cd2
added error_message field
juliaElastic Oct 28, 2021
eafad0a
Merge branch 'main' into upgrade-telemetry
juliaElastic Nov 1, 2021
1d876cd
review fixes
juliaElastic Nov 1, 2021
f7dcd64
Merge branch 'main' into upgrade-telemetry
kibanamachine Nov 3, 2021
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
2 changes: 1 addition & 1 deletion x-pack/plugins/fleet/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"ui": true,
"configPath": ["xpack", "fleet"],
"requiredPlugins": ["licensing", "data", "encryptedSavedObjects", "navigation", "customIntegrations", "share"],
"optionalPlugins": ["security", "features", "cloud", "usageCollection", "home", "globalSearch"],
"optionalPlugins": ["security", "features", "cloud", "usageCollection", "home", "globalSearch", "telemetry"],
"extraPublicDirs": ["common"],
"requiredBundles": ["kibanaReact", "esUiShared", "home", "infra", "kibanaUtils", "usageCollection"]
}
2 changes: 2 additions & 0 deletions x-pack/plugins/fleet/server/mocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { securityMock } from '../../../security/server/mocks';
import type { PackagePolicyServiceInterface } from '../services/package_policy';
import type { AgentPolicyServiceInterface, AgentService } from '../services';
import type { FleetAppContext } from '../plugin';
import { createMockTelemetryEventsSender } from '../telemetry/__mocks__';

// Export all mocks from artifacts
export * from '../services/artifacts/mocks';
Expand Down Expand Up @@ -59,6 +60,7 @@ export const createAppContextStartContractMock = (): MockedFleetAppContext => {
config$,
kibanaVersion: '8.99.0', // Fake version :)
kibanaBranch: 'main',
telemetryEventsSender: createMockTelemetryEventsSender(),
};
};

Expand Down
14 changes: 14 additions & 0 deletions x-pack/plugins/fleet/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import type {
} from 'kibana/server';
import type { UsageCollectionSetup } from 'src/plugins/usage_collection/server';

import type { TelemetryPluginSetup, TelemetryPluginStart } from 'src/plugins/telemetry/server';

import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server';
import type { PluginStart as DataPluginStart } from '../../../../src/plugins/data/server';
import type { LicensingPluginSetup, ILicense } from '../../licensing/server';
Expand Down Expand Up @@ -83,6 +85,7 @@ import { RouterWrappers } from './routes/security';
import { startFleetServerSetup } from './services/fleet_server';
import { FleetArtifactsClient } from './services/artifacts';
import type { FleetRouter } from './types/request_context';
import { TelemetryEventsSender } from './telemetry/sender';

export interface FleetSetupDeps {
licensing: LicensingPluginSetup;
Expand All @@ -91,12 +94,14 @@ export interface FleetSetupDeps {
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup;
cloud?: CloudSetup;
usageCollection?: UsageCollectionSetup;
telemetry?: TelemetryPluginSetup;
}

export interface FleetStartDeps {
data: DataPluginStart;
encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
security?: SecurityPluginStart;
telemetry?: TelemetryPluginStart;
}

export interface FleetAppContext {
Expand All @@ -115,6 +120,7 @@ export interface FleetAppContext {
cloud?: CloudSetup;
logger?: Logger;
httpSetup?: HttpServiceSetup;
telemetryEventsSender: TelemetryEventsSender;
}

export type FleetSetupContract = void;
Expand Down Expand Up @@ -176,6 +182,7 @@ export class FleetPlugin
private httpSetup?: HttpServiceSetup;
private securitySetup?: SecurityPluginSetup;
private encryptedSavedObjectsSetup?: EncryptedSavedObjectsPluginSetup;
private readonly telemetryEventsSender: TelemetryEventsSender;

constructor(private readonly initializerContext: PluginInitializerContext) {
this.config$ = this.initializerContext.config.create<FleetConfigType>();
Expand All @@ -184,6 +191,7 @@ export class FleetPlugin
this.kibanaBranch = this.initializerContext.env.packageInfo.branch;
this.logger = this.initializerContext.logger.get();
this.configInitialValue = this.initializerContext.config.get();
this.telemetryEventsSender = new TelemetryEventsSender(this.logger.get('telemetry_events'));
}

public setup(core: CoreSetup, deps: FleetSetupDeps) {
Expand Down Expand Up @@ -302,6 +310,8 @@ export class FleetPlugin
});
}
}

this.telemetryEventsSender.setup(deps.telemetry);
}

public start(core: CoreStart, plugins: FleetStartDeps): FleetStartContract {
Expand All @@ -321,11 +331,14 @@ export class FleetPlugin
httpSetup: this.httpSetup,
cloud: this.cloud,
logger: this.logger,
telemetryEventsSender: this.telemetryEventsSender,
});
licenseService.start(this.licensing$);

const fleetServerSetup = startFleetServerSetup();

this.telemetryEventsSender.start(plugins.telemetry, core);

return {
fleetSetupCompleted: () =>
new Promise<void>((resolve) => {
Expand Down Expand Up @@ -362,5 +375,6 @@ export class FleetPlugin
public async stop() {
appContextService.stop();
licenseService.stop();
this.telemetryEventsSender.stop();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ export const updatePackagePolicyHandler: RequestHandler<
esClient,
request.params.packagePolicyId,
{ ...newData, package: pkg, inputs },
{ user }
{ user },
packagePolicy.package?.version
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we switch to named parameters / object on this API? The list is quite long and it's not obvious what each arg is for.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm a bit cautious of changing this, the update method is being called from a few plugins outside of fleet like apm and security_solution.

);
return response.ok({
body: { item: updatedPackagePolicy },
Expand Down
7 changes: 7 additions & 0 deletions x-pack/plugins/fleet/server/services/app_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import type {
} from '../types';
import type { FleetAppContext } from '../plugin';
import type { CloudSetup } from '../../../cloud/server';
import type { TelemetryEventsSender } from '../telemetry/sender';

class AppContextService {
private encryptedSavedObjects: EncryptedSavedObjectsClient | undefined;
Expand All @@ -51,6 +52,7 @@ class AppContextService {
private logger: Logger | undefined;
private httpSetup?: HttpServiceSetup;
private externalCallbacks: ExternalCallbacksStorage = new Map();
private telemetryEventsSender: TelemetryEventsSender | undefined;

public start(appContext: FleetAppContext) {
this.data = appContext.data;
Expand All @@ -66,6 +68,7 @@ class AppContextService {
this.kibanaVersion = appContext.kibanaVersion;
this.kibanaBranch = appContext.kibanaBranch;
this.httpSetup = appContext.httpSetup;
this.telemetryEventsSender = appContext.telemetryEventsSender;

if (appContext.config$) {
this.config$ = appContext.config$;
Expand Down Expand Up @@ -203,6 +206,10 @@ class AppContextService {
>;
}
}

public getTelemetryEventsSender() {
return this.telemetryEventsSender;
}
}

export const appContextService = new AppContextService();
6 changes: 6 additions & 0 deletions x-pack/plugins/fleet/server/services/package_policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ jest.mock('./epm/packages/cleanup', () => {
};
});

jest.mock('./upgrade_usage', () => {
return {
sendTelemetryEvents: jest.fn(),
};
});

const mockedFetchInfo = fetchInfo as jest.Mock<ReturnType<typeof fetchInfo>>;

type CombinedExternalCallback = PutPackagePolicyUpdateCallback | PostPackagePolicyCreateCallback;
Expand Down
76 changes: 39 additions & 37 deletions x-pack/plugins/fleet/server/services/package_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { omit, partition } from 'lodash';
import { i18n } from '@kbn/i18n';
import semverLte from 'semver/functions/lte';
import { getFlattenedObject } from '@kbn/std';
import type { KibanaRequest, LogMeta } from 'src/core/server';
import type { KibanaRequest } from 'src/core/server';
import type {
ElasticsearchClient,
RequestHandlerContext,
Expand Down Expand Up @@ -68,6 +68,8 @@ import { compileTemplate } from './epm/agent/agent';
import { normalizeKuery } from './saved_object';
import { appContextService } from '.';
import { removeOldAssets } from './epm/packages/cleanup';
import type { PackagePolicyUpgradeUsage } from './upgrade_usage';
import { sendTelemetryEvents } from './upgrade_usage';

export type InputsOverride = Partial<NewPackagePolicyInput> & {
vars?: Array<NewPackagePolicyInput['vars'] & { name: string }>;
Expand All @@ -84,17 +86,6 @@ export const DATA_STREAM_ALLOWED_INDEX_PRIVILEGES = new Set([
'read_cross_cluster',
]);

interface PackagePolicyUpgradeLogMeta extends LogMeta {
package_policy_upgrade: {
package_name: string;
current_version: string;
new_version: string;
status: 'success' | 'failure';
error?: any[];
dryRun?: boolean;
};
}

class PackagePolicyService {
public async create(
soClient: SavedObjectsClientContract,
Expand Down Expand Up @@ -369,7 +360,8 @@ class PackagePolicyService {
esClient: ElasticsearchClient,
id: string,
packagePolicy: UpdatePackagePolicy,
options?: { user?: AuthenticatedUser }
options?: { user?: AuthenticatedUser },
currentVersion?: string
): Promise<PackagePolicy> {
const oldPackagePolicy = await this.get(soClient, id);
const { version, ...restOfPackagePolicy } = packagePolicy;
Expand Down Expand Up @@ -445,22 +437,22 @@ class PackagePolicyService {
currentVersion: packagePolicy.package.version,
});

const upgradeMeta: PackagePolicyUpgradeLogMeta = {
package_policy_upgrade: {
if (packagePolicy.package.version !== currentVersion) {
const upgradeTelemetry: PackagePolicyUpgradeUsage = {
package_name: packagePolicy.package.name,
current_version: currentVersion || 'unknown',
new_version: packagePolicy.package.version,
current_version: 'unknown',
status: 'success',
dryRun: false,
},
};

appContextService
.getLogger()
.info<PackagePolicyUpgradeLogMeta>(
`Package policy successfully upgraded ${JSON.stringify(upgradeMeta)}`,
upgradeMeta
};
sendTelemetryEvents(
appContextService.getLogger(),
appContextService.getTelemetryEventsSender(),
upgradeTelemetry
);
appContextService.getLogger().info(`Package policy upgraded successfully`);
appContextService.getLogger().debug(JSON.stringify(upgradeTelemetry));
}
}

return newPolicy;
Expand Down Expand Up @@ -629,7 +621,14 @@ class PackagePolicyService {
);
updatePackagePolicy.elasticsearch = registryPkgInfo.elasticsearch;

await this.update(soClient, esClient, id, updatePackagePolicy, options);
await this.update(
soClient,
esClient,
id,
updatePackagePolicy,
options,
packagePolicy.package.version
);
result.push({
id,
name: packagePolicy.name,
Expand Down Expand Up @@ -691,24 +690,27 @@ class PackagePolicyService {
const hasErrors = 'errors' in updatedPackagePolicy;

if (packagePolicy.package.version !== packageInfo.version) {
const upgradeMeta: PackagePolicyUpgradeLogMeta = {
package_policy_upgrade: {
package_name: packageInfo.name,
current_version: packagePolicy.package.version,
new_version: packageInfo.version,
status: hasErrors ? 'failure' : 'success',
error: hasErrors ? updatedPackagePolicy.errors : undefined,
dryRun: true,
},
const upgradeTelemetry: PackagePolicyUpgradeUsage = {
package_name: packageInfo.name,
current_version: packagePolicy.package.version,
new_version: packageInfo.version,
status: hasErrors ? 'failure' : 'success',
error: hasErrors ? updatedPackagePolicy.errors : undefined,
dryRun: true,
};
sendTelemetryEvents(
appContextService.getLogger(),
appContextService.getTelemetryEventsSender(),
upgradeTelemetry
);
appContextService
.getLogger()
.info<PackagePolicyUpgradeLogMeta>(
.info(
`Package policy upgrade dry run ${
hasErrors ? 'resulted in errors' : 'ran successfully'
} ${JSON.stringify(upgradeMeta)}`,
upgradeMeta
}`
);
appContextService.getLogger().debug(JSON.stringify(upgradeTelemetry));
}

return {
Expand Down
70 changes: 70 additions & 0 deletions x-pack/plugins/fleet/server/services/upgrade_usage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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 { Logger } from 'src/core/server';
import { loggingSystemMock } from 'src/core/server/mocks';

import type { TelemetryEventsSender } from '../telemetry/sender';
import { createMockTelemetryEventsSender } from '../telemetry/__mocks__';

import { sendTelemetryEvents, capErrorSize } from './upgrade_usage';
import type { PackagePolicyUpgradeUsage } from './upgrade_usage';

describe('sendTelemetryEvents', () => {
let eventsTelemetryMock: jest.Mocked<TelemetryEventsSender>;
let loggerMock: jest.Mocked<Logger>;

beforeEach(() => {
eventsTelemetryMock = createMockTelemetryEventsSender();
loggerMock = loggingSystemMock.createLogger();
});

it('should queue telemetry events with generic error', () => {
const upgardeMessage: PackagePolicyUpgradeUsage = {
package_name: 'aws',
current_version: '0.6.1',
new_version: '1.3.0',
status: 'failure',
error: [
{ key: 'queueUrl', message: ['Queue URL is required'] },
{ message: 'Invalid format' },
],
dryRun: true,
};

sendTelemetryEvents(loggerMock, eventsTelemetryMock, upgardeMessage);

expect(eventsTelemetryMock.queueTelemetryEvents).toHaveBeenCalledWith('fleet-upgrades', [
{
current_version: '0.6.1',
error: [
{
key: 'queueUrl',
message: ['Queue URL is required'],
},
{
message: 'Invalid format',
},
],
error_message: ['Field is required', 'Invalid format'],
new_version: '1.3.0',
package_name: 'aws',
status: 'failure',
dryRun: true,
},
]);
});

it('should cap error size', () => {
const maxSize = 2;
const errors = [{ message: '1' }, { message: '2' }, { message: '3' }];

const result = capErrorSize(errors, maxSize);

expect(result.length).toEqual(maxSize);
});
});
Loading