Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
c23db52
moving action batching to async
juliaElastic Aug 16, 2022
6cdbf4e
created new api for action status
juliaElastic Aug 16, 2022
d9da608
fix types
juliaElastic Aug 16, 2022
d8e968f
fix types
juliaElastic Aug 16, 2022
e7fd443
added time measurement
juliaElastic Aug 16, 2022
8e5667a
added action status component, refactored reassign logic
juliaElastic Aug 17, 2022
3c94459
refactored retry task to be reusable by other actions
juliaElastic Aug 17, 2022
8a31e6c
refactored action runner to class
juliaElastic Aug 18, 2022
8d705a6
changed bulk update tags
juliaElastic Aug 22, 2022
8d692d2
Merge branch 'main' into feat/action-batch-async
juliaElastic Aug 22, 2022
8da6894
build fixes
juliaElastic Aug 22, 2022
1afb694
fix checks
juliaElastic Aug 22, 2022
1137591
passing retry options
juliaElastic Aug 22, 2022
6de094e
fixed action status
juliaElastic Aug 23, 2022
07f3283
improvements to action status api
juliaElastic Aug 23, 2022
83fe4b6
improvements to action status api
juliaElastic Aug 23, 2022
ec22cbc
removed timed out status
juliaElastic Aug 23, 2022
ddca370
simplified code around retry tasks
juliaElastic Aug 23, 2022
0fb109c
added missing unenroll params
juliaElastic Aug 23, 2022
a117ba3
Merge branch 'main' into feat/action-batch-async
juliaElastic Aug 24, 2022
dab606f
fix checks
juliaElastic Aug 24, 2022
f4653e2
increased upgrade action expiration to 2h for immediately
juliaElastic Aug 24, 2022
e38afd0
moved getActionStatusHandler to handlers, using its own response type
juliaElastic Aug 24, 2022
edd2ab5
capturing failures with error message in a new .fleet-actions-status …
juliaElastic Aug 24, 2022
0bf645a
renamed status index so it doesn't get the same alias as .fleet-actions
juliaElastic Aug 24, 2022
88a6362
cleaned up action and retry params
juliaElastic Aug 25, 2022
a59df36
refactored to simplify passing params
juliaElastic Aug 26, 2022
92db446
Merge branch 'main' into feat/action-batch-async
kibanamachine Aug 26, 2022
53c55c4
added script to generate agent docs, fixed bugs
juliaElastic Aug 26, 2022
dc10593
renamed action_status response props
juliaElastic Aug 26, 2022
76a87fd
Merge branch 'main' into feat/action-batch-async
kibanamachine Aug 29, 2022
abea983
Merge branch 'main' into feat/action-batch-async
juliaElastic Aug 30, 2022
32579ff
Merge branch 'main' into feat/action-batch-async
juliaElastic Sep 5, 2022
6d73425
Merge branch 'main' into feat/action-batch-async
juliaElastic Sep 5, 2022
6c81a64
small cleanup
juliaElastic Sep 5, 2022
6978057
refactor, cleanup
juliaElastic Sep 5, 2022
f02b268
added tests
juliaElastic Sep 6, 2022
5525dc1
fixed tests
juliaElastic Sep 7, 2022
97b1edd
upgrade api test
juliaElastic Sep 7, 2022
951566c
Merge branch 'main' into feat/action-batch-async
kibanamachine Sep 7, 2022
f1eb373
renamed CurrentAction to ActionStatus model object
juliaElastic Sep 7, 2022
be50c8f
moved out action status UI and .fleet-action-status changes to simpli…
juliaElastic Sep 7, 2022
c0b02d7
fixed toast message for single agent actions
juliaElastic Sep 8, 2022
f26a1f5
added comments
juliaElastic Sep 8, 2022
9e3c01c
Merge branch 'main' into feat/action-batch-async
juliaElastic Sep 9, 2022
fa9bb47
fixed conflict resolution
juliaElastic Sep 9, 2022
7e95311
reverted refreshAgents change
juliaElastic Sep 9, 2022
1d7643b
Merge branch 'main' into feat/action-batch-async
kibanamachine Sep 12, 2022
921e96c
review comments
juliaElastic Sep 12, 2022
c303407
Merge branch 'main' into feat/action-batch-async
kibanamachine Sep 12, 2022
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
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/common/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export const AGENT_API_ROUTES = {
UPGRADE_PATTERN: `${API_ROOT}/agents/{agentId}/upgrade`,
BULK_UPGRADE_PATTERN: `${API_ROOT}/agents/bulk_upgrade`,
CURRENT_UPGRADES_PATTERN: `${API_ROOT}/agents/current_upgrades`,
ACTION_STATUS_PATTERN: `${API_ROOT}/agents/action_status`,
LIST_TAGS_PATTERN: `${API_ROOT}/agents/tags`,
};

Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/common/services/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ export const agentRouteService = {
getUpgradePath: (agentId: string) =>
AGENT_API_ROUTES.UPGRADE_PATTERN.replace('{agentId}', agentId),
getBulkUpgradePath: () => AGENT_API_ROUTES.BULK_UPGRADE_PATTERN,
getActionStatusPath: () => AGENT_API_ROUTES.ACTION_STATUS_PATTERN,
getCurrentUpgradesPath: () => AGENT_API_ROUTES.CURRENT_UPGRADES_PATTERN,
getCancelActionPath: (actionId: string) =>
AGENT_API_ROUTES.CANCEL_ACTIONS_PATTERN.replace('{actionId}', actionId),
Expand Down
21 changes: 19 additions & 2 deletions x-pack/plugins/fleet/common/types/models/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface NewAgentAction {
start_time?: string;
minimum_execution_duration?: number;
source_uri?: string;
total?: number;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added the total count that represents how many agents were actioned (clicked by user), this helps with status reporting in case something went wrong while creating the action documents in batches.

}

export interface AgentAction extends NewAgentAction {
Expand Down Expand Up @@ -104,6 +105,22 @@ export interface CurrentUpgrade {
startTime?: string;
}

export interface ActionStatus {
actionId: string;
// how many agents are successfully included in action documents
nbAgentsActionCreated: number;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

nbAgentsActionCreated represents how many agents are included in .fleet-actions documents, if this is less than nbAgentsActioned, it indicates something went wrong with kibana batch processing.

// how many agents acknowledged the action sucessfully (completed)
nbAgentsAck: number;
version: string;
startTime?: string;
type?: string;
// how many agents were actioned by the user
nbAgentsActioned: number;
status: 'complete' | 'expired' | 'cancelled' | 'failed' | 'in progress';
errorMessage?: string;
}

// Generated from FleetServer schema.json
interface FleetServerAgentComponentUnit {
id: string;
type: 'input' | 'output';
Expand All @@ -122,8 +139,6 @@ interface FleetServerAgentComponent {
units: FleetServerAgentComponentUnit[];
}

// Initially generated from FleetServer schema.json

/**
* An Elastic Agent that has enrolled into Fleet
*/
Expand Down Expand Up @@ -309,5 +324,7 @@ export interface FleetServerAgentAction {
data?: {
[k: string]: unknown;
};

total?: number;
[k: string]: unknown;
}
6 changes: 5 additions & 1 deletion x-pack/plugins/fleet/common/types/rest_spec/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import type { SearchHit } from '@kbn/core/types/elasticsearch';

import type { Agent, AgentAction, CurrentUpgrade, NewAgentAction } from '../models';
import type { Agent, AgentAction, ActionStatus, CurrentUpgrade, NewAgentAction } from '../models';

import type { ListResult, ListWithKuery } from './common';

Expand Down Expand Up @@ -125,6 +125,7 @@ export interface PostBulkAgentReassignRequest {
body: {
policy_id: string;
agents: string[] | string;
batchSize?: number;
};
}

Expand Down Expand Up @@ -205,6 +206,9 @@ export interface GetAgentIncomingDataResponse {
export interface GetCurrentUpgradesResponse {
items: CurrentUpgrade[];
}
export interface GetActionStatusResponse {
items: ActionStatus[];
}
export interface GetAvailableVersionsResponse {
items: string[];
}
2 changes: 1 addition & 1 deletion x-pack/plugins/fleet/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"server": true,
"ui": true,
"configPath": ["xpack", "fleet"],
"requiredPlugins": ["licensing", "data", "encryptedSavedObjects", "navigation", "customIntegrations", "share", "spaces", "security", "unifiedSearch", "savedObjectsTagging"],
"requiredPlugins": ["licensing", "data", "encryptedSavedObjects", "navigation", "customIntegrations", "share", "spaces", "security", "unifiedSearch", "savedObjectsTagging", "taskManager"],
"optionalPlugins": ["features", "cloud", "usageCollection", "home", "globalSearch", "telemetry", "discover", "ingestPipelines"],
"extraPublicDirs": ["common"],
"requiredBundles": ["kibanaReact", "cloud", "esUiShared", "infra", "kibanaUtils", "usageCollection", "unifiedSearch"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,20 @@ export const AgentReassignAgentPolicyModal: React.FunctionComponent<Props> = ({
throw res.error;
}
setIsSubmitting(false);
const hasCompleted = isSingleAgent || Object.keys(res.data ?? {}).length > 0;
const successMessage = i18n.translate(
'xpack.fleet.agentReassignPolicy.successSingleNotificationTitle',
{
defaultMessage: 'Agent policy reassigned',
}
);
notifications.toasts.addSuccess(successMessage);
const submittedMessage = i18n.translate(
'xpack.fleet.agentReassignPolicy.submittedNotificationTitle',
{
defaultMessage: 'Agent policy reassign submitted',
}
);
notifications.toasts.addSuccess(hasCompleted ? successMessage : submittedMessage);
onClose();
} catch (error) {
setIsSubmitting(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const AgentUnenrollAgentModal: React.FunctionComponent<Props> = ({
async function onSubmit() {
try {
setIsSubmitting(true);
const { error } = isSingleAgent
const { error, data } = isSingleAgent
? await sendPostAgentUnenroll((agents[0] as Agent).id, {
revoke: forceUnenroll,
})
Expand All @@ -52,6 +52,13 @@ export const AgentUnenrollAgentModal: React.FunctionComponent<Props> = ({
throw error;
}
setIsSubmitting(false);
const hasCompleted = isSingleAgent || Object.keys(data ?? {}).length > 0;
const submittedMessage = i18n.translate(
'xpack.fleet.unenrollAgents.submittedNotificationTitle',
{
defaultMessage: 'Agent(s) unenroll submitted',
}
);
if (forceUnenroll) {
const successMessage = isSingleAgent
? i18n.translate('xpack.fleet.unenrollAgents.successForceSingleNotificationTitle', {
Expand All @@ -60,7 +67,7 @@ export const AgentUnenrollAgentModal: React.FunctionComponent<Props> = ({
: i18n.translate('xpack.fleet.unenrollAgents.successForceMultiNotificationTitle', {
defaultMessage: 'Agents unenrolled',
});
notifications.toasts.addSuccess(successMessage);
notifications.toasts.addSuccess(hasCompleted ? successMessage : submittedMessage);
} else {
const successMessage = isSingleAgent
? i18n.translate('xpack.fleet.unenrollAgents.successSingleNotificationTitle', {
Expand All @@ -69,7 +76,7 @@ export const AgentUnenrollAgentModal: React.FunctionComponent<Props> = ({
: i18n.translate('xpack.fleet.unenrollAgents.successMultiNotificationTitle', {
defaultMessage: 'Unenrolling agents',
});
notifications.toasts.addSuccess(successMessage);
notifications.toasts.addSuccess(hasCompleted ? successMessage : submittedMessage);
}
onClose();
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,17 @@ export const AgentUpgradeAgentModal: React.FunctionComponent<AgentUpgradeAgentMo
);
setIsSubmitting(false);

if (isSingleAgent && counts.success === counts.total) {
const hasCompleted = isSingleAgent || Object.keys(data ?? {}).length > 0;
const submittedMessage = i18n.translate(
'xpack.fleet.upgradeAgents.submittedNotificationTitle',
{
defaultMessage: 'Agent(s) upgrade submitted',
}
);

if (!hasCompleted) {
notifications.toasts.addSuccess(submittedMessage);
} else if (isSingleAgent && counts.success === counts.total) {
notifications.toasts.addSuccess(
i18n.translate('xpack.fleet.upgradeAgents.successSingleNotificationTitle', {
defaultMessage: 'Upgrading {count} agent',
Expand Down
8 changes: 8 additions & 0 deletions x-pack/plugins/fleet/public/hooks/use_request/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import type {
GetActionStatusResponse,
GetAgentTagsResponse,
PostBulkUpdateAgentTagsRequest,
UpdateAgentRequest,
Expand Down Expand Up @@ -195,6 +196,13 @@ export function sendPostBulkAgentUpgrade(
});
}

export function sendGetActionStatus() {
return sendRequest<GetActionStatusResponse>({
path: agentRouteService.getActionStatusPath(),
method: 'get',
});
}

export function sendGetCurrentUpgrades() {
return sendRequest<GetCurrentUpgradesResponse>({
path: agentRouteService.getCurrentUpgradesPath(),
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/public/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export type {
DownloadSource,
DataStream,
Settings,
ActionStatus,
CurrentUpgrade,
GetFleetStatusResponse,
GetAgentPoliciesRequest,
Expand Down
143 changes: 143 additions & 0 deletions x-pack/plugins/fleet/scripts/create_agents/create_agents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
Copy link
Member

Choose a reason for hiding this comment

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

Thanks for adding this script to help with testing and local dev setup.

* 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 fetch from 'node-fetch';
import { ToolingLog } from '@kbn/tooling-log';
import uuid from 'uuid/v4';

const KIBANA_URL = 'http://localhost:5601';
const KIBANA_USERNAME = 'elastic';
const KIBANA_PASSWORD = 'changeme';

const ES_URL = 'http://localhost:9200';
const ES_SUPERUSER = 'fleet_superuser';
const ES_PASSWORD = 'password';

async function createAgentDocsBulk(policyId: string, count: number) {
const auth = 'Basic ' + Buffer.from(ES_SUPERUSER + ':' + ES_PASSWORD).toString('base64');
const body = (
'{ "index":{ } }\n' +
JSON.stringify({
access_api_key_id: 'api-key-1',
active: true,
policy_id: policyId,
type: 'PERMANENT',
local_metadata: {
elastic: {
agent: {
snapshot: false,
upgradeable: true,
version: '8.2.0',
},
},
host: { hostname: uuid() },
},
user_provided_metadata: {},
enrolled_at: new Date().toISOString(),
last_checkin: new Date().toISOString(),
tags: ['script_create_agents'],
}) +
'\n'
).repeat(count);
const res = await fetch(`${ES_URL}/.fleet-agents/_bulk`, {
method: 'post',
body,
headers: {
Authorization: auth,
'Content-Type': 'application/x-ndjson',
},
});
const data = await res.json();
return data;
}

async function createSuperUser() {
const auth = 'Basic ' + Buffer.from(KIBANA_USERNAME + ':' + KIBANA_PASSWORD).toString('base64');
const roleRes = await fetch(`${ES_URL}/_security/role/${ES_SUPERUSER}`, {
method: 'post',
body: JSON.stringify({
indices: [
{
names: ['.fleet*'],
privileges: ['all'],
allow_restricted_indices: true,
},
],
}),
headers: {
Authorization: auth,
'Content-Type': 'application/json',
},
});
const role = await roleRes.json();
const userRes = await fetch(`${ES_URL}/_security/user/${ES_SUPERUSER}`, {
method: 'post',
body: JSON.stringify({
password: ES_PASSWORD,
roles: ['superuser', ES_SUPERUSER],
}),
headers: {
Authorization: auth,
'Content-Type': 'application/json',
},
});
const user = await userRes.json();
return { role, user };
}

async function createAgentPolicy(id: string) {
const auth = 'Basic ' + Buffer.from(KIBANA_USERNAME + ':' + KIBANA_PASSWORD).toString('base64');
const res = await fetch(`${KIBANA_URL}/api/fleet/agent_policies`, {
method: 'post',
body: JSON.stringify({
id,
name: id,
namespace: 'default',
description: '',
monitoring_enabled: ['logs'],
data_output_id: 'fleet-default-output',
monitoring_output_id: 'fleet-default-output',
}),
headers: {
Authorization: auth,
'Content-Type': 'application/json',
'kbn-xsrf': 'kibana',
'x-elastic-product-origin': 'fleet',
},
});
const data = await res.json();
return data;
}

/**
* Script to create large number of agent documents at once.
* This is helpful for testing agent bulk actions locally as the kibana async logic kicks in for >10k agents.
*/
export async function run() {
const logger = new ToolingLog({
level: 'info',
writeTo: process.stdout,
});

logger.info('Creating agent policy');

const agentPolicyId = uuid();
const agentPolicy = await createAgentPolicy(agentPolicyId);
logger.info(`Created agent policy ${agentPolicy.item.id}`);

logger.info('Creating fleet superuser');
const { role, user } = await createSuperUser();
logger.info(`Created role ${ES_SUPERUSER}, created: ${role.role.created}`);
logger.info(`Created user ${ES_SUPERUSER}, created: ${user.created}`);

logger.info('Creating agent documents');
const count = 50000;
const agents = await createAgentDocsBulk(agentPolicyId, count);
logger.info(
`Created ${agents.items.length} agent docs, took ${agents.took}, errors: ${agents.errors}`
Copy link
Contributor Author

Choose a reason for hiding this comment

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

With this script I managed to test the kibana logic with 200k+ agent docs.
I didn't notice UI slowness even when the async processing took about 60s.

image

);
}
17 changes: 17 additions & 0 deletions x-pack/plugins/fleet/scripts/create_agents/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* 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.
*/

require('../../../../../src/setup_node_env');
require('./create_agents').run();

/*
Usage:

cd x-pack/plugins/fleet
node scripts/create_agents/index.js

*/
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/server/mocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export const createAppContextStartContractMock = (
kibanaVersion: '8.99.0', // Fake version :)
kibanaBranch: 'main',
telemetryEventsSender: createMockTelemetryEventsSender(),
bulkActionsResolver: {} as any,
};
};

Expand Down
Loading