Skip to content
This repository was archived by the owner on Apr 3, 2024. It is now read-only.

Commit a188e34

Browse files
authored
feat: add error handling to firebase controller. (#1116)
Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: - [x] Make sure to open an issue as a [bug/issue](https://togithub.com/googleapis/cloud-debug-nodejs/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [x] Ensure the tests and linter pass - [ ] Code coverage does not decrease (if any source code was changed) - [x] Appropriate docs were updated (if necessary) Fixes #1115 🦕
1 parent 640708c commit a188e34

File tree

2 files changed

+285
-54
lines changed

2 files changed

+285
-54
lines changed

src/agent/firebase-controller.ts

Lines changed: 120 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import * as gcpMetadata from 'gcp-metadata';
2929
import * as util from 'util';
3030
const debuglog = util.debuglog('cdbg.firebase');
3131

32+
const FIREBASE_APP_NAME = 'cdbg';
33+
3234
export class FirebaseController implements Controller {
3335
db: firebase.database.Database;
3436
debuggeeId?: string;
@@ -74,26 +76,49 @@ export class FirebaseController implements Controller {
7476
if (options.databaseUrl) {
7577
databaseUrl = options.databaseUrl;
7678
} else {
77-
// TODO: Test whether this exists. If not, fall back to -default.
79+
// TODO: Add fallback to -default
7880
databaseUrl = `https://${projectId}-cdbg.firebaseio.com`;
7981
}
8082

83+
let app: firebase.app.App;
8184
if (credential) {
82-
firebase.initializeApp({
83-
credential: credential,
84-
databaseURL: databaseUrl,
85-
});
85+
app = firebase.initializeApp(
86+
{
87+
credential: credential,
88+
databaseURL: databaseUrl,
89+
},
90+
FIREBASE_APP_NAME
91+
);
8692
} else {
8793
// Use the default credentials.
88-
firebase.initializeApp({
89-
databaseURL: databaseUrl,
90-
});
94+
app = firebase.initializeApp(
95+
{
96+
databaseURL: databaseUrl,
97+
},
98+
'cdbg'
99+
);
91100
}
92101

93102
const db = firebase.database();
94103

95-
// TODO: Test this setup and emit a reasonable error.
96-
debuglog('Firebase app initialized. Connected to', databaseUrl);
104+
// Test the connection by reading the schema version.
105+
try {
106+
const version_snapshot = await db.ref('cdbg/schema_version').get();
107+
if (version_snapshot) {
108+
const version = version_snapshot.val();
109+
debuglog(
110+
`Firebase app initialized. Connected to ${databaseUrl}` +
111+
` with schema version ${version}`
112+
);
113+
} else {
114+
app.delete();
115+
throw new Error('failed to fetch schema version from database');
116+
}
117+
} catch (e) {
118+
app.delete();
119+
throw e;
120+
}
121+
97122
return db;
98123
}
99124

@@ -135,6 +160,7 @@ export class FirebaseController implements Controller {
135160
// This MUST be consistent across all debuggee instances.
136161
// TODO: JSON.stringify may provide different strings if labels are added
137162
// in different orders.
163+
debuggee.id = ''; // Don't use the debuggee id when computing the id.
138164
const debuggeeHash = crypto
139165
.createHash('sha1')
140166
.update(JSON.stringify(debuggee))
@@ -143,11 +169,14 @@ export class FirebaseController implements Controller {
143169
debuggee.id = this.debuggeeId;
144170

145171
const debuggeeRef = this.db.ref(`cdbg/debuggees/${this.debuggeeId}`);
146-
debuggeeRef.set(debuggee);
147-
148-
// TODO: Handle errors. I can .set(data, (error) => if (error) {})
149-
const agentId = 'unsupported';
150-
callback(null, {debuggee, agentId});
172+
debuggeeRef.set(debuggee, err => {
173+
if (err) {
174+
callback(err);
175+
} else {
176+
const agentId = 'unsupported';
177+
callback(null, {debuggee, agentId});
178+
}
179+
});
151180
}
152181

153182
/**
@@ -156,11 +185,11 @@ export class FirebaseController implements Controller {
156185
* @param {!Breakpoint} breakpoint
157186
* @param {!Function} callback accepting (err, body)
158187
*/
159-
updateBreakpoint(
188+
async updateBreakpoint(
160189
debuggee: Debuggee,
161190
breakpoint: stackdriver.Breakpoint,
162191
callback: (err?: Error, body?: {}) => void
163-
): void {
192+
): Promise<void> {
164193
debuglog('updating a breakpoint');
165194
assert(debuggee.id, 'should have a registered debuggee');
166195

@@ -184,32 +213,43 @@ export class FirebaseController implements Controller {
184213
// https://firebase.google.com/docs/reference/rest/database#section-server-values
185214
breakpoint_map['finalTimeUnixMsec'] = {'.sv': 'timestamp'};
186215

187-
this.db
188-
.ref(`cdbg/breakpoints/${this.debuggeeId}/active/${breakpoint.id}`)
189-
.remove();
216+
try {
217+
await this.db
218+
.ref(`cdbg/breakpoints/${this.debuggeeId}/active/${breakpoint.id}`)
219+
.remove();
220+
} catch (err) {
221+
debuglog(`failed to delete breakpoint ${breakpoint.id}: ` + err);
222+
callback(err as Error);
223+
throw err;
224+
}
225+
226+
try {
227+
if (is_snapshot) {
228+
// We could also restrict this to only write to this node if it wasn't
229+
// an error and there is actual snapshot data. For now though we'll
230+
// write it regardless, makes sense if you want to get everything for
231+
// a snapshot it's at this location, regardless of what it contains.
232+
await this.db
233+
.ref(`cdbg/breakpoints/${this.debuggeeId}/snapshot/${breakpoint.id}`)
234+
.set(breakpoint_map);
235+
// Now strip the snapshot data for the write to 'final' path.
236+
const fields_to_strip = [
237+
'evaluatedExpressions',
238+
'stackFrames',
239+
'variableTable',
240+
];
241+
fields_to_strip.forEach(field => delete breakpoint_map[field]);
242+
}
190243

191-
// TODO: error handling from here on
192-
if (is_snapshot) {
193-
// We could also restrict this to only write to this node if it wasn't
194-
// an error and there is actual snapshot data. For now though we'll
195-
// write it regardless, makes sense if you want to get everything for
196-
// a snapshot it's at this location, regardless of what it contains.
197-
this.db
198-
.ref(`cdbg/breakpoints/${this.debuggeeId}/snapshot/${breakpoint.id}`)
244+
await this.db
245+
.ref(`cdbg/breakpoints/${this.debuggeeId}/final/${breakpoint.id}`)
199246
.set(breakpoint_map);
200-
// Now strip the snapshot data for the write to 'final' path.
201-
const fields_to_strip = [
202-
'evaluatedExpressions',
203-
'stackFrames',
204-
'variableTable',
205-
];
206-
fields_to_strip.forEach(field => delete breakpoint_map[field]);
247+
} catch (err) {
248+
debuglog(`failed to finalize breakpoint ${breakpoint.id}: ` + err);
249+
callback(err as Error);
250+
throw err;
207251
}
208252

209-
this.db
210-
.ref(`cdbg/breakpoints/${this.debuggeeId}/final/${breakpoint.id}`)
211-
.set(breakpoint_map);
212-
213253
// Indicate success to the caller.
214254
callback();
215255
}
@@ -224,26 +264,53 @@ export class FirebaseController implements Controller {
224264
this.bpRef = this.db.ref(`cdbg/breakpoints/${this.debuggeeId}/active`);
225265

226266
let breakpoints = [] as stackdriver.Breakpoint[];
227-
this.bpRef.on('child_added', (snapshot: firebase.database.DataSnapshot) => {
228-
debuglog(`new breakpoint: ${snapshot.key}`);
229-
const breakpoint = snapshot.val();
230-
breakpoint.id = snapshot.key;
231-
breakpoints.push(breakpoint);
232-
callback(null, breakpoints);
233-
});
234-
this.bpRef.on('child_removed', snapshot => {
235-
// remove the breakpoint.
236-
const bpId = snapshot.key;
237-
breakpoints = breakpoints.filter(bp => bp.id !== bpId);
238-
debuglog(`breakpoint removed: ${bpId}`);
239-
callback(null, breakpoints);
240-
});
267+
this.bpRef.on(
268+
'child_added',
269+
(snapshot: firebase.database.DataSnapshot) => {
270+
debuglog(`new breakpoint: ${snapshot.key}`);
271+
const breakpoint = snapshot.val();
272+
breakpoint.id = snapshot.key;
273+
breakpoints.push(breakpoint);
274+
callback(null, breakpoints);
275+
},
276+
(e: Error) => {
277+
debuglog(
278+
'unable to listen to child_added events on ' +
279+
`cdbg/breakpoints/${this.debuggeeId}/active. ` +
280+
'Please check your database settings.'
281+
);
282+
callback(e, []);
283+
}
284+
);
285+
this.bpRef.on(
286+
'child_removed',
287+
snapshot => {
288+
// remove the breakpoint.
289+
const bpId = snapshot.key;
290+
breakpoints = breakpoints.filter(bp => bp.id !== bpId);
291+
debuglog(`breakpoint removed: ${bpId}`);
292+
callback(null, breakpoints);
293+
},
294+
(e: Error) => {
295+
debuglog(
296+
'unable to listen to child_removed events on ' +
297+
`cdbg/breakpoints/${this.debuggeeId}/active. ` +
298+
'Please check your database settings.'
299+
);
300+
callback(e, []);
301+
}
302+
);
241303
}
242304

243305
stop(): void {
244306
if (this.bpRef) {
245307
this.bpRef.off();
246308
this.bpRef = undefined;
247309
}
310+
try {
311+
firebase.app(FIREBASE_APP_NAME).delete();
312+
} catch (err) {
313+
debuglog(`failed to tear down firebase app: ${err})`);
314+
}
248315
}
249316
}

0 commit comments

Comments
 (0)