mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
[Serverless FTRs] Ensure security index exists before running the tests (#169926)
This commit is contained in:
parent
7573853b7e
commit
b3955ce0c1
4 changed files with 164 additions and 0 deletions
|
@ -40,6 +40,7 @@ import {
|
||||||
SERVERLESS_JWKS_PATH,
|
SERVERLESS_JWKS_PATH,
|
||||||
} from '../paths';
|
} from '../paths';
|
||||||
import * as waitClusterUtil from './wait_until_cluster_ready';
|
import * as waitClusterUtil from './wait_until_cluster_ready';
|
||||||
|
import * as waitForSecurityIndexUtil from './wait_for_security_index';
|
||||||
|
|
||||||
jest.mock('execa');
|
jest.mock('execa');
|
||||||
const execa = jest.requireMock('execa');
|
const execa = jest.requireMock('execa');
|
||||||
|
@ -53,6 +54,10 @@ jest.mock('./wait_until_cluster_ready', () => ({
|
||||||
waitUntilClusterReady: jest.fn(),
|
waitUntilClusterReady: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('./wait_for_security_index', () => ({
|
||||||
|
waitForSecurityIndex: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
const log = new ToolingLog();
|
const log = new ToolingLog();
|
||||||
const logWriter = new ToolingLogCollectingWriter();
|
const logWriter = new ToolingLogCollectingWriter();
|
||||||
log.setWriters([logWriter]);
|
log.setWriters([logWriter]);
|
||||||
|
@ -63,6 +68,7 @@ const serverlessDir = 'stateless';
|
||||||
const serverlessObjectStorePath = `${baseEsPath}/${serverlessDir}`;
|
const serverlessObjectStorePath = `${baseEsPath}/${serverlessDir}`;
|
||||||
|
|
||||||
const waitUntilClusterReadyMock = jest.spyOn(waitClusterUtil, 'waitUntilClusterReady');
|
const waitUntilClusterReadyMock = jest.spyOn(waitClusterUtil, 'waitUntilClusterReady');
|
||||||
|
const waitForSecurityIndexMock = jest.spyOn(waitForSecurityIndexUtil, 'waitForSecurityIndex');
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
|
@ -560,6 +566,32 @@ describe('runServerlessCluster()', () => {
|
||||||
expect(waitUntilClusterReadyMock.mock.calls[0][0].expectedStatus).toEqual('green');
|
expect(waitUntilClusterReadyMock.mock.calls[0][0].expectedStatus).toEqual('green');
|
||||||
expect(waitUntilClusterReadyMock.mock.calls[0][0].readyTimeout).toEqual(undefined);
|
expect(waitUntilClusterReadyMock.mock.calls[0][0].readyTimeout).toEqual(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test(`should wait for the security index`, async () => {
|
||||||
|
waitForSecurityIndexMock.mockResolvedValue();
|
||||||
|
mockFs({
|
||||||
|
[baseEsPath]: {},
|
||||||
|
});
|
||||||
|
execa.mockImplementation(() => Promise.resolve({ stdout: '' }));
|
||||||
|
|
||||||
|
await runServerlessCluster(log, { basePath: baseEsPath, waitForReady: true });
|
||||||
|
expect(waitForSecurityIndexMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(waitForSecurityIndexMock.mock.calls[0][0].readyTimeout).toEqual(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`should not wait for the security index when security is disabled`, async () => {
|
||||||
|
mockFs({
|
||||||
|
[baseEsPath]: {},
|
||||||
|
});
|
||||||
|
execa.mockImplementation(() => Promise.resolve({ stdout: '' }));
|
||||||
|
|
||||||
|
await runServerlessCluster(log, {
|
||||||
|
basePath: baseEsPath,
|
||||||
|
waitForReady: true,
|
||||||
|
esArgs: ['xpack.security.enabled=false'],
|
||||||
|
});
|
||||||
|
expect(waitForSecurityIndexMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('stopServerlessCluster()', () => {
|
describe('stopServerlessCluster()', () => {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {
|
||||||
kibanaDevServiceAccount,
|
kibanaDevServiceAccount,
|
||||||
} from '@kbn/dev-utils';
|
} from '@kbn/dev-utils';
|
||||||
|
|
||||||
|
import { waitForSecurityIndex } from './wait_for_security_index';
|
||||||
import { createCliError } from '../errors';
|
import { createCliError } from '../errors';
|
||||||
import { EsClusterExecOptions } from '../cluster_exec_options';
|
import { EsClusterExecOptions } from '../cluster_exec_options';
|
||||||
import {
|
import {
|
||||||
|
@ -687,6 +688,10 @@ export async function runServerlessCluster(log: ToolingLog, options: ServerlessO
|
||||||
: {}),
|
: {}),
|
||||||
});
|
});
|
||||||
await waitUntilClusterReady({ client, expectedStatus: 'green', log });
|
await waitUntilClusterReady({ client, expectedStatus: 'green', log });
|
||||||
|
if (!options.esArgs || !options.esArgs.includes('xpack.security.enabled=false')) {
|
||||||
|
// If security is not disabled, make sure the security index exists before running the test to avoid flakiness
|
||||||
|
await waitForSecurityIndex({ client, log });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.background) {
|
if (!options.background) {
|
||||||
|
|
66
packages/kbn-es/src/utils/wait_for_security_index.test.ts
Normal file
66
packages/kbn-es/src/utils/wait_for_security_index.test.ts
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* 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 { Client } from '@elastic/elasticsearch';
|
||||||
|
import { ToolingLog, ToolingLogCollectingWriter } from '@kbn/tooling-log';
|
||||||
|
import { waitForSecurityIndex } from './wait_for_security_index';
|
||||||
|
|
||||||
|
jest.mock('@elastic/elasticsearch', () => {
|
||||||
|
return {
|
||||||
|
Client: jest.fn(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const log = new ToolingLog();
|
||||||
|
const logWriter = new ToolingLogCollectingWriter();
|
||||||
|
log.setWriters([logWriter]);
|
||||||
|
|
||||||
|
const createApiKey = jest.fn();
|
||||||
|
const invalidateApiKey = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
jest
|
||||||
|
.requireMock('@elastic/elasticsearch')
|
||||||
|
.Client.mockImplementation(() => ({ security: { createApiKey, invalidateApiKey } }));
|
||||||
|
log.indent(-log.getIndent());
|
||||||
|
logWriter.messages.length = 0;
|
||||||
|
createApiKey.mockResolvedValue({ id: 'test-id' });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('waitForSecurityIndex', () => {
|
||||||
|
test(`waits for the security request to succeed`, async () => {
|
||||||
|
invalidateApiKey.mockImplementationOnce(() => Promise.reject(new Error('foo')));
|
||||||
|
invalidateApiKey.mockImplementationOnce(() => Promise.resolve({}));
|
||||||
|
|
||||||
|
const client = new Client({});
|
||||||
|
|
||||||
|
await waitForSecurityIndex({ client, log });
|
||||||
|
expect(invalidateApiKey).toHaveBeenCalledTimes(2);
|
||||||
|
expect(logWriter.messages).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
" [34minfo[39m waiting for ES cluster to bootstrap the security index",
|
||||||
|
" [33mwarn[39m waiting for ES cluster to bootstrap the security index, attempt 1 failed with: foo",
|
||||||
|
" [32msucc[39m ES security index is ready",
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
test(`rejects when 'readyTimeout' is exceeded`, async () => {
|
||||||
|
invalidateApiKey.mockImplementationOnce(() => Promise.reject(new Error('foo')));
|
||||||
|
invalidateApiKey.mockImplementationOnce(() => Promise.reject(new Error('foo')));
|
||||||
|
const client = new Client({});
|
||||||
|
await expect(waitForSecurityIndex({ client, log, readyTimeout: 1000 })).rejects.toThrow(
|
||||||
|
'ES cluster failed to bootstrap the security index with the 1 second timeout'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
61
packages/kbn-es/src/utils/wait_for_security_index.ts
Normal file
61
packages/kbn-es/src/utils/wait_for_security_index.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* 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 { Client } from '@elastic/elasticsearch';
|
||||||
|
import { ToolingLog } from '@kbn/tooling-log';
|
||||||
|
const DEFAULT_READY_TIMEOUT = 10 * 1000; // 10 seconds
|
||||||
|
|
||||||
|
export interface WaitOptions {
|
||||||
|
client: Client;
|
||||||
|
log: ToolingLog;
|
||||||
|
readyTimeout?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General method to wait for the ES cluster status to be yellow or green
|
||||||
|
*/
|
||||||
|
export async function waitForSecurityIndex({
|
||||||
|
client,
|
||||||
|
log,
|
||||||
|
readyTimeout = DEFAULT_READY_TIMEOUT,
|
||||||
|
}: WaitOptions) {
|
||||||
|
let attempt = 0;
|
||||||
|
const start = Date.now();
|
||||||
|
|
||||||
|
// The loop will continue until timeout even if SIGINT is signaled, so force exit
|
||||||
|
process.on('SIGINT', () => process.exit());
|
||||||
|
|
||||||
|
log.info(`waiting for ES cluster to bootstrap the security index`);
|
||||||
|
|
||||||
|
// Hack to force the creation of the index `.security-7` index
|
||||||
|
const response = await client.security.createApiKey({ name: 'test-api-key-to-delete' });
|
||||||
|
while (true) {
|
||||||
|
attempt += 1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await client.security.invalidateApiKey({ ids: [response.id] });
|
||||||
|
log.success('ES security index is ready');
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
const timeSinceStart = Date.now() - start;
|
||||||
|
if (timeSinceStart > readyTimeout) {
|
||||||
|
const sec = readyTimeout / 1000;
|
||||||
|
throw new Error(
|
||||||
|
`ES cluster failed to bootstrap the security index with the ${sec} second timeout`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.warning(
|
||||||
|
`waiting for ES cluster to bootstrap the security index, attempt ${attempt} failed with: ${error?.message}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const waitSec = attempt * 1.5;
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, waitSec * 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue