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

Commit

Permalink
feat: add support to breakpoint canary (#883)
Browse files Browse the repository at this point in the history
  • Loading branch information
Louis-Ye committed Jun 23, 2020
1 parent 660ce1a commit 692d0a7
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 14 deletions.
13 changes: 13 additions & 0 deletions src/agent/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,17 @@ export interface ResolvedDebugAgentConfig extends GoogleAuthOptions {
* A unique deployment identifier. This is used internally only.
*/
minorVersion_?: string;

/**
* The flag to decide whether to enable breakpoint canary.
*/
enableCanary?: boolean;

/**
* The flag to decide whether to allow individual setbreakpoint request to
* override the canary behavior.
*/
allowCanaryOverride?: boolean;
};

/**
Expand Down Expand Up @@ -366,6 +377,8 @@ export const defaultConfig: ResolvedDebugAgentConfig = {
service: undefined,
version: undefined,
minorVersion_: undefined,
enableCanary: undefined,
allowCanaryOverride: undefined,
},

appPathRelativeToRepository: undefined,
Expand Down
36 changes: 23 additions & 13 deletions src/agent/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import * as stackdriver from '../types/stackdriver';

export class Controller extends ServiceObject {
private nextWaitToken: string | null;
private agentId: string | null;

apiUrl: string;

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

/** @private {string} */
this.nextWaitToken = null;
this.agentId = null;

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

Expand All @@ -61,6 +63,7 @@ export class Controller extends ServiceObject {
err: Error | null,
result?: {
debuggee: Debuggee;
agentId: string;
}
) => void
): void {
Expand All @@ -70,20 +73,24 @@ export class Controller extends ServiceObject {
json: true,
body: {debuggee},
};
this.request(options, (err, body: {debuggee: Debuggee}, response) => {
if (err) {
callback(err);
} else if (response!.statusCode !== 200) {
callback(
new Error('unable to register, statusCode ' + response!.statusCode)
);
} else if (!body.debuggee) {
callback(new Error('invalid response body from server'));
} else {
debuggee.id = body.debuggee.id;
callback(null, body);
this.request(
options,
(err, body: {debuggee: Debuggee; agentId: string}, response) => {
if (err) {
callback(err);
} else if (response!.statusCode !== 200) {
callback(
new Error('unable to register, statusCode ' + response!.statusCode)
);
} else if (!body.debuggee) {
callback(new Error('invalid response body from server'));
} else {
debuggee.id = body.debuggee.id;
this.agentId = body.agentId;
callback(null, body);
}
}
});
);
}

/**
Expand All @@ -106,6 +113,9 @@ export class Controller extends ServiceObject {
if (that.nextWaitToken) {
query.waitToken = that.nextWaitToken;
}
if (that.agentId) {
query.agentId = that.agentId;
}

const uri =
this.apiUrl +
Expand Down
23 changes: 22 additions & 1 deletion src/agent/debuglet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import * as util from 'util';

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

import {defaultConfig} from './config';
Expand Down Expand Up @@ -517,6 +517,8 @@ export class Debuglet extends EventEmitter {
service?: string;
version?: string;
minorVersion_?: string;
enableCanary?: boolean;
allowCanaryOverride?: boolean;
},
sourceContext: SourceContext | undefined,
onGCP: boolean,
Expand Down Expand Up @@ -594,6 +596,7 @@ export class Debuglet extends EventEmitter {
labels,
statusMessage,
packageInfo,
canaryMode: Debuglet._getCanaryMode(serviceContext),
};
if (sourceContext) {
properties.sourceContexts = [sourceContext];
Expand Down Expand Up @@ -1177,4 +1180,22 @@ export class Debuglet extends EventEmitter {
JSON.stringify(labels);
return crypto.createHash('sha1').update(uniquifier).digest('hex');
}

static _getCanaryMode(serviceContext: {
enableCanary?: boolean;
allowCanaryOverride?: boolean;
}): CanaryMode {
const enableCanary = serviceContext?.enableCanary;
const allowCanaryOverride = serviceContext?.allowCanaryOverride;

if (enableCanary && allowCanaryOverride) {
return 'CANARY_MODE_DEFAULT_ENABLED';
} else if (enableCanary && !allowCanaryOverride) {
return 'CANARY_MODE_ALWAYS_ENABLED';
} else if (!enableCanary && allowCanaryOverride) {
return 'CANARY_MODE_DEFAULT_DISABLED';
} else {
return 'CANARY_MODE_ALWAYS_DISABLED';
}
}
}
10 changes: 10 additions & 0 deletions src/debuggee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@
import {PackageInfo} from './client/stackdriver/debug';
import {StatusMessage} from './client/stackdriver/status-message';

export declare type CanaryMode =
| 'CANARY_MODE_UNSPECIFIED'
| 'CANARY_MODE_ALWAYS_ENABLED'
| 'CANARY_MODE_ALWAYS_DISABLED'
| 'CANARY_MODE_DEFAULT_ENABLED'
| 'CANARY_MODE_DEFAULT_DISABLED';

// TODO: Determine how to get this interface to satisfy both the code and the
// docs
// In particular, the comments below state some of the properties are
Expand All @@ -30,6 +37,7 @@ export interface DebuggeeProperties {
sourceContexts?: Array<{[key: string]: {}}>;
statusMessage?: StatusMessage;
packageInfo?: PackageInfo;
canaryMode?: CanaryMode;
}

export class Debuggee {
Expand All @@ -49,6 +57,7 @@ export class Debuggee {
// debuglet.ts file.
isDisabled?: boolean;
isInactive?: boolean;
canaryMode?: CanaryMode;

/**
* Creates a Debuggee service object.
Expand Down Expand Up @@ -95,6 +104,7 @@ export class Debuggee {
this.uniquifier = properties.uniquifier;
this.description = properties.description;
this.agentVersion = properties.agentVersion;
this.canaryMode = properties.canaryMode;
if (properties.labels) {
this.labels = properties.labels;
}
Expand Down
1 change: 1 addition & 0 deletions src/types/stackdriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export type BreakpointId = string;
export interface ListBreakpointsQuery {
waitToken?: string;
successOnTimeout?: boolean;
agentId?: string;
}

export interface ListBreakpointsResponse {
Expand Down
57 changes: 57 additions & 0 deletions test/test-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,31 @@ describe('Controller API', () => {
});
});

it('should get an agentId', done => {
const scope = nock(url)
.post(api + '/debuggees/register')
.reply(200, {
debuggee: {id: 'fake-debuggee'},
agentId: 'fake-agent-id',
activePeriodSec: 600,
});
const debuggee = new Debuggee({
project: 'fake-project',
uniquifier: 'fake-id',
description: 'unit test',
agentVersion,
});
const controller = new Controller(fakeDebug);
// TODO: Determine if this type signature is correct.
controller.register(debuggee, (err, result) => {
assert(!err, 'not expecting an error');
assert.ok(result);
assert.strictEqual(result!.agentId, 'fake-agent-id');
scope.done();
done();
});
});

it('should not return an error when the debuggee isDisabled', done => {
const scope = nock(url)
.post(api + '/debuggees/register')
Expand Down Expand Up @@ -209,6 +234,38 @@ describe('Controller API', () => {
});
});

it('should work with agentId provided from registration', done => {
const scope = nock(url)
.post(api + '/debuggees/register')
.reply(200, {
debuggee: {id: 'fake-debuggee'},
agentId: 'fake-agent-id',
activePeriodSec: 600,
})
.get(
api +
'/debuggees/fake-debuggee/breakpoints?successOnTimeout=true&agentId=fake-agent-id'
)
.reply(200, {waitExpired: true});
const debuggee = new Debuggee({
project: 'fake-project',
uniquifier: 'fake-id',
description: 'unit test',
agentVersion,
});
const controller = new Controller(fakeDebug);
controller.register(debuggee, (err1 /*, response1*/) => {
assert.ifError(err1);
const debuggeeWithId: Debuggee = {id: 'fake-debuggee'} as Debuggee;
// TODO: Determine if the result parameter should be used.
controller.listBreakpoints(debuggeeWithId, (err2 /*, response2*/) => {
assert.ifError(err2);
scope.done();
done();
});
});
});

