mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[Stack Monitoring] Collect metrics errors in health api (#148750)
Closes https://github.com/elastic/kibana/issues/140358 ## Summary This PR adds integration package errors to the `_health` endpoint. The response should look similar to the metricbeat one we already have. The difference will be that the package will use `data_stream.dataset` instead of `metricset.name` ## Testing 1. Setup an integration - Follow this [guide](0e924730cc/docs/infra-obs-ui/stack-monitoring_integration-packages.md
) to install the package. ~~⚠️ Note that you need to have `metricset.name` mapping to see the errors added in this [PR](https://github.com/elastic/integrations/pull/4973)~~ - No need anymore as we use `data_stream.dataset` for packages 2. Installing the package: - In this case, we want to enable metrics and set the Agent policy to Elastic-Agent (elastic-package). To see an error I used the wrong host URL (inside `Change Defaults`) when I was configuring the elasticsearch integration: <img width="1313" alt="image" src="https://user-images.githubusercontent.com/14139027/212313537-f1f6f5b1-6e4d-40f2-8f78-9f0e5f48c434.png"> 3. To see the package errors run `curl --user elastic:changeme http://localhost:5602/ftw/api/monitoring/v1/_health` or open http://localhost:5602/ftw/api/monitoring/v1/_health in a browser where you are logged in in your kibana. (The port of the kibana is 5602 as mentioned in the [guide](0e924730cc/docs/infra-obs-ui/stack-monitoring_integration-packages.md (connecting-a-local-kibana)
) this will be your local kibana running - I recommend to add the package to the elastic package kibana running on 5601 and to run your local kibana after the setup is complete) Example response: ``` { "metricbeatErrors": { ... }, "packageErrors": { "products": { "elasticsearch": { "elasticsearch.stack_monitoring.node": [ { "message": "error making http request: Get \"http://localhost:9200/_nodes/_local\": dial tcp 127.0.0.1:9200: connect: connection refused", "lastSeen": "2023-01-12T17:27:01.862Z" } ], "elasticsearch.stack_monitoring.node_stats": [ { "message": "error making http request: Get \"http://localhost:9200/_nodes/_local/stats\": dial tcp [::1]:9200: connect: cannot assign requested address", "lastSeen": "2023-01-12T17:26:31.883Z" } ], ..... }, "execution": { "timedOut": false, "errors": [] } } } } ```
This commit is contained in:
parent
4bdf1d4b0c
commit
066ee1c9e8
17 changed files with 1975 additions and 105 deletions
|
@ -12,3 +12,4 @@ The response includes sections that can provide useful informations in a debuggi
|
|||
- settings: a subset of the kibana.yml settings relevant to stack monitoring
|
||||
- monitoredClusters: a representation of the monitoring documents available to the running kibana. It exposes which metricsets are collected by what collection mode and when was the last time it was ingested. The query groups the metricsets by products and can help identify missing documents that could explain why a page is not loading or crashing
|
||||
- metricbeatErrors: a list of errors encountered by metricbeat processes when collecting data
|
||||
- packageErrors: a list of errors encountered by integration package processes when collecting data
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { buildErrors } from './build_errors';
|
||||
import assert from 'assert';
|
||||
|
||||
describe(__filename, () => {
|
||||
describe('buildErrors', () => {
|
||||
test('Metricbeat: it should build an object containing dedup error messages per event.dataset', () => {
|
||||
const metricbeatErrors = [
|
||||
{
|
||||
key: 'beat',
|
||||
errors_by_dataset: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'state',
|
||||
latest_docs: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-07-26T08:43:32.625Z',
|
||||
error: {
|
||||
message:
|
||||
'error making http request: Get "http://host.docker.internal:5067/state": dial tcp 192.168.65.2:5067: connect: connection refused',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-07-26T08:42:32.625Z',
|
||||
error: {
|
||||
message:
|
||||
'error making http request: Get "http://host.docker.internal:5067/state": dial tcp 192.168.65.2:5067: connect: connection refused',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-07-26T08:41:32.625Z',
|
||||
error: {
|
||||
message: 'Generic random error',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const monitoredClusters = buildErrors(metricbeatErrors);
|
||||
assert.deepEqual(monitoredClusters, {
|
||||
beat: {
|
||||
state: [
|
||||
{
|
||||
lastSeen: '2022-07-26T08:43:32.625Z',
|
||||
message:
|
||||
'error making http request: Get "http://host.docker.internal:5067/state": dial tcp 192.168.65.2:5067: connect: connection refused',
|
||||
},
|
||||
{
|
||||
lastSeen: '2022-07-26T08:41:32.625Z',
|
||||
message: 'Generic random error',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('Packages: it should build an object containing dedup error messages per event.dataset', () => {
|
||||
const packageErrors = [
|
||||
{
|
||||
key: 'elasticsearch',
|
||||
errors_by_dataset: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'state',
|
||||
latest_docs: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2023-01-10T14:39:37.114Z',
|
||||
error: {
|
||||
message:
|
||||
'error making http request: Get "https://localhost:9200/_nodes/_local": dial tcp [::1]:9200: connect: cannot assign requested address',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2023-01-10T14:39:27.114Z',
|
||||
error: {
|
||||
message:
|
||||
'error making http request: Get "https://localhost:9200/_nodes/_local": dial tcp [::1]:9200: connect: cannot assign requested address',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-07-26T08:41:32.625Z',
|
||||
error: {
|
||||
message: 'Generic random error',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const monitoredClusters = buildErrors(packageErrors);
|
||||
assert.deepEqual(monitoredClusters, {
|
||||
elasticsearch: {
|
||||
state: [
|
||||
{
|
||||
lastSeen: '2023-01-10T14:39:37.114Z',
|
||||
message:
|
||||
'error making http request: Get "https://localhost:9200/_nodes/_local": dial tcp [::1]:9200: connect: cannot assign requested address',
|
||||
},
|
||||
{
|
||||
lastSeen: '2022-07-26T08:41:32.625Z',
|
||||
message: 'Generic random error',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,10 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { MetricbeatMonitoredProduct } from '../types';
|
||||
import type { MonitoredProduct } from '../types';
|
||||
|
||||
export type MetricbeatProducts = {
|
||||
[product in MetricbeatMonitoredProduct]?: ErrorsByMetricset;
|
||||
export type Products = {
|
||||
[product in MonitoredProduct]?: ErrorsByMetricset;
|
||||
};
|
||||
|
||||
interface ErrorsByMetricset {
|
||||
|
@ -21,14 +21,14 @@ interface ErrorDetails {
|
|||
}
|
||||
|
||||
/**
|
||||
* builds a normalized representation of the metricbeat errors from the provided
|
||||
* builds a normalized representation of the metricbeat and integration package errors from the provided
|
||||
* query buckets with a product->metricset hierarchy where
|
||||
* product: the monitored products (eg elasticsearch)
|
||||
* metricset: the collected metricsets for a given entity
|
||||
*
|
||||
* example:
|
||||
* {
|
||||
* "product": {
|
||||
* "products": {
|
||||
* "logstash": {
|
||||
* "node": {
|
||||
* "message": "some error message",
|
||||
|
@ -38,7 +38,7 @@ interface ErrorDetails {
|
|||
* }
|
||||
* }
|
||||
*/
|
||||
export const buildMetricbeatErrors = (modulesBucket: any[]): MetricbeatProducts => {
|
||||
export const buildErrors = (modulesBucket: any[]): Products => {
|
||||
return (modulesBucket ?? []).reduce((module, { key, errors_by_dataset: errorsByDataset }) => {
|
||||
const datasets = buildMetricsets(errorsByDataset.buckets);
|
||||
if (Object.keys(datasets).length === 0) {
|
||||
|
@ -49,7 +49,7 @@ export const buildMetricbeatErrors = (modulesBucket: any[]): MetricbeatProducts
|
|||
...module,
|
||||
[key]: datasets,
|
||||
};
|
||||
}, {} as MetricbeatProducts);
|
||||
}, {} as Products);
|
||||
};
|
||||
|
||||
const buildMetricsets = (errorsByDataset: any[]): ErrorsByMetricset => {
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { MetricbeatMonitoredProduct, QueryOptions } from '../types';
|
||||
import type { MetricbeatMonitoredProduct, PackagesMonitoredProduct, QueryOptions } from '../types';
|
||||
|
||||
const MAX_BUCKET_SIZE = 50;
|
||||
|
||||
|
@ -13,16 +13,20 @@ const MAX_BUCKET_SIZE = 50;
|
|||
* Returns a nested aggregation of error messages per event.datasets.
|
||||
* Each module (beats, kibana...) can contain one or multiple metricsets with error messages
|
||||
*/
|
||||
interface MetricbeatErrorsQueryOptions extends QueryOptions {
|
||||
products: MetricbeatMonitoredProduct[];
|
||||
interface ErrorsQueryOptions extends QueryOptions {
|
||||
products: MetricbeatMonitoredProduct[] | PackagesMonitoredProduct[];
|
||||
errorQueryType: 'metricbeatErrorsQuery' | 'packageErrorsQuery';
|
||||
errorQueryIsDataStream?: boolean;
|
||||
}
|
||||
|
||||
export const metricbeatErrorsQuery = ({
|
||||
export const errorsQuery = ({
|
||||
timeRange,
|
||||
timeout,
|
||||
products,
|
||||
}: MetricbeatErrorsQueryOptions) => {
|
||||
if (!timeRange) throw new Error('metricbeatErrorsQuery: missing timeRange parameter');
|
||||
errorQueryType,
|
||||
errorQueryIsDataStream,
|
||||
}: ErrorsQueryOptions) => {
|
||||
if (!timeRange) throw new Error(`${errorQueryType}: missing timeRange parameter`);
|
||||
return {
|
||||
timeout: `${timeout}s`,
|
||||
query: {
|
||||
|
@ -37,7 +41,8 @@ export const metricbeatErrorsQuery = ({
|
|||
},
|
||||
{
|
||||
terms: {
|
||||
'event.module': Object.values(products),
|
||||
[errorQueryIsDataStream ? 'service.type' : 'event.module']:
|
||||
Object.values(products),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -54,7 +59,7 @@ export const metricbeatErrorsQuery = ({
|
|||
},
|
||||
},
|
||||
aggs: {
|
||||
errors_aggregation: errorsAggregation,
|
||||
errors_aggregation: errorsAggregation(errorQueryIsDataStream),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -82,11 +87,34 @@ const errorsByMetricset = {
|
|||
},
|
||||
};
|
||||
|
||||
const errorsAggregation = {
|
||||
const errorsByDataStream = {
|
||||
terms: {
|
||||
field: 'event.module',
|
||||
field: 'data_stream.dataset',
|
||||
},
|
||||
aggs: {
|
||||
errors_by_dataset: errorsByMetricset,
|
||||
latest_docs: {
|
||||
top_hits: {
|
||||
sort: [
|
||||
{
|
||||
'@timestamp': {
|
||||
order: 'desc',
|
||||
},
|
||||
},
|
||||
],
|
||||
size: MAX_BUCKET_SIZE,
|
||||
_source: {
|
||||
includes: ['@timestamp', 'error', 'data_stream'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const errorsAggregation = (errorQueryIsDataStream?: boolean) => ({
|
||||
terms: {
|
||||
field: errorQueryIsDataStream ? 'service.type' : 'event.module',
|
||||
},
|
||||
aggs: {
|
||||
errors_by_dataset: errorQueryIsDataStream ? errorsByDataStream : errorsByMetricset,
|
||||
},
|
||||
});
|
|
@ -8,13 +8,14 @@
|
|||
import type { LegacyRequest, MonitoringCore } from '../../../../types';
|
||||
import type { MonitoringConfig } from '../../../../config';
|
||||
import { createValidationFunction } from '../../../../lib/create_route_validation_function';
|
||||
import { getIndexPatterns } from '../../../../lib/cluster/get_index_patterns';
|
||||
import { getIndexPatterns, getDsIndexPattern } from '../../../../lib/cluster/get_index_patterns';
|
||||
import { getHealthRequestQueryRT } from '../../../../../common/http_api/_health';
|
||||
import type { TimeRange } from '../../../../../common/http_api/shared';
|
||||
|
||||
import { fetchMonitoredClusters } from './monitored_clusters';
|
||||
import { fetchMetricbeatErrors } from './metricbeat';
|
||||
import type { FetchParameters } from './types';
|
||||
import { fetchPackageErrors } from './package/fetch_package_errors';
|
||||
|
||||
const DEFAULT_QUERY_TIMERANGE = { min: 'now-15m', max: 'now' };
|
||||
const DEFAULT_QUERY_TIMEOUT_SECONDS = 15;
|
||||
|
@ -53,6 +54,14 @@ export function registerV1HealthRoute(server: MonitoringCore) {
|
|||
getIndexPatterns({ config, moduleType: 'logstash' }),
|
||||
getIndexPatterns({ config, moduleType: 'beats' }),
|
||||
].join(',');
|
||||
|
||||
const metricsPackageIndex = [
|
||||
getDsIndexPattern({ config, moduleType: 'elasticsearch' }),
|
||||
getDsIndexPattern({ config, moduleType: 'kibana' }),
|
||||
getDsIndexPattern({ config, moduleType: 'logstash' }),
|
||||
getDsIndexPattern({ config, moduleType: 'beats' }),
|
||||
].join(',');
|
||||
|
||||
const entSearchIndex = getIndexPatterns({ config, moduleType: 'enterprise_search' });
|
||||
|
||||
const monitoredClustersFn = () =>
|
||||
|
@ -74,12 +83,22 @@ export function registerV1HealthRoute(server: MonitoringCore) {
|
|||
return { error: err.message };
|
||||
});
|
||||
|
||||
const [monitoredClusters, metricbeatErrors] = await Promise.all([
|
||||
const packageErrorsFn = () =>
|
||||
fetchPackageErrors({
|
||||
...fetchArgs,
|
||||
packageIndex: metricsPackageIndex,
|
||||
}).catch((err: Error) => {
|
||||
logger.error(`_health: failed to retrieve package data:\n${err.stack}`);
|
||||
return { error: err.message };
|
||||
});
|
||||
|
||||
const [monitoredClusters, metricbeatErrors, packageErrors] = await Promise.all([
|
||||
monitoredClustersFn(),
|
||||
metricbeatErrorsFn(),
|
||||
packageErrorsFn(),
|
||||
]);
|
||||
|
||||
return { monitoredClusters, metricbeatErrors, settings };
|
||||
return { monitoredClusters, metricbeatErrors, packageErrors, settings };
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { buildMetricbeatErrors } from './build_metricbeat_errors';
|
||||
import assert from 'assert';
|
||||
|
||||
describe(__filename, () => {
|
||||
describe('buildMetricbeatErrors', () => {
|
||||
test('it should build an object containing dedup error messages per event.dataset', () => {
|
||||
const metricbeatErrors = [
|
||||
{
|
||||
key: 'beat',
|
||||
errors_by_dataset: {
|
||||
buckets: [
|
||||
{
|
||||
key: 'state',
|
||||
latest_docs: {
|
||||
hits: {
|
||||
hits: [
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-07-26T08:43:32.625Z',
|
||||
error: {
|
||||
message:
|
||||
'error making http request: Get "http://host.docker.internal:5067/state": dial tcp 192.168.65.2:5067: connect: connection refused',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-07-26T08:42:32.625Z',
|
||||
error: {
|
||||
message:
|
||||
'error making http request: Get "http://host.docker.internal:5067/state": dial tcp 192.168.65.2:5067: connect: connection refused',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
_source: {
|
||||
'@timestamp': '2022-07-26T08:41:32.625Z',
|
||||
error: {
|
||||
message: 'Generic random error',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const monitoredClusters = buildMetricbeatErrors(metricbeatErrors);
|
||||
assert.deepEqual(monitoredClusters, {
|
||||
beat: {
|
||||
state: [
|
||||
{
|
||||
lastSeen: '2022-07-26T08:43:32.625Z',
|
||||
message:
|
||||
'error making http request: Get "http://host.docker.internal:5067/state": dial tcp 192.168.65.2:5067: connect: connection refused',
|
||||
},
|
||||
{
|
||||
lastSeen: '2022-07-26T08:41:32.625Z',
|
||||
message: 'Generic random error',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -7,13 +7,13 @@
|
|||
|
||||
import { FetchParameters, FetchExecution, MonitoredProduct } from '../types';
|
||||
|
||||
import type { MetricbeatProducts } from './build_metricbeat_errors';
|
||||
import type { Products } from '../errors_helpers/build_errors';
|
||||
|
||||
import { metricbeatErrorsQuery } from './metricbeat_errors_query';
|
||||
import { buildMetricbeatErrors } from './build_metricbeat_errors';
|
||||
import { errorsQuery } from '../errors_helpers/errors_query';
|
||||
import { buildErrors } from '../errors_helpers/build_errors';
|
||||
|
||||
interface MetricbeatResponse {
|
||||
products?: MetricbeatProducts;
|
||||
products?: Products;
|
||||
execution: FetchExecution;
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ export const fetchMetricbeatErrors = async ({
|
|||
const getMetricbeatErrors = async () => {
|
||||
const { aggregations, timed_out: timedOut } = await search({
|
||||
index: metricbeatIndex,
|
||||
body: metricbeatErrorsQuery({
|
||||
body: errorsQuery({
|
||||
timeRange,
|
||||
timeout,
|
||||
products: [
|
||||
|
@ -39,12 +39,13 @@ export const fetchMetricbeatErrors = async ({
|
|||
MonitoredProduct.Kibana,
|
||||
MonitoredProduct.Logstash,
|
||||
],
|
||||
errorQueryType: 'metricbeatErrorsQuery',
|
||||
}),
|
||||
size: 0,
|
||||
ignore_unavailable: true,
|
||||
});
|
||||
const buckets = aggregations?.errors_aggregation?.buckets ?? [];
|
||||
return { products: buildMetricbeatErrors(buckets), timedOut: Boolean(timedOut) };
|
||||
return { products: buildErrors(buckets), timedOut: Boolean(timedOut) };
|
||||
};
|
||||
|
||||
try {
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import assert from 'assert';
|
||||
import sinon from 'sinon';
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import { fetchPackageErrors } from './fetch_package_errors';
|
||||
|
||||
const getMockLogger = () =>
|
||||
({
|
||||
warn: sinon.spy(),
|
||||
error: sinon.spy(),
|
||||
} as unknown as Logger);
|
||||
|
||||
describe(__filename, () => {
|
||||
describe('fetchPackageErrors', () => {
|
||||
test('it fetch and build package errors response', async () => {
|
||||
const response = {
|
||||
aggregations: {
|
||||
errors_aggregation: {
|
||||
doc_count_error_upper_bound: 0,
|
||||
sum_other_doc_count: 0,
|
||||
buckets: [
|
||||
{
|
||||
key: 'elasticsearch',
|
||||
doc_count: 22,
|
||||
errors_by_dataset: {
|
||||
doc_count_error_upper_bound: 0,
|
||||
sum_other_doc_count: 0,
|
||||
buckets: [
|
||||
{
|
||||
key: 'elasticsearch.stack_monitoring.pending_tasks',
|
||||
doc_count: 22,
|
||||
latest_docs: {
|
||||
hits: {
|
||||
total: {
|
||||
value: 22,
|
||||
relation: 'eq',
|
||||
},
|
||||
max_score: null,
|
||||
hits: [
|
||||
{
|
||||
_index:
|
||||
'.ds-metrics-elasticsearch.stack_monitoring.node-default-2023.01.10-000001',
|
||||
_id: '-oAfnIUB94omKO-pWCeN',
|
||||
_score: null,
|
||||
_source: {
|
||||
'@timestamp': '2023-01-10T14:39:37.114Z',
|
||||
metricset: {
|
||||
period: 10000,
|
||||
name: 'node',
|
||||
},
|
||||
error: {
|
||||
message:
|
||||
'error making http request: Get "https://localhost:9200/_nodes/_local": dial tcp [::1]:9200: connect: cannot assign requested address',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'elasticsearch.stack_monitoring.node',
|
||||
doc_count: 22,
|
||||
latest_docs: {
|
||||
hits: {
|
||||
total: {
|
||||
value: 22,
|
||||
relation: 'eq',
|
||||
},
|
||||
max_score: null,
|
||||
hits: [
|
||||
{
|
||||
_index:
|
||||
'.ds-metrics-elasticsearch.stack_monitoring.node-default-2023.01.10-000001',
|
||||
_id: '-oAfnIUB94omKO-pWCeN',
|
||||
_score: null,
|
||||
_source: {
|
||||
'@timestamp': '2023-01-10T14:39:37.156Z',
|
||||
metricset: {
|
||||
period: 10000,
|
||||
name: 'node',
|
||||
},
|
||||
error: {
|
||||
message:
|
||||
'error making http request: Get "https://localhost:9200/_nodes/_local": dial tcp [::1]:9200: connect: cannot assign requested address',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const searchFn = jest.fn().mockResolvedValueOnce(response);
|
||||
|
||||
const monitoredClusters = await fetchPackageErrors({
|
||||
timeout: 10,
|
||||
timeRange: { min: 1673361577110, max: 1673361567118 },
|
||||
packageIndex: 'metrics-*',
|
||||
search: searchFn,
|
||||
logger: getMockLogger(),
|
||||
});
|
||||
|
||||
assert.deepEqual(monitoredClusters, {
|
||||
execution: {
|
||||
timedOut: false,
|
||||
errors: [],
|
||||
},
|
||||
products: {
|
||||
elasticsearch: {
|
||||
'elasticsearch.stack_monitoring.node': [
|
||||
{
|
||||
message:
|
||||
'error making http request: Get "https://localhost:9200/_nodes/_local": dial tcp [::1]:9200: connect: cannot assign requested address',
|
||||
lastSeen: '2023-01-10T14:39:37.156Z',
|
||||
},
|
||||
],
|
||||
'elasticsearch.stack_monitoring.pending_tasks': [
|
||||
{
|
||||
message:
|
||||
'error making http request: Get "https://localhost:9200/_nodes/_local": dial tcp [::1]:9200: connect: cannot assign requested address',
|
||||
lastSeen: '2023-01-10T14:39:37.114Z',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { FetchParameters, FetchExecution, MonitoredProduct } from '../types';
|
||||
|
||||
import type { Products } from '../errors_helpers/build_errors';
|
||||
|
||||
import { errorsQuery } from '../errors_helpers/errors_query';
|
||||
import { buildErrors } from '../errors_helpers/build_errors';
|
||||
|
||||
interface PackageResponse {
|
||||
products?: Products;
|
||||
execution: FetchExecution;
|
||||
}
|
||||
|
||||
export const fetchPackageErrors = async ({
|
||||
timeout,
|
||||
timeRange,
|
||||
search,
|
||||
logger,
|
||||
packageIndex,
|
||||
}: FetchParameters & {
|
||||
packageIndex: string;
|
||||
}): Promise<PackageResponse> => {
|
||||
const getPackageErrors = async () => {
|
||||
const { aggregations, timed_out: timedOut } = await search({
|
||||
index: packageIndex,
|
||||
body: errorsQuery({
|
||||
timeRange,
|
||||
timeout,
|
||||
products: [
|
||||
MonitoredProduct.Beats,
|
||||
MonitoredProduct.Elasticsearch,
|
||||
MonitoredProduct.Kibana,
|
||||
MonitoredProduct.Logstash,
|
||||
],
|
||||
errorQueryType: 'packageErrorsQuery',
|
||||
errorQueryIsDataStream: true,
|
||||
}),
|
||||
size: 0,
|
||||
ignore_unavailable: true,
|
||||
});
|
||||
const buckets = aggregations?.errors_aggregation?.buckets ?? [];
|
||||
return { products: buildErrors(buckets), timedOut: Boolean(timedOut) };
|
||||
};
|
||||
|
||||
try {
|
||||
const { products, timedOut } = await getPackageErrors();
|
||||
return {
|
||||
products,
|
||||
execution: {
|
||||
timedOut,
|
||||
errors: [],
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
logger.error(`fetchPackageErrors: failed to fetch:\n${err.stack}`);
|
||||
return {
|
||||
execution: {
|
||||
timedOut: false,
|
||||
errors: [err.message],
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { fetchPackageErrors } from './fetch_package_errors';
|
|
@ -18,6 +18,10 @@ export enum MonitoredProduct {
|
|||
EnterpriseSearch = 'enterpriseSearch',
|
||||
}
|
||||
export type MetricbeatMonitoredProduct = Exclude<MonitoredProduct, MonitoredProduct.Cluster>;
|
||||
export type PackagesMonitoredProduct = Exclude<
|
||||
MetricbeatMonitoredProduct,
|
||||
MonitoredProduct.EnterpriseSearch
|
||||
>;
|
||||
|
||||
export type SearchFn = (params: any) => Promise<ElasticsearchResponse>;
|
||||
|
||||
|
|
|
@ -12,5 +12,12 @@
|
|||
"timedOut": false
|
||||
},
|
||||
"products": {}
|
||||
},
|
||||
"packageErrors": {
|
||||
"execution": {
|
||||
"errors": [],
|
||||
"timedOut": false
|
||||
},
|
||||
"products": {}
|
||||
}
|
||||
}
|
|
@ -122,5 +122,12 @@ export const esBeatsResponse = (date = moment().format('YYYY.MM.DD')) => {
|
|||
},
|
||||
},
|
||||
},
|
||||
packageErrors: {
|
||||
execution: {
|
||||
errors: [],
|
||||
timedOut: false,
|
||||
},
|
||||
products: {},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const esPackageResponse = () => {
|
||||
return {
|
||||
monitoredClusters: {
|
||||
clusters: {
|
||||
standalone: {},
|
||||
},
|
||||
execution: {
|
||||
timedOut: false,
|
||||
errors: [],
|
||||
},
|
||||
},
|
||||
metricbeatErrors: {
|
||||
execution: {
|
||||
errors: [],
|
||||
timedOut: false,
|
||||
},
|
||||
products: {},
|
||||
},
|
||||
packageErrors: {
|
||||
execution: {
|
||||
errors: [],
|
||||
timedOut: false,
|
||||
},
|
||||
products: {
|
||||
elasticsearch: {
|
||||
'elasticsearch.stack_monitoring.node_stats': [
|
||||
{
|
||||
lastSeen: '2023-01-13T15:11:40.458Z',
|
||||
message:
|
||||
'error making http request: Get "http://localhost:9200/_nodes/_local/stats": dial tcp [::1]:9200: connect: cannot assign requested address',
|
||||
},
|
||||
{
|
||||
lastSeen: '2023-01-13T15:11:30.458Z',
|
||||
message:
|
||||
'error making http request: Get "http://localhost:9200/_nodes/_local/stats": dial tcp 127.0.0.1:9200: connect: connection refused',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
|
@ -12,7 +12,10 @@ import { getLifecycleMethods } from '../data_stream';
|
|||
|
||||
import emptyResponse from './fixtures/response_empty.json';
|
||||
import { esBeatsResponse } from './fixtures/response_es_beats';
|
||||
import { esPackageResponse } from './fixtures/response_es_package';
|
||||
|
||||
const ELASTICSEARCH_PACKAGE_ARCHIVE =
|
||||
'x-pack/test/api_integration/apis/monitoring/es_archives/_health/elasticsearch_package_error';
|
||||
const METRICBEAT_ARCHIVE =
|
||||
'x-pack/test/api_integration/apis/monitoring/es_archives/_health/metricbeat_8';
|
||||
export default function ({ getService }) {
|
||||
|
@ -36,7 +39,7 @@ export default function ({ getService }) {
|
|||
});
|
||||
});
|
||||
|
||||
describe('with data', () => {
|
||||
describe('with metricbeat data', () => {
|
||||
const archives = [
|
||||
'x-pack/test/api_integration/apis/monitoring/es_archives/_health/monitoring_es_8',
|
||||
'x-pack/test/api_integration/apis/monitoring/es_archives/_health/monitoring_beats_8',
|
||||
|
@ -82,5 +85,32 @@ export default function ({ getService }) {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with integration package data', () => {
|
||||
const timeRange = {
|
||||
min: '2023-01-10T14:46:10.461Z',
|
||||
max: '2023-01-15T22:30:00.000Z',
|
||||
};
|
||||
const archives = [ELASTICSEARCH_PACKAGE_ARCHIVE];
|
||||
const { setup, tearDown } = getLifecycleMethods(getService);
|
||||
|
||||
before('load archive', () => {
|
||||
return setup(archives);
|
||||
});
|
||||
|
||||
after('unload archive', () => {
|
||||
return tearDown([ELASTICSEARCH_PACKAGE_ARCHIVE]);
|
||||
});
|
||||
|
||||
it('returns the state of the monitoring documents', async () => {
|
||||
const { body } = await supertest
|
||||
.get(`/api/monitoring/v1/_health?min=${timeRange.min}&max=${timeRange.max}`)
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.expect(200);
|
||||
|
||||
delete body.settings;
|
||||
expect(body).to.eql(esPackageResponse());
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue