Skip to content

Commit 4ca8610

Browse files
[Endpoint]EMT-146: use ingest agent for status info (#63921)
[Endpoint]EMT-146: use ingest agent for status info
1 parent ff5971a commit 4ca8610

File tree

14 files changed

+260
-33
lines changed

14 files changed

+260
-33
lines changed

x-pack/plugins/endpoint/server/mocks.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7+
import { IngestManagerSetupContract } from '../../ingest_manager/server';
8+
import { AgentService } from '../../ingest_manager/common/types';
9+
710
/**
811
* Creates a mock IndexPatternRetriever for use in tests.
912
*
@@ -28,17 +31,29 @@ export const createMockMetadataIndexPatternRetriever = () => {
2831
return createMockIndexPatternRetriever(MetadataIndexPattern);
2932
};
3033

34+
/**
35+
* Creates a mock AgentService
36+
*/
37+
export const createMockAgentService = (): jest.Mocked<AgentService> => {
38+
return {
39+
getAgentStatusById: jest.fn(),
40+
};
41+
};
42+
3143
/**
3244
* Creates a mock IndexPatternService for use in tests that need to interact with the Ingest Manager's
3345
* ESIndexPatternService.
3446
*
3547
* @param indexPattern a string index pattern to return when called by a test
3648
* @returns the same value as `indexPattern` parameter
3749
*/
38-
export const createMockIndexPatternService = (indexPattern: string) => {
50+
export const createMockIngestManagerSetupContract = (
51+
indexPattern: string
52+
): IngestManagerSetupContract => {
3953
return {
4054
esIndexPatternService: {
4155
getESIndexPattern: jest.fn().mockResolvedValue(indexPattern),
4256
},
57+
agentService: createMockAgentService(),
4358
};
4459
};

x-pack/plugins/endpoint/server/plugin.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import { EndpointPlugin, EndpointPluginSetupDependencies } from './plugin';
88
import { coreMock } from '../../../../src/core/server/mocks';
99
import { PluginSetupContract } from '../../features/server';
10-
import { createMockIndexPatternService } from './mocks';
10+
import { createMockIngestManagerSetupContract } from './mocks';
1111

1212
describe('test endpoint plugin', () => {
1313
let plugin: EndpointPlugin;
@@ -31,7 +31,7 @@ describe('test endpoint plugin', () => {
3131
};
3232
mockedEndpointPluginSetupDependencies = {
3333
features: mockedPluginSetupContract,
34-
ingestManager: createMockIndexPatternService(''),
34+
ingestManager: createMockIngestManagerSetupContract(''),
3535
};
3636
});
3737

x-pack/plugins/endpoint/server/plugin.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export class EndpointPlugin
7070
plugins.ingestManager.esIndexPatternService,
7171
this.initializerContext.logger
7272
),
73+
agentService: plugins.ingestManager.agentService,
7374
logFactory: this.initializerContext.logger,
7475
config: (): Promise<EndpointConfigType> => {
7576
return createConfig$(this.initializerContext)

x-pack/plugins/endpoint/server/routes/alerts/alerts.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
import { registerAlertRoutes } from './index';
1313
import { EndpointConfigSchema } from '../../config';
1414
import { alertingIndexGetQuerySchema } from '../../../common/schema/alert_index';
15-
import { createMockIndexPatternRetriever } from '../../mocks';
15+
import { createMockAgentService, createMockIndexPatternRetriever } from '../../mocks';
1616

1717
describe('test alerts route', () => {
1818
let routerMock: jest.Mocked<IRouter>;
@@ -26,6 +26,7 @@ describe('test alerts route', () => {
2626
routerMock = httpServiceMock.createRouter();
2727
registerAlertRoutes(routerMock, {
2828
indexPatternRetriever: createMockIndexPatternRetriever('events-endpoint-*'),
29+
agentService: createMockAgentService(),
2930
logFactory: loggingServiceMock.create(),
3031
config: () => Promise.resolve(EndpointConfigSchema.validate({})),
3132
});

x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,13 @@ export const alertDetailsHandlerWrapper = function(
3737
indexPattern
3838
);
3939

40-
const currentHostInfo = await getHostData(ctx, response._source.host.id, indexPattern);
40+
const currentHostInfo = await getHostData(
41+
{
42+
endpointAppContext,
43+
requestHandlerContext: ctx,
44+
},
45+
response._source.host.id
46+
);
4147

4248
return res.ok({
4349
body: {

x-pack/plugins/endpoint/server/routes/metadata/index.ts

Lines changed: 78 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,29 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
import { IRouter, RequestHandlerContext } from 'kibana/server';
7+
import { IRouter, Logger, RequestHandlerContext } from 'kibana/server';
88
import { SearchResponse } from 'elasticsearch';
99
import { schema } from '@kbn/config-schema';
1010

11-
import { kibanaRequestToMetadataListESQuery, getESQueryHostMetadataByID } from './query_builders';
11+
import { getESQueryHostMetadataByID, kibanaRequestToMetadataListESQuery } from './query_builders';
1212
import { HostInfo, HostMetadata, HostResultList, HostStatus } from '../../../common/types';
1313
import { EndpointAppContext } from '../../types';
14+
import { AgentStatus } from '../../../../ingest_manager/common/types/models';
1415

1516
interface HitSource {
1617
_source: HostMetadata;
1718
}
1819

20+
interface MetadataRequestContext {
21+
requestHandlerContext: RequestHandlerContext;
22+
endpointAppContext: EndpointAppContext;
23+
}
24+
25+
const HOST_STATUS_MAPPING = new Map<AgentStatus, HostStatus>([
26+
['online', HostStatus.ONLINE],
27+
['offline', HostStatus.OFFLINE],
28+
]);
29+
1930
export function registerEndpointRoutes(router: IRouter, endpointAppContext: EndpointAppContext) {
2031
router.post(
2132
{
@@ -62,7 +73,12 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp
6273
'search',
6374
queryParams
6475
)) as SearchResponse<HostMetadata>;
65-
return res.ok({ body: mapToHostResultList(queryParams, response) });
76+
return res.ok({
77+
body: await mapToHostResultList(queryParams, response, {
78+
endpointAppContext,
79+
requestHandlerContext: context,
80+
}),
81+
});
6682
} catch (err) {
6783
return res.internalError({ body: err });
6884
}
@@ -79,11 +95,13 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp
7995
},
8096
async (context, req, res) => {
8197
try {
82-
const index = await endpointAppContext.indexPatternRetriever.getMetadataIndexPattern(
83-
context
98+
const doc = await getHostData(
99+
{
100+
endpointAppContext,
101+
requestHandlerContext: context,
102+
},
103+
req.params.id
84104
);
85-
86-
const doc = await getHostData(context, req.params.id, index);
87105
if (doc) {
88106
return res.ok({ body: doc });
89107
}
@@ -96,12 +114,14 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp
96114
}
97115

98116
export async function getHostData(
99-
context: RequestHandlerContext,
100-
id: string,
101-
index: string
117+
metadataRequestContext: MetadataRequestContext,
118+
id: string
102119
): Promise<HostInfo | undefined> {
120+
const index = await metadataRequestContext.endpointAppContext.indexPatternRetriever.getMetadataIndexPattern(
121+
metadataRequestContext.requestHandlerContext
122+
);
103123
const query = getESQueryHostMetadataByID(id, index);
104-
const response = (await context.core.elasticsearch.dataClient.callAsCurrentUser(
124+
const response = (await metadataRequestContext.requestHandlerContext.core.elasticsearch.dataClient.callAsCurrentUser(
105125
'search',
106126
query
107127
)) as SearchResponse<HostMetadata>;
@@ -110,22 +130,25 @@ export async function getHostData(
110130
return undefined;
111131
}
112132

113-
return enrichHostMetadata(response.hits.hits[0]._source);
133+
return await enrichHostMetadata(response.hits.hits[0]._source, metadataRequestContext);
114134
}
115135

116-
function mapToHostResultList(
136+
async function mapToHostResultList(
117137
queryParams: Record<string, any>,
118-
searchResponse: SearchResponse<HostMetadata>
119-
): HostResultList {
138+
searchResponse: SearchResponse<HostMetadata>,
139+
metadataRequestContext: MetadataRequestContext
140+
): Promise<HostResultList> {
120141
const totalNumberOfHosts = searchResponse?.aggregations?.total?.value || 0;
121142
if (searchResponse.hits.hits.length > 0) {
122143
return {
123144
request_page_size: queryParams.size,
124145
request_page_index: queryParams.from,
125-
hosts: searchResponse.hits.hits
126-
.map(response => response.inner_hits.most_recent.hits.hits)
127-
.flatMap(data => data as HitSource)
128-
.map(entry => enrichHostMetadata(entry._source)),
146+
hosts: await Promise.all(
147+
searchResponse.hits.hits
148+
.map(response => response.inner_hits.most_recent.hits.hits)
149+
.flatMap(data => data as HitSource)
150+
.map(async entry => enrichHostMetadata(entry._source, metadataRequestContext))
151+
),
129152
total: totalNumberOfHosts,
130153
};
131154
} else {
@@ -138,9 +161,43 @@ function mapToHostResultList(
138161
}
139162
}
140163

141-
function enrichHostMetadata(hostMetadata: HostMetadata): HostInfo {
164+
async function enrichHostMetadata(
165+
hostMetadata: HostMetadata,
166+
metadataRequestContext: MetadataRequestContext
167+
): Promise<HostInfo> {
168+
let hostStatus = HostStatus.ERROR;
169+
let elasticAgentId = hostMetadata?.elastic?.agent?.id;
170+
const log = logger(metadataRequestContext.endpointAppContext);
171+
try {
172+
/**
173+
* Get agent status by elastic agent id if available or use the host id.
174+
* https://github.com/elastic/endpoint-app-team/issues/354
175+
*/
176+
177+
if (!elasticAgentId) {
178+
elasticAgentId = hostMetadata.host.id;
179+
log.warn(`Missing elastic agent id, using host id instead ${elasticAgentId}`);
180+
}
181+
182+
const status = await metadataRequestContext.endpointAppContext.agentService.getAgentStatusById(
183+
metadataRequestContext.requestHandlerContext.core.savedObjects.client,
184+
elasticAgentId
185+
);
186+
hostStatus = HOST_STATUS_MAPPING.get(status) || HostStatus.ERROR;
187+
} catch (e) {
188+
if (e.isBoom && e.output.statusCode === 404) {
189+
log.warn(`agent with id ${elasticAgentId} not found`);
190+
} else {
191+
log.error(e);
192+
throw e;
193+
}
194+
}
142195
return {
143196
metadata: hostMetadata,
144-
host_status: HostStatus.ERROR,
197+
host_status: hostStatus,
145198
};
146199
}
200+
201+
const logger = (endpointAppContext: EndpointAppContext): Logger => {
202+
return endpointAppContext.logFactory.get('metadata');
203+
};

0 commit comments

Comments
 (0)