Skip to content

Commit 95a5db4

Browse files
feat: Cloud functions example for Bigtable (#728)
* Cloud functions example for Bigtable * lint * lint * reorganize code and lint * Merge branch 'master' into function-samples # Conflicts: # samples/functions/index.js # samples/functions/package.json # samples/readSnippets.js * Move functions test * Lint * Use util method for instance
1 parent 31031f2 commit 95a5db4

File tree

6 files changed

+273
-12
lines changed

6 files changed

+273
-12
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
"@types/uuid": "^7.0.0",
7878
"c8": "^7.1.0",
7979
"codecov": "^3.6.5",
80-
"gts": "^2.0.0",
80+
"gts": "^2.0.2",
8181
"jsdoc": "^3.6.3",
8282
"jsdoc-fresh": "^1.0.2",
8383
"jsdoc-region-tag": "^1.0.4",

samples/functions/index.js

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2020 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+
// [START bigtable_functions_quickstart]
18+
// Imports the Google Cloud client library
19+
const {Bigtable} = require('@google-cloud/bigtable');
20+
21+
// Instantiates a client
22+
const bigtable = new Bigtable();
23+
24+
exports.readRows = async (req, res) => {
25+
// Gets a reference to a Cloud Bigtable instance and database
26+
const instance = bigtable.instance(req.body.instanceId);
27+
const table = instance.table(req.body.tableId);
28+
29+
// Execute the query
30+
try {
31+
const prefix = 'phone#';
32+
const rows = [];
33+
await table
34+
.createReadStream({
35+
prefix,
36+
})
37+
.on('error', err => {
38+
res.send(`Error querying Bigtable: ${err}`);
39+
res.status(500).end();
40+
})
41+
.on('data', row => {
42+
rows.push(
43+
`rowkey: ${row.id}, ` +
44+
`os_build: ${row.data['stats_summary']['os_build'][0].value}\n`
45+
);
46+
})
47+
.on('end', () => {
48+
rows.forEach(r => res.write(r));
49+
res.status(200).end();
50+
});
51+
} catch (err) {
52+
res.send(`Error querying Bigtable: ${err}`);
53+
res.status(500).end();
54+
}
55+
};
56+
57+
// [END bigtable_functions_quickstart]

samples/functions/package.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "@google-cloud/bigtable-samples",
3+
"private": true,
4+
"license": "Apache-2.0",
5+
"author": "Google Inc.",
6+
"repository": "googleapis/nodejs-bigtable",
7+
"engines": {
8+
"node": ">=8.0.0"
9+
},
10+
"scripts": {
11+
"test": "mocha test/*.test.js --timeout=20000",
12+
"start": "functions-framework --target=get"
13+
},
14+
"dependencies": {
15+
"@google-cloud/bigtable": "^2.3.2",
16+
"@google-cloud/functions-framework": "^1.5.1"
17+
}
18+
}

