Skip to content

Commit

Permalink
[Endpoint]EMT-146: use ingest agent for status info (#63921)
Browse files Browse the repository at this point in the history
[Endpoint]EMT-146: use ingest agent for status info
  • Loading branch information
nnamdifrankie committed Apr 21, 2020
1 parent ff5971a commit 4ca8610
Show file tree
Hide file tree
Showing 14 changed files with 260 additions and 33 deletions.
17 changes: 16 additions & 1 deletion x-pack/plugins/endpoint/server/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { IngestManagerSetupContract } from '../../ingest_manager/server';
import { AgentService } from '../../ingest_manager/common/types';

/**
* Creates a mock IndexPatternRetriever for use in tests.
*
Expand All @@ -28,17 +31,29 @@ export const createMockMetadataIndexPatternRetriever = () => {
return createMockIndexPatternRetriever(MetadataIndexPattern);
};

/**
* Creates a mock AgentService
*/
export const createMockAgentService = (): jest.Mocked<AgentService> => {
return {
getAgentStatusById: jest.fn(),
};
};

/**
* Creates a mock IndexPatternService for use in tests that need to interact with the Ingest Manager's
* ESIndexPatternService.
*
* @param indexPattern a string index pattern to return when called by a test
* @returns the same value as `indexPattern` parameter
*/
export const createMockIndexPatternService = (indexPattern: string) => {
export const createMockIngestManagerSetupContract = (
indexPattern: string
): IngestManagerSetupContract => {
return {
esIndexPatternService: {
getESIndexPattern: jest.fn().mockResolvedValue(indexPattern),
},
agentService: createMockAgentService(),
};
};
4 changes: 2 additions & 2 deletions x-pack/plugins/endpoint/server/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { EndpointPlugin, EndpointPluginSetupDependencies } from './plugin';
import { coreMock } from '../../../../src/core/server/mocks';
import { PluginSetupContract } from '../../features/server';
import { createMockIndexPatternService } from './mocks';
import { createMockIngestManagerSetupContract } from './mocks';

describe('test endpoint plugin', () => {
let plugin: EndpointPlugin;
Expand All @@ -31,7 +31,7 @@ describe('test endpoint plugin', () => {
};
mockedEndpointPluginSetupDependencies = {
features: mockedPluginSetupContract,
ingestManager: createMockIndexPatternService(''),
ingestManager: createMockIngestManagerSetupContract(''),
};
});

Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/endpoint/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export class EndpointPlugin
plugins.ingestManager.esIndexPatternService,
this.initializerContext.logger
),
agentService: plugins.ingestManager.agentService,
logFactory: this.initializerContext.logger,
config: (): Promise<EndpointConfigType> => {
return createConfig$(this.initializerContext)
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/endpoint/server/routes/alerts/alerts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import { registerAlertRoutes } from './index';
import { EndpointConfigSchema } from '../../config';
import { alertingIndexGetQuerySchema } from '../../../common/schema/alert_index';
import { createMockIndexPatternRetriever } from '../../mocks';
import { createMockAgentService, createMockIndexPatternRetriever } from '../../mocks';

describe('test alerts route', () => {
let routerMock: jest.Mocked<IRouter>;
Expand All @@ -26,6 +26,7 @@ describe('test alerts route', () => {
routerMock = httpServiceMock.createRouter();
registerAlertRoutes(routerMock, {
indexPatternRetriever: createMockIndexPatternRetriever('events-endpoint-*'),
agentService: createMockAgentService(),
logFactory: loggingServiceMock.create(),
config: () => Promise.resolve(EndpointConfigSchema.validate({})),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@ export const alertDetailsHandlerWrapper = function(
indexPattern
);

const currentHostInfo = await getHostData(ctx, response._source.host.id, indexPattern);
const currentHostInfo = await getHostData(
{
endpointAppContext,
requestHandlerContext: ctx,
},
response._source.host.id
);

return res.ok({
body: {
Expand Down
99 changes: 78 additions & 21 deletions x-pack/plugins/endpoint/server/routes/metadata/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,29 @@
* you may not use this file except in compliance with the Elastic License.
*/

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

import { kibanaRequestToMetadataListESQuery, getESQueryHostMetadataByID } from './query_builders';
import { getESQueryHostMetadataByID, kibanaRequestToMetadataListESQuery } from './query_builders';
import { HostInfo, HostMetadata, HostResultList, HostStatus } from '../../../common/types';
import { EndpointAppContext } from '../../types';
import { AgentStatus } from '../../../../ingest_manager/common/types/models';

interface HitSource {
_source: HostMetadata;
}

interface MetadataRequestContext {
requestHandlerContext: RequestHandlerContext;
endpointAppContext: EndpointAppContext;
}

const HOST_STATUS_MAPPING = new Map<AgentStatus, HostStatus>([
['online', HostStatus.ONLINE],
['offline', HostStatus.OFFLINE],
]);

export function registerEndpointRoutes(router: IRouter, endpointAppContext: EndpointAppContext) {
router.post(
{
Expand Down Expand Up @@ -62,7 +73,12 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp
'search',
queryParams
)) as SearchResponse<HostMetadata>;
return res.ok({ body: mapToHostResultList(queryParams, response) });
return res.ok({
body: await mapToHostResultList(queryParams, response, {
endpointAppContext,
requestHandlerContext: context,
}),
});
} catch (err) {
return res.internalError({ body: err });
}
Expand All @@ -79,11 +95,13 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp
},
async (context, req, res) => {
try {
const index = await endpointAppContext.indexPatternRetriever.getMetadataIndexPattern(
context
const doc = await getHostData(
{
endpointAppContext,
requestHandlerContext: context,
},
req.params.id
);

const doc = await getHostData(context, req.params.id, index);
if (doc) {
return res.ok({ body: doc });
}
Expand All @@ -96,12 +114,14 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp
}

export async function getHostData(
context: RequestHandlerContext,
id: string,
index: string
metadataRequestContext: MetadataRequestContext,
id: string
): Promise<HostInfo | undefined> {
const index = await metadataRequestContext.endpointAppContext.indexPatternRetriever.getMetadataIndexPattern(
metadataRequestContext.requestHandlerContext
);
const query = getESQueryHostMetadataByID(id, index);
const response = (await context.core.elasticsearch.dataClient.callAsCurrentUser(
const response = (await metadataRequestContext.requestHandlerContext.core.elasticsearch.dataClient.callAsCurrentUser(
'search',
query
)) as SearchResponse<HostMetadata>;
Expand All @@ -110,22 +130,25 @@ export async function getHostData(
return undefined;
}

return enrichHostMetadata(response.hits.hits[0]._source);
return await enrichHostMetadata(response.hits.hits[0]._source, metadataRequestContext);
}

function mapToHostResultList(
async function mapToHostResultList(
queryParams: Record<string, any>,
searchResponse: SearchResponse<HostMetadata>
): HostResultList {
searchResponse: SearchResponse<HostMetadata>,
metadataRequestContext: MetadataRequestContext
): Promise<HostResultList> {
const totalNumberOfHosts = searchResponse?.aggregations?.total?.value || 0;
if (searchResponse.hits.hits.length > 0) {
return {
request_page_size: queryParams.size,
request_page_index: queryParams.from,
hosts: searchResponse.hits.hits
.map(response => response.inner_hits.most_recent.hits.hits)
.flatMap(data => data as HitSource)
.map(entry => enrichHostMetadata(entry._source)),
hosts: await Promise.all(
searchResponse.hits.hits
.map(response => response.inner_hits.most_recent.hits.hits)
.flatMap(data => data as HitSource)
.map(async entry => enrichHostMetadata(entry._source, metadataRequestContext))
),
total: totalNumberOfHosts,
};
} else {
Expand All @@ -138,9 +161,43 @@ function mapToHostResultList(
}
}

function enrichHostMetadata(hostMetadata: HostMetadata): HostInfo {
async function enrichHostMetadata(
hostMetadata: HostMetadata,
metadataRequestContext: MetadataRequestContext
): Promise<HostInfo> {
let hostStatus = HostStatus.ERROR;
let elasticAgentId = hostMetadata?.elastic?.agent?.id;
const log = logger(metadataRequestContext.endpointAppContext);
try {
/**
* Get agent status by elastic agent id if available or use the host id.
* https://github.com/elastic/endpoint-app-team/issues/354
*/

if (!elasticAgentId) {
elasticAgentId = hostMetadata.host.id;
log.warn(`Missing elastic agent id, using host id instead ${elasticAgentId}`);
}

const status = await metadataRequestContext.endpointAppContext.agentService.getAgentStatusById(
metadataRequestContext.requestHandlerContext.core.savedObjects.client,
elasticAgentId
);
hostStatus = HOST_STATUS_MAPPING.get(status) || HostStatus.ERROR;
} catch (e) {
if (e.isBoom && e.output.statusCode === 404) {
log.warn(`agent with id ${elasticAgentId} not found`);
} else {
log.error(e);
throw e;
}
}
return {
metadata: hostMetadata,
host_status: HostStatus.ERROR,
host_status: hostStatus,
};
}

const logger = (endpointAppContext: EndpointAppContext): Logger => {
return endpointAppContext.logFactory.get('metadata');
};
Loading

0 comments on commit 4ca8610

Please sign in to comment.