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

Commit 692d0a7

Browse files
authored
feat: add support to breakpoint canary (#883)
1 parent 660ce1a commit 692d0a7

File tree

7 files changed

+203
-14
lines changed

7 files changed

+203
-14
lines changed

src/agent/config.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,17 @@ export interface ResolvedDebugAgentConfig extends GoogleAuthOptions {
147147
* A unique deployment identifier. This is used internally only.
148148
*/
149149
minorVersion_?: string;
150+
151+
/**
152+
* The flag to decide whether to enable breakpoint canary.
153+
*/
154+
enableCanary?: boolean;
155+
156+
/**
157+
* The flag to decide whether to allow individual setbreakpoint request to
158+
* override the canary behavior.
159+
*/
160+
allowCanaryOverride?: boolean;
150161
};
151162

152163
/**
@@ -366,6 +377,8 @@ export const defaultConfig: ResolvedDebugAgentConfig = {
366377
service: undefined,
367378
version: undefined,
368379
minorVersion_: undefined,
380+
enableCanary: undefined,
381+
allowCanaryOverride: undefined,
369382
},
370383

371384
appPathRelativeToRepository: undefined,

src/agent/controller.ts

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import * as stackdriver from '../types/stackdriver';
2929

3030
export class Controller extends ServiceObject {
3131
private nextWaitToken: string | null;
32+
private agentId: string | null;
3233

3334
apiUrl: string;
3435

@@ -41,6 +42,7 @@ export class Controller extends ServiceObject {
4142

4243
/** @private {string} */
4344
this.nextWaitToken = null;
45+
this.agentId = null;
4446

4547
this.apiUrl = `https://${debug.apiEndpoint}/v2/controller`;
4648

@@ -61,6 +63,7 @@ export class Controller extends ServiceObject {
6163
err: Error | null,
6264
result?: {
6365
debuggee: Debuggee;
66+
agentId: string;
6467
}
6568
) => void
6669
): void {
@@ -70,20 +73,24 @@ export class Controller extends ServiceObject {
7073
json: true,
7174
body: {debuggee},
7275
};
73-
this.request(options, (err, body: {debuggee: Debuggee}, response) => {
74-
if (err) {
75-
callback(err);
76-
} else if (response!.statusCode !== 200) {
77-
callback(
78-
new Error('unable to register, statusCode ' + response!.statusCode)
79-
);
80-
} else if (!body.debuggee) {
81-
callback(new Error('invalid response body from server'));
82-
} else {
83-
debuggee.id = body.debuggee.id;
84-
callback(null, body);
76+
this.request(
77+
options,
78+
(err, body: {debuggee: Debuggee; agentId: string}, response) => {
79+
if (err) {
80+
callback(err);
81+
} else if (response!.statusCode !== 200) {
82+
callback(
83+
new Error('unable to register, statusCode ' + response!.statusCode)
84+
);
85+
} else if (!body.debuggee) {
86+
callback(new Error('invalid response body from server'));
87+
} else {
88+
debuggee.id = body.debuggee.id;
89+
this.agentId = body.agentId;
90+
callback(null, body);
91+
}
8592
}
86-
});
93+
);
8794
}
8895

8996
/**
@@ -106,6 +113,9 @@ export class Controller extends ServiceObject {
106113
if (that.nextWaitToken) {
107114
query.waitToken = that.nextWaitToken;
108115
}
116+
if (that.agentId) {
117+
query.agentId = that.agentId;
118+
}
109119

110120
const uri =
111121
this.apiUrl +

src/agent/debuglet.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import * as util from 'util';
2424

2525
import {Debug, PackageInfo} from '../client/stackdriver/debug';
2626
import {StatusMessage} from '../client/stackdriver/status-message';
27-
import {Debuggee, DebuggeeProperties} from '../debuggee';
27+
import {CanaryMode, Debuggee, DebuggeeProperties} from '../debuggee';
2828
import * as stackdriver from '../types/stackdriver';
2929

3030
import {defaultConfig} from './config';
@@ -517,6 +517,8 @@ export class Debuglet extends EventEmitter {
517517
service?: string;
518518
version?: string;
519519
minorVersion_?: string;
520+
enableCanary?: boolean;
521+
allowCanaryOverride?: boolean;
520522
},
521523
sourceContext: SourceContext | undefined,
522524
onGCP: boolean,
@@ -594,6 +596,7 @@ export class Debuglet extends EventEmitter {
594596
labels,
595597
statusMessage,
596598
packageInfo,
599+
canaryMode: Debuglet._getCanaryMode(serviceContext),
597600
};
598601
if (sourceContext) {
599602
properties.sourceContexts = [sourceContext];
@@ -1177,4 +1180,22 @@ export class Debuglet extends EventEmitter {
11771180
JSON.stringify(labels);
11781181
return crypto.createHash('sha1').update(uniquifier).digest('hex');
11791182
}
1183+
1184+
static _getCanaryMode(serviceContext: {
1185+
enableCanary?: boolean;
1186+
allowCanaryOverride?: boolean;
1187+
}): CanaryMode {
1188+
const enableCanary = serviceContext?.enableCanary;
1189+
const allowCanaryOverride = serviceContext?.allowCanaryOverride;
1190+
1191+
if (enableCanary && allowCanaryOverride) {
1192+
return 'CANARY_MODE_DEFAULT_ENABLED';
1193+
} else if (enableCanary && !allowCanaryOverride) {
1194+
return 'CANARY_MODE_ALWAYS_ENABLED';
1195+
} else if (!enableCanary && allowCanaryOverride) {
1196+
return 'CANARY_MODE_DEFAULT_DISABLED';
1197+
} else {
1198+
return 'CANARY_MODE_ALWAYS_DISABLED';
1199+
}
1200+
}
11801201
}

src/debuggee.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515
import {PackageInfo} from './client/stackdriver/debug';
1616
import {StatusMessage} from './client/stackdriver/status-message';
1717

18+
export declare type CanaryMode =
19+
| 'CANARY_MODE_UNSPECIFIED'
20+
| 'CANARY_MODE_ALWAYS_ENABLED'
21+
| 'CANARY_MODE_ALWAYS_DISABLED'
22+
| 'CANARY_MODE_DEFAULT_ENABLED'
23+
| 'CANARY_MODE_DEFAULT_DISABLED';
24+
1825
// TODO: Determine how to get this interface to satisfy both the code and the
1926
// docs
2027
// In particular, the comments below state some of the properties are
@@ -30,6 +37,7 @@ export interface DebuggeeProperties {
3037
sourceContexts?: Array<{[key: string]: {}}>;
3138
statusMessage?: StatusMessage;
3239
packageInfo?: PackageInfo;
40+
canaryMode?: CanaryMode;
3341
}
3442

3543
export class Debuggee {
@@ -49,6 +57,7 @@ export class Debuggee {
4957
// debuglet.ts file.
5058
isDisabled?: boolean;
5159
isInactive?: boolean;
60+
canaryMode?: CanaryMode;
5261

5362
/**
5463
* Creates a Debuggee service object.
@@ -95,6 +104,7 @@ export class Debuggee {
95104
this.uniquifier = properties.uniquifier;
96105
this.description = properties.description;
97106
this.agentVersion = properties.agentVersion;
107+
this.canaryMode = properties.canaryMode;
98108
if (properties.labels) {
99109
this.labels = properties.labels;
100110
}

src/types/stackdriver.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ export type BreakpointId = string;
110110
export interface ListBreakpointsQuery {
111111
waitToken?: string;
112112
successOnTimeout?: boolean;
113+
agentId?: string;
113114
}
114115

115116
export interface ListBreakpointsResponse {

test/test-controller.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,31 @@ describe('Controller API', () => {
6969
});
7070
});
7171

72+
it('should get an agentId', done => {
73+
const scope = nock(url)
74+
.post(api + '/debuggees/register')
75+
.reply(200, {
76+
debuggee: {id: 'fake-debuggee'},
77+
agentId: 'fake-agent-id',
78+
activePeriodSec: 600,
79+
});
80+
const debuggee = new Debuggee({
81+
project: 'fake-project',
82+
uniquifier: 'fake-id',
83+
description: 'unit test',
84+
agentVersion,
85+
});
86+
const controller = new Controller(fakeDebug);
87+
// TODO: Determine if this type signature is correct.
88+
controller.register(debuggee, (err, result) => {
89+
assert(!err, 'not expecting an error');
90+
assert.ok(result);
91+
assert.strictEqual(result!.agentId, 'fake-agent-id');
92+
scope.done();
93+
done();
94+
});
95+
});
96+
7297
it('should not return an error when the debuggee isDisabled', done => {
7398
const scope = nock(url)
7499
.post(api + '/debuggees/register')
@@ -209,6 +234,38 @@ describe('Controller API', () => {
209234
});
210235
});
211236

237+
it('should work with agentId provided from registration', done => {
238+
const scope = nock(url)
239+
.post(api + '/debuggees/register')
240+
.reply(200, {
241+
debuggee: {id: 'fake-debuggee'},
242+
agentId: 'fake-agent-id',
243+
activePeriodSec: 600,
244+
})
245+
.get(
246+
api +
247+
'/debuggees/fake-debuggee/breakpoints?successOnTimeout=true&agentId=fake-agent-id'
248+
)
249+
.reply(200, {waitExpired: true});
250+
const debuggee = new Debuggee({
251+
project: 'fake-project',
252+
uniquifier: 'fake-id',
253+
description: 'unit test',
254+
agentVersion,
255+
});
256+
const controller = new Controller(fakeDebug);
257+
controller.register(debuggee, (err1 /*, response1*/) => {
258+
assert.ifError(err1);
259+
const debuggeeWithId: Debuggee = {id: 'fake-debuggee'} as Debuggee;
260+
// TODO: Determine if the result parameter should be used.
261+
controller.listBreakpoints(debuggeeWithId, (err2 /*, response2*/) => {
262+
assert.ifError(err2);
263+
scope.done();
264+
done();
265+
});
266+
});
267+
});
268+
212269
// TODO: Fix this so that each element of the array is actually an
213270
// array of Breakpoints.
214271
const testsBreakpoints: stackdriver.Breakpoint[][] = [

test/test-debuglet.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,35 @@ describe('Debuglet', () => {
251251
debuglet.start();
252252
});
253253

254+
it('should enable breakpoint canary when enableCanary is set', done => {
255+
const debug = new Debug(
256+
{projectId: 'fake-project', credentials: fakeCredentials},
257+
packageInfo
258+
);
259+
nocks.oauth2();
260+
261+
const config = debugletConfig();
262+
config.serviceContext.enableCanary = true;
263+
const debuglet = new Debuglet(debug, config);
264+
const scope = nock(config.apiUrl)
265+
.post(REGISTER_PATH)
266+
.reply(200, {
267+
debuggee: {id: DEBUGGEE_ID},
268+
});
269+
270+
debuglet.once('registered', () => {
271+
assert.strictEqual(
272+
(debuglet.debuggee as Debuggee).canaryMode,
273+
'CANARY_MODE_ALWAYS_ENABLED'
274+
);
275+
debuglet.stop();
276+
scope.done();
277+
done();
278+
});
279+
280+
debuglet.start();
281+
});
282+
254283
it('should not fail if files cannot be read', done => {
255284
const MOCKED_DIRECTORY = process.cwd();
256285
const errors: Array<{filename: string; error: string}> = [];
@@ -1465,6 +1494,54 @@ describe('Debuglet', () => {
14651494
assert.ok(debuggee);
14661495
assert.ok(debuggee.statusMessage);
14671496
});
1497+
1498+
it('should be in CANARY_MODE_DEFAULT_ENABLED canaryMode', () => {
1499+
const debuggee = Debuglet.createDebuggee(
1500+
'some project',
1501+
'id',
1502+
{enableCanary: true, allowCanaryOverride: true},
1503+
{},
1504+
false,
1505+
packageInfo
1506+
);
1507+
assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_DEFAULT_ENABLED');
1508+
});
1509+
1510+
it('should be in CANARY_MODE_ALWAYS_ENABLED canaryMode', () => {
1511+
const debuggee = Debuglet.createDebuggee(
1512+
'some project',
1513+
'id',
1514+
{enableCanary: true, allowCanaryOverride: false},
1515+
{},
1516+
false,
1517+
packageInfo
1518+
);
1519+
assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_ALWAYS_ENABLED');
1520+
});
1521+
1522+
it('should be in CANARY_MODE_DEFAULT_DISABLED canaryMode', () => {
1523+
const debuggee = Debuglet.createDebuggee(
1524+
'some project',
1525+
'id',
1526+
{enableCanary: false, allowCanaryOverride: true},
1527+
{},
1528+
false,
1529+
packageInfo
1530+
);
1531+
assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_DEFAULT_DISABLED');
1532+
});
1533+
1534+
it('should be in CANARY_MODE_ALWAYS_DISABLED canaryMode', () => {
1535+
const debuggee = Debuglet.createDebuggee(
1536+
'some project',
1537+
'id',
1538+
{enableCanary: false, allowCanaryOverride: false},
1539+
{},
1540+
false,
1541+
packageInfo
1542+
);
1543+
assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_ALWAYS_DISABLED');
1544+
});
14681545
});
14691546

14701547
describe('_createUniquifier', () => {

0 commit comments

Comments
 (0)