samples/package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"repository": "googleapis/nodejs-bigtable",
77
"files": [
88
"*.js",
9+
"functions/*.js",
910
"hello-world/*.js",
1011
"document-snippets/*.js"
1112
],
@@ -18,9 +19,12 @@
1819
"yargs": "^15.0.0"
1920
},
2021
"devDependencies": {
22+
"@google-cloud/functions-framework": "^1.5.1",
2123
"chai": "^4.2.0",
2224
"mocha": "^7.0.0",
23-
"snap-shot-it": "^7.9.1"
25+
"snap-shot-it": "^7.9.1",
26+
"request": "^2.88.0",
27+
"requestretry": "^4.1.0"
2428
},
2529
"scripts": {
2630
"test": "mocha --timeout=60000",

samples/readSnippets.js

+9-10
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,8 @@ async function main(
2929
const {Bigtable} = require('@google-cloud/bigtable');
3030
const bigtable = new Bigtable();
3131

32-
/**
33-
* TODO(developer): Uncomment these variables before running the sample.
34-
*/
32+
// TODO(developer): Uncomment these variables before running the sample
33+
3534
// const instanceId = 'YOUR_INSTANCE_ID';
3635
// const tableId = 'YOUR_TABLE_ID';
3736
const instance = bigtable.instance(instanceId);
@@ -44,8 +43,8 @@ async function main(
4443
// [END bigtable_reads_prefix]
4544
// [END bigtable_reads_filter]
4645
switch (readType) {
47-
// [START bigtable_reads_row]
4846
case 'readRow': {
47+
// [START bigtable_reads_row]
4948
const rowkey = 'phone#4c410523#20190501';
5049

5150
const [row] = await table.row(rowkey).get();
@@ -54,8 +53,8 @@ async function main(
5453
break;
5554
}
5655

57-
// [START bigtable_reads_row_partial]
5856
case 'readRowPartial': {
57+
// [START bigtable_reads_row_partial]
5958
const COLUMN_FAMILY = 'stats_summary';
6059
const COLUMN_QUALIFIER = 'os_build';
6160
const rowkey = 'phone#4c410523#20190501';
@@ -69,17 +68,17 @@ async function main(
6968
break;
7069
}
7170

72-
// [START bigtable_reads_rows]
7371
case 'readRows': {
72+
// [START bigtable_reads_rows]
7473
const rowKeys = ['phone#4c410523#20190501', 'phone#4c410523#20190502'];
7574
const [rows] = await table.getRows({keys: rowKeys});
7675
rows.forEach(row => printRow(row.id, row.data));
7776
// [END bigtable_reads_rows]
7877
break;
7978
}
8079

81-
// [START bigtable_reads_row_range]
8280
case 'readRowRange': {
81+
// [START bigtable_reads_row_range]
8382
const start = 'phone#4c410523#20190501';
8483
const end = 'phone#4c410523#201906201';
8584

@@ -100,8 +99,8 @@ async function main(
10099
break;
101100
}
102101

103-
// [START bigtable_reads_row_ranges]
104102
case 'readRowRanges': {
103+
// [START bigtable_reads_row_ranges]
105104
await table
106105
.createReadStream({
107106
ranges: [
@@ -127,8 +126,8 @@ async function main(
127126
break;
128127
}
129128

130-
// [START bigtable_reads_prefix]
131129
case 'readPrefix': {
130+
// [START bigtable_reads_prefix]
132131
const prefix = 'phone#';
133132

134133
await table
@@ -147,8 +146,8 @@ async function main(
147146
break;
148147
}
149148

150-
// [START bigtable_reads_filter]
151149
case 'readFilter': {
150+
// [START bigtable_reads_filter]
152151
const filter = {
153152
value: /PQ2A.*$/,
154153
};

samples/test/functions.js

+183
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
// Copyright 2020 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 assert = require('assert');
18+
const {execSync} = require('child_process');
19+
const path = require('path');
20+
const requestRetry = require('requestretry');
21+
const uuid = require('uuid');
22+
const {obtainTestInstance} = require('./util');
23+
const {describe, it, before, after} = require('mocha');
24+
25+
const PORT = 9010;
26+
const BASE_URL = `http://localhost:${PORT}`;
27+
const cwd = path.join(__dirname, '..');
28+
29+
const TABLE_ID = `mobile-time-series-${uuid.v4()}`.substr(0, 30); // Bigtable naming rules
30+
31+
describe('functions', async () => {
32+
const instance = await obtainTestInstance();
33+
const INSTANCE_ID = instance.id;
34+
let table;
35+
const TIMESTAMP = new Date(2019, 5, 1);
36+
TIMESTAMP.setUTCHours(0);
37+
38+
let ffProc;
39+
40+
before(async () => {
41+
table = instance.table(TABLE_ID);
42+
43+
await table.create().catch(console.error);
44+
await table.createFamily('stats_summary').catch(console.error);
45+
46+
const rowsToInsert = [
47+
{
48+
key: 'phone#4c410523#20190501',
49+
data: {
50+
stats_summary: {
51+
connected_cell: {
52+
value: 1,
53+
timestamp: TIMESTAMP,
54+
},
55+
connected_wifi: {
56+
value: 1,
57+
timestamp: TIMESTAMP,
58+
},
59+
os_build: {
60+
value: 'PQ2A.190405.003',
61+
timestamp: TIMESTAMP,
62+
},
63+
},
64+
},
65+
},
66+
{
67+
key: 'phone#4c410523#20190502',
68+
data: {
69+
stats_summary: {
70+
connected_cell: {
71+
value: 1,
72+
timestamp: TIMESTAMP,
73+
},
74+
connected_wifi: {
75+
value: 1,
76+
timestamp: TIMESTAMP,
77+
},
78+
os_build: {
79+
value: 'PQ2A.190405.004',
80+
timestamp: TIMESTAMP,
81+
},
82+
},
83+
},
84+
},
85+
{
86+
key: 'phone#4c410523#20190505',
87+
data: {
88+
stats_summary: {
89+
connected_cell: {
90+
value: 0,
91+
timestamp: TIMESTAMP,
92+
},
93+
connected_wifi: {
94+
value: 1,
95+
timestamp: TIMESTAMP,
96+
},
97+
os_build: {
98+
value: 'PQ2A.190406.000',
99+
timestamp: TIMESTAMP,
100+
},
101+
},
102+
},
103+
},
104+
{
105+
key: 'phone#5c10102#20190501',
106+
data: {
107+
stats_summary: {
108+
connected_cell: {
109+
value: 1,
110+
timestamp: TIMESTAMP,
111+
},
112+
connected_wifi: {
113+
value: 1,
114+
timestamp: TIMESTAMP,
115+
},
116+
os_build: {
117+
value: 'PQ2A.190401.002',
118+
timestamp: TIMESTAMP,
119+
},
120+
},
121+
},
122+
},
123+
{
124+
key: 'phone#5c10102#20190502',
125+
data: {
126+
stats_summary: {
127+
connected_cell: {
128+
value: 1,
129+
timestamp: TIMESTAMP,
130+
},
131+
connected_wifi: {
132+
value: 0,
133+
timestamp: TIMESTAMP,
134+
},
135+
os_build: {
136+
value: 'PQ2A.190406.000',
137+
timestamp: TIMESTAMP,
138+
},
139+
},
140+
},
141+
},
142+
];
143+
144+
await table.insert(rowsToInsert).catch(console.error);
145+
146+
// Run the functions-framework instance to host functions locally
147+
// exec's 'timeout' param won't kill children of "shim" /bin/sh process
148+
// Workaround: include "& sleep <TIMEOUT>; kill $!" in executed command
149+
ffProc = execSync(
150+
`functions-framework --target=readRows --signature-type=http --port ${PORT} & sleep 2; kill $!`,
151+
{shell: true, cwd}
152+
);
153+
});
154+
155+
after(async () => {
156+
// Wait for the functions framework to stop
157+
await ffProc;
158+
});
159+
160+
it('should read one row', async () => {
161+
const response = await requestRetry({
162+
url: `${BASE_URL}/readRows`,
163+
method: 'GET',
164+
body: {
165+
instanceId: INSTANCE_ID,
166+
tableId: TABLE_ID,
167+
},
168+
retryDelay: 200,
169+
json: true,
170+
});
171+
172+
assert.strictEqual(response.statusCode, 200);
173+
assert.strictEqual(
174+
response.body,
175+
`rowkey: phone#4c410523#20190501, os_build: PQ2A.190405.003
176+
rowkey: phone#4c410523#20190502, os_build: PQ2A.190405.004
177+
rowkey: phone#4c410523#20190505, os_build: PQ2A.190406.000
178+
rowkey: phone#5c10102#20190501, os_build: PQ2A.190401.002
179+
rowkey: phone#5c10102#20190502, os_build: PQ2A.190406.000
180+
`
181+
);
182+
});
183+
});

0 commit comments

Comments
 (0)