[Session Management] Fix session index creation and update (#208244)

Closes https://github.com/elastic/kibana/issues/208243

## Summary

[This PR](https://github.com/elastic/kibana/pull/204097) introduced an
unintended issue for users running 8.x with a 7.x archive. This has been
fixed by correctly searching for existing index by both name and alias.


### To test:
You can follow the same steps as the above PR:
https://github.com/elastic/kibana/pull/204097

### Checklist

Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.

- [ ] 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/src/platform/packages/shared/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
- [ ] 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 was checked for breaking HTTP API changes, and any breaking
changes have been approved by the breaking-change committee. The
`release_note:breaking` label should be applied in these situations.
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [ ] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

### Identify risks

Does this PR introduce any risks? For example, consider risks like hard
to test bugs, performance regression, potential of data loss.

Describe the risk, its severity, and mitigation for each identified
risk. Invite stakeholders and evaluate how to proceed before merging.

- [ ] [See some risk
examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)
- [ ] ...
This commit is contained in:
Sid 2025-01-29 16:28:30 +01:00 committed by GitHub
parent 38fc6344c6
commit 449ac98572
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 110 additions and 23 deletions

View file

@ -96,10 +96,76 @@ describe('Session index', () => {
expect(mockElasticsearchClient.indices.deleteTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putIndexTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledWith({
index: indexName,
name: aliasName,
});
expect(mockElasticsearchClient.indices.create).not.toHaveBeenCalled();
});
it('does not create index if alias exists', async () => {
mockElasticsearchClient.indices.existsTemplate.mockResponse(false);
mockElasticsearchClient.indices.existsIndexTemplate.mockResponse(true);
mockElasticsearchClient.indices.exists.mockImplementation(
async ({ index }) => index === aliasName
);
await sessionIndex.initialize();
expect(mockElasticsearchClient.indices.exists).toHaveBeenCalledWith({ index: aliasName });
expect(mockElasticsearchClient.indices.exists).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.deleteTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putIndexTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledWith({
index: indexName,
name: aliasName,
});
expect(mockElasticsearchClient.indices.create).not.toHaveBeenCalled();
});
it('does not create index if index exists', async () => {
mockElasticsearchClient.indices.existsTemplate.mockResponse(false);
mockElasticsearchClient.indices.existsIndexTemplate.mockResponse(true);
mockElasticsearchClient.indices.exists.mockImplementation(
async ({ index }) => index === indexName
);
await sessionIndex.initialize();
expect(mockElasticsearchClient.indices.exists).toHaveBeenCalledWith({ index: aliasName });
expect(mockElasticsearchClient.indices.exists).toHaveBeenCalledWith({ index: indexName });
expect(mockElasticsearchClient.indices.exists).toHaveBeenCalledTimes(2);
expect(mockElasticsearchClient.indices.deleteTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putIndexTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledWith({
index: indexName,
name: aliasName,
});
expect(mockElasticsearchClient.indices.create).not.toHaveBeenCalled();
});
it('creates index if neither index or alias exists', async () => {
mockElasticsearchClient.indices.existsTemplate.mockResponse(false);
mockElasticsearchClient.indices.existsIndexTemplate.mockResponse(true);
mockElasticsearchClient.indices.exists.mockResponse(false);
await sessionIndex.initialize();
expect(mockElasticsearchClient.indices.exists).toHaveBeenCalledWith({ index: aliasName });
expect(mockElasticsearchClient.indices.exists).toHaveBeenCalledWith({ index: indexName });
expect(mockElasticsearchClient.indices.exists).toHaveBeenCalledTimes(2);
expect(mockElasticsearchClient.indices.deleteTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putIndexTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledWith({
index: indexName,
name: aliasName,
});
expect(mockElasticsearchClient.indices.create).toHaveBeenCalled();
});
it('does not delete legacy index template if the legacy template API is not available (410)', async () => {
const goneError = new errors.ResponseError(
securityMock.createApiResponse(
@ -216,7 +282,10 @@ describe('Session index', () => {
expect(mockElasticsearchClient.indices.deleteIndexTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.create).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledWith({
index: indexName,
name: aliasName,
});
});
it('updates mappings for existing index without version in the meta', async () => {
@ -237,7 +306,10 @@ describe('Session index', () => {
expect(mockElasticsearchClient.indices.deleteIndexTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.create).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledWith({
index: indexName,
name: aliasName,
});
expect(mockElasticsearchClient.indices.getMapping).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.getMapping).toHaveBeenCalledWith({ index: aliasName });
@ -266,7 +338,10 @@ describe('Session index', () => {
expect(mockElasticsearchClient.indices.deleteIndexTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.create).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledWith({
index: indexName,
name: aliasName,
});
expect(mockElasticsearchClient.indices.getMapping).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.getMapping).toHaveBeenCalledWith({ index: aliasName });
@ -295,7 +370,10 @@ describe('Session index', () => {
expect(mockElasticsearchClient.indices.deleteIndexTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.create).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledWith({
index: indexName,
name: aliasName,
});
expect(mockElasticsearchClient.indices.getMapping).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.getMapping).toHaveBeenCalledWith({ index: aliasName });
@ -447,7 +525,7 @@ describe('Session index', () => {
{ meta: true }
);
expect(mockElasticsearchClient.indices.exists).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.exists).toHaveBeenCalledTimes(2);
expect(mockElasticsearchClient.indices.create).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.search).toHaveBeenCalledTimes(1);
@ -1430,7 +1508,7 @@ describe('Session index', () => {
metadata: { primaryTerm: 321, sequenceNumber: 654 },
});
expect(mockElasticsearchClient.indices.exists).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.exists).toHaveBeenCalledTimes(2);
expect(mockElasticsearchClient.indices.create).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledTimes(1);

View file

@ -625,16 +625,35 @@ export class SessionIndex {
);
}
private async attachAliasToIndex() {
// Prior to https://github.com/elastic/kibana/pull/134900, sessions would be written directly against the session index.
// Now, we write sessions against a new session index alias. This call ensures that the alias exists, and is attached to the index.
// This operation is safe to repeat, even if the alias already exists. This seems safer than retrieving the index details, and inspecting
// it to see if the alias already exists.
try {
await this.options.elasticsearchClient.indices.putAlias({
index: this.indexName,
name: this.aliasName,
});
} catch (err) {
this.options.logger.error(`Failed to attach alias to session index: ${err.message}`);
throw err;
}
}
/**
* Creates the session index if it doesn't already exist.
*/
private async ensureSessionIndexExists() {
// Check if required index exists.
// It is possible for users to migrate from older versions of Kibana where the session index was created without
// an alias (pre-8.4). In this case, we need to check if the index exists under the alias name, or the index name.
// If the index exists under the alias name, we can assume that the alias is already attached.
let indexExists = false;
try {
indexExists = await this.options.elasticsearchClient.indices.exists({
index: this.aliasName,
});
indexExists =
(await this.options.elasticsearchClient.indices.exists({ index: this.aliasName })) ||
(await this.options.elasticsearchClient.indices.exists({ index: this.indexName }));
} catch (err) {
this.options.logger.error(`Failed to check if session index exists: ${err.message}`);
throw err;
@ -662,19 +681,7 @@ export class SessionIndex {
}
}
// Prior to https://github.com/elastic/kibana/pull/134900, sessions would be written directly against the session index.
// Now, we write sessions against a new session index alias. This call ensures that the alias exists, and is attached to the index.
// This operation is safe to repeat, even if the alias already exists. This seems safer than retrieving the index details, and inspecting
// it to see if the alias already exists.
try {
await this.options.elasticsearchClient.indices.putAlias({
index: this.indexName,
name: this.aliasName,
});
} catch (err) {
this.options.logger.error(`Failed to attach alias to session index: ${err.message}`);
throw err;
}
await this.attachAliasToIndex();
return;
}
@ -683,6 +690,8 @@ export class SessionIndex {
'Session index already exists. Attaching alias to the index and ensuring up-to-date mappings...'
);
await this.attachAliasToIndex();
let indexMappingsVersion: string | undefined;
try {
const indexMappings = await this.options.elasticsearchClient.indices.getMapping({