Skip to content

Commit

Permalink
[Fleet] Add config revision to fleet agents (#60292)
Browse files Browse the repository at this point in the history
  • Loading branch information
nchaulet authored Mar 17, 2020
1 parent 9318862 commit 156066d
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 21 deletions.
3 changes: 2 additions & 1 deletion x-pack/plugins/ingest_manager/common/types/models/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ interface AgentBase {
access_api_key_id?: string;
default_api_key?: string;
config_id?: string;
config_revision?: number;
config_newest_revision?: number;
last_checkin?: string;
config_updated_at?: string;
actions: AgentAction[];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
EuiButtonIcon,
EuiContextMenuPanel,
EuiContextMenuItem,
EuiIcon,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react';
Expand Down Expand Up @@ -289,6 +290,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
},
{
field: 'active',
width: '100px',
name: i18n.translate('xpack.ingestManager.agentList.statusColumnTitle', {
defaultMessage: 'Status',
}),
Expand All @@ -299,10 +301,10 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
name: i18n.translate('xpack.ingestManager.agentList.configColumnTitle', {
defaultMessage: 'Configuration',
}),
render: (configId: string) => {
render: (configId: string, agent: Agent) => {
const configName = agentConfigs.find(p => p.id === configId)?.name;
return (
<EuiFlexGroup gutterSize="s" alignItems="baseline" style={{ minWidth: 0 }}>
<EuiFlexGroup gutterSize="s" alignItems="center" style={{ minWidth: 0 }}>
<EuiFlexItem grow={false} style={NO_WRAP_TRUNCATE_STYLE}>
<EuiLink
href={`${CONFIG_DETAILS_URI}${configId}`}
Expand All @@ -312,21 +314,42 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
{configName || configId}
</EuiLink>
</EuiFlexItem>
<EuiFlexItem grow={true}>
<EuiText color="subdued" size="xs" style={{ whiteSpace: 'nowrap' }}>
<FormattedMessage
id="xpack.ingestManager.agentList.revisionNumber"
defaultMessage="rev. {revNumber}"
values={{ revNumber: '999' }} // TODO fix when we have revision
/>
</EuiText>
</EuiFlexItem>
{agent.config_revision && (
<EuiFlexItem grow={false}>
<EuiText color="default" size="xs" className="eui-textNoWrap">
<FormattedMessage
id="xpack.ingestManager.agentList.revisionNumber"
defaultMessage="rev. {revNumber}"
values={{ revNumber: agent.config_revision }}
/>
</EuiText>
</EuiFlexItem>
)}
{agent.config_revision &&
agent.config_newest_revision &&
agent.config_newest_revision > agent.config_revision && (
<EuiFlexItem grow={false}>
<EuiText color="subdued" size="xs" className="eui-textNoWrap">
<EuiIcon size="m" type="alert" color="warning" />
&nbsp;
{true && (
<>
<FormattedMessage
id="xpack.ingestManager.agentList.outOfDateLabel"
defaultMessage="Out-of-date"
/>
</>
)}
</EuiText>
</EuiFlexItem>
)}
</EuiFlexGroup>
);
},
},
{
field: 'local_metadata.agent_version',
width: '100px',
name: i18n.translate('xpack.ingestManager.agentList.versionTitle', {
defaultMessage: 'Version',
}),
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/ingest_manager/server/saved_objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ export const savedObjectMappings = {
config_id: { type: 'keyword' },
last_updated: { type: 'date' },
last_checkin: { type: 'date' },
config_updated_at: { type: 'date' },
config_revision: { type: 'integer' },
config_newest_revision: { type: 'integer' },
// FIXME_INGEST https://github.com/elastic/kibana/issues/56554
default_api_key: { type: 'keyword' },
updated_at: { type: 'date' },
Expand Down
14 changes: 14 additions & 0 deletions x-pack/plugins/ingest_manager/server/services/agents/acks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,22 @@ export async function acknowledgeAgentActions(
});

if (matchedUpdatedActions.length > 0) {
const configRevision = matchedUpdatedActions.reduce((acc, action) => {
if (action.type !== 'CONFIG_CHANGE') {
return acc;
}
const data = action.data ? JSON.parse(action.data as string) : {};

if (data?.config?.id !== agent.config_id) {
return acc;
}

return data?.config?.revision > acc ? data?.config?.revision : acc;
}, agent.config_revision || 0);

await soClient.update<AgentSOAttributes>(AGENT_SAVED_OBJECT_TYPE, agent.id, {
actions: matchedUpdatedActions,
config_revision: configRevision,
});
}

Expand Down
117 changes: 117 additions & 0 deletions x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* 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 { shouldCreateConfigAction } from './checkin';
import { Agent } from '../../types';

function getAgent(data: Partial<Agent>) {
return { actions: [], ...data } as Agent;
}

describe('Agent checkin service', () => {
describe('shouldCreateConfigAction', () => {
it('should return false if the agent do not have an assigned config', () => {
const res = shouldCreateConfigAction(getAgent({}));

expect(res).toBeFalsy();
});

it('should return true if this is agent first checkin', () => {
const res = shouldCreateConfigAction(getAgent({ config_id: 'config1' }));

expect(res).toBeTruthy();
});

it('should return false agent is already running latest revision', () => {
const res = shouldCreateConfigAction(
getAgent({
config_id: 'config1',
last_checkin: '2018-01-02T00:00:00',
config_revision: 1,
config_newest_revision: 1,
})
);

expect(res).toBeFalsy();
});

it('should return false agent has already latest revision config change action', () => {
const res = shouldCreateConfigAction(
getAgent({
config_id: 'config1',
last_checkin: '2018-01-02T00:00:00',
config_revision: 1,
config_newest_revision: 2,
actions: [
{
id: 'action1',
type: 'CONFIG_CHANGE',
created_at: new Date().toISOString(),
data: JSON.stringify({
config: {
id: 'config1',
revision: 2,
},
}),
},
],
})
);

expect(res).toBeFalsy();
});

it('should return true agent has unrelated config change actions', () => {
const res = shouldCreateConfigAction(
getAgent({
config_id: 'config1',
last_checkin: '2018-01-02T00:00:00',
config_revision: 1,
config_newest_revision: 2,
actions: [
{
id: 'action1',
type: 'CONFIG_CHANGE',
created_at: new Date().toISOString(),
data: JSON.stringify({
config: {
id: 'config2',
revision: 2,
},
}),
},
{
id: 'action1',
type: 'CONFIG_CHANGE',
created_at: new Date().toISOString(),
data: JSON.stringify({
config: {
id: 'config1',
revision: 1,
},
}),
},
],
})
);

expect(res).toBeTruthy();
});

it('should return true if this agent has a new revision', () => {
const res = shouldCreateConfigAction(
getAgent({
config_id: 'config1',
last_checkin: '2018-01-02T00:00:00',
config_revision: 1,
config_newest_revision: 2,
})
);

expect(res).toBeTruthy();
});
});
});
35 changes: 30 additions & 5 deletions x-pack/plugins/ingest_manager/server/services/agents/checkin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export async function agentCheckin(
const actions = filterActionsForCheckin(agent);

// Generate new agent config if config is updated
if (isNewAgentConfig(agent) && agent.config_id) {
if (agent.config_id && shouldCreateConfigAction(agent)) {
const config = await agentConfigService.getFullConfig(soClient, agent.config_id);
if (config) {
// Assign output API keys
Expand Down Expand Up @@ -149,12 +149,37 @@ function isActionEvent(event: AgentEvent) {
);
}

function isNewAgentConfig(agent: Agent) {
export function shouldCreateConfigAction(agent: Agent): boolean {
if (!agent.config_id) {
return false;
}

const isFirstCheckin = !agent.last_checkin;
const isConfigUpdatedSinceLastCheckin =
agent.last_checkin && agent.config_updated_at && agent.last_checkin <= agent.config_updated_at;
if (isFirstCheckin) {
return true;
}

const isAgentConfigOutdated =
agent.config_revision &&
agent.config_newest_revision &&
agent.config_revision < agent.config_newest_revision;
if (!isAgentConfigOutdated) {
return false;
}

const isActionAlreadyGenerated = !!agent.actions.find(action => {
if (!action.data || action.type !== 'CONFIG_CHANGE') {
return false;
}

const data = JSON.parse(action.data);

return (
data.config.id === agent.config_id && data.config.revision === agent.config_newest_revision
);
});

return isFirstCheckin || isConfigUpdatedSinceLastCheckin;
return !isActionAlreadyGenerated;
}

function filterActionsForCheckin(agent: Agent): AgentAction[] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export async function enroll(
current_error_events: undefined,
actions: [],
access_api_key_id: undefined,
config_updated_at: undefined,
last_checkin: undefined,
default_api_key: undefined,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@ import { SavedObjectsClientContract } from 'src/core/server';
import { listAgents } from './crud';
import { AGENT_SAVED_OBJECT_TYPE } from '../../constants';
import { unenrollAgents } from './unenroll';
import { agentConfigService } from '../agent_config';

export async function updateAgentsForConfigId(
soClient: SavedObjectsClientContract,
configId: string
) {
const config = await agentConfigService.get(soClient, configId);
if (!config) {
throw new Error('Config not found');
}
let hasMore = true;
let page = 1;
const now = new Date().toISOString();
while (hasMore) {
const { agents } = await listAgents(soClient, {
kuery: `agents.config_id:"${configId}"`,
Expand All @@ -30,7 +34,7 @@ export async function updateAgentsForConfigId(
const agentUpdate = agents.map(agent => ({
id: agent.id,
type: AGENT_SAVED_OBJECT_TYPE,
attributes: { config_updated_at: now },
attributes: { config_newest_revision: config.revision },
}));

await soClient.bulkUpdate(agentUpdate);
Expand Down

0 comments on commit 156066d

Please sign in to comment.