-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Code] add NodeRepositoriesService to watch new repositories on local…
… node (#44677) (#44966) * add NodeRepositoriesService to watch new repositories on local node. * catch exceptions from cluster state listener * add TODO for repo clean ups * do not start reclone scheduler in cluster mode
- Loading branch information
Yang Yang
authored
Sep 6, 2019
1 parent
cffc08a
commit 5d6ab10
Showing
7 changed files
with
233 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
122 changes: 122 additions & 0 deletions
122
x-pack/legacy/plugins/code/server/distributed/cluster/node_repositories_service.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import sinon from 'sinon'; | ||
import { Logger } from '../../log'; | ||
import { ConsoleLoggerFactory } from '../../utils/console_logger_factory'; | ||
import { NodeRepositoriesService } from './node_repositories_service'; | ||
import { ClusterService } from './cluster_service'; | ||
import { ClusterMembershipService } from './cluster_membership_service'; | ||
import { CodeNode, CodeNodes } from './code_nodes'; | ||
import { emptyAsyncFunc } from '../../test_utils'; | ||
import { CloneWorker } from '../../queue'; | ||
import { ClusterStateEvent } from './cluster_state_event'; | ||
import { ClusterState } from './cluster_state'; | ||
import { ClusterMetadata } from './cluster_meta'; | ||
import { Repository } from '../../../model'; | ||
import { ResourceAssignment, RoutingTable } from './routing_table'; | ||
|
||
const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); | ||
|
||
afterEach(() => { | ||
sinon.restore(); | ||
}); | ||
|
||
const cloneWorker = ({ | ||
enqueueJob: emptyAsyncFunc, | ||
} as any) as CloneWorker; | ||
|
||
const clusterService = {} as ClusterService; | ||
|
||
const testNodes = [ | ||
{ id: 'node1', address: 'http://node1' } as CodeNode, | ||
{ id: 'node2', address: 'http://node2' } as CodeNode, | ||
]; | ||
|
||
const testRepos = [ | ||
{ uri: 'test1', url: 'http://test1' } as Repository, | ||
{ uri: 'test2', url: 'http://test2' } as Repository, | ||
]; | ||
|
||
test('Enqueue clone job after new repository is added to the local node', async () => { | ||
const enqueueJobSpy = sinon.spy(cloneWorker, 'enqueueJob'); | ||
|
||
const clusterMembershipService = { | ||
localNode: testNodes[0], | ||
} as ClusterMembershipService; | ||
|
||
const nodeService = new NodeRepositoriesService( | ||
log, | ||
clusterService, | ||
clusterMembershipService, | ||
cloneWorker | ||
); | ||
|
||
// event with no new repositories | ||
let event = new ClusterStateEvent(ClusterState.empty(), ClusterState.empty()); | ||
await nodeService.onClusterStateChanged(event); | ||
expect(enqueueJobSpy.called).toBeFalsy(); | ||
expect(nodeService.localRepos.size).toBe(0); | ||
|
||
// event with a new repository | ||
event = new ClusterStateEvent( | ||
new ClusterState( | ||
new ClusterMetadata([testRepos[0]]), | ||
new RoutingTable([ | ||
{ nodeId: testNodes[0].id, resource: testRepos[0].uri } as ResourceAssignment, | ||
]), | ||
new CodeNodes([testNodes[0]]) | ||
), | ||
event.current | ||
); | ||
await nodeService.onClusterStateChanged(event); | ||
expect(enqueueJobSpy.calledOnce).toBeTruthy(); | ||
expect(nodeService.localRepos.size).toBe(1); | ||
|
||
// event with removed repository | ||
event = new ClusterStateEvent(ClusterState.empty(), event.current); | ||
await nodeService.onClusterStateChanged(event); | ||
expect(enqueueJobSpy.calledOnce).toBeTruthy(); | ||
expect(nodeService.localRepos.size).toBe(0); | ||
|
||
// event with two added repositories | ||
event = new ClusterStateEvent( | ||
new ClusterState( | ||
new ClusterMetadata([testRepos[0], testRepos[1]]), | ||
new RoutingTable([ | ||
{ nodeId: testNodes[0].id, resource: testRepos[0].uri } as ResourceAssignment, | ||
{ nodeId: testNodes[0].id, resource: testRepos[1].uri } as ResourceAssignment, | ||
]), | ||
new CodeNodes([testNodes[0]]) | ||
), | ||
event.current | ||
); | ||
await nodeService.onClusterStateChanged(event); | ||
expect(enqueueJobSpy.callCount).toBe(3); | ||
expect(nodeService.localRepos.size).toBe(2); | ||
|
||
// event with removed repository | ||
event = new ClusterStateEvent(ClusterState.empty(), event.current); | ||
await nodeService.onClusterStateChanged(event); | ||
expect(enqueueJobSpy.callCount).toBe(3); | ||
expect(nodeService.localRepos.size).toBe(0); | ||
|
||
// event with two added repositories, one for the other node | ||
event = new ClusterStateEvent( | ||
new ClusterState( | ||
new ClusterMetadata([testRepos[0], testRepos[1]]), | ||
new RoutingTable([ | ||
{ nodeId: testNodes[0].id, resource: testRepos[0].uri } as ResourceAssignment, | ||
{ nodeId: testNodes[1].id, resource: testRepos[1].uri } as ResourceAssignment, | ||
]), | ||
new CodeNodes([testNodes[0]]) | ||
), | ||
event.current | ||
); | ||
await nodeService.onClusterStateChanged(event); | ||
expect(enqueueJobSpy.callCount).toBe(4); | ||
expect(nodeService.localRepos.size).toBe(1); | ||
}); |
71 changes: 71 additions & 0 deletions
71
x-pack/legacy/plugins/code/server/distributed/cluster/node_repositories_service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import util from 'util'; | ||
import { ClusterService, ClusterStateListener } from './cluster_service'; | ||
import { ClusterStateEvent } from './cluster_state_event'; | ||
import { ClusterMembershipService } from './cluster_membership_service'; | ||
import { CloneWorker } from '../../queue'; | ||
import { Repository, RepositoryUri } from '../../../model'; | ||
import { Logger } from '../../log'; | ||
import { RepoState } from '../../../public/actions'; | ||
|
||
export class NodeRepositoriesService implements ClusterStateListener { | ||
// visible for test | ||
readonly localRepos = new Map<RepositoryUri, LocalRepository>(); | ||
private readonly localNodeId = this.clusterMembershipService.localNode.id; | ||
|
||
constructor( | ||
private readonly log: Logger, | ||
private readonly clusterService: ClusterService, | ||
private readonly clusterMembershipService: ClusterMembershipService, | ||
private readonly cloneWorker: CloneWorker | ||
) {} | ||
|
||
public async start() { | ||
/** | ||
* we can add locally exists repositories to localRepos when the service is started to avoid unnecessarily add clone | ||
* tasks for them, but for now it's OK because clone job is idempotent. | ||
*/ | ||
this.clusterService.addClusterStateListener(this); | ||
} | ||
|
||
public async stop() {} | ||
|
||
async onClusterStateChanged(event: ClusterStateEvent): Promise<void> { | ||
// compare repositories in the cluster state with repositories in the local node, and remove | ||
const repos = event.current.getNodeRepositories(this.clusterMembershipService.localNode.id); | ||
const localNewRepos = repos.filter(repo => !this.localRepos.has(repo.uri)); | ||
const localRemovedRepos = Array.from(this.localRepos.values()).filter( | ||
repo => | ||
event.current.routingTable.getNodeIdByRepositoryURI(repo.metadata.uri) !== this.localNodeId | ||
); | ||
for (const localNewRepo of localNewRepos) { | ||
this.log.info( | ||
`Repository added to node [${this.localNodeId}]: ${util.inspect(localNewRepo)}` | ||
); | ||
await this.cloneWorker.enqueueJob({ url: localNewRepo.url }, {}); | ||
this.localRepos.set(localNewRepo.uri, { | ||
metadata: localNewRepo, | ||
currentState: RepoState.CLONING, | ||
}); | ||
} | ||
// TODO remove the stale local repo after the Kibana HA is ready | ||
for (const localRemovedRepo of localRemovedRepos) { | ||
this.log.info( | ||
`Repository removed from node [${this.localNodeId}]: ${util.inspect( | ||
localRemovedRepo.metadata | ||
)}` | ||
); | ||
this.localRepos.delete(localRemovedRepo.metadata.uri); | ||
} | ||
} | ||
} | ||
|
||
interface LocalRepository { | ||
metadata: Repository; | ||
currentState: RepoState; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters