Skip to content

Commit

Permalink
[Index Management] Add serverless tests for indices routes (elastic#1…
Browse files Browse the repository at this point in the history
…71773)

## Summary

This PR adds api integration tests for indices routes that were missing
for serverless ("reload" and "delete index"). To avoid copy-pasting the
code, this PR also adds an index management service to re-use in
serverless tests. Also some refactoring of js code into ts.


### Checklist

Delete any items that are not applicable to this PR.

- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [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
- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)


### Risk Matrix

Delete this section if it is not applicable to this PR.

Before closing this PR, invite QA, stakeholders, and other developers to
identify risks that should be tested prior to the change/feature
release.

When forming the risk matrix, consider some of the following examples
and how they may potentially impact the change:

| Risk | Probability | Severity | Mitigation/Notes |

|---------------------------|-------------|----------|-------------------------|
| Multiple Spaces—unexpected behavior in non-default Kibana Space.
| Low | High | Integration tests will verify that all features are still
supported in non-default Kibana Space and when user switches between
spaces. |
| Multiple nodes—Elasticsearch polling might have race conditions
when multiple Kibana nodes are polling for the same tasks. | High | Low
| Tasks are idempotent, so executing them multiple times will not result
in logical error, but will degrade performance. To test for this case we
add plenty of unit tests around this logic and document manual testing
procedure. |
| Code should gracefully handle cases when feature X or plugin Y are
disabled. | Medium | High | Unit tests will verify that any feature flag
or plugin combination still results in our service operational. |
| [See more potential risk
examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) |


### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
  • Loading branch information
yuliacech authored Nov 24, 2023
1 parent 1a4ac01 commit 52d0e38
Show file tree
Hide file tree
Showing 14 changed files with 233 additions and 158 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,13 @@

import expect from '@kbn/expect';

import { initElasticsearchHelpers } from './lib';
import { registerHelpers } from './indices.helpers';
import { sortedExpectedIndexKeys } from './constants';
import { indicesApi } from './lib/indices.api';
import { indicesHelpers } from './lib/indices.helpers';
import { FtrProviderContext } from '../../../ftr_provider_context';

