[8.x] [ResponseOps][TaskManager] Discovery service running after shutdown (next one!) (#193478) (#194225)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[ResponseOps][TaskManager] Discovery service running after shutdown
(next one!) (#193478)](https://github.com/elastic/kibana/pull/193478)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Alexi
Doak","email":"109488926+doakalexi@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-09-26T20:13:32Z","message":"[ResponseOps][TaskManager]
Discovery service running after shutdown (next one!)
(#193478)\n\nResolves
https://github.com/elastic/kibana/issues/192505\r\n\r\n##
Summary\r\n\r\nThis PR updates the discovery service to not schedule the
current node\r\nupsert if Kibana is shutting down and also clear the
timer. I removed\r\nthe verification steps bc I am not sure of the best
way to verify this\r\nother than running locally and verifying
that\r\n`scheduleUpsertCurrentNode()` does schedule
anything.\r\n\r\n\r\n### Checklist\r\n\r\n- [ ] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"5cce92b005fdbe9eff5e4057656ce8c8b2c740b2","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:ResponseOps","v9.0.0","backport:prev-minor","v8.16.0"],"title":"[ResponseOps][TaskManager]
Discovery service running after shutdown (next
one!)","number":193478,"url":"https://github.com/elastic/kibana/pull/193478","mergeCommit":{"message":"[ResponseOps][TaskManager]
Discovery service running after shutdown (next one!)
(#193478)\n\nResolves
https://github.com/elastic/kibana/issues/192505\r\n\r\n##
Summary\r\n\r\nThis PR updates the discovery service to not schedule the
current node\r\nupsert if Kibana is shutting down and also clear the
timer. I removed\r\nthe verification steps bc I am not sure of the best
way to verify this\r\nother than running locally and verifying
that\r\n`scheduleUpsertCurrentNode()` does schedule
anything.\r\n\r\n\r\n### Checklist\r\n\r\n- [ ] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"5cce92b005fdbe9eff5e4057656ce8c8b2c740b2"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/193478","number":193478,"mergeCommit":{"message":"[ResponseOps][TaskManager]
Discovery service running after shutdown (next one!)
(#193478)\n\nResolves
https://github.com/elastic/kibana/issues/192505\r\n\r\n##
Summary\r\n\r\nThis PR updates the discovery service to not schedule the
current node\r\nupsert if Kibana is shutting down and also clear the
timer. I removed\r\nthe verification steps bc I am not sure of the best
way to verify this\r\nother than running locally and verifying
that\r\n`scheduleUpsertCurrentNode()` does schedule
anything.\r\n\r\n\r\n### Checklist\r\n\r\n- [ ] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"5cce92b005fdbe9eff5e4057656ce8c8b2c740b2"}},{"branch":"8.x","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Alexi Doak <109488926+doakalexi@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2024-09-27 07:39:11 +10:00 committed by GitHub
parent 81664a0bde
commit e1d68c81b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 96 additions and 22 deletions

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { savedObjectsRepositoryMock, loggingSystemMock } from '@kbn/core/server/mocks';
import { KibanaDiscoveryService } from './kibana_discovery_service';
import { DEFAULT_TIMEOUT, KibanaDiscoveryService } from './kibana_discovery_service';
import { BACKGROUND_TASK_NODE_SO_NAME } from '../saved_objects';
import { SavedObjectsBulkDeleteResponse, SavedObjectsUpdateResponse } from '@kbn/core/server';
@ -26,6 +26,7 @@ describe('KibanaDiscoveryService', () => {
beforeEach(() => {
jest.useFakeTimers();
jest.spyOn(global, 'setTimeout');
jest.spyOn(global, 'clearTimeout');
jest.setSystemTime(new Date(now));
});
@ -110,6 +111,23 @@ describe('KibanaDiscoveryService', () => {
expect(savedObjectsRepository.update).toHaveBeenCalledTimes(2);
});
it('timeout should not be less than two seconds', async () => {
savedObjectsRepository.find.mockResolvedValueOnce(createFindResponse([]));
const kibanaDiscoveryService = new KibanaDiscoveryService({
savedObjectsRepository,
logger,
currentNode,
config: {
active_nodes_lookback: DEFAULT_ACTIVE_NODES_LOOK_BACK_DURATION,
interval: 500,
},
});
await kibanaDiscoveryService.start();
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenNthCalledWith(1, expect.any(Function), DEFAULT_TIMEOUT);
});
it('reschedules when upsert fails on start', async () => {
savedObjectsRepository.update.mockRejectedValueOnce(new Error('foo'));
@ -180,6 +198,47 @@ describe('KibanaDiscoveryService', () => {
"Kibana Discovery Service couldn't update this node's last_seen timestamp. id: current-node-id, last_seen: 2024-08-10T10:00:10.000Z, error:foo"
);
});
it('does not schedule when Kibana is shutting down', async () => {
savedObjectsRepository.update.mockResolvedValueOnce(
{} as SavedObjectsUpdateResponse<unknown>
);
const kibanaDiscoveryService = new KibanaDiscoveryService({
savedObjectsRepository,
logger,
currentNode,
config: {
active_nodes_lookback: DEFAULT_ACTIVE_NODES_LOOK_BACK_DURATION,
interval: DEFAULT_DISCOVERY_INTERVAL_MS,
},
});
await kibanaDiscoveryService.start();
expect(savedObjectsRepository.update).toHaveBeenCalledTimes(1);
expect(logger.error).not.toHaveBeenCalled();
expect(logger.info).toHaveBeenCalledWith('Kibana Discovery Service has been started');
expect(kibanaDiscoveryService.isStarted()).toBe(true);
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenNthCalledWith(
1,
expect.any(Function),
DEFAULT_DISCOVERY_INTERVAL_MS
);
kibanaDiscoveryService.stop();
await jest.advanceTimersByTimeAsync(15000);
expect(savedObjectsRepository.update).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenNthCalledWith(
1,
expect.any(Function),
DEFAULT_DISCOVERY_INTERVAL_MS
);
expect(clearTimeout).toHaveBeenCalledTimes(1);
});
});
describe('getActiveKibanaNodes', () => {

View file

@ -23,6 +23,8 @@ interface DiscoveryServiceUpsertParams {
lastSeen: string;
}
export const DEFAULT_TIMEOUT = 2000;
export class KibanaDiscoveryService {
private readonly activeNodesLookBack: string;
private readonly discoveryInterval: number;
@ -30,6 +32,8 @@ export class KibanaDiscoveryService {
private started = false;
private savedObjectsRepository: ISavedObjectsRepository;
private logger: Logger;
private stopped = false;
private timer: NodeJS.Timeout | undefined;
constructor({ config, currentNode, savedObjectsRepository, logger }: DiscoveryServiceParams) {
this.activeNodesLookBack = config.active_nodes_lookback;
@ -52,29 +56,32 @@ export class KibanaDiscoveryService {
}
private async scheduleUpsertCurrentNode() {
const lastSeenDate = new Date();
const lastSeen = lastSeenDate.toISOString();
try {
await this.upsertCurrentNode({ id: this.currentNode, lastSeen });
if (!this.started) {
this.logger.info('Kibana Discovery Service has been started');
this.started = true;
}
} catch (e) {
if (!this.started) {
this.logger.error(
`Kibana Discovery Service couldn't be started and will be retried in ${this.discoveryInterval}ms, error:${e.message}`
);
} else {
this.logger.error(
`Kibana Discovery Service couldn't update this node's last_seen timestamp. id: ${this.currentNode}, last_seen: ${lastSeen}, error:${e.message}`
if (!this.stopped) {
const lastSeenDate = new Date();
const lastSeen = lastSeenDate.toISOString();
try {
await this.upsertCurrentNode({ id: this.currentNode, lastSeen });
if (!this.started) {
this.logger.info('Kibana Discovery Service has been started');
this.started = true;
}
} catch (e) {
if (!this.started) {
this.logger.error(
`Kibana Discovery Service couldn't be started and will be retried in ${this.discoveryInterval}ms, error:${e.message}`
);
} else {
this.logger.error(
`Kibana Discovery Service couldn't update this node's last_seen timestamp. id: ${this.currentNode}, last_seen: ${lastSeen}, error:${e.message}`
);
}
} finally {
this.timer = setTimeout(
async () => await this.scheduleUpsertCurrentNode(),
// The timeout should not be less than the default timeout of two seconds
Math.max(this.discoveryInterval - (Date.now() - lastSeenDate.getTime()), DEFAULT_TIMEOUT)
);
}
} finally {
setTimeout(
async () => await this.scheduleUpsertCurrentNode(),
this.discoveryInterval - (Date.now() - lastSeenDate.getTime())
);
}
}
@ -106,4 +113,11 @@ export class KibanaDiscoveryService {
await this.savedObjectsRepository.delete(BACKGROUND_TASK_NODE_SO_NAME, this.currentNode);
this.logger.info('Removed this node from the Kibana Discovery Service');
}
public stop() {
this.stopped = true;
if (this.timer) {
clearTimeout(this.timer);
}
}
}

View file

@ -404,6 +404,7 @@ export class TaskManagerPlugin
public async stop() {
if (this.kibanaDiscoveryService?.isStarted()) {
this.kibanaDiscoveryService.stop();
try {
await this.kibanaDiscoveryService.deleteCurrentNode();
} catch (e) {