Skip to content

Commit bbe560a

Browse files
Muzaffer AydinMuzaffer Aydin
authored andcommitted
Fix polling mechanism for DocDB explorer
1 parent 5b325d4 commit bbe560a

File tree

7 files changed

+181
-61
lines changed

7 files changed

+181
-61
lines changed

packages/core/src/docdb/activation.ts

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -23,56 +23,78 @@ import { renameInstance } from './commands/renameInstance'
2323
import { addTag, listTags, removeTag } from './commands/tagCommands'
2424
import { Uri } from 'vscode'
2525
import { openUrl } from '../shared/utilities/vsCodeUtils'
26+
import { getLogger } from '../shared/logger'
2627

2728
/**
28-
* Activates DocumentDB components.
29+
* A utility function to automatically invoke trackChanges after a command.
2930
*/
3031

32+
function withTrackChanges<T extends DBResourceNode>(
33+
command: (node: T) => Promise<void>,
34+
commandName: string = 'UnnamedCommand'
35+
): (node: T) => Promise<void> {
36+
return async (node: T) => {
37+
const arn = node.arn || 'UnknownARN'
38+
const startTime = new Date().toISOString()
39+
40+
getLogger().info(
41+
`[${startTime}] Executing command "${commandName}" for resource with ARN: ${arn}. Tracking changes will be invoked post-execution.`
42+
)
43+
44+
await command(node)
45+
46+
const endTime = new Date().toISOString()
47+
getLogger().info(
48+
`[${endTime}] Successfully executed command "${commandName}" for resource with ARN: ${arn}. Invoking trackChanges now.`
49+
)
50+
51+
await node.trackChangesWithWaitProcessingStatus()
52+
}
53+
}
54+
55+
/**
56+
* Activates DocumentDB components.
57+
*/
3158
export async function activate(ctx: ExtContext): Promise<void> {
3259
ctx.extensionContext.subscriptions.push(
3360
Commands.register('aws.docdb.createCluster', async (node?: DocumentDBNode) => {
3461
await createCluster(node)
3562
}),
3663

37-
Commands.register('aws.docdb.deleteCluster', async (node: DBClusterNode) => {
38-
await deleteCluster(node)
39-
}),
64+
Commands.register('aws.docdb.deleteCluster', withTrackChanges<DBClusterNode>(deleteCluster, 'deleteCluster')),
4065

41-
Commands.register('aws.docdb.renameCluster', async (node: DBClusterNode) => {
42-
await renameCluster(node)
43-
}),
66+
Commands.register('aws.docdb.renameCluster', withTrackChanges<DBClusterNode>(renameCluster, 'renameCluster')),
4467

45-
Commands.register('aws.docdb.startCluster', async (node?: DBClusterNode) => {
46-
await startCluster(node)
47-
}),
68+
Commands.register('aws.docdb.startCluster', withTrackChanges<DBClusterNode>(startCluster, 'startCluster')),
4869

49-
Commands.register('aws.docdb.stopCluster', async (node?: DBClusterNode) => {
50-
await stopCluster(node)
51-
}),
70+
Commands.register('aws.docdb.stopCluster', withTrackChanges<DBClusterNode>(stopCluster, 'stopCluster')),
5271

53-
Commands.register('aws.docdb.addRegion', async (node: DBClusterNode) => {
54-
await addRegion(node)
55-
}),
72+
Commands.register('aws.docdb.addRegion', withTrackChanges<DBClusterNode>(addRegion, 'addRegion')),
5673

57-
Commands.register('aws.docdb.createInstance', async (node: DBClusterNode) => {
58-
await createInstance(node)
59-
}),
74+
Commands.register(
75+
'aws.docdb.createInstance',
76+
withTrackChanges<DBClusterNode>(createInstance, 'createInstance')
77+
),
6078

61-
Commands.register('aws.docdb.deleteInstance', async (node: DBInstanceNode) => {
62-
await deleteInstance(node)
63-
}),
79+
Commands.register(
80+
'aws.docdb.deleteInstance',
81+
withTrackChanges<DBInstanceNode>(deleteInstance, 'deleteInstance')
82+
),
6483

65-
Commands.register('aws.docdb.modifyInstance', async (node: DBInstanceNode) => {
66-
await modifyInstance(node)
67-
}),
84+
Commands.register(
85+
'aws.docdb.modifyInstance',
86+
withTrackChanges<DBInstanceNode>(modifyInstance, 'modifyInstance')
87+
),
6888

69-
Commands.register('aws.docdb.rebootInstance', async (node: DBInstanceNode) => {
70-
await rebootInstance(node)
71-
}),
89+
Commands.register(
90+
'aws.docdb.rebootInstance',
91+
withTrackChanges<DBInstanceNode>(rebootInstance, 'rebootInstance')
92+
),
7293

73-
Commands.register('aws.docdb.renameInstance', async (node: DBInstanceNode) => {
74-
await renameInstance(node)
75-
}),
94+
Commands.register(
95+
'aws.docdb.renameInstance',
96+
withTrackChanges<DBInstanceNode>(renameInstance, 'renameInstance')
97+
),
7698

7799
Commands.register('aws.docdb.listTags', async (node: DBResourceNode) => {
78100
await listTags(node)
@@ -90,7 +112,7 @@ export async function activate(ctx: ExtContext): Promise<void> {
90112
await node?.openInBrowser()
91113
}),
92114

93-
Commands.register('aws.docdb.viewDocs', async (node?: DBResourceNode) => {
115+
Commands.register('aws.docdb.viewDocs', async () => {
94116
await openUrl(
95117
Uri.parse('https://docs.aws.amazon.com/documentdb/latest/developerguide/get-started-guide.html')
96118
)

packages/core/src/docdb/explorer/dbClusterNode.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,17 @@ export class DBClusterNode extends DBResourceNode {
5050
)
5151
this.description = this.getDescription()
5252
this.tooltip = `${this.name}${os.EOL}Engine: ${this.cluster.EngineVersion}${os.EOL}Status: ${this.cluster.Status}`
53-
this.trackChanges()
53+
if (this.isStatusRequiringPolling()) {
54+
getLogger().info(`${this.arn} requires polling.`)
55+
this.trackChanges()
56+
} else {
57+
getLogger().info(`${this.arn} does NOT require polling.`)
58+
}
5459
}
5560

5661
public override async getChildren(): Promise<AWSTreeNodeBase[]> {
62+
getLogger().info(`DBClusterNode.getChildren() called`)
5763
return telemetry.docdb_listInstances.run(async () => {
58-
this.childNodes.forEach((node) => {
59-
getLogger().info(`(getChildren) Removing Polling from node: ${node.arn}`)
60-
node.pollingSet.delete(node.arn)
61-
node.pollingSet.clearTimer()
62-
})
6364
return await makeChildrenNodes({
6465
getChildNodes: async () => {
6566
this.instances = (await this.client.listInstances([this.arn])).map((i) => {
@@ -143,7 +144,7 @@ export class DBClusterNode extends DBResourceNode {
143144

144145
override async getStatus() {
145146
const [cluster] = await this.client.listClusters(this.arn)
146-
getLogger().info(`Updating Status: new status ${cluster?.Status} for cluster ${this.arn}`)
147+
getLogger().info(`Get Status: status ${cluster?.Status} for cluster ${this.arn}`)
147148
this.cluster.Status = cluster?.Status
148149
return cluster.Status
149150
}
@@ -162,7 +163,6 @@ export class DBClusterNode extends DBResourceNode {
162163
}
163164

164165
override refreshTree(): void {
165-
this.clearTimer()
166166
this.refresh()
167167
this.parent.refresh()
168168
}

packages/core/src/docdb/explorer/dbElasticClusterNode.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { DocDBContext } from './docdbContext'
1212
import { copyToClipboard } from '../../shared/utilities/messages'
1313
import { localize } from '../../shared/utilities/vsCodeUtils'
1414
import { getAwsConsoleUrl } from '../../shared/awsConsole'
15+
import { getLogger } from '../../shared/logger'
1516

1617
/**
1718
* An AWS Explorer node representing DocumentDB elastic clusters.
@@ -32,6 +33,12 @@ export class DBElasticClusterNode extends DBResourceNode {
3233
)
3334
this.description = this.getDescription()
3435
this.tooltip = `${this.name}\nStatus: ${this.status}`
36+
if (this.isStatusRequiringPolling()) {
37+
getLogger().info(`${this.arn} requires polling.`)
38+
this.trackChanges()
39+
} else {
40+
getLogger().info(`${this.arn} does NOT require polling.`)
41+
}
3542
}
3643

3744
private getContext() {
@@ -70,7 +77,9 @@ export class DBElasticClusterNode extends DBResourceNode {
7077

7178
override async getStatus() {
7279
const cluster = await this.client.getElasticCluster(this.arn)
73-
return cluster?.status
80+
getLogger().info(`Get Status: status ${cluster?.status} for elastic-cluster ${this.arn}`)
81+
this.cluster.status = cluster?.status
82+
return cluster?.status?.toLowerCase()
7483
}
7584

7685
override get isAvailable() {

packages/core/src/docdb/explorer/dbGlobalClusterNode.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { DBResourceNode } from './dbResourceNode'
1717
import { DocDBContext } from './docdbContext'
1818
import { copyToClipboard } from '../../shared/utilities/messages'
1919
import { getAwsConsoleUrl } from '../../shared/awsConsole'
20+
import { getLogger } from '../../shared/logger'
2021

2122
function getRegionFromArn(arn: string) {
2223
const match = arn.match(/:rds:([^:]+):.*:cluster:/)
@@ -46,6 +47,12 @@ export class DBGlobalClusterNode extends DBResourceNode {
4647
this.iconPath = new vscode.ThemeIcon('globe') //TODO: determine icon for global cluster
4748
this.description = 'Global cluster'
4849
this.tooltip = `${this.name}\nEngine: ${this.cluster.EngineVersion}\nStatus: ${this.cluster.Status} (read-only)`
50+
if (this.isStatusRequiringPolling()) {
51+
getLogger().info(`${this.arn} requires polling.`)
52+
this.trackChanges()
53+
} else {
54+
getLogger().info(`${this.arn} does NOT require polling.`)
55+
}
4956
}
5057

5158
public override async getChildren(): Promise<AWSTreeNodeBase[]> {

packages/core/src/docdb/explorer/dbInstanceNode.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,25 @@ export class DBInstanceNode extends DBResourceNode {
3232
this.contextValue = this.getContext()
3333
this.iconPath = this.isAvailable || this.isStopped ? undefined : new vscode.ThemeIcon('loading~spin')
3434
this.tooltip = `${this.name}\nClass: ${this.instance.DBInstanceClass}\nStatus: ${this.status}`
35-
this.trackChanges()
35+
getLogger().info(`Parent of ${instance.DBInstanceArn} is ${parent.arn}`)
36+
if (this.isStatusRequiringPolling()) {
37+
getLogger().info(`${instance.DBInstanceArn} requires polling.`)
38+
this.trackChanges()
39+
} else {
40+
getLogger().info(`${instance.DBInstanceArn} does NOT require polling.`)
41+
}
42+
}
43+
44+
public override isStatusRequiringPolling(): boolean {
45+
const instanceRequiresPolling = super.isStatusRequiringPolling()
46+
const parentRequiresPolling = this.parent.isStatusRequiringPolling()
47+
const requiresPolling = instanceRequiresPolling || parentRequiresPolling
48+
49+
getLogger().info(
50+
`isStatusRequiringPolling (DBInstanceNode): Instance ${this.arn} requires polling: ${instanceRequiresPolling}, Parent ${this.parent.arn} requires polling: ${parentRequiresPolling}, Combined result: ${requiresPolling}`
51+
)
52+
53+
return requiresPolling
3654
}
3755

3856
private makeDescription(): string {
@@ -85,7 +103,6 @@ export class DBInstanceNode extends DBResourceNode {
85103
}
86104

87105
override refreshTree(): void {
88-
this.clearTimer()
89106
this.refresh()
90107
this.parent.refresh()
91108
}

packages/core/src/docdb/explorer/dbResourceNode.ts

Lines changed: 82 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,16 @@ export abstract class DBResourceNode extends AWSTreeNodeBase implements AWSResou
1717
public override readonly regionCode: string
1818
public abstract readonly arn: string
1919
public abstract readonly name: string
20-
public readonly pollingSet: PollingSet<string> = new PollingSet(10000, this.updateNodeStatus.bind(this))
20+
public readonly pollingSet: PollingSet<string> = new PollingSet(30000, this.updateNodeStatus.bind(this))
21+
private static readonly globalPollingArns: Set<string> = new Set<string>()
22+
public processingStatuses = new Set<string>([
23+
'creating',
24+
'modifying',
25+
'rebooting',
26+
'starting',
27+
'stopping',
28+
'renaming',
29+
])
2130

2231
protected constructor(
2332
public readonly client: DocumentDBClient,
@@ -29,6 +38,15 @@ export abstract class DBResourceNode extends AWSTreeNodeBase implements AWSResou
2938
getLogger().info(`NEW DBResourceNode`)
3039
}
3140

41+
public isStatusRequiringPolling(): boolean {
42+
const currentStatus = this.status?.toLowerCase()
43+
const isProcessingStatus = currentStatus !== undefined && this.processingStatuses.has(currentStatus)
44+
getLogger().info(
45+
`isStatusRequiringPolling (DBResourceNode):: Checking if status "${currentStatus}" for ARN: ${this.arn} requires polling: ${isProcessingStatus}`
46+
)
47+
return isProcessingStatus
48+
}
49+
3250
public [inspect.custom](): string {
3351
return 'DBResourceNode'
3452
}
@@ -49,30 +67,75 @@ export abstract class DBResourceNode extends AWSTreeNodeBase implements AWSResou
4967
return this.status === 'stopped'
5068
}
5169

52-
public async waitUntilStatusChanged(): Promise<boolean> {
53-
const currentStatus = this.status
70+
public get isPolling(): boolean {
71+
const isPolling = DBResourceNode.globalPollingArns.has(this.arn)
72+
getLogger().info(`isPolling: ARN ${this.arn} is ${isPolling ? '' : 'not '}being polled.`)
73+
return isPolling
74+
}
5475

76+
public set isPolling(value: boolean) {
77+
if (value) {
78+
if (!this.isPolling) {
79+
DBResourceNode.globalPollingArns.add(this.arn)
80+
getLogger().info(`Polling started for ARN: ${this.arn}`)
81+
} else {
82+
getLogger().info(`Polling already active for ARN: ${this.arn}`)
83+
}
84+
} else {
85+
if (this.isPolling) {
86+
DBResourceNode.globalPollingArns.delete(this.arn)
87+
getLogger().info(`Polling stopped for ARN: ${this.arn}`)
88+
} else {
89+
getLogger().info(`Polling was not active for ARN: ${this.arn}`)
90+
}
91+
}
92+
}
93+
94+
public async waitUntilStatusChanged(
95+
checkProcessingStatuses: boolean = false,
96+
timeout: number = 1200000,
97+
interval: number = 5000
98+
): Promise<boolean> {
5599
await waitUntil(
56100
async () => {
57101
const status = await this.getStatus()
58-
getLogger().info('docdb: waitUntilStatusChanged (status): %O', status)
59-
return status !== currentStatus
102+
if (checkProcessingStatuses) {
103+
const isProcessingStatus = status !== undefined && this.processingStatuses.has(status.toLowerCase())
104+
getLogger().info('docdb: waitUntilStatusChangedToProcessingStatus: %O', isProcessingStatus)
105+
return isProcessingStatus
106+
} else {
107+
const hasStatusChanged = status !== this.status
108+
getLogger().info('docdb: waitUntilStatusChanged (status): %O', hasStatusChanged)
109+
return hasStatusChanged
110+
}
60111
},
61-
{ timeout: 1200000, interval: 5000, truthy: true }
112+
{ timeout, interval, truthy: true }
62113
)
63-
114+
this.refreshTree()
64115
return false
65116
}
66117

67-
public trackChanges() {
118+
public async trackChangesWithWaitProcessingStatus() {
68119
getLogger().info(
69-
`Preparing to track changes for ARN: ${this.arn}; pollingSet: ${this.pollingSet.has(this.arn)}; condition: ${this.pollingSet.has(this.arn) === false}`
120+
`Preparing to track changes with waiting a processing status for ARN: ${this.arn}; condition: ${this.isPolling};`
70121
)
71-
if (this.pollingSet.has(this.arn) === false) {
122+
if (!this.isPolling) {
123+
this.isPolling = true
124+
await this.waitUntilStatusChanged(true, 60000, 1000)
125+
getLogger().info(`Tracking changes for a processing status wait is over`)
72126
this.pollingSet.start(this.arn)
73-
getLogger().info(
74-
`Tracking changes for ARN: ${this.arn}; pollingSet: ${this.pollingSet.has(this.arn)}; condition: ${this.pollingSet.has(this.arn) === false}`
75-
)
127+
getLogger().info(`Tracking changes for ARN: ${this.arn}; condition: ${this.isPolling};`)
128+
} else {
129+
getLogger().info(`ARN: ${this.arn} already being tracked`)
130+
}
131+
}
132+
133+
public trackChanges() {
134+
getLogger().info(`Preparing to track immdiately for ARN: ${this.arn}; condition: ${this.isPolling};`)
135+
if (!this.isPolling) {
136+
this.isPolling = true
137+
this.pollingSet.start(this.arn)
138+
getLogger().info(`Tracking changes for ARN: ${this.arn}; condition: ${this.isPolling};`)
76139
} else {
77140
getLogger().info(`ARN: ${this.arn} already being tracked`)
78141
}
@@ -98,8 +161,14 @@ export abstract class DBResourceNode extends AWSTreeNodeBase implements AWSResou
98161
)
99162
if (currentStatus !== newStatus) {
100163
getLogger().info(`docdb: ${this.arn} updateNodeStatus - refreshing UI`)
164+
this.refreshTree()
165+
}
166+
if (!this.isStatusRequiringPolling()) {
167+
getLogger().info(`docdb: ${this.arn} updateNodeStatus - refreshing UI`)
168+
getLogger().info(`pollingSet delete ${this.arn} updateNodeStatus`)
101169
this.pollingSet.delete(this.arn)
102170
this.pollingSet.clearTimer()
171+
this.isPolling = false
103172
this.refreshTree()
104173
}
105174
}

0 commit comments

Comments
 (0)