Skip to content

Commit 2e647dd

Browse files
author
csplinter
committed
initial commit
1 parent f83d4fa commit 2e647dd

File tree

5 files changed

+281
-29
lines changed

5 files changed

+281
-29
lines changed

README.md

+63-29
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,68 @@
1-
# datastax-examples-template
2-
This a sample template repo for contributions to the DataStax Examples platform. This provides the minimum set of items needed to create a new example for submission to the DataStax Examples platform.
1+
# Cassandra REST API with Google Cloud Functions in Node.js
2+
Before running with this example, head over to the [SETUP-README](SETUP-README.md) for instructions on how to
3+
1. launch an instance in Google Cloud
4+
2. install and start a DataStax Distribution of Apache Cassandra database
5+
3. setup your local development environment for Node.js and [serverless](https://serverless.com)
36

4-
## Prerequisites
5-
* Git must be installed
6-
* If it is not installed then follow the guide found [here](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).
7-
7+
Once the above is completed, you will have all of the needed pieces in place to run this example.
88

9+
## Project Details
10+
### Files
11+
- [index.js](index.js): Contains the Cassandra Driver connection and queries as well as the Google Cloud Function entry points.
12+
- [serverless.yml](serverless.yml): Used by serverless to deploy and configure the Google Cloud artifacts needed to run the function.
13+
- [package.json](package.json): Defines the dependencies and descriptive example metadata.
914

10-
## Cloning this Repo to get started
11-
In a Terminal window.
15+
## Run it
16+
1. Clone this repository
17+
```
18+
git clone https://github.com/csplinter/datastax-serverless-examples
19+
```
20+
2. Go to the `datastax-serverless-examples/gcp` directory
21+
```
22+
cd datastax-serverless-examples/gcp
23+
```
24+
3. Install the DataStax Cassandra Driver
25+
```
26+
npm install cassandra-driver
27+
```
28+
4. Install serverless-google-cloudfunctions plugin
29+
```
30+
npm install serverless-google-cloudfunctions
31+
```
32+
5. Configure `serverless.yml` with your project-id, credentials file, Contact Points ( public IP of GCP instance ), and Local Data Center ( likely `datacenter1` )
33+
6. From the gcp directory, deploy your function. This should output the endpoints that you can use to access the database.
34+
```
35+
sls deploy
36+
```
37+
* When you are done, don't forget to clean things up with
38+
```
39+
sls remove
40+
```
1241

13-
1) Create a bare clone of the repository.
42+
## Using the HTTP Endpoints
43+
#### createCatalog
44+
```
45+
curl -X POST https://us-central1-<project-id>.cloudfunctions.net/createCatalog
46+
````
47+
expected output:
48+
```
49+
"Successfully created shopping.catalog schema"
50+
```
51+
#### addItem
52+
Note the `-H "Content-Type:application/json"` is required here.
53+
```
54+
curl -X POST -H "Content-Type:application/json" -d '{"item_id": 0, "name": "name_0", "description": "desc_0", "price": 10.1}' https://us-central1-<project-id>.cloudfunctions.net/addItem
55+
```
56+
expected output:
57+
```
58+
{"query":"INSERT INTO shopping.catalog (item_id, name, description, price) VALUES (?, ?, ?, ?)","item_id":0,"name":"name_0","description":"desc_0","price":10.1}
59+
```
60+
#### getItem
61+
```
62+
curl -X GET https://us-central1-<project-id>.cloudfunctions.net/getItem/0
63+
```
64+
expected output:
65+
```
66+
{"query":"SELECT name, description, price FROM shopping.catalog WHERE item_id = ?","item_id":["0"],"name":"name_0","description":"desc_0","price":"10.1"}
67+
```
1468
15-
git clone --bare https://github.com/bechbd/datastax-examples-template.git`
16-
17-
2) Mirror-push to the new repository.
18-
19-
cd datastax-examples-template.git
20-
git push --mirror https://github.com/bechbd/<new repo name>.git`
21-
22-
3) Remove the temporary local repository you created in step 1.
23-
24-
cd ..
25-
rm -rf datastax-examples-template.git
26-
27-
28-
29-
4) Clone the newly created repository from step 3.
30-
31-
git clone https://github.com/bechbd/<new repo name>.git
32-
33-
You are now ready to work away in this duplicated repo
34-