// TODO: Fix this so that each element of the array is actually an
// array of Breakpoints.
const testsBreakpoints: stackdriver.Breakpoint[][] = [
Expand Down
77 changes: 77 additions & 0 deletions test/test-debuglet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,35 @@ describe('Debuglet', () => {
debuglet.start();
});

it('should enable breakpoint canary when enableCanary is set', done => {
const debug = new Debug(
{projectId: 'fake-project', credentials: fakeCredentials},
packageInfo
);
nocks.oauth2();

const config = debugletConfig();
config.serviceContext.enableCanary = true;
const debuglet = new Debuglet(debug, config);
const scope = nock(config.apiUrl)
.post(REGISTER_PATH)
.reply(200, {
debuggee: {id: DEBUGGEE_ID},
});

debuglet.once('registered', () => {
assert.strictEqual(
(debuglet.debuggee as Debuggee).canaryMode,
'CANARY_MODE_ALWAYS_ENABLED'
);
debuglet.stop();
scope.done();
done();
});

debuglet.start();
});

it('should not fail if files cannot be read', done => {
const MOCKED_DIRECTORY = process.cwd();
const errors: Array<{filename: string; error: string}> = [];
Expand Down Expand Up @@ -1465,6 +1494,54 @@ describe('Debuglet', () => {
assert.ok(debuggee);
assert.ok(debuggee.statusMessage);
});

it('should be in CANARY_MODE_DEFAULT_ENABLED canaryMode', () => {
const debuggee = Debuglet.createDebuggee(
'some project',
'id',
{enableCanary: true, allowCanaryOverride: true},
{},
false,
packageInfo
);
assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_DEFAULT_ENABLED');
});

it('should be in CANARY_MODE_ALWAYS_ENABLED canaryMode', () => {
const debuggee = Debuglet.createDebuggee(
'some project',
'id',
{enableCanary: true, allowCanaryOverride: false},
{},
false,
packageInfo
);
assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_ALWAYS_ENABLED');
});

it('should be in CANARY_MODE_DEFAULT_DISABLED canaryMode', () => {
const debuggee = Debuglet.createDebuggee(
'some project',
'id',
{enableCanary: false, allowCanaryOverride: true},
{},
false,
packageInfo
);
assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_DEFAULT_DISABLED');
});

it('should be in CANARY_MODE_ALWAYS_DISABLED canaryMode', () => {
const debuggee = Debuglet.createDebuggee(
'some project',
'id',
{enableCanary: false, allowCanaryOverride: false},
{},
false,
packageInfo
);
assert.strictEqual(debuggee.canaryMode, 'CANARY_MODE_ALWAYS_DISABLED');
});
});

describe('_createUniquifier', () => {
Expand Down

0 comments on commit 692d0a7

Please sign in to comment.