[Monitoring] change errors dir to typescript (#108297)

* change errors dir to ts

* fix internationalization

* fix internationalization

* change test name
This commit is contained in:
Sandra G 2021-08-16 16:58:27 -04:00 committed by GitHub
parent febcfa6ee3
commit 7ce9d07746
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 218 additions and 157 deletions

View file

@ -6,6 +6,7 @@
*/
import { forbidden, unauthorized } from '@hapi/boom';
import { ResponseError } from '@elastic/elasticsearch/lib/errors';
import { isAuthError, handleAuthError } from './auth_errors';
describe('Error handling for 401/403 errors', () => {
@ -58,7 +59,19 @@ describe('Error handling for 401/403 errors', () => {
describe('Elasticsearch errors', () => {
it('handles Forbidden error defined by ElasticsearchJS', () => {
const err = { statusCode: 401 };
const err = new ResponseError({
statusCode: 401,
body: {
error: {
type: 'Forbidden',
},
},
warnings: [],
headers: {
'WWW-Authenticate': 'content',
},
meta: {} as any,
});
expect(isAuthError(err)).toBe(true);
const wrappedErr = handleAuthError(err);
@ -78,7 +91,19 @@ describe('Error handling for 401/403 errors', () => {
});
it('handles Unauthorized error defined by ElasticsearchJS', () => {
const err = { statusCode: 403 };
const err = new ResponseError({
statusCode: 403,
body: {
error: {
type: 'Unauthorized',
},
},
warnings: [],
headers: {
'WWW-Authenticate': 'content',
},
meta: {} as any,
});
expect(isAuthError(err)).toBe(true);
const wrappedErr = handleAuthError(err);

View file

@ -7,17 +7,15 @@
import { forbidden } from '@hapi/boom';
import { i18n } from '@kbn/i18n';
import { getStatusCode } from './handle_error';
import { ErrorTypes } from '../../types';
const getStatusCode = (err) => {
return err.isBoom ? err.output.statusCode : err.statusCode;
};
export function isAuthError(err) {
export function isAuthError(err: ErrorTypes) {
const statusCode = getStatusCode(err);
return statusCode === 401 || statusCode === 403;
}
export function handleAuthError(err) {
export function handleAuthError(err: ErrorTypes) {
const statusCode = getStatusCode(err);
let message;

View file

@ -0,0 +1,37 @@
/*
* 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 { handleCustomError, isCustomError, MonitoringLicenseError } from './custom_errors';
describe('Error handling for custom monitoring errors', () => {
it('handles the custom MonitoringLicenseError error', () => {
const clusterName = 'main';
const err = new MonitoringLicenseError(clusterName);
expect(isCustomError(err)).toBe(true);
const wrappedErr = handleCustomError(err);
expect(wrappedErr.message).toBe(
'Monitoring License Error: ' +
`Could not find license information for cluster = '${clusterName}'. ` +
`Please check the cluster's master node server logs for errors or warnings.`
);
expect(wrappedErr.isBoom).toBe(true);
expect(wrappedErr.isServer).toBe(true);
expect(wrappedErr.data).toBe(null);
expect(wrappedErr.output).toEqual({
statusCode: 503,
payload: {
statusCode: 503,
error: 'Service Unavailable',
message:
`Monitoring License Error: Could not find license information for cluster = '${clusterName}'. ` +
`Please check the cluster's master node server logs for errors or warnings.`,
},
headers: {},
});
});
});

View file

@ -5,10 +5,21 @@
* 2.0.
*/
/* eslint-disable max-classes-per-file */
import { i18n } from '@kbn/i18n';
import { boomify } from '@hapi/boom';
import { ErrorTypes } from '../../types';
export class MonitoringLicenseError extends Error {
constructor(clusterId) {
export class MonitoringCustomError extends Error {
readonly description?: string;
constructor(message?: string) {
super(message);
this.name = this.constructor.name; // for stack traces
}
}
export class MonitoringLicenseError extends MonitoringCustomError {
readonly description: string;
constructor(clusterId: string) {
super();
this.message = i18n.translate('xpack.monitoring.errors.monitoringLicenseErrorTitle', {
defaultMessage: 'Monitoring License Error',
@ -23,3 +34,14 @@ export class MonitoringLicenseError extends Error {
});
}
}
export function isCustomError(err: ErrorTypes) {
if (err instanceof MonitoringCustomError) {
return true;
}
}
export function handleCustomError(err: MonitoringCustomError) {
err.message = err.message + ': ' + err.description;
return boomify(err, { statusCode: 503 });
}

View file

@ -6,23 +6,21 @@
*/
import { errors } from '@elastic/elasticsearch';
import { isKnownError, handleKnownError } from './known_errors';
import { MonitoringLicenseError } from './custom_errors';
import { isESClientError, handleESClientError } from './esclient_errors';
// TODO: tests were not running and are not up to date
describe.skip('Error handling for 503 errors', () => {
describe('Error handling for ESClient errors', () => {
it('ignores an unknown type', () => {
const err = new Error();
expect(isKnownError(err)).toBe(false);
expect(isESClientError(err)).toBe(false);
});
it('handles ConnectionError', () => {
const err = new errors.ConnectionError();
expect(isKnownError(err)).toBe(true);
const err = new errors.ConnectionError('test 123', {} as any);
expect(isESClientError(err)).toBe(true);
const wrappedErr = handleKnownError(err);
const wrappedErr = handleESClientError(err);
expect(wrappedErr.message).toBe(
'Connection Failure: ' +
'Connection error: ' +
'Check the Elasticsearch Monitoring cluster network connection and refer to the Kibana logs for more information.'
);
expect(wrappedErr.isBoom).toBe(true);
@ -34,7 +32,7 @@ describe.skip('Error handling for 503 errors', () => {
statusCode: 503,
error: 'Service Unavailable',
message:
'Connection Failure: ' +
'Connection error: ' +
'Check the Elasticsearch Monitoring cluster network connection and refer to the Kibana logs for more information.',
},
headers: {},
@ -42,12 +40,12 @@ describe.skip('Error handling for 503 errors', () => {
});
it('handles NoLivingConnectionsError', () => {
const err = new errors.NoLivingConnectionsError();
expect(isKnownError(err)).toBe(true);
const err = new errors.NoLivingConnectionsError('test 123', {} as any);
expect(isESClientError(err)).toBe(true);
const wrappedErr = handleKnownError(err);
const wrappedErr = handleESClientError(err);
expect(wrappedErr.message).toBe(
'No Living connections: ' +
'No living connections: ' +
'Check the Elasticsearch Monitoring cluster network connection and refer to the Kibana logs for more information.'
);
expect(wrappedErr.isBoom).toBe(true);
@ -59,7 +57,7 @@ describe.skip('Error handling for 503 errors', () => {
statusCode: 503,
error: 'Service Unavailable',
message:
'No Living connections: ' +
'No living connections: ' +
'Check the Elasticsearch Monitoring cluster network connection and refer to the Kibana logs for more information.',
},
headers: {},
@ -67,12 +65,12 @@ describe.skip('Error handling for 503 errors', () => {
});
it('handles TimeoutError', () => {
const err = new errors.TimeoutError();
expect(isKnownError(err)).toBe(true);
const err = new errors.TimeoutError('test 123', {} as any);
expect(isESClientError(err)).toBe(true);
const wrappedErr = handleKnownError(err);
const wrappedErr = handleESClientError(err);
expect(wrappedErr.message).toBe(
'Request Timeout: ' +
'Request timeout: ' +
'Check the Elasticsearch Monitoring cluster network connection or the load level of the nodes.'
);
expect(wrappedErr.isBoom).toBe(true);
@ -84,34 +82,7 @@ describe.skip('Error handling for 503 errors', () => {
statusCode: 503,
error: 'Service Unavailable',
message:
'Request Timeout: Check the Elasticsearch Monitoring cluster network connection or the load level of the nodes.',
},
headers: {},
});
});
it('handles the custom MonitoringLicenseError error', () => {
const clusterName = 'main';
const err = new MonitoringLicenseError(clusterName);
expect(isKnownError(err)).toBe(true);
const wrappedErr = handleKnownError(err);
expect(wrappedErr.message).toBe(
'Monitoring License Error: ' +
`Could not find license information for cluster = '${clusterName}'. ` +
`Please check the cluster's master node server logs for errors or warnings.`
);
expect(wrappedErr.isBoom).toBe(true);
expect(wrappedErr.isServer).toBe(true);
expect(wrappedErr.data).toBe(null);
expect(wrappedErr.output).toEqual({
statusCode: 503,
payload: {
statusCode: 503,
error: 'Service Unavailable',
message:
`Monitoring License Error: Could not find license information for cluster = '${clusterName}'. ` +
`Please check the cluster's master node server logs for errors or warnings.`,
'Request timeout: Check the Elasticsearch Monitoring cluster network connection or the load level of the nodes.',
},
headers: {},
});

View file

@ -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.
*/
import { ElasticsearchClientError } from '@elastic/elasticsearch/lib/errors';
import { boomify } from '@hapi/boom';
import { i18n } from '@kbn/i18n';
import { ErrorTypes } from '../../types';
/*
* Check if the given error message is a known "safe" type of error
* in which case we want to give the status as 503 and show the error message.
*
* This is necessary because Boom's default status code is 500, and has
* behavior to suppress the original message to the client for security
* reasons.
*/
const mapTypeMessage: { [key: string]: string } = {
ConnectionError: i18n.translate('xpack.monitoring.errors.connectionErrorMessage', {
defaultMessage:
'Connection error: Check the Elasticsearch Monitoring cluster network connection and refer to the Kibana logs for more information.',
}),
NoLivingConnectionsError: i18n.translate(
'xpack.monitoring.errors.noLivingConnectionsErrorMessage',
{
defaultMessage:
'No living connections: Check the Elasticsearch Monitoring cluster network connection and refer to the Kibana logs for more information.',
}
),
TimeoutError: i18n.translate('xpack.monitoring.errors.TimeoutErrorMessage', {
defaultMessage:
'Request timeout: Check the Elasticsearch Monitoring cluster network connection or the load level of the nodes.',
}),
};
export function isESClientError(err: ErrorTypes) {
if (err instanceof ElasticsearchClientError === false) return false;
const knownTypes = Object.keys(mapTypeMessage);
return knownTypes.includes(err.constructor.name);
}
export function handleESClientError(err: ElasticsearchClientError) {
err.message = mapTypeMessage[err.constructor.name];
return boomify(err, { statusCode: 503 });
}

View file

@ -1,38 +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 { boomify } from '@hapi/boom';
import { isKnownError, handleKnownError } from './known_errors';
import { isAuthError, handleAuthError } from './auth_errors';
export function handleError(err, req) {
req && req.logger && req.logger.error(err);
// specially handle auth errors
if (isAuthError(err)) {
return handleAuthError(err);
}
// specially "service unavailable" errors
if (isKnownError(err)) {
return handleKnownError(err);
}
if (err.isBoom) {
return err;
}
// boom expects err.message, not err.msg
if (err.msg) {
err.message = err.msg;
delete err.msg;
}
const statusCode = err.isBoom ? err.output.statusCode : err.statusCode;
// wrap the error; defaults to statusCode = 500 if statusCode is undefined
return boomify(err, { statusCode, message: err.message });
}

View file

@ -0,0 +1,50 @@
/*
* 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 { boomify, isBoom } from '@hapi/boom';
import { ResponseError } from '@elastic/elasticsearch/lib/errors';
import { isCustomError, handleCustomError } from './custom_errors';
import { isAuthError, handleAuthError } from './auth_errors';
import { ErrorTypes, LegacyRequest } from '../../types';
import { handleESClientError, isESClientError } from './esclient_errors';
export const getStatusCode = (err: ErrorTypes) => {
return isBoom(err)
? err.output.statusCode
: err instanceof ResponseError
? err.statusCode
: undefined;
};
export function handleError(err: ErrorTypes, req?: LegacyRequest) {
if (req) {
req.logger.error(err);
}
// handle auth errors
if (isAuthError(err)) {
return handleAuthError(err);
}
// handle custom Monitoring errors
if (isCustomError(err)) {
return handleCustomError(err);
}
// handle certain EsClientError errors
if (isESClientError(err)) {
return handleESClientError(err);
}
if (isBoom(err)) {
return err;
}
const statusCode = getStatusCode(err);
// wrap the error; defaults to statusCode = 500 if statusCode is undefined
return boomify(err, { statusCode, message: err.message });
}

View file

@ -6,7 +6,10 @@
*/
import { boomify } from '@hapi/boom';
import { ErrorTypes } from '../../types';
import { getStatusCode } from './handle_error';
export function handleSettingsError(err) {
return boomify(err, { statusCode: err.statusCode });
export function handleSettingsError(err: ErrorTypes) {
const statusCode = getStatusCode(err);
return boomify(err, { statusCode });
}

View file

@ -1,54 +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 { boomify } from '@hapi/boom';
import { i18n } from '@kbn/i18n';
import { MonitoringLicenseError } from './custom_errors';
/*
* Check if the given error message is a known "safe" type of error
* in which case we want to give the status as 503 and show the error message.
*
* This is necessary because Boom's default status code is 500, and has
* behavior to suppress the original message to the client for security
* reasons.
*/
const KNOWN_ERROR_STATUS_CODE = 503;
const mapTypeMessage = {
ConnectionFault: i18n.translate('xpack.monitoring.errors.connectionFaultErrorMessage', {
defaultMessage:
'Check the Elasticsearch Monitoring cluster network connection and refer to the Kibana logs for more information.',
}),
NoConnections: i18n.translate('xpack.monitoring.errors.noConnectionsErrorMessage', {
defaultMessage:
'Check the Elasticsearch Monitoring cluster network connection and refer to the Kibana logs for more information.',
}),
StatusCodeError: i18n.translate('xpack.monitoring.errors.statusCodeErrorMessage', {
defaultMessage:
'Check the Elasticsearch Monitoring cluster network connection or the load level of the nodes.',
}),
};
const customErrors = [MonitoringLicenseError];
export function isKnownError(err) {
for (const customError of customErrors) {
if (err instanceof customError) {
return true;
}
}
const knownTypes = Object.keys(mapTypeMessage);
return knownTypes.includes(err.constructor.name);
}
export function handleKnownError(err) {
err.message = err.message + ': ' + (err.description || mapTypeMessage[err.constructor.name]);
let statusCode = err.statusCode || err.status;
statusCode = statusCode !== 500 ? statusCode : KNOWN_ERROR_STATUS_CODE;
return boomify(err, { statusCode });
}

View file

@ -13,6 +13,8 @@ import type {
RequestHandlerContext,
ElasticsearchClient,
} from 'kibana/server';
import type Boom from '@hapi/boom';
import { ElasticsearchClientError, ResponseError } from '@elastic/elasticsearch/lib/errors';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { LicenseFeature, ILicense } from '../../licensing/server';
import type {
@ -179,3 +181,5 @@ export interface ClusterSettingsReasonResponse {
data?: string;
};
}
export type ErrorTypes = Error | Boom.Boom | ResponseError | ElasticsearchClientError;

View file

@ -17385,13 +17385,10 @@
"xpack.monitoring.elasticsearch.shardAllocation.unassignedDisplayName": "割り当てなし",
"xpack.monitoring.elasticsearch.shardAllocation.unassignedPrimaryLabel": "未割り当てプライマリ",
"xpack.monitoring.elasticsearch.shardAllocation.unassignedReplicaLabel": "未割り当てレプリカ",
"xpack.monitoring.errors.connectionFaultErrorMessage": "Elasticsearch 監視クラスターのネットワーク接続を確認し、詳細は Kibana のログをご覧ください。",
"xpack.monitoring.errors.insufficientUserErrorMessage": "データの監視に必要なユーザーパーミッションがありません",
"xpack.monitoring.errors.invalidAuthErrorMessage": "クラスターの監視に無効な認証です",
"xpack.monitoring.errors.monitoringLicenseErrorDescription": "クラスター = 「{clusterId}」のライセンス情報が見つかりませんでした。クラスターのマスターノードサーバーログにエラーや警告がないか確認してください。",
"xpack.monitoring.errors.monitoringLicenseErrorTitle": "監視ライセンスエラー",
"xpack.monitoring.errors.noConnectionsErrorMessage": "Elasticsearch 監視クラスターのネットワーク接続を確認し、詳細は Kibana のログをご覧ください。",
"xpack.monitoring.errors.statusCodeErrorMessage": "Elasticsearch 監視クラスターのネットワーク接続、またはノードの負荷レベルを確認してください。",
"xpack.monitoring.es.indices.deletedClosedStatusLabel": "削除済み / クローズ済み",
"xpack.monitoring.es.indices.notAvailableStatusLabel": "利用不可",
"xpack.monitoring.es.indices.unknownStatusLabel": "不明",

View file

@ -17796,13 +17796,10 @@
"xpack.monitoring.elasticsearch.shardAllocation.unassignedDisplayName": "未分配",
"xpack.monitoring.elasticsearch.shardAllocation.unassignedPrimaryLabel": "未分配主分片",
"xpack.monitoring.elasticsearch.shardAllocation.unassignedReplicaLabel": "未分配副本分片",
"xpack.monitoring.errors.connectionFaultErrorMessage": "检查 Elasticsearch Monitoring 集群网络连接,并参考 Kibana 日志以了解详情。",
"xpack.monitoring.errors.insufficientUserErrorMessage": "对监测数据没有足够的用户权限",
"xpack.monitoring.errors.invalidAuthErrorMessage": "监测集群的身份验证无效",
"xpack.monitoring.errors.monitoringLicenseErrorDescription": "无法找到集群“{clusterId}”的许可信息。请在集群的主节点服务器日志中查看相关错误或警告。",
"xpack.monitoring.errors.monitoringLicenseErrorTitle": "监测许可错误",
"xpack.monitoring.errors.noConnectionsErrorMessage": "检查 Elasticsearch Monitoring 集群网络连接,并参考 Kibana 日志以了解详情。",
"xpack.monitoring.errors.statusCodeErrorMessage": "检查 Elasticsearch Monitoring 集群网络连接或节点的负载水平。",
"xpack.monitoring.es.indices.deletedClosedStatusLabel": "已删除 / 已关闭",
"xpack.monitoring.es.indices.notAvailableStatusLabel": "不可用",
"xpack.monitoring.es.indices.unknownStatusLabel": "未知",