export default function ({ getService }) {
const supertest = getService('supertest');

const {
createIndex,
catIndex,
indexStats,
cleanUp: cleanUpEsResources,
} = initElasticsearchHelpers(getService);
export default function ({ getService }: FtrProviderContext) {
const { createIndex, deleteAllIndices, catIndex, indexStats } = indicesHelpers(getService);

const {
closeIndex,
Expand All @@ -32,10 +26,10 @@ export default function ({ getService }) {
list,
reload,
clearCache,
} = registerHelpers({ supertest });
} = indicesApi(getService);

describe('indices', () => {
after(() => Promise.all([cleanUpEsResources()]));
after(async () => await deleteAllIndices());

describe('clear cache', () => {
it('should clear the cache on a single index', async () => {
Expand All @@ -45,9 +39,6 @@ export default function ({ getService }) {
});

describe('close', function () {
// The Cloud backend disallows users from closing indices.
this.tags(['skipCloud']);

it('should close an index', async () => {
const index = await createIndex();

Expand All @@ -68,10 +59,6 @@ export default function ({ getService }) {
});

describe('open', function () {
// The Cloud backend disallows users from closing indices, so there's no point testing
// the open behavior.
this.tags(['skipCloud']);

it('should open an index', async () => {
const index = await createIndex();

Expand All @@ -98,12 +85,12 @@ export default function ({ getService }) {
const index = await createIndex();

const { body: indices1 } = await catIndex(undefined, 'i');
expect(indices1.map((index) => index.i)).to.contain(index);
expect(indices1.map((indexItem) => indexItem.i)).to.contain(index);

await deleteIndex([index]).expect(200);
await deleteIndex(index).expect(200);

const { body: indices2 } = await catIndex(undefined, 'i');
expect(indices2.map((index) => index.i)).not.to.contain(index);
expect(indices2.map((indexItem) => indexItem.i)).not.to.contain(index);
});

it('should require index or indices to be provided', async () => {
Expand All @@ -119,13 +106,15 @@ export default function ({ getService }) {
const {
body: { indices: indices1 },
} = await indexStats(index, 'flush');
// @ts-ignore
expect(indices1[index].total.flush.total).to.be(0);

await flushIndex(index).expect(200);

const {
body: { indices: indices2 },
} = await indexStats(index, 'flush');
// @ts-ignore
expect(indices2[index].total.flush.total).to.be(1);
});
});
Expand All @@ -137,13 +126,15 @@ export default function ({ getService }) {
const {
body: { indices: indices1 },
} = await indexStats(index, 'refresh');
// @ts-ignore
const previousRefreshes = indices1[index].total.refresh.total;

await refreshIndex(index).expect(200);

const {
body: { indices: indices2 },
} = await indexStats(index, 'refresh');
// @ts-ignore
expect(indices2[index].total.refresh.total).to.be(previousRefreshes + 1);
});
});
Expand Down Expand Up @@ -175,8 +166,6 @@ export default function ({ getService }) {
});

describe('list', function () {
this.tags(['skipCloud']);

it('should list all the indices with the expected properties and data enrichers', async function () {
// Create an index that we can assert against
await createIndex('test_index');
Expand All @@ -185,7 +174,7 @@ export default function ({ getService }) {
const { body: indices } = await list().expect(200);

// Find the "test_index" created to verify expected keys
const indexCreated = indices.find((index) => index.name === 'test_index');
const indexCreated = indices.find((index: { name: string }) => index.name === 'test_index');

const sortedReceivedKeys = Object.keys(indexCreated).sort();

Expand All @@ -194,19 +183,17 @@ export default function ({ getService }) {
});

describe('reload', function () {
describe('(not on Cloud)', function () {
this.tags(['skipCloud']);

it('should list all the indices with the expected properties and data enrichers', async function () {
// create an index to assert against, otherwise the test is flaky
await createIndex('reload-test-index');
const { body } = await reload().expect(200);

const indexCreated = body.find((index) => index.name === 'reload-test-index');
const sortedReceivedKeys = Object.keys(indexCreated).sort();
expect(sortedReceivedKeys).to.eql(sortedExpectedIndexKeys);
expect(body.length > 1).to.be(true); // to contrast it with the next test
});
it('should list all the indices with the expected properties and data enrichers', async function () {
// create an index to assert against, otherwise the test is flaky
await createIndex('reload-test-index');
const { body } = await reload().expect(200);

const indexCreated = body.find(
(index: { name: string }) => index.name === 'reload-test-index'
);
const sortedReceivedKeys = Object.keys(indexCreated).sort();
expect(sortedReceivedKeys).to.eql(sortedExpectedIndexKeys);
expect(body.length > 1).to.be(true); // to contrast it with the next test
});

it('should allow reloading only certain indices', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,14 @@
* 2.0.
*/

import { getRandomString } from './random';

/**
* Helpers to create and delete indices on the Elasticsearch instance
* during our tests.
* @param {ElasticsearchClient} es The Elasticsearch client instance
*/
export const initElasticsearchHelpers = (getService) => {
const es = getService('es');
const esDeleteAllIndices = getService('esDeleteAllIndices');

let indicesCreated = [];
let datastreamCreated = [];
let indexTemplatesCreated = [];
let componentTemplatesCreated = [];
Expand All @@ -40,22 +36,6 @@ export const initElasticsearchHelpers = (getService) => {
console.log(`[Cleanup error] Error deleting ES resources: ${err.message}`);
});

const createIndex = (index = getRandomString(), body) => {
indicesCreated.push(index);
return es.indices.create({ index, body }).then(() => index);
};

const deleteAllIndices = async () => {
await esDeleteAllIndices(indicesCreated);
indicesCreated = [];
};

const catIndex = (index, h) => es.cat.indices({ index, format: 'json', h }, { meta: true });

const indexStats = (index, metric) => es.indices.stats({ index, metric }, { meta: true });

const cleanUp = () => deleteAllIndices();

const catTemplate = (name) => es.cat.templates({ name, format: 'json' }, { meta: true });

const createIndexTemplate = (indexTemplate, shouldCacheTemplate) => {
Expand Down Expand Up @@ -103,14 +83,9 @@ export const initElasticsearchHelpers = (getService) => {
});

return {
createIndex,
deleteAllIndices,
catIndex,
indexStats,
createDatastream,
deleteDatastream,
cleanupDatastreams,
cleanUp,
catTemplate,
createIndexTemplate,
deleteIndexTemplate,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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 { API_BASE_PATH } from '../constants';
import { FtrProviderContext } from '../../../../ftr_provider_context';

export function indicesApi(getService: FtrProviderContext['getService']) {
const supertest = getService('supertest');
const executeActionOnIndices = ({
index,
urlParam,
args,
}: {
index?: string | string[];
urlParam: string;
args?: any;
}) => {
const indices = Array.isArray(index) ? index : [index];

return supertest
.post(`${API_BASE_PATH}/indices/${urlParam}`)
.set('kbn-xsrf', 'xxx')
.send({ indices, ...args });
};

const closeIndex = (index: string) => executeActionOnIndices({ index, urlParam: 'close' });

const openIndex = (index: string) => executeActionOnIndices({ index, urlParam: 'open' });

const deleteIndex = (index?: string) => executeActionOnIndices({ index, urlParam: 'delete' });

const flushIndex = (index: string) => executeActionOnIndices({ index, urlParam: 'flush' });

const refreshIndex = (index: string) => executeActionOnIndices({ index, urlParam: 'refresh' });

const forceMerge = (index: string, args?: { maxNumSegments: number }) =>
executeActionOnIndices({ index, urlParam: 'forcemerge', args });

const unfreeze = (index: string) => executeActionOnIndices({ index, urlParam: 'unfreeze' });

const clearCache = (index: string) => executeActionOnIndices({ index, urlParam: 'clear_cache' });

const list = () => supertest.get(`${API_BASE_PATH}/indices`);

const reload = (indexNames?: string[]) =>
supertest.post(`${API_BASE_PATH}/indices/reload`).set('kbn-xsrf', 'xxx').send({ indexNames });

return {
closeIndex,
openIndex,
deleteIndex,
flushIndex,
refreshIndex,
forceMerge,
unfreeze,
list,
reload,
clearCache,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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 { getRandomString } from './random';
import { FtrProviderContext } from '../../../../ftr_provider_context';

export function indicesHelpers(getService: FtrProviderContext['getService']) {
const es = getService('es');
const esDeleteAllIndices = getService('esDeleteAllIndices');

let indicesCreated: string[] = [];

const createIndex = async (index: string = getRandomString(), mappings?: any) => {
indicesCreated.push(index);
await es.indices.create({ index, mappings });
return index;
};

const deleteAllIndices = async () => {
await esDeleteAllIndices(indicesCreated);
indicesCreated = [];
};

const catIndex = (index?: string, h?: any) =>
es.cat.indices({ index, format: 'json', h }, { meta: true });

const indexStats = (index: string, metric: string) =>
es.indices.stats({ index, metric }, { meta: true });

return { createIndex, deleteAllIndices, catIndex, indexStats };
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@

import expect from '@kbn/expect';

import { initElasticsearchHelpers } from './lib';
import { indicesHelpers } from './lib/indices.helpers';
import { registerHelpers } from './mapping.helpers';

export default function ({ getService }) {
const supertest = getService('supertest');

const { createIndex, cleanUp: cleanUpEsResources } = initElasticsearchHelpers(getService);
const { createIndex, deleteAllIndices } = indicesHelpers(getService);

const { getIndexMapping } = registerHelpers({ supertest });

describe('mapping', () => {
after(() => Promise.all([cleanUpEsResources()]));
after(async () => await deleteAllIndices());

it('should fetch the index mapping', async () => {
const mappings = {
Expand All @@ -28,7 +28,7 @@ export default function ({ getService }) {
createdAt: { type: 'date' },
},
};
const index = await createIndex(undefined, { mappings });
const index = await createIndex(undefined, mappings);

const { body } = await getIndexMapping(index).expect(200);

Expand Down
Loading

0 comments on commit 52d0e38

Please sign in to comment.