Skip to content

Commit 58af663

Browse files
authored
Merge pull request #36 from mwvgroup/schedule_consumers
Deploys preliminary broker
2 parents 8083aa1 + c3dcb43 commit 58af663

File tree

16 files changed

+813
-87
lines changed

16 files changed

+813
-87
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<img src="https://avatars2.githubusercontent.com/u/2810941?v=3&s=96" alt="Google Cloud Platform logo" title="Google Cloud Platform" align="right" height="96" width="96"/>
2+
3+
# Google Cloud Functions - Scheduling GCE Instances sample
4+
5+
## Deploy and run the sample
6+
7+
See the [Scheduling Instances with Cloud Scheduler tutorial][tutorial].
8+
9+
[tutorial]: https://cloud.google.com/scheduler/docs/scheduling-instances-with-cloud-scheduler
10+
11+
## Run the tests
12+
13+
1. Install dependencies:
14+
15+
npm install
16+
17+
1. Run the tests:
18+
19+
npm test
20+
21+
## Additional resources
22+
23+
* [GCE NodeJS Client Library documentation][compute_nodejs_docs]
24+
* [Background Cloud Functions documentation][background_functions_docs]
25+
26+
[compute_nodejs_docs]: https://cloud.google.com/compute/docs/tutorials/nodejs-guide
27+
[background_functions_docs]: https://cloud.google.com/functions/docs/writing/background
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright 2018 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// [START functions_start_instance_pubsub]
16+
// [START functions_stop_instance_pubsub]
17+
const Compute = require('@google-cloud/compute');
18+
const compute = new Compute();
19+
// [END functions_stop_instance_pubsub]
20+
21+
/**
22+
* Starts Compute Engine instances.
23+
*
24+
* Expects a PubSub message with JSON-formatted event data containing the
25+
* following attributes:
26+
* zone - the GCP zone the instances are located in.
27+
* label - the label of instances to start.
28+
*
29+
* @param {!object} event Cloud Function PubSub message event.
30+
* @param {!object} callback Cloud Function PubSub callback indicating
31+
* completion.
32+
*/
33+
exports.startInstancePubSub = async (event, context, callback) => {
34+
try {
35+
const payload = _validatePayload(
36+
JSON.parse(Buffer.from(event.data, 'base64').toString())
37+
);
38+
const options = {filter: `labels.${payload.label}`};
39+
const [vms] = await compute.getVMs(options);
40+
await Promise.all(
41+
vms.map(async (instance) => {
42+
if (payload.zone === instance.zone.id) {
43+
const [operation] = await compute
44+
.zone(payload.zone)
45+
.vm(instance.name)
46+
.start();
47+
48+
// Operation pending
49+
return operation.promise();
50+
}
51+
})
52+
);
53+
54+
// Operation complete. Instance successfully started.
55+
const message = `Successfully started instance(s)`;
56+
console.log(message);
57+
callback(null, message);
58+
} catch (err) {
59+
console.log(err);
60+
callback(err);
61+
}
62+
};
63+
// [END functions_start_instance_pubsub]
64+
// [START functions_stop_instance_pubsub]
65+
66+
/**
67+
* Stops Compute Engine instances.
68+
*
69+
* Expects a PubSub message with JSON-formatted event data containing the
70+
* following attributes:
71+
* zone - the GCP zone the instances are located in.
72+
* label - the label of instances to stop.
73+
*
74+
* @param {!object} event Cloud Function PubSub message event.
75+
* @param {!object} callback Cloud Function PubSub callback indicating completion.
76+
*/
77+
exports.stopInstancePubSub = async (event, context, callback) => {
78+
try {
79+
const payload = _validatePayload(
80+
JSON.parse(Buffer.from(event.data, 'base64').toString())
81+
);
82+
const options = {filter: `labels.${payload.label}`};
83+
const [vms] = await compute.getVMs(options);
84+
await Promise.all(
85+
vms.map(async (instance) => {
86+
if (payload.zone === instance.zone.id) {
87+
const [operation] = await compute
88+
.zone(payload.zone)
89+
.vm(instance.name)
90+
.stop();
91+
92+
// Operation pending
93+
return operation.promise();
94+
} else {
95+
return Promise.resolve();
96+
}
97+
})
98+
);
99+
100+
// Operation complete. Instance successfully stopped.
101+
const message = `Successfully stopped instance(s)`;
102+
console.log(message);
103+
callback(null, message);
104+
} catch (err) {
105+
console.log(err);
106+
callback(err);
107+
}
108+
};
109+
// [START functions_start_instance_pubsub]
110+
111+
/**
112+
* Validates that a request payload contains the expected fields.
113+
*
114+
* @param {!object} payload the request payload to validate.
115+
* @return {!object} the payload object.
116+
*/
117+
const _validatePayload = (payload) => {
118+
if (!payload.zone) {
119+
throw new Error(`Attribute 'zone' missing from payload`);
120+
} else if (!payload.label) {
121+
throw new Error(`Attribute 'label' missing from payload`);
122+
}
123+
return payload;
124+
};
125+
// [END functions_start_instance_pubsub]
126+
// [END functions_stop_instance_pubsub]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "cloud-functions-schedule-instance",
3+
"version": "0.1.0",
4+
"private": true,
5+
"license": "Apache-2.0",
6+
"author": "Google Inc.",
7+
"repository": {
8+
"type": "git",
9+
"url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git"
10+
},
11+
"engines": {
12+
"node": ">=8.0.0"
13+
},
14+
"scripts": {
15+
"test": "mocha test/*.test.js --timeout=20000"
16+
},
17+
"devDependencies": {
18+
"@google-cloud/nodejs-repo-tools": "^3.3.0",
19+
"mocha": "^7.0.0",
20+
"proxyquire": "^2.0.0",
21+
"sinon": "^9.0.0"
22+
},
23+
"dependencies": {
24+
"@google-cloud/compute": "^1.0.0"
25+
}
26+
}
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
// Copyright 2018 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
'use strict';
16+
17+
const proxyquire = require('proxyquire').noCallThru();
18+
const sinon = require('sinon');
19+
const assert = require('assert');
20+
21+
const getSample = () => {
22+
const requestPromise = sinon
23+
.stub()
24+
.returns(new Promise((resolve) => resolve('request sent')));
25+
26+
return {
27+
program: proxyquire('../', {
28+
'request-promise': requestPromise,
29+
}),
30+
mocks: {
31+
requestPromise: requestPromise,
32+
},
33+
};
34+
};
35+
36+
const getMocks = () => {
37+
const event = {
38+
data: {},
39+
};
40+
41+
const callback = sinon.spy();
42+
43+
return {
44+
event: event,
45+
context: {},
46+
callback: callback,
47+
};
48+
};
49+
const stubConsole = function () {
50+
sinon.stub(console, `error`);
51+
sinon.stub(console, `log`);
52+
};
53+
54+
//Restore console
55+
const restoreConsole = function () {
56+
console.log.restore();
57+
console.error.restore();
58+
};
59+
beforeEach(stubConsole);
60+
afterEach(restoreConsole);
61+
62+
/** Tests for startInstancePubSub */
63+
describe('functions_start_instance_pubsub', () => {
64+
it('startInstancePubSub: should accept JSON-formatted event payload with label', async () => {
65+
const mocks = getMocks();
66+
const sample = getSample();
67+
const pubsubData = {zone: 'test-zone', label: 'testkey=value'};
68+
mocks.event.data = Buffer.from(JSON.stringify(pubsubData)).toString(
69+
'base64'
70+
);
71+
sample.program.startInstancePubSub(
72+
mocks.event,
73+
mocks.context,
74+
mocks.callback
75+
);
76+
77+
const data = await sample.mocks.requestPromise();
78+
// The request was successfully sent.
79+
assert.strictEqual(data, 'request sent');
80+
});
81+
82+
it(`startInstancePubSub: should fail with missing 'zone' attribute`, () => {
83+
const mocks = getMocks();
84+
const sample = getSample();
85+
const pubsubData = {label: 'testkey=value'};
86+
mocks.event.data = Buffer.from(JSON.stringify(pubsubData)).toString(
87+
'base64'
88+
);
89+
sample.program.startInstancePubSub(
90+
mocks.event,
91+
mocks.context,
92+
mocks.callback
93+
);
94+
95+
assert.deepStrictEqual(
96+
mocks.callback.firstCall.args[0],
97+
new Error(`Attribute 'zone' missing from payload`)
98+
);
99+
});
100+
101+
it(`startInstancePubSub: should fail with missing 'label' attribute`, () => {
102+
const mocks = getMocks();
103+
const sample = getSample();
104+
const pubsubData = {zone: 'test-zone'};
105+
mocks.event.data = Buffer.from(JSON.stringify(pubsubData)).toString(
106+
'base64'
107+
);
108+
sample.program.startInstancePubSub(
109+
mocks.event,
110+
mocks.context,
111+
mocks.callback
112+
);
113+
114+
assert.deepStrictEqual(
115+
mocks.callback.firstCall.args[0],
116+
new Error(`Attribute 'label' missing from payload`)
117+
);
118+
});
119+
120+
it('startInstancePubSub: should fail with empty event payload', () => {
121+
const mocks = getMocks();
122+
const sample = getSample();
123+
const pubsubData = {};
124+
mocks.event.data = Buffer.from(JSON.stringify(pubsubData)).toString(
125+
'base64'
126+
);
127+
sample.program.startInstancePubSub(
128+
mocks.event,
129+
mocks.context,
130+
mocks.callback
131+
);
132+
133+
assert.deepStrictEqual(
134+
mocks.callback.firstCall.args[0],
135+
new Error(`Attribute 'zone' missing from payload`)
136+
);
137+
});
138+
});
139+
140+
/** Tests for stopInstancePubSub */
141+
describe('functions_stop_instance_pubsub', () => {
142+
it('stopInstancePubSub: should accept JSON-formatted event payload with label', async () => {
143+
const mocks = getMocks();
144+
const sample = getSample();
145+
const pubsubData = {zone: 'test-zone', label: 'testkey=value'};
146+
mocks.event.data = Buffer.from(JSON.stringify(pubsubData)).toString(
147+
'base64'
148+
);
149+
sample.program.stopInstancePubSub(
150+
mocks.event,
151+
mocks.context,
152+
mocks.callback
153+
);
154+
155+
const data = await sample.mocks.requestPromise();
156+
// The request was successfully sent.
157+
assert.strictEqual(data, 'request sent');
158+
});
159+
160+
it(`stopInstancePubSub: should fail with missing 'zone' attribute`, () => {
161+
const mocks = getMocks();
162+
const sample = getSample();
163+
const pubsubData = {label: 'testkey=value'};
164+
mocks.event.data = Buffer.from(JSON.stringify(pubsubData)).toString(
165+
'base64'
166+
);
167+
sample.program.stopInstancePubSub(
168+
mocks.event,
169+
mocks.context,
170+
mocks.callback
171+
);
172+
173+
assert.deepStrictEqual(
174+
mocks.callback.firstCall.args[0],
175+
new Error(`Attribute 'zone' missing from payload`)
176+
);
177+
});
178+
179+
it(`stopInstancePubSub: should fail with missing 'label' attribute`, () => {
180+
const mocks = getMocks();
181+
const sample = getSample();
182+
const pubsubData = {zone: 'test-zone'};
183+
mocks.event.data = Buffer.from(JSON.stringify(pubsubData)).toString(
184+
'base64'
185+
);
186+
sample.program.stopInstancePubSub(
187+
mocks.event,
188+
mocks.context,
189+
mocks.callback
190+
);
191+
192+
assert.deepStrictEqual(
193+
mocks.callback.firstCall.args[0],
194+
new Error(`Attribute 'label' missing from payload`)
195+
);
196+
});
197+
198+
it('stopInstancePubSub: should fail with empty event payload', () => {
199+
const mocks = getMocks();
200+
const sample = getSample();
201+
const pubsubData = {};
202+
mocks.event.data = Buffer.from(JSON.stringify(pubsubData)).toString(
203+
'base64'
204+
);
205+
sample.program.stopInstancePubSub(
206+
mocks.event,
207+
mocks.context,
208+
mocks.callback
209+
);
210+
211+
assert.deepStrictEqual(
212+
mocks.callback.firstCall.args[0],
213+
new Error(`Attribute 'zone' missing from payload`)
214+
);
215+
});
216+
});

0 commit comments

Comments
 (0)