Revert "[Status service] log plugin status changes (#126320)" (#128096)

This reverts commit 6e11beadcf.
This commit is contained in:
Jonathan Budzenski 2022-03-21 17:12:01 -05:00 committed by GitHub
parent 0682219a8d
commit a1085f4b75
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 3 additions and 568 deletions

View file

@ -1,332 +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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { TestScheduler } from 'rxjs/testing';
import { PluginName } from '../plugins';
import { ServiceStatus, ServiceStatusLevel, ServiceStatusLevels } from './types';
import {
getPluginsStatusChanges,
getPluginsStatusDiff,
getServiceLevelChangeMessage,
} from './log_plugins_status';
type ObsInputType = Record<PluginName, ServiceStatus>;
const getTestScheduler = () =>
new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
const createServiceStatus = (level: ServiceStatusLevel): ServiceStatus => ({
level,
summary: 'summary',
});
const createPluginsStatuses = (
input: Record<PluginName, ServiceStatusLevel>
): Record<PluginName, ServiceStatus> => {
return Object.entries(input).reduce((output, [name, level]) => {
output[name] = createServiceStatus(level);
return output;
}, {} as Record<PluginName, ServiceStatus>);
};
describe('getPluginsStatusChanges', () => {
it('does not emit on first plugins$ emission', () => {
getTestScheduler().run(({ expectObservable, hot }) => {
const statuses = createPluginsStatuses({
pluginA: ServiceStatusLevels.available,
pluginB: ServiceStatusLevels.degraded,
});
const overall$ = hot<ObsInputType>('-a', {
a: statuses,
});
const stop$ = hot<void>('');
const expected = '--';
expectObservable(getPluginsStatusChanges(overall$, stop$, 1)).toBe(expected);
});
});
it('does not emit if statuses do not change', () => {
getTestScheduler().run(({ expectObservable, hot }) => {
const statuses = createPluginsStatuses({
pluginA: ServiceStatusLevels.available,
pluginB: ServiceStatusLevels.degraded,
});
const overall$ = hot<ObsInputType>('-a-b', {
a: statuses,
b: statuses,
});
const stop$ = hot<void>('');
const expected = '----';
expectObservable(getPluginsStatusChanges(overall$, stop$, 1)).toBe(expected);
});
});
it('emits if any plugin status changes', () => {
getTestScheduler().run(({ expectObservable, hot }) => {
const statusesA = createPluginsStatuses({
pluginA: ServiceStatusLevels.available,
pluginB: ServiceStatusLevels.degraded,
});
const statusesB = createPluginsStatuses({
pluginA: ServiceStatusLevels.available,
pluginB: ServiceStatusLevels.available,
});
const overall$ = hot<ObsInputType>('-a-b', {
a: statusesA,
b: statusesB,
});
const stop$ = hot<void>('');
const expected = '---a';
expectObservable(getPluginsStatusChanges(overall$, stop$, 1)).toBe(expected, {
a: [
{
previousLevel: 'degraded',
nextLevel: 'available',
impactedServices: ['pluginB'],
},
],
});
});
});
it('emits everytime any plugin status changes', () => {
getTestScheduler().run(({ expectObservable, hot }) => {
const availableStatus = createPluginsStatuses({
pluginA: ServiceStatusLevels.available,
});
const degradedStatus = createPluginsStatuses({
pluginA: ServiceStatusLevels.degraded,
});
const overall$ = hot<ObsInputType>('-a-b-c-d', {
a: availableStatus,
b: degradedStatus,
c: degradedStatus,
d: availableStatus,
});
const stop$ = hot<void>('');
const expected = '---a---b';
expectObservable(getPluginsStatusChanges(overall$, stop$, 1)).toBe(expected, {
a: [
{
previousLevel: 'available',
nextLevel: 'degraded',
impactedServices: ['pluginA'],
},
],
b: [
{
previousLevel: 'degraded',
nextLevel: 'available',
impactedServices: ['pluginA'],
},
],
});
});
});
it('throttle events', () => {
getTestScheduler().run(({ expectObservable, hot }) => {
const statusesA = createPluginsStatuses({
pluginA: ServiceStatusLevels.available,
pluginB: ServiceStatusLevels.degraded,
});
const statusesB = createPluginsStatuses({
pluginA: ServiceStatusLevels.available,
pluginB: ServiceStatusLevels.available,
});
const statusesC = createPluginsStatuses({
pluginA: ServiceStatusLevels.degraded,
pluginB: ServiceStatusLevels.available,
});
const overall$ = hot<ObsInputType>('-a-b--c', {
a: statusesA,
b: statusesB,
c: statusesC,
});
const stop$ = hot<void>('');
const expected = '------a';
expectObservable(getPluginsStatusChanges(overall$, stop$, 5)).toBe(expected, {
a: [
{
previousLevel: 'available',
nextLevel: 'degraded',
impactedServices: ['pluginA'],
},
{
previousLevel: 'degraded',
nextLevel: 'available',
impactedServices: ['pluginB'],
},
],
});
});
});
it('stops emitting once `stop$` emits', () => {
getTestScheduler().run(({ expectObservable, hot }) => {
const statusesA = createPluginsStatuses({
pluginA: ServiceStatusLevels.available,
pluginB: ServiceStatusLevels.degraded,
});
const statusesB = createPluginsStatuses({
pluginA: ServiceStatusLevels.available,
pluginB: ServiceStatusLevels.available,
});
const statusesC = createPluginsStatuses({
pluginA: ServiceStatusLevels.degraded,
pluginB: ServiceStatusLevels.available,
});
const overall$ = hot<ObsInputType>('-a-b-c', {
a: statusesA,
b: statusesB,
c: statusesC,
});
const stop$ = hot<void>('----(s|)');
const expected = '---a|';
expectObservable(getPluginsStatusChanges(overall$, stop$, 1)).toBe(expected, {
a: [
{
previousLevel: 'degraded',
nextLevel: 'available',
impactedServices: ['pluginB'],
},
],
});
});
});
});
describe('getPluginsStatusDiff', () => {
it('returns an empty list if level is the same for all plugins', () => {
const previousStatus = createPluginsStatuses({
pluginA: ServiceStatusLevels.available,
pluginB: ServiceStatusLevels.degraded,
pluginC: ServiceStatusLevels.unavailable,
});
const nextStatus = createPluginsStatuses({
pluginA: ServiceStatusLevels.available,
pluginB: ServiceStatusLevels.degraded,
pluginC: ServiceStatusLevels.unavailable,
});
const result = getPluginsStatusDiff(previousStatus, nextStatus);
expect(result).toEqual([]);
});
it('returns an single entry if only one status changed', () => {
const previousStatus = createPluginsStatuses({
pluginA: ServiceStatusLevels.available,
pluginB: ServiceStatusLevels.degraded,
pluginC: ServiceStatusLevels.unavailable,
});
const nextStatus = createPluginsStatuses({
pluginA: ServiceStatusLevels.degraded,
pluginB: ServiceStatusLevels.degraded,
pluginC: ServiceStatusLevels.unavailable,
});
const result = getPluginsStatusDiff(previousStatus, nextStatus);
expect(result).toEqual([
{
previousLevel: 'available',
nextLevel: 'degraded',
impactedServices: ['pluginA'],
},
]);
});
it('groups plugins by previous and next level tuples', () => {
const previousStatus = createPluginsStatuses({
pluginA: ServiceStatusLevels.available,
pluginB: ServiceStatusLevels.available,
pluginC: ServiceStatusLevels.unavailable,
});
const nextStatus = createPluginsStatuses({
pluginA: ServiceStatusLevels.degraded,
pluginB: ServiceStatusLevels.degraded,
pluginC: ServiceStatusLevels.unavailable,
});
const result = getPluginsStatusDiff(previousStatus, nextStatus);
expect(result).toEqual([
{
previousLevel: 'available',
nextLevel: 'degraded',
impactedServices: ['pluginA', 'pluginB'],
},
]);
});
it('returns one entry per previous and next level tuples', () => {
const previousStatus = createPluginsStatuses({
pluginA: ServiceStatusLevels.available,
pluginB: ServiceStatusLevels.degraded,
pluginC: ServiceStatusLevels.unavailable,
});
const nextStatus = createPluginsStatuses({
pluginA: ServiceStatusLevels.degraded,
pluginB: ServiceStatusLevels.unavailable,
pluginC: ServiceStatusLevels.available,
});
const result = getPluginsStatusDiff(previousStatus, nextStatus);
expect(result).toEqual([
{
previousLevel: 'available',
nextLevel: 'degraded',
impactedServices: ['pluginA'],
},
{
previousLevel: 'degraded',
nextLevel: 'unavailable',
impactedServices: ['pluginB'],
},
{
previousLevel: 'unavailable',
nextLevel: 'available',
impactedServices: ['pluginC'],
},
]);
});
});
describe('getServiceLevelChangeMessage', () => {
it('returns a human readable message about the change', () => {
expect(
getServiceLevelChangeMessage({
previousLevel: 'available',
nextLevel: 'degraded',
impactedServices: ['pluginA', 'pluginB'],
})
).toMatchInlineSnapshot(
`"2 plugins changed status from 'available' to 'degraded': pluginA, pluginB"`
);
});
});

View file

@ -1,104 +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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { isDeepStrictEqual } from 'util';
import { Observable, asyncScheduler } from 'rxjs';
import {
distinctUntilChanged,
pairwise,
takeUntil,
map,
filter,
throttleTime,
} from 'rxjs/operators';
import { PluginName } from '../plugins';
import { ServiceStatus } from './types';
export type ServiceStatusWithName = ServiceStatus & {
name: PluginName;
};
export interface ServiceLevelChange {
previousLevel: string;
nextLevel: string;
impactedServices: string[];
}
export const getPluginsStatusChanges = (
plugins$: Observable<Record<PluginName, ServiceStatus>>,
stop$: Observable<void>,
throttleDuration: number = 250
): Observable<ServiceLevelChange[]> => {
return plugins$.pipe(
takeUntil(stop$),
distinctUntilChanged((previous, next) =>
isDeepStrictEqual(getStatusLevelMap(previous), getStatusLevelMap(next))
),
throttleTime(throttleDuration, asyncScheduler, { leading: true, trailing: true }),
pairwise(),
map(([oldStatus, newStatus]) => {
return getPluginsStatusDiff(oldStatus, newStatus);
}),
filter((statusChanges) => statusChanges.length > 0)
);
};
const getStatusLevelMap = (
plugins: Record<PluginName, ServiceStatus>
): Record<PluginName, string> => {
return Object.entries(plugins).reduce((levelMap, [key, value]) => {
levelMap[key] = value.level.toString();
return levelMap;
}, {} as Record<PluginName, string>);
};
export const getPluginsStatusDiff = (
previous: Record<PluginName, ServiceStatus>,
next: Record<PluginName, ServiceStatus>
): ServiceLevelChange[] => {
const statusChanges: Map<string, ServiceLevelChange> = new Map();
Object.entries(next).forEach(([pluginName, nextStatus]) => {
const previousStatus = previous[pluginName];
if (!previousStatus) {
return;
}
const previousLevel = statusLevel(previousStatus);
const nextLevel = statusLevel(nextStatus);
if (previousLevel === nextLevel) {
return;
}
const changeKey = statusChangeKey(previousLevel, nextLevel);
let statusChange = statusChanges.get(changeKey);
if (!statusChange) {
statusChange = {
previousLevel,
nextLevel,
impactedServices: [],
};
statusChanges.set(changeKey, statusChange);
}
statusChange.impactedServices.push(pluginName);
});
return [...statusChanges.values()];
};
export const getServiceLevelChangeMessage = ({
impactedServices: services,
nextLevel: next,
previousLevel: previous,
}: ServiceLevelChange): string => {
return `${
services.length
} plugins changed status from '${previous}' to '${next}': ${services.join(', ')}`;
};
const statusLevel = (status: ServiceStatus) => status.level.toString();
const statusChangeKey = (previous: string, next: string) => `${previous}:${next}`;

View file

@ -1,19 +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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export const getOverallStatusChangesMock = jest.fn();
jest.doMock('./log_overall_status', () => ({
getOverallStatusChanges: getOverallStatusChangesMock,
}));
export const getPluginsStatusChangesMock = jest.fn();
export const getServiceLevelChangeMessageMock = jest.fn();
jest.doMock('./log_plugins_status', () => ({
getPluginsStatusChanges: getPluginsStatusChangesMock,
getServiceLevelChangeMessage: getServiceLevelChangeMessageMock,
}));

View file

@ -6,13 +6,7 @@
* Side Public License, v 1.
*/
import {
getOverallStatusChangesMock,
getPluginsStatusChangesMock,
getServiceLevelChangeMessageMock,
} from './status_service.test.mocks';
import { of, BehaviorSubject, Subject } from 'rxjs';
import { of, BehaviorSubject } from 'rxjs';
import { ServiceStatus, ServiceStatusLevels, CoreStatus } from './types';
import { StatusService } from './status_service';
@ -25,27 +19,14 @@ import { mockRouter, RouterMock } from '../http/router/router.mock';
import { metricsServiceMock } from '../metrics/metrics_service.mock';
import { configServiceMock } from '../config/mocks';
import { coreUsageDataServiceMock } from '../core_usage_data/core_usage_data_service.mock';
import { loggingSystemMock } from '../logging/logging_system.mock';
import type { ServiceLevelChange } from './log_plugins_status';
expect.addSnapshotSerializer(ServiceStatusLevelSnapshotSerializer);
describe('StatusService', () => {
let service: StatusService;
let logger: ReturnType<typeof loggingSystemMock.create>;
beforeEach(() => {
logger = loggingSystemMock.create();
service = new StatusService(mockCoreContext.create({ logger }));
getOverallStatusChangesMock.mockReturnValue({ subscribe: jest.fn() });
getPluginsStatusChangesMock.mockReturnValue({ subscribe: jest.fn() });
});
afterEach(() => {
getOverallStatusChangesMock.mockReset();
getPluginsStatusChangesMock.mockReset();
getServiceLevelChangeMessageMock.mockReset();
service = new StatusService(mockCoreContext.create());
});
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
@ -64,7 +45,7 @@ describe('StatusService', () => {
};
type SetupDeps = Parameters<StatusService['setup']>[0];
const setupDeps = (overrides: Partial<SetupDeps> = {}): SetupDeps => {
const setupDeps = (overrides: Partial<SetupDeps>): SetupDeps => {
return {
elasticsearch: {
status$: of(available),
@ -555,88 +536,4 @@ describe('StatusService', () => {
});
});
});
describe('start', () => {
it('calls getOverallStatusChanges and subscribe to the returned observable', async () => {
const mockSubscribe = jest.fn();
getOverallStatusChangesMock.mockReturnValue({
subscribe: mockSubscribe,
});
await service.setup(setupDeps());
await service.start();
expect(getOverallStatusChangesMock).toHaveBeenCalledTimes(1);
expect(mockSubscribe).toHaveBeenCalledTimes(1);
});
it('logs a message everytime the getOverallStatusChangesMock observable emits', async () => {
const subject = new Subject<string>();
getOverallStatusChangesMock.mockReturnValue(subject);
await service.setup(setupDeps());
await service.start();
subject.next('some message');
subject.next('another message');
const log = logger.get();
expect(log.info).toHaveBeenCalledTimes(2);
expect(log.info).toHaveBeenCalledWith('some message');
expect(log.info).toHaveBeenCalledWith('another message');
});
it('calls getPluginsStatusChanges and subscribe to the returned observable', async () => {
const mockSubscribe = jest.fn();
getPluginsStatusChangesMock.mockReturnValue({
subscribe: mockSubscribe,
});
await service.setup(setupDeps());
await service.start();
expect(getPluginsStatusChangesMock).toHaveBeenCalledTimes(1);
expect(mockSubscribe).toHaveBeenCalledTimes(1);
});
it('logs messages everytime the getPluginsStatusChangesMock observable emits', async () => {
const subject = new Subject<ServiceLevelChange[]>();
getPluginsStatusChangesMock.mockReturnValue(subject);
getServiceLevelChangeMessageMock.mockImplementation(
({
impactedServices: services,
nextLevel: next,
previousLevel: previous,
}: ServiceLevelChange) => {
return `${previous}-${next}-${services[0]}`;
}
);
await service.setup(setupDeps());
await service.start();
subject.next([
{
previousLevel: 'available',
nextLevel: 'degraded',
impactedServices: ['pluginA'],
},
]);
subject.next([
{
previousLevel: 'degraded',
nextLevel: 'available',
impactedServices: ['pluginB'],
},
]);
const log = logger.get();
expect(log.info).toHaveBeenCalledTimes(2);
expect(log.info).toHaveBeenCalledWith('available-degraded-pluginA');
expect(log.info).toHaveBeenCalledWith('degraded-available-pluginB');
});
});
});

View file

@ -27,7 +27,6 @@ import { ServiceStatus, CoreStatus, InternalStatusServiceSetup } from './types';
import { getSummaryStatus } from './get_summary_status';
import { PluginsStatusService } from './plugins_status';
import { getOverallStatusChanges } from './log_overall_status';
import { getPluginsStatusChanges, getServiceLevelChangeMessage } from './log_plugins_status';
interface StatusLogMeta extends LogMeta {
kibana: { status: ServiceStatus };
@ -166,12 +165,6 @@ export class StatusService implements CoreService<InternalStatusServiceSetup> {
getOverallStatusChanges(this.overall$, this.stop$).subscribe((message) => {
this.logger.info(message);
});
getPluginsStatusChanges(this.pluginsStatus.getAll$(), this.stop$).subscribe((statusChanges) => {
statusChanges.forEach((statusChange) => {
this.logger.info(getServiceLevelChangeMessage(statusChange));
});
});
}
public stop() {