[Jest tests] Update create serverless root helpers to use kill (#165467)

## Summary

Follow up on https://github.com/elastic/kibana/pull/165316, address
https://github.com/elastic/kibana/pull/165316#discussion_r1312403704

* Use the `kill` option in Jest integration test utilities for
serverless roots
* Fix a typo in a type import
* Add the `waitForReady` flag to optionally wait until the cluster is
ready to server requests (default: `false`)

## TODO

- [x] Add a test for `waitForReady`
This commit is contained in:
Jean-Louis Leysens 2023-09-01 18:54:53 +02:00 committed by GitHub
parent d2381149fd
commit cf464a91b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 65 additions and 24 deletions

View file

@ -78,9 +78,9 @@ function createServerlessES() {
teardown: true,
background: true,
clean: true,
kill: true,
waitForReady: true,
});
// runServerless doesn't wait until the nodes are up
await waitUntilClusterReady(getServerlessESClient());
return {
getClient: getServerlessESClient,
stop: async () => {
@ -91,22 +91,6 @@ function createServerlessES() {
};
}
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const waitUntilClusterReady = async (client: Client, timeoutMs = 60 * 1000) => {
const started = Date.now();
while (started + timeoutMs > Date.now()) {
try {
await client.info();
break;
} catch (e) {
await delay(1000);
/* trap to continue */
}
}
};
const getServerlessESClient = () => {
return new Client({
// node ports not configurable from

View file

@ -11,7 +11,7 @@ import getopts from 'getopts';
import { ToolingLog } from '@kbn/tooling-log';
import { getTimeReporter } from '@kbn/ci-stats-reporter';
import { Cluster } from '../cluster';
import { Cluster, type ServerlessOptions } from '../cluster';
import { SERVERLESS_REPO, SERVERLESS_TAG, SERVERLESS_IMG, DEFAULT_PORT } from '../utils';
import { Command } from './types';
@ -58,7 +58,7 @@ export const serverless: Command = {
boolean: ['clean', 'ssl', 'kill', 'background'],
default: defaults,
});
}) as unknown as ServerlessOptions;
const cluster = new Cluster();
await cluster.runServerless({

View file

@ -36,7 +36,7 @@ const DEFAULT_READY_TIMEOUT = parseTimeoutToMs('1m');
/** @typedef {import('./cluster_exec_options').EsClusterExecOptions} ExecOptions */
/** @typedef {import('./utils').DockerOptions} DockerOptions */
/** @typedef {import('./utils').ServerlessOptions}ServerlessrOptions */
/** @typedef {import('./utils').ServerlessOptions}ServerlessOptions */
// listen to data on stream until map returns anything but undefined
const first = (stream, map) =>
@ -579,8 +579,6 @@ exports.Cluster = class Cluster {
* @param {ServerlessOptions} options
*/
async runServerless(options = {}) {
// Ensure serverless ES nodes are not running
teardownServerlessClusterSync(this._log, options);
if (this._process || this._outcome) {
throw new Error('ES has already been started');
}

View file

@ -34,6 +34,11 @@ import { ESS_RESOURCES_PATHS } from '../paths';
jest.mock('execa');
const execa = jest.requireMock('execa');
jest.mock('@elastic/elasticsearch', () => {
return {
Client: jest.fn(),
};
});
const log = new ToolingLog();
const logWriter = new ToolingLogCollectingWriter();
@ -465,6 +470,22 @@ describe('runServerlessCluster()', () => {
// setupDocker execa calls then run three nodes and attach logger
expect(execa.mock.calls).toHaveLength(8);
});
describe('waitForReady', () => {
test('should wait for serverless nodes to be ready to serve requests', async () => {
mockFs({
[baseEsPath]: {},
});
execa.mockImplementation(() => Promise.resolve({ stdout: '' }));
const info = jest.fn();
jest.requireMock('@elastic/elasticsearch').Client.mockImplementation(() => ({ info }));
info.mockImplementationOnce(() => Promise.reject()); // first call fails
info.mockImplementationOnce(() => Promise.resolve()); // then succeeds
await runServerlessCluster(log, { basePath: baseEsPath, waitForReady: true });
expect(info).toHaveBeenCalledTimes(2);
});
});
});
describe('stopServerlessCluster()', () => {

View file

@ -10,6 +10,7 @@ import execa from 'execa';
import fs from 'fs';
import Fsp from 'fs/promises';
import { resolve, basename, join } from 'path';
import { Client, HttpConnection } from '@elastic/elasticsearch';
import { ToolingLog } from '@kbn/tooling-log';
import { kibanaPackageJson as pkg, REPO_ROOT } from '@kbn/repo-info';
@ -35,6 +36,7 @@ interface BaseOptions {
image?: string;
port?: number;
ssl?: boolean;
/** Kill running cluster before starting a new cluster */
kill?: boolean;
files?: string | string[];
}
@ -44,10 +46,16 @@ export interface DockerOptions extends EsClusterExecOptions, BaseOptions {
}
export interface ServerlessOptions extends EsClusterExecOptions, BaseOptions {
/** Clean (or delete) all data created by the ES cluster after it is stopped */
clean?: boolean;
/** Path to the directory where the ES cluster will store data */
basePath: string;
/** If this process exits, teardown the ES cluster as well */
teardown?: boolean;
/** Start the ES cluster in the background instead of remaining attached: useful for running tests */
background?: boolean;
/** Wait for the ES cluster to be ready to serve requests */
waitForReady?: boolean;
}
interface ServerlessEsNodeArgs {
@ -539,6 +547,30 @@ export async function runServerlessEsNode(
);
}
function getESClient(
{ node }: { node: string } = { node: `http://localhost:${DEFAULT_PORT}` }
): Client {
return new Client({
node,
Connection: HttpConnection,
});
}
const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
async function waitUntilClusterReady(timeoutMs = 60 * 1000): Promise<void> {
const started = Date.now();
const client = getESClient();
while (started + timeoutMs > Date.now()) {
try {
await client.info();
break;
} catch (e) {
await delay(1000);
/* trap to continue */
}
}
}
/**
* Runs an ES Serverless Cluster through Docker
*/
@ -583,10 +615,16 @@ export async function runServerlessCluster(log: ToolingLog, options: ServerlessO
`);
log.warning(`Kibana should be started with the SSL flag so that it can authenticate with ES.
See packages/kbn-es/src/ess_resources/README.md for additional information on authentication.
See packages/kbn-es/src/ess_resources/README.md for additional information on authentication.
`);
}
if (options.waitForReady) {
log.info('Waiting until ES is ready to serve requests...');
await waitUntilClusterReady();
log.success('ES is ready');
}
if (!options.background) {
// The ESS cluster has to be started detached, so we attach a logger afterwards for output
await execa('docker', ['logs', '-f', SERVERLESS_NODES[0].name], {