Skip to content

Commit

Permalink
[Ingest Manager] Upgrade Agents in Fleet (#78810)
Browse files Browse the repository at this point in the history
* add kibanaVersion context and hook, add upgrade available indications

* add agent upgrade modals and action buttons

* fix import

* add bulk actions api and remove source_uri as required

* add upgrading to AgentHealth status

* buildKueryForUpgradingAgents

* bulk actions UI

* remove source_uri

* add release type to agent details

* don't allow upgrade of unenrolled/unenrolling agent

* hide upgradeable button when not upgradeable

* fix test

* add udpating agent status

* remove upgrade available filter button for now

* update isUpgradeAvailable to use local_metadata upgradeable

* add UPDATING to agent event subtype

* use saved object for updating agent status

* add updating badge type label

* add upgrade available button and update agent list endpoint to accept showUpgradeable

* add schema and type for UPDATING

* fix type

* dont try to upgrade local_metadata

* exclude from AAD upgrade_started_at and upgraded_at

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
neptunian and kibanamachine committed Oct 5, 2020
1 parent 40bae24 commit fcdab35
Show file tree
Hide file tree
Showing 35 changed files with 901 additions and 57 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/ingest_manager/common/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export const AGENT_API_ROUTES = {
BULK_REASSIGN_PATTERN: `${FLEET_API_ROOT}/agents/bulk_reassign`,
STATUS_PATTERN: `${FLEET_API_ROOT}/agent-status`,
UPGRADE_PATTERN: `${FLEET_API_ROOT}/agents/{agentId}/upgrade`,
BULK_UPGRADE_PATTERN: `${FLEET_API_ROOT}/agents/bulk_upgrade`,
};

export const ENROLLMENT_API_KEY_ROUTES = {
Expand Down
10 changes: 7 additions & 3 deletions x-pack/plugins/ingest_manager/common/services/agent_status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ export function getAgentStatus(agent: Agent, now: number = Date.now()): AgentSta
if (!agent.last_checkin) {
return 'enrolling';
}
if (agent.upgrade_started_at && !agent.upgraded_at) {
return 'upgrading';
}

const msLastCheckIn = new Date(lastCheckIn || 0).getTime();
const msSinceLastCheckIn = new Date().getTime() - msLastCheckIn;
Expand All @@ -33,6 +30,9 @@ export function getAgentStatus(agent: Agent, now: number = Date.now()): AgentSta
if (agent.last_checkin_status === 'degraded') {
return 'degraded';
}
if (agent.upgrade_started_at && !agent.upgraded_at) {
return 'updating';
}
if (intervalsSinceLastCheckIn >= 4) {
return 'offline';
}
Expand Down Expand Up @@ -61,3 +61,7 @@ export function buildKueryForOfflineAgents() {
(4 * AGENT_POLLING_THRESHOLD_MS) / 1000
}s AND not (${buildKueryForErrorAgents()})`;
}

export function buildKueryForUpdatingAgents() {
return `${AGENT_SAVED_OBJECT_TYPE}.upgrade_started_at:*`;
}
1 change: 1 addition & 0 deletions x-pack/plugins/ingest_manager/common/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export { decodeCloudId } from './decode_cloud_id';
export { isValidNamespace } from './is_valid_namespace';
export { isDiffPathProtocol } from './is_diff_path_protocol';
export { LicenseService } from './license';
export { isAgentUpgradeable } from './is_agent_upgradeable';
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { isAgentUpgradeable } from './is_agent_upgradeable';
import { Agent } from '../types/models/agent';

const getAgent = (version: string, upgradeable: boolean): Agent => {
const agent: Agent = {
id: 'de9006e1-54a7-4320-b24e-927e6fe518a8',
active: true,
policy_id: '63a284b0-0334-11eb-a4e0-09883c57114b',
type: 'PERMANENT',
enrolled_at: '2020-09-30T20:24:08.347Z',
user_provided_metadata: {},
local_metadata: {
elastic: {
agent: {
id: 'de9006e1-54a7-4320-b24e-927e6fe518a8',
version,
snapshot: false,
'build.original':
'8.0.0 (build: e2ef4fc375a5ece83d5d38f57b2977d7866b5819 at 2020-09-30 20:21:35 +0000 UTC)',
},
},
host: {
architecture: 'x86_64',
hostname: 'Sandras-MBP.fios-router.home',
name: 'Sandras-MBP.fios-router.home',
id: '1112D0AD-526D-5268-8E86-765D35A0F484',
ip: [
'127.0.0.1/8',
'::1/128',
'fe80::1/64',
'fe80::aede:48ff:fe00:1122/64',
'fe80::4fc:2526:7d51:19cc/64',
'192.168.1.161/24',
'fe80::3083:5ff:fe30:4b00/64',
'fe80::3083:5ff:fe30:4b00/64',
'fe80::f7fb:518e:2c3c:7815/64',
'fe80::2abd:20e3:9bc3:c054/64',
'fe80::531a:20ab:1f38:7f9/64',
],
mac: [
'a6:83:e7:b0:1a:d2',
'ac:de:48:00:11:22',
'a4:83:e7:b0:1a:d2',
'82:c5:c2:25:b0:01',
'82:c5:c2:25:b0:00',
'82:c5:c2:25:b0:05',
'82:c5:c2:25:b0:04',
'82:c5:c2:25:b0:01',
'06:83:e7:b0:1a:d2',
'32:83:05:30:4b:00',
'32:83:05:30:4b:00',
],
},
os: {
family: 'darwin',
kernel: '19.4.0',
platform: 'darwin',
version: '10.15.4',
name: 'Mac OS X',
full: 'Mac OS X(10.15.4)',
},
},
access_api_key_id: 'A_6v4HQBEEDXi-A9vxPE',
default_api_key_id: 'BP6v4HQBEEDXi-A95xMk',
policy_revision: 1,
packages: ['system'],
last_checkin: '2020-10-01T14:43:27.255Z',
current_error_events: [],
status: 'online',
};
if (upgradeable) {
agent.local_metadata.elastic.agent.upgradeable = true;
}
return agent;
};
describe('Ingest Manager - isAgentUpgradeable', () => {
it('returns false if agent reports not upgradeable with agent version < kibana version', () => {
expect(isAgentUpgradeable(getAgent('7.9.0', false), '8.0.0')).toBe(false);
});
it('returns false if agent reports not upgradeable with agent version > kibana version', () => {
expect(isAgentUpgradeable(getAgent('8.0.0', false), '7.9.0')).toBe(false);
});
it('returns false if agent reports not upgradeable with agent version === kibana version', () => {
expect(isAgentUpgradeable(getAgent('8.0.0', false), '8.0.0')).toBe(false);
});
it('returns false if agent reports upgradeable, with agent version === kibana version', () => {
expect(isAgentUpgradeable(getAgent('8.0.0', true), '8.0.0')).toBe(false);
});
it('returns false if agent reports upgradeable, with agent version > kibana version', () => {
expect(isAgentUpgradeable(getAgent('8.0.0', true), '7.9.0')).toBe(false);
});
it('returns true if agent reports upgradeable, with agent version < kibana version', () => {
expect(isAgentUpgradeable(getAgent('7.9.0', true), '8.0.0')).toBe(true);
});
});
Original file line number Diff line number Diff line change
@@ -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;
* you may not use this file except in compliance with the Elastic License.
*/
import semver from 'semver';
import { Agent } from '../types';

export function isAgentUpgradeable(agent: Agent, kibanaVersion: string) {
let agentVersion: string;
if (typeof agent?.local_metadata?.elastic?.agent?.version === 'string') {
agentVersion = agent.local_metadata.elastic.agent.version;
} else {
return false;
}
const kibanaVersionParsed = semver.parse(kibanaVersion);
const agentVersionParsed = semver.parse(agentVersion);
if (!agentVersionParsed || !kibanaVersionParsed) return false;
if (!agent.local_metadata.elastic.agent.upgradeable) return false;
return semver.lt(agentVersionParsed, kibanaVersionParsed);
}
3 changes: 3 additions & 0 deletions x-pack/plugins/ingest_manager/common/services/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ export const agentRouteService = {
getReassignPath: (agentId: string) =>
AGENT_API_ROUTES.REASSIGN_PATTERN.replace('{agentId}', agentId),
getBulkReassignPath: () => AGENT_API_ROUTES.BULK_REASSIGN_PATTERN,
getUpgradePath: (agentId: string) =>
AGENT_API_ROUTES.UPGRADE_PATTERN.replace('{agentId}', agentId),
getBulkUpgradePath: () => AGENT_API_ROUTES.BULK_UPGRADE_PATTERN,
getListPath: () => AGENT_API_ROUTES.LIST_PATTERN,
getStatusPath: () => AGENT_API_ROUTES.STATUS_PATTERN,
};
Expand Down
9 changes: 4 additions & 5 deletions x-pack/plugins/ingest_manager/common/types/models/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export type AgentStatus =
| 'warning'
| 'enrolling'
| 'unenrolling'
| 'upgrading'
| 'updating'
| 'degraded';

export type AgentActionType = 'POLICY_CHANGE' | 'UNENROLL' | 'UPGRADE';
Expand Down Expand Up @@ -89,6 +89,7 @@ export interface NewAgentEvent {
| 'STOPPING'
| 'STOPPED'
| 'DEGRADED'
| 'UPDATING'
// Action results
| 'DATA_DUMP'
// Actions
Expand All @@ -109,10 +110,8 @@ export interface AgentEvent extends NewAgentEvent {

export type AgentEventSOAttributes = NewAgentEvent;

type MetadataValue = string | AgentMetadata;

export interface AgentMetadata {
[x: string]: MetadataValue;
[x: string]: any;
}
interface AgentBase {
type: AgentType;
Expand All @@ -129,7 +128,7 @@ interface AgentBase {
policy_id?: string;
policy_revision?: number | null;
last_checkin?: string;
last_checkin_status?: 'error' | 'online' | 'degraded';
last_checkin_status?: 'error' | 'online' | 'degraded' | 'updating';
user_provided_metadata: AgentMetadata;
local_metadata: AgentMetadata;
}
Expand Down
25 changes: 21 additions & 4 deletions x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface GetAgentsRequest {
perPage: number;
kuery?: string;
showInactive: boolean;
showUpgradeable?: boolean;
};
}

Expand Down Expand Up @@ -113,22 +114,38 @@ export interface PostAgentUnenrollRequest {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface PostAgentUnenrollResponse {}

export interface PostBulkAgentUnenrollRequest {
body: {
agents: string[] | string;
force?: boolean;
};
}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface PostBulkAgentUnenrollResponse {}

export interface PostAgentUpgradeRequest {
params: {
agentId: string;
};
body: {
source_uri?: string;
version: string;
};
}
export interface PostBulkAgentUnenrollRequest {

export interface PostBulkAgentUpgradeRequest {
body: {
agents: string[] | string;
force?: boolean;
source_uri?: string;
version: string;
};
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface PostBulkAgentUpgradeResponse {}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface PostAgentUpgradeResponse {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface PostBulkAgentUnenrollResponse {}

export interface PutAgentReassignRequest {
params: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
export { useCapabilities } from './use_capabilities';
export { useCore } from './use_core';
export { useConfig, ConfigContext } from './use_config';
export { useKibanaVersion, KibanaVersionContext } from './use_kibana_version';
export { useSetupDeps, useStartDeps, DepsContext } from './use_deps';
export { licenseService, useLicense } from './use_license';
export { useBreadcrumbs } from './use_breadcrumbs';
Expand Down
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;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { useContext } from 'react';

export const KibanaVersionContext = React.createContext<string | null>(null);

export function useKibanaVersion() {
const version = useContext(KibanaVersionContext);
if (version === null) {
throw new Error('KibanaVersionContext is not initialized');
}
return version;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import {
GetAgentsResponse,
GetAgentStatusRequest,
GetAgentStatusResponse,
PostAgentUpgradeRequest,
PostBulkAgentUpgradeRequest,
PostAgentUpgradeResponse,
PostBulkAgentUpgradeResponse,
} from '../../types';

type RequestOptions = Pick<Partial<UseRequestConfig>, 'pollIntervalMs'>;
Expand Down Expand Up @@ -126,3 +130,28 @@ export function sendPostBulkAgentUnenroll(
...options,
});
}

export function sendPostAgentUpgrade(
agentId: string,
body: PostAgentUpgradeRequest['body'],
options?: RequestOptions
) {
return sendRequest<PostAgentUpgradeResponse>({
path: agentRouteService.getUpgradePath(agentId),
method: 'post',
body,
...options,
});
}

export function sendPostBulkAgentUpgrade(
body: PostBulkAgentUpgradeRequest['body'],
options?: RequestOptions
) {
return sendRequest<PostBulkAgentUpgradeResponse>({
path: agentRouteService.getBulkUpgradePath(),
method: 'post',
body,
...options,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
sendSetup,
sendGetPermissionsCheck,
licenseService,
KibanaVersionContext,
} from './hooks';
import { PackageInstallProvider } from './sections/epm/hooks';
import { FleetStatusProvider } from './hooks/use_fleet_status';
Expand Down Expand Up @@ -235,23 +236,27 @@ const IngestManagerApp = ({
startDeps,
config,
history,
kibanaVersion,
}: {
basepath: string;
coreStart: CoreStart;
setupDeps: IngestManagerSetupDeps;
startDeps: IngestManagerStartDeps;
config: IngestManagerConfigType;
history: AppMountParameters['history'];
kibanaVersion: string;
}) => {
const isDarkMode = useObservable<boolean>(coreStart.uiSettings.get$('theme:darkMode'));
return (
<coreStart.i18n.Context>
<KibanaContextProvider services={{ ...coreStart }}>
<DepsContext.Provider value={{ setup: setupDeps, start: startDeps }}>
<ConfigContext.Provider value={config}>
<EuiThemeProvider darkMode={isDarkMode}>
<IngestManagerRoutes history={history} basepath={basepath} />
</EuiThemeProvider>
<KibanaVersionContext.Provider value={kibanaVersion}>
<EuiThemeProvider darkMode={isDarkMode}>
<IngestManagerRoutes history={history} basepath={basepath} />
</EuiThemeProvider>
</KibanaVersionContext.Provider>
</ConfigContext.Provider>
</DepsContext.Provider>
</KibanaContextProvider>
Expand All @@ -264,7 +269,8 @@ export function renderApp(
{ element, appBasePath, history }: AppMountParameters,
setupDeps: IngestManagerSetupDeps,
startDeps: IngestManagerStartDeps,
config: IngestManagerConfigType
config: IngestManagerConfigType,
kibanaVersion: string
) {
ReactDOM.render(
<IngestManagerApp
Expand All @@ -274,6 +280,7 @@ export function renderApp(
startDeps={startDeps}
config={config}
history={history}
kibanaVersion={kibanaVersion}
/>,
element
);
Expand Down
Loading

0 comments on commit fcdab35

Please sign in to comment.