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

[Ingest Manager] Fix and improve agent status #71009

Merged
merged 4 commits into from
Jul 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
57 changes: 23 additions & 34 deletions x-pack/plugins/ingest_manager/common/services/agent_status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,63 +5,52 @@
*/

import {
AGENT_TYPE_TEMPORARY,
AGENT_POLLING_THRESHOLD_MS,
AGENT_TYPE_PERMANENT,
AGENT_TYPE_EPHEMERAL,
AGENT_SAVED_OBJECT_TYPE,
} from '../constants';
import { Agent, AgentStatus } from '../types';

export function getAgentStatus(agent: Agent, now: number = Date.now()): AgentStatus {
const { type, last_checkin: lastCheckIn } = agent;
const msLastCheckIn = new Date(lastCheckIn || 0).getTime();
const msSinceLastCheckIn = new Date().getTime() - msLastCheckIn;
const intervalsSinceLastCheckIn = Math.floor(msSinceLastCheckIn / AGENT_POLLING_THRESHOLD_MS);
const { last_checkin: lastCheckIn } = agent;

if (!agent.active) {
return 'inactive';
}
if (!agent.last_checkin) {
return 'enrolling';
}
if (agent.unenrollment_started_at && !agent.unenrolled_at) {
return 'unenrolling';
}
if (agent.current_error_events.length > 0) {

const msLastCheckIn = new Date(lastCheckIn || 0).getTime();
const msSinceLastCheckIn = new Date().getTime() - msLastCheckIn;
const intervalsSinceLastCheckIn = Math.floor(msSinceLastCheckIn / AGENT_POLLING_THRESHOLD_MS);

if (agent.last_checkin_status === 'error') {
return 'error';
}
switch (type) {
case AGENT_TYPE_PERMANENT:
if (intervalsSinceLastCheckIn >= 4) {
return 'error';
}
case AGENT_TYPE_TEMPORARY:
if (intervalsSinceLastCheckIn >= 3) {
return 'offline';
}
case AGENT_TYPE_EPHEMERAL:
if (intervalsSinceLastCheckIn >= 3) {
return 'inactive';
}
if (agent.last_checkin_status === 'degraded') {
return 'degraded';
}
if (intervalsSinceLastCheckIn >= 4) {
return 'offline';
}

return 'online';
}

export function buildKueryForOnlineAgents() {
return `(${AGENT_SAVED_OBJECT_TYPE}.type:${AGENT_TYPE_PERMANENT} and ${AGENT_SAVED_OBJECT_TYPE}.last_checkin >= now-${
(4 * AGENT_POLLING_THRESHOLD_MS) / 1000
}s) or (${AGENT_SAVED_OBJECT_TYPE}.type:${AGENT_TYPE_TEMPORARY} and ${AGENT_SAVED_OBJECT_TYPE}.last_checkin >= now-${
(3 * AGENT_POLLING_THRESHOLD_MS) / 1000
}s) or (${AGENT_SAVED_OBJECT_TYPE}.type:${AGENT_TYPE_EPHEMERAL} and ${AGENT_SAVED_OBJECT_TYPE}.last_checkin >= now-${
(3 * AGENT_POLLING_THRESHOLD_MS) / 1000
}s)`;
return `not (${buildKueryForOfflineAgents()}) AND not (${buildKueryForErrorAgents()})`;
}

export function buildKueryForOfflineAgents() {
return `${AGENT_SAVED_OBJECT_TYPE}.type:${AGENT_TYPE_TEMPORARY} AND ${AGENT_SAVED_OBJECT_TYPE}.last_checkin < now-${
(3 * AGENT_POLLING_THRESHOLD_MS) / 1000
}s`;
export function buildKueryForErrorAgents() {
return `( ${AGENT_SAVED_OBJECT_TYPE}.last_checkin_status:error or ${AGENT_SAVED_OBJECT_TYPE}.last_checkin_status:degraded )`;
}

export function buildKueryForErrorAgents() {
return `${AGENT_SAVED_OBJECT_TYPE}.type:${AGENT_TYPE_PERMANENT} AND ${AGENT_SAVED_OBJECT_TYPE}.last_checkin < now-${
export function buildKueryForOfflineAgents() {
return `((${AGENT_SAVED_OBJECT_TYPE}.type:${AGENT_TYPE_PERMANENT} AND ${AGENT_SAVED_OBJECT_TYPE}.last_checkin < now-${
(4 * AGENT_POLLING_THRESHOLD_MS) / 1000
}s`;
}s) AND not ( ${buildKueryForErrorAgents()} ))`;
}
12 changes: 11 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 @@ -11,7 +11,16 @@ export type AgentType =
| typeof AGENT_TYPE_PERMANENT
| typeof AGENT_TYPE_TEMPORARY;

export type AgentStatus = 'offline' | 'error' | 'online' | 'inactive' | 'warning' | 'unenrolling';
export type AgentStatus =
| 'offline'
| 'error'
| 'online'
| 'inactive'
| 'warning'
| 'enrolling'
| 'unenrolling'
| 'degraded';

export type AgentActionType = 'CONFIG_CHANGE' | 'DATA_DUMP' | 'RESUME' | 'PAUSE' | 'UNENROLL';
export interface NewAgentAction {
type: AgentActionType;
Expand Down Expand Up @@ -82,6 +91,7 @@ interface AgentBase {
config_id?: string;
config_revision?: number | null;
last_checkin?: string;
last_checkin_status?: 'error' | 'online' | 'degraded';
user_provided_metadata: AgentMetadata;
local_metadata: AgentMetadata;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface PostAgentCheckinRequest {
agentId: string;
};
body: {
status?: 'online' | 'error' | 'degraded';
local_metadata?: Record<string, any>;
events?: NewAgentEvent[];
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
}

if (selectedStatus.length) {
if (kuery) {
kuery = `(${kuery}) and`;
}

kuery = selectedStatus
const kueryStatus = selectedStatus
.map((status) => {
switch (status) {
case 'online':
Expand All @@ -196,6 +192,12 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
return '';
})
.join(' or ');

if (kuery) {
kuery = `(${kuery}) and ${kueryStatus}`;
} else {
kuery = kueryStatus;
}
}

const agentsRequest = useGetAgents(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@ const Status = {
/>
</EuiHealth>
),
Degraded: (
<EuiHealth color="danger">
<FormattedMessage
id="xpack.ingestManager.agentHealth.degradedStatusText"
defaultMessage="Degraded"
/>
</EuiHealth>
),
Enrolling: (
<EuiHealth color="warning">
<FormattedMessage
id="xpack.ingestManager.agentHealth.enrollingStatusText"
defaultMessage="Enrolling"
/>
</EuiHealth>
),
Unenrolling: (
<EuiHealth color="warning">
<FormattedMessage
Expand All @@ -67,6 +83,8 @@ function getStatusComponent(agent: Agent): React.ReactElement {
switch (agent.status) {
case 'error':
return Status.Error;
case 'degraded':
return Status.Degraded;
case 'inactive':
return Status.Inactive;
case 'offline':
Expand All @@ -75,6 +93,8 @@ function getStatusComponent(agent: Agent): React.ReactElement {
return Status.Warning;
case 'unenrolling':
return Status.Unenrolling;
case 'enrolling':
return Status.Enrolling;
default:
return Status.Online;
}
Expand Down
7 changes: 5 additions & 2 deletions x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,11 @@ export const postAgentCheckinHandler: RequestHandler<
const { actions } = await AgentService.agentCheckin(
soClient,
agent,
request.body.events || [],
request.body.local_metadata,
{
events: request.body.events || [],
localMetadata: request.body.local_metadata,
status: request.body.status,
},
{ signal }
);
const body: PostAgentCheckinResponse = {
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/ingest_manager/server/saved_objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const savedObjectTypes: { [key: string]: SavedObjectsType } = {
config_id: { type: 'keyword' },
last_updated: { type: 'date' },
last_checkin: { type: 'date' },
last_checkin_status: { type: 'keyword' },
config_revision: { type: 'integer' },
default_api_key_id: { type: 'keyword' },
default_api_key: { type: 'binary', index: false },
Expand Down Expand Up @@ -310,6 +311,7 @@ export function registerEncryptedSavedObjects(
'config_id',
'last_updated',
'last_checkin',
'last_checkin_status',
'config_revision',
'config_newest_revision',
'updated_at',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
AgentEvent,
AgentSOAttributes,
AgentEventSOAttributes,
AgentMetadata,
} from '../../../types';

import { AGENT_SAVED_OBJECT_TYPE, AGENT_EVENT_SAVED_OBJECT_TYPE } from '../../../constants';
Expand All @@ -21,20 +20,24 @@ import { getAgentActionsForCheckin } from '../actions';
export async function agentCheckin(
soClient: SavedObjectsClientContract,
agent: Agent,
events: NewAgentEvent[],
localMetadata?: any,
data: {
events: NewAgentEvent[];
localMetadata?: any;
status?: 'online' | 'error' | 'degraded';
},
options?: { signal: AbortSignal }
) {
const updateData: {
local_metadata?: AgentMetadata;
current_error_events?: string;
} = {};
const { updatedErrorEvents } = await processEventsForCheckin(soClient, agent, events);
const updateData: Partial<AgentSOAttributes> = {};
const { updatedErrorEvents } = await processEventsForCheckin(soClient, agent, data.events);
if (updatedErrorEvents) {
updateData.current_error_events = JSON.stringify(updatedErrorEvents);
}
if (localMetadata) {
updateData.local_metadata = localMetadata;
if (data.localMetadata) {
updateData.local_metadata = data.localMetadata;
}

if (data.status !== agent.last_checkin_status) {
updateData.last_checkin_status = data.status;
}
if (Object.keys(updateData).length > 0) {
await soClient.update<AgentSOAttributes>(AGENT_SAVED_OBJECT_TYPE, agent.id, updateData);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export function agentCheckinStateConnectedAgentsFactory() {
const internalSOClient = getInternalUserSOClient();
const now = new Date().toISOString();
const updates: Array<SavedObjectsBulkUpdateObject<AgentSOAttributes>> = [
...connectedAgentsIds.values(),
...agentToUpdate.values(),
].map((agentId) => ({
type: AGENT_SAVED_OBJECT_TYPE,
id: agentId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,44 @@ describe('Agent status service', () => {
type: AGENT_TYPE_PERMANENT,
attributes: {
active: true,
last_checkin: new Date().toISOString(),
local_metadata: {},
user_provided_metadata: {},
},
} as SavedObject<AgentSOAttributes>);
const status = await getAgentStatusById(mockSavedObjectsClient, 'id');
expect(status).toEqual('online');
});

it('should return enrolling when agent is active but never checkin', async () => {
const mockSavedObjectsClient = savedObjectsClientMock.create();
mockSavedObjectsClient.get = jest.fn().mockReturnValue({
id: 'id',
type: AGENT_TYPE_PERMANENT,
attributes: {
active: true,
local_metadata: {},
user_provided_metadata: {},
},
} as SavedObject<AgentSOAttributes>);
const status = await getAgentStatusById(mockSavedObjectsClient, 'id');
expect(status).toEqual('enrolling');
});

it('should return unenrolling when agent is unenrolling', async () => {
const mockSavedObjectsClient = savedObjectsClientMock.create();
mockSavedObjectsClient.get = jest.fn().mockReturnValue({
id: 'id',
type: AGENT_TYPE_PERMANENT,
attributes: {
active: true,
last_checkin: new Date().toISOString(),
unenrollment_started_at: new Date().toISOString(),
local_metadata: {},
user_provided_metadata: {},
},
} as SavedObject<AgentSOAttributes>);
const status = await getAgentStatusById(mockSavedObjectsClient, 'id');
expect(status).toEqual('unenrolling');
});
});
3 changes: 3 additions & 0 deletions x-pack/plugins/ingest_manager/server/types/rest_spec/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export const PostAgentCheckinRequestSchema = {
agentId: schema.string(),
}),
body: schema.object({
status: schema.maybe(
schema.oneOf([schema.literal('online'), schema.literal('error'), schema.literal('degraded')])
),
local_metadata: schema.maybe(schema.recordOf(schema.string(), schema.any())),
events: schema.maybe(schema.arrayOf(NewAgentEventSchema)),
}),
Expand Down