SETUP-README.md

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Prerequisites Setup
2+
## Cassandra REST API with Google Cloud Functions in Node.js
3+
4+
In this example we will manually set up a DataStax Distribution of Apache Cassandra instance in GCP, the same could also be done with Apache Cassandra or DataStax Enterprise.
5+
6+
### Launch an instance in Google Cloud
7+
8+
1. To get things started, log into the Google Cloud Console, if you do not have an account, create one. https://console.cloud.google.com/
9+
2. After logging in, create a new project for this example.
10+
3. Once the project is created, go to the Compute Engine panel via the navigation menu in the top left and select "VM Instances" and click "Create" when available.
11+
4. Follow the Create VM Instance wizard and change the image to be Ubuntu 18.04 LTS, also go to the Security portion and add your public key so that you can SSH to the instance from your computer.
12+
5. Once the instance is created, go to the Networking Details to create firewall rules that will allow inbound and outbound TCP traffic on the C* CQL port 9042. **Note: This is simply for example purposes and this security setting should never be deployed for any real use cases**.
13+
14+
### Start the database
15+
Now that we have an instance running in Google Cloud, we will install and start DDAC on that instance.
16+
17+
1. To SSH to your instance in Google Cloud, you will need to use the private key that is the pair to the public key that was uploaded in Step 4 above as well as the Public IP.
18+
```
19+
ssh -i <path-to-private-key> <user-name>@<public-ip>
20+
```
21+
2. Install Java 8
22+
```
23+
sudo apt-get update
24+
sudo apt-get install -y openjdk-8-jdk-headless
25+
```
26+
3. Install DDAC ( by installing you agree to the [terms of use](https://www.datastax.com/legal/datastax-distribution-apache-cassandra-ddac-terms) )
27+
```
28+
mkdir ddac; wget -c https://downloads.datastax.com/ddac/ddac-bin.tar.gz -O - | tar -xz -C ddac --strip-components=1
29+
```
30+
4. Set `broadcast_rpc_address` to the Public IP ( needed for client connections )
31+
```
32+
sed -i 's/# broadcast_rpc_address:.*/broadcast_rpc_address: <public-ip>/' ddac/conf/cassandra.yaml
33+
```
34+
5. Set `rpc_address` to listen on 0.0.0.0
35+
```
36+
sed -i 's/^rpc_address:.*/rpc_address: 0.0.0.0/' ddac/conf/cassandra.yaml
37+
```
38+
6. Start the database
39+
```
40+
ddac/bin/cassandra
41+
```
42+
7. Grab the data center name from the last line of output from the startup process, in the following example it is "datacenter1". You will need to set this as the `LOCAL_DC` value in serverless.yml
43+
```
44+
INFO [main] 2019-09-22 16:38:48,833 StorageService.java:722 - Snitch information: DynamicEndpointSnitch{registered=true, subsnitch=SimpleSnitch{, DC='datacenter1', rack='rack1'}}, local DC:datacenter1 / rack:rack1
45+
```
46+
47+
### Set up local development environment
48+
1. Install Node.js and NPM: https://docs.npmjs.com/downloading-and-installing-node-js-and-npm
49+
2. Install Serverless
50+
```
51+
npm install -g serverless
52+
```
53+
3. Follow the instructions for setting up serverless with your GCP Credentials https://serverless.com/framework/docs/providers/google/guide/credentials/
54+
55+
56+
Hope that wasn't too difficult and you are still here, if so now head on over to the main [README](README.md) to run this example.
57+
58+

index.js

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
'use strict';
2+
3+
const cassandra = require('cassandra-driver');
4+
const contactPoints = process.env['CONTACT_POINTS'];
5+
const localDataCenter = process.env['LOCAL_DC'];
6+
const keyspace = 'shopping';
7+
const table = 'catalog';
8+
9+
if (!contactPoints) throw new Error('Environment variable CONTACT_POINTS not set');
10+
if (!localDataCenter) throw new Error('Environment variable LOCAL_DC not set');
11+
12+
// useful for determining container re-use
13+
const myuuid = cassandra.types.TimeUuid.now();
14+
console.log('timeuuid in container startup: ' + myuuid);
15+
16+
const client = new cassandra.Client({
17+
contactPoints: contactPoints.split(','),
18+
localDataCenter,
19+
20+
// AWS Lambda freezes the function execution context after the callback has been invoked.
21+
// This means that no background activity can occur between lambda invocations,
22+
// including the heartbeat that the driver uses to prevent idle disconnects in some environments.
23+
// At this time we have not done enough testing to validate if this behavior applies to Google Cloud Functions as well,
24+
// though it may be best to disable heartbeats if this is the case. This is accomplished with the setting below.
25+
//
26+
// pooling: { heartBeatInterval: 0 }
27+
28+
// If trying to reduce Cold Start time, the driver's automatic metadata synchronization and pool warmup can be disabled
29+
//
30+
// isMetadataSyncEnabled: false,
31+
// pooling: { warmup: false }
32+
});
33+
34+
// Enable logging for the purpose of example
35+
client.on('log', (level, className, message) => {
36+
if (level !== 'verbose') {
37+
console.log('Driver log event', level, className, message);
38+
}
39+
});
40+
41+
const createKeyspace = `CREATE KEYSPACE IF NOT EXISTS ${keyspace} ` +
42+
`WITH REPLICATION = {'class':'NetworkTopologyStrategy','${localDataCenter}': 1}`;
43+
44+
const createTable = `CREATE TABLE IF NOT EXISTS ${keyspace}.${table} (item_id int, name text, description text, price decimal, ` +
45+
`PRIMARY KEY (item_id))`;
46+
47+
const writeQuery = `INSERT INTO ${keyspace}.${table} (item_id, name, description, price) VALUES (?, ?, ?, ?)`;
48+
49+
const readQuery = `SELECT name, description, price FROM ${keyspace}.${table} WHERE item_id = ?`;
50+
51+
client.connect()
52+
.then(() => console.log('Connected to the DSE cluster, discovered %d nodes', client.hosts.length))
53+
.catch(err => console.error('There was an error trying to connect', err));
54+
55+
async function createSchema() {
56+
await client.execute(createKeyspace);
57+
await client.execute(createTable);
58+
return {statusCode: 200, body: `Successfully created ${keyspace}.${table} schema`};
59+
}
60+
61+
async function addItem(item_id, name, description, price) {
62+
const params = [ item_id, name, description, price ];
63+
await client.execute(writeQuery, params, { prepare: true, isIdempotent: true });
64+
return {
65+
statusCode: 200,
66+
body: JSON.stringify({
67+
query: writeQuery,
68+
item_id: item_id,
69+
name: name,
70+
description: description,
71+
price: price
72+
})
73+
};
74+
}
75+
76+
async function getItem(item_id) {
77+
const params = [ item_id ];
78+
const result = await client.execute(readQuery, params, { prepare : true });
79+
const row = result.first();
80+
return {
81+
statusCode: 200,
82+
body: JSON.stringify({
83+
query: readQuery,
84+
item_id: item_id,
85+
name: row.name,
86+
description: row.description,
87+
price: row.price
88+
})
89+
};
90+
}
91+
92+
exports.createCatalog = async (req, res) => {
93+
console.log('timeuuid in createCatalog: ' + myuuid);
94+
const result = await createSchema();
95+
res.status(result.statusCode).json(result.body);
96+
};
97+
98+
exports.addItem = async (req, res) => {
99+
console.log('timeuuid in addItem: ' + myuuid);
100+
const result = await addItem(req.body.item_id, req.body.name, req.body.description, req.body.price);
101+
res.status(result.statusCode).json(JSON.parse(result.body));
102+
};
103+
104+
exports.getItem = async (req, res) => {
105+
console.log('timeuuid in getItem: ' + myuuid);
106+
const id = req.path.match(/\d+/g);
107+
const result = await getItem(id)
108+
res.status(result.statusCode).json(JSON.parse(result.body));
109+
};
110+

package.json

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "gcp-node-cassandra-http-api",
3+
"version": "1.0.0",
4+
"description": "Example shows how to build a Cassandra REST API with Google Cloud Function HTTP Endpoints in Node.js",
5+
"dependencies": {
6+
"cassandra-driver": "^4.3.0",
7+
"serverless-google-cloudfunctions": "^2.3.3"
8+
}
9+
}

serverless.yml

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
service: gcp-node-cassandra-http-api
2+
3+
provider:
4+
name: google
5+
stage: dev
6+
runtime: nodejs8
7+
region: us-central1
8+
project: <change-to-project-id>
9+
# See the document below for setting up the credentials for GCP and Google Cloud Functions:
10+
# https://serverless.com/framework/docs/providers/google/guide/credentials/
11+
#
12+
# the path to the credentials file needs to be absolute
13+
credentials: <change-to-path-to-keyfile-json>
14+
environment:
15+
CONTACT_POINTS: <change-to-public-ip>
16+
LOCAL_DC: <change-to-dc-name>
17+
18+
plugins:
19+
- serverless-google-cloudfunctions
20+
21+
functions:
22+
createCatalog:
23+
handler: createCatalog
24+
events:
25+
# it appears that the path here does not take effect and the endpoint is instead /createCatalog
26+
- http: catalog/create
27+
addItem:
28+
handler: addItem
29+
events:
30+
# it appears that the path here does not take effect and the endpoint is instead /addItem
31+
- http: catalog/add
32+
getItem:
33+
handler: getItem
34+
events:
35+
# it appears that the path here does not take effect and the endpoint is instead /getItem/{id}
36+
- http: catalog/get/{id}
37+
38+
package:
39+
include:
40+
- index.js
41+
excludeDevDependencies: false

0 commit comments

Comments
 (0)