mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[RAM] Maintenance Window - Expose Active Maintenance Window Route (#155321)
## Summary Resolves: https://github.com/elastic/kibana/issues/155306 Exposes the `/_active` route to get all currently active maintenance windows ``` GET `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window/_active` body: {} Response: MaintenanceWindow[] ``` ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
This commit is contained in:
parent
80fb9390fc
commit
4d89152326
6 changed files with 292 additions and 2 deletions
|
@ -29,7 +29,7 @@ export interface MaintenanceWindowAggregationResult {
|
|||
|
||||
export interface ActiveParams {
|
||||
start?: string;
|
||||
interval: string;
|
||||
interval?: string;
|
||||
}
|
||||
|
||||
export async function getActiveMaintenanceWindows(
|
||||
|
@ -40,7 +40,7 @@ export async function getActiveMaintenanceWindows(
|
|||
const { start, interval } = params;
|
||||
|
||||
const startDate = start ? new Date(start) : new Date();
|
||||
const duration = parseDuration(interval);
|
||||
const duration = interval ? parseDuration(interval) : 0;
|
||||
const endDate = moment.utc(startDate).add(duration, 'ms').toDate();
|
||||
|
||||
const startDateISO = startDate.toISOString();
|
||||
|
|
|
@ -53,6 +53,7 @@ import { deleteMaintenanceWindowRoute } from './maintenance_window/delete_mainte
|
|||
import { findMaintenanceWindowsRoute } from './maintenance_window/find_maintenance_windows';
|
||||
import { archiveMaintenanceWindowRoute } from './maintenance_window/archive_maintenance_window';
|
||||
import { finishMaintenanceWindowRoute } from './maintenance_window/finish_maintenance_window';
|
||||
import { activeMaintenanceWindowsRoute } from './maintenance_window/active_maintenance_windows';
|
||||
|
||||
export interface RouteOptions {
|
||||
router: IRouter<AlertingRequestHandlerContext>;
|
||||
|
@ -108,4 +109,5 @@ export function defineRoutes(opts: RouteOptions) {
|
|||
findMaintenanceWindowsRoute(router, licenseState);
|
||||
archiveMaintenanceWindowRoute(router, licenseState);
|
||||
finishMaintenanceWindowRoute(router, licenseState);
|
||||
activeMaintenanceWindowsRoute(router, licenseState);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* 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 { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../../lib/license_state.mock';
|
||||
import { verifyApiAccess } from '../../lib/license_api_access';
|
||||
import { mockHandlerArguments } from '../_mock_handler_arguments';
|
||||
import { maintenanceWindowClientMock } from '../../maintenance_window_client.mock';
|
||||
import { activeMaintenanceWindowsRoute } from './active_maintenance_windows';
|
||||
import { getMockMaintenanceWindow } from '../../maintenance_window_client/methods/test_helpers';
|
||||
import { MaintenanceWindowStatus } from '../../../common';
|
||||
import { rewriteMaintenanceWindowRes } from '../lib';
|
||||
|
||||
const maintenanceWindowClient = maintenanceWindowClientMock.create();
|
||||
|
||||
jest.mock('../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockMaintenanceWindows = [
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
eventStartTime: new Date().toISOString(),
|
||||
eventEndTime: new Date().toISOString(),
|
||||
status: MaintenanceWindowStatus.Running,
|
||||
id: 'test-id1',
|
||||
},
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
eventStartTime: new Date().toISOString(),
|
||||
eventEndTime: new Date().toISOString(),
|
||||
status: MaintenanceWindowStatus.Running,
|
||||
id: 'test-id2',
|
||||
},
|
||||
];
|
||||
|
||||
describe('activeMaintenanceWindowsRoute', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
test('should get the currently active maintenance windows', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
activeMaintenanceWindowsRoute(router, licenseState);
|
||||
|
||||
maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValueOnce(
|
||||
mockMaintenanceWindows
|
||||
);
|
||||
const [config, handler] = router.get.mock.calls[0];
|
||||
const [context, req, res] = mockHandlerArguments({ maintenanceWindowClient }, { body: {} });
|
||||
|
||||
expect(config.path).toEqual('/internal/alerting/rules/maintenance_window/_active');
|
||||
expect(config.options?.tags?.[0]).toEqual('access:read-maintenance-window');
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(maintenanceWindowClient.getActiveMaintenanceWindows).toHaveBeenCalled();
|
||||
expect(res.ok).toHaveBeenLastCalledWith({
|
||||
body: mockMaintenanceWindows.map((data) => rewriteMaintenanceWindowRes(data)),
|
||||
});
|
||||
});
|
||||
|
||||
test('ensures the license allows for getting active maintenance windows', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
activeMaintenanceWindowsRoute(router, licenseState);
|
||||
|
||||
maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValueOnce(
|
||||
mockMaintenanceWindows
|
||||
);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
const [context, req, res] = mockHandlerArguments({ maintenanceWindowClient }, { body: {} });
|
||||
await handler(context, req, res);
|
||||
expect(verifyApiAccess).toHaveBeenCalledWith(licenseState);
|
||||
});
|
||||
|
||||
test('ensures the license check prevents for getting active maintenance windows', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
activeMaintenanceWindowsRoute(router, licenseState);
|
||||
|
||||
(verifyApiAccess as jest.Mock).mockImplementation(() => {
|
||||
throw new Error('Failure');
|
||||
});
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
const [context, req, res] = mockHandlerArguments({ maintenanceWindowClient }, { body: {} });
|
||||
expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: Failure]`);
|
||||
});
|
||||
});
|
|
@ -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 { IRouter } from '@kbn/core/server';
|
||||
import { ILicenseState } from '../../lib';
|
||||
import { verifyAccessAndContext, rewriteMaintenanceWindowRes } from '../lib';
|
||||
import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../../types';
|
||||
import { MAINTENANCE_WINDOW_API_PRIVILEGES } from '../../../common';
|
||||
|
||||
export const activeMaintenanceWindowsRoute = (
|
||||
router: IRouter<AlertingRequestHandlerContext>,
|
||||
licenseState: ILicenseState
|
||||
) => {
|
||||
router.get(
|
||||
{
|
||||
path: `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window/_active`,
|
||||
validate: {},
|
||||
options: {
|
||||
tags: [`access:${MAINTENANCE_WINDOW_API_PRIVILEGES.READ_MAINTENANCE_WINDOW}`],
|
||||
},
|
||||
},
|
||||
router.handleLegacyErrors(
|
||||
verifyAccessAndContext(licenseState, async function (context, req, res) {
|
||||
const maintenanceWindowClient = (await context.alerting).getMaintenanceWindowClient();
|
||||
const result = await maintenanceWindowClient.getActiveMaintenanceWindows({});
|
||||
|
||||
return res.ok({
|
||||
body: result.map((maintenanceWindow) => rewriteMaintenanceWindowRes(maintenanceWindow)),
|
||||
});
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* 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 moment from 'moment';
|
||||
import expect from '@kbn/expect';
|
||||
import { UserAtSpaceScenarios } from '../../../scenarios';
|
||||
import { getUrlPrefix, ObjectRemover } from '../../../../common/lib';
|
||||
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function activeMaintenanceWindowTests({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||
|
||||
describe('activeMaintenanceWindow', () => {
|
||||
const objectRemover = new ObjectRemover(supertest);
|
||||
const createParams = {
|
||||
title: 'test-maintenance-window',
|
||||
duration: 60 * 60 * 1000, // 1 hr
|
||||
r_rule: {
|
||||
dtstart: new Date().toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: 2, // weekly
|
||||
},
|
||||
};
|
||||
after(() => objectRemover.removeAll());
|
||||
|
||||
for (const scenario of UserAtSpaceScenarios) {
|
||||
const { user, space } = scenario;
|
||||
describe(scenario.id, () => {
|
||||
afterEach(() => objectRemover.removeAll());
|
||||
it('should handle update maintenance window request appropriately', async () => {
|
||||
// Create 2 active and 1 inactive maintenance window
|
||||
const { body: createdMaintenanceWindow1 } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/internal/alerting/rules/maintenance_window`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(createParams);
|
||||
|
||||
const { body: createdMaintenanceWindow2 } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/internal/alerting/rules/maintenance_window`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send(createParams);
|
||||
|
||||
const { body: createdMaintenanceWindow3 } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/internal/alerting/rules/maintenance_window`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
...createParams,
|
||||
r_rule: {
|
||||
...createParams.r_rule,
|
||||
dtstart: moment.utc().add(1, 'day').toISOString(),
|
||||
},
|
||||
});
|
||||
|
||||
objectRemover.add(
|
||||
space.id,
|
||||
createdMaintenanceWindow1.id,
|
||||
'rules/maintenance_window',
|
||||
'alerting',
|
||||
true
|
||||
);
|
||||
objectRemover.add(
|
||||
space.id,
|
||||
createdMaintenanceWindow2.id,
|
||||
'rules/maintenance_window',
|
||||
'alerting',
|
||||
true
|
||||
);
|
||||
objectRemover.add(
|
||||
space.id,
|
||||
createdMaintenanceWindow3.id,
|
||||
'rules/maintenance_window',
|
||||
'alerting',
|
||||
true
|
||||
);
|
||||
|
||||
const response = await supertestWithoutAuth
|
||||
.get(`${getUrlPrefix(space.id)}/internal/alerting/rules/maintenance_window/_active`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.auth(user.username, user.password)
|
||||
.send({});
|
||||
|
||||
switch (scenario.id) {
|
||||
case 'no_kibana_privileges at space1':
|
||||
case 'space_1_all at space2':
|
||||
case 'space_1_all_with_restricted_fixture at space1':
|
||||
case 'space_1_all_alerts_none_actions at space1':
|
||||
expect(response.statusCode).to.eql(403);
|
||||
expect(response.body).to.eql({
|
||||
error: 'Forbidden',
|
||||
message: 'Forbidden',
|
||||
statusCode: 403,
|
||||
});
|
||||
break;
|
||||
case 'global_read at space1':
|
||||
case 'superuser at space1':
|
||||
case 'space_1_all at space1':
|
||||
expect(response.body.length).to.eql(2);
|
||||
expect(response.statusCode).to.eql(200);
|
||||
expect(response.body[0].title).to.eql('test-maintenance-window');
|
||||
expect(response.body[0].duration).to.eql(3600000);
|
||||
expect(response.body[0].r_rule.dtstart).to.eql(createParams.r_rule.dtstart);
|
||||
expect(response.body[0].events.length).to.be.greaterThan(0);
|
||||
expect(response.body[0].status).to.eql('running');
|
||||
|
||||
const ids = response.body.map(
|
||||
(maintenanceWindow: { id: string }) => maintenanceWindow.id
|
||||
);
|
||||
expect(ids.sort()).to.eql(
|
||||
[createdMaintenanceWindow1.id, createdMaintenanceWindow2.id].sort()
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it('should return an empty array if there are no active maintenance windows', async () => {
|
||||
const { body: createdMaintenanceWindow } = await supertest
|
||||
.post(`${getUrlPrefix('space1')}/internal/alerting/rules/maintenance_window`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
...createParams,
|
||||
r_rule: {
|
||||
...createParams.r_rule,
|
||||
dtstart: moment.utc().add(1, 'day').toISOString(),
|
||||
},
|
||||
});
|
||||
|
||||
objectRemover.add(
|
||||
'space1',
|
||||
createdMaintenanceWindow.id,
|
||||
'rules/maintenance_window',
|
||||
'alerting',
|
||||
true
|
||||
);
|
||||
|
||||
const response = await supertest
|
||||
.get(`${getUrlPrefix('space1')}/internal/alerting/rules/maintenance_window/_active`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({})
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).to.eql([]);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -27,6 +27,7 @@ export default function maintenanceWindowTests({ loadTestFile, getService }: Ftr
|
|||
loadTestFile(require.resolve('./archive_maintenance_window'));
|
||||
loadTestFile(require.resolve('./finish_maintenance_window'));
|
||||
loadTestFile(require.resolve('./find_maintenance_windows'));
|
||||
loadTestFile(require.resolve('./active_maintenance_windows'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue