Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds tests for HTTP device sample #534

Merged
merged 7 commits into from
Dec 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions iot/http_example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,29 @@ Example Google Cloud IoT Core HTTP device connection code.

Options:

-h, --help output usage information
--project_id <project_id> GCP cloud project name.
--registry_id <registry_id> Cloud IoT Core registry id.
--device_id <device_id> Cloud IoT Core device id.
--private_key_file <key_file> Path to private key file.
--algorithm <algorithm> Encryption algorithm to generate the JWT.
Either RS256 (RSA) or ES256 (Eliptic Curve)
--cloud_region [region] GCP cloud region
--num_messages [num] Number of messages to publish.
--token_exp_mins [num] Minutes to JWT token expiration.
--http_bridge_address [address] HTTP bridge address.
--message_type [events|state] The message type to publish.
-h, --help output usage information
--projectId <projectId> GCP cloud project name.
--registryId <registryId> Cloud IoT Core registry id.
--deviceId <deviceId> Cloud IoT Core device id.
--privateKeyFile <key_file> Path to private key file.
--algorithm <algorithm> Encryption algorithm to generate the JWT.
Either RS256 (RSA) or ES256 (Eliptic Curve)
--cloudRegion [region] GCP cloud region
--numMessages [num] Number of messages to publish.
--tokenExpMins [num] Minutes to JWT token expiration.
--httpBridgeAddress [address] HTTP bridge address.
--messageType [events|state] The message type to publish.

For example, if your project ID is `blue-jet-123`, your service account
credentials are stored in your home folder in creds.json and you have generated
your credentials using the shell script provided in the parent folder, you can
run the sample as:

node cloudiot_http_example_nodejs.js \
--project_id=blue-jet-123 \
--registry_id=my-registry \
--device_id=my-node-device \
--private_key_file=../rsa_private.pem \
--projectId=blue-jet-123 \
--registryId=my-registry \
--deviceId=my-node-device \
--privateKeyFile=../rsa_private.pem \
--algorithm=RS256

# Reading Cloud Pub/Sub messages written by the sample client
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,31 +23,31 @@ const request = require('request');
console.log('Google Cloud IoT Core HTTP example.');
var argv = require(`yargs`)
.options({
project_id: {
projectId: {
default: process.env.GCLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT,
description: 'The Project ID to use. Defaults to the value of the GCLOUD_PROJECT or GOOGLE_CLOUD_PROJECT environment variables.',
requiresArg: true,
type: 'string'
},
cloud_region: {
cloudRegion: {
default: 'us-central1',
description: 'GCP cloud region.',
requiresArg: true,
type: 'string'
},
registry_id: {
registryId: {
description: 'Cloud IoT registry ID.',
requiresArg: true,
demandOption: true,
type: 'string'
},
device_id: {
deviceId: {
description: 'Cloud IoT device ID.',
requiresArg: true,
demandOption: true,
type: 'string'
},
private_key_file: {
privateKeyFile: {
description: 'Path to private key file.',
requiresArg: true,
demandOption: true,
Expand All @@ -60,40 +60,55 @@ var argv = require(`yargs`)
choices: ['RS256', 'ES256'],
type: 'string'
},
num_messages: {
numMessages: {
default: 100,
description: 'Number of messages to publish.',
requiresArg: true,
type: 'number'
},
token_exp_mins: {
tokenExpMins: {
default: 20,
description: 'Minutes to JWT token expiration.',
requiresArg: true,
type: 'number'
},
http_bridge_address: {
httpBridgeAddress: {
default: 'cloudiot-device.googleapis.com',
description: 'HTTP bridge address.',
requiresArg: true,
type: 'string'
},
message_type: {
messageType: {
default: 'events',
description: 'Message type to publish.',
requiresArg: true,
choices: ['events', 'state'],
type: 'string'
}
})
.example(`node $0 cloudiot_http_example_nodejs.js --project_id=blue-jet-123 --registry_id=my-registry --device_id=my-node-device --private_key_file=../rsa_private.pem --algorithm=RS256`)
.example(`node $0 cloudiotHttp_example_nodejs.js --projectId=blue-jet-123 --registryId=my-registry --deviceId=my-node-device --privateKeyFile=../rsaPrivate.pem --algorithm=RS256`)
.wrap(120)
.recommendCommands()
.epilogue(`For more information, see https://cloud.google.com/iot-core/docs`)
.help()
.strict()
.argv;

// [START iot_http_variables]
// A unique string that identifies this device. For Google Cloud IoT Core, it
// must be in the format below.

let iatTime = parseInt(Date.now() / 1000);
let authToken = createJwt(argv.projectId, argv.privateKeyFile, argv.algorithm);
const devicePath = `projects/${argv.projectId}/locations/${argv.cloudRegion}/registries/${argv.registryId}/devices/${argv.deviceId}`;

// The request path, set accordingly depending on the message type.
const pathSuffix = argv.messageType === 'events'
? ':publishEvent' : ':setState';
const urlBase = `https://${argv.httpBridgeAddress}/v1beta1/${devicePath}`;
const url = `${urlBase}${pathSuffix}`;
// [END iot_http_variables]

// Create a Cloud IoT Core JWT for the given project ID, signed with the given
// private key.
// [START iot_http_jwt]
Expand All @@ -115,11 +130,11 @@ function createJwt (projectId, privateKeyFile, algorithm) {
// messageCount. Telemetry events are published at a rate of 1 per second and
// states at a rate of 1 every 2 seconds.
// [START iot_http_publish]
function publishAsync (messageCount, numMessages) {
const payload = `${argv.registry_id}/${argv.device_id}-payload-${messageCount}`;
function publishAsync (authToken, messageCount, numMessages) {
const payload = `${argv.registryId}/${argv.deviceId}-payload-${messageCount}`;
console.log('Publishing message:', payload);
const binaryData = Buffer.from(payload).toString('base64');
const postData = argv.message_type === 'events' ? {
const postData = argv.messageType === 'events' ? {
binary_data: binaryData
} : {
state: {
Expand All @@ -129,16 +144,15 @@ function publishAsync (messageCount, numMessages) {
const options = {
url: url,
headers: {
'authorization': 'Bearer ' + authToken,
'authorization': `Bearer ${authToken}`,
'content-type': 'application/json',
'cache-control': 'no-cache'

},
json: true,
body: postData
};
// Send events for high-frequency updates, update state only occasionally.
const delayMs = argv.message_type === 'events' ? 1000 : 2000;
const delayMs = argv.messageType === 'events' ? 1000 : 2000;
request.post(options, function (error, response, body) {
if (error) {
console.error('Received error: ', error);
Expand All @@ -152,11 +166,10 @@ function publishAsync (messageCount, numMessages) {
// messageCount + 1.
setTimeout(function () {
let secsFromIssue = parseInt(Date.now() / 1000) - iatTime;
if (secsFromIssue > argv.token_exp_mins * 60) {
if (secsFromIssue > argv.tokenExpMins * 60) {
iatTime = parseInt(Date.now() / 1000);
console.log(`\tRefreshing token after ${secsFromIssue} seconds.`);

authToken = createJwt(argv.project_id, argv.private_key_file, argv.algorithm);
authToken = createJwt(argv.projectId, argv.privateKeyFile, argv.algorithm);
}

publishAsync(messageCount + 1, numMessages);
Expand All @@ -166,18 +179,36 @@ function publishAsync (messageCount, numMessages) {
}
// [END iot_http_publish]

// [START iot_http_getconfig]
function getConfig (authToken, version) {
console.log(`Getting config from URL: ${urlBase}`);
const options = {
url: urlBase + '/config?local_version=' + version,
headers: {
'authorization': `Bearer ${authToken}`,
'content-type': 'application/json',
'cache-control': 'no-cache'

},
json: true
};
request.get(options, function (error, response, body) {
if (error) {
console.error('Received error: ', error);
} else if (response.body.error) {
console.error(`Received error: ${JSON.stringify(response.body.error)}`);
} else {
console.log('Received config', JSON.stringify(body));
}
});
}
// [END iot_http_getconfig]

// [START iot_run_http]
// A unique string that identifies this device. For Google Cloud IoT Core, it
// must be in the format below.
const devicePath = `projects/${argv.project_id}/locations/${argv.cloud_region}/registries/${argv.registry_id}/devices/${argv.device_id}`;

// The request path, set accordingly depending on the message type.
const pathSuffix = argv.message_type === 'events'
? ':publishEvent' : ':setState';
const url = `https://${argv.http_bridge_address}/v1beta1/${devicePath}${pathSuffix}`;
let iatTime = parseInt(Date.now() / 1000);
let authToken = createJwt(argv.project_id, argv.private_key_file, argv.algorithm);
// Print latest configuration
getConfig(authToken, 0);

// Publish messages.
publishAsync(1, argv.num_messages);
publishAsync(authToken, 1, argv.numMessages);
// [END iot_run_http]
13 changes: 12 additions & 1 deletion iot/http_example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,21 @@
"license": "Apache-2.0",
"author": "Google Inc.",
"main": "cloudiot_http_example_nodejs.js",
"scripts": {
"lint": "samples lint",
"pretest": "npm run lint",
"test": "samples test run --cmd ava -- -T 3m --verbose system-test/*.test.js"
},
"dependencies": {
"@google-cloud/pubsub": "0.13.2",
"@google-cloud/nodejs-repo-tools": "1.4.17",
"ava": "0.22.0",
"yargs": "8.0.2",
"jsonwebtoken": "7.4.1",
"request": "2.82.0"
"request": "2.82.0",
"uuid": "3.1.0"
},
"testDependencies": {
},
"devDependencies": {}
}
4 changes: 4 additions & 0 deletions iot/http_example/resources/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Test public certificate files

The public certificates in this folder are only provided for testing and should
not be used for registering your devices.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we use these in our CI tests - if so, I assume they are sufficiently secure?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, these certificates are used in our CI tests. The authorization credentials are revoked when the tests tear down.

18 changes: 18 additions & 0 deletions iot/http_example/resources/rsa_cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC9TCCAd2gAwIBAgIJALM44e3ivEWkMA0GCSqGSIb3DQEBCwUAMBExDzANBgNV
BAMMBnVudXNlZDAeFw0xNzEyMDcwMDQ1MjdaFw0yNzEyMDUwMDQ1MjdaMBExDzAN
BgNVBAMMBnVudXNlZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL4w
BxHuEyYdbiwKiD8yXY7vYpcygeOQ4/ZdEg3wCB2OuYcaFRcCuqLcTLMnuzdcL+y3
HBjWkrRW658cg3NG93Vj0iwSrga6u24CGBNYV+h8MBvwaDxk+uubnd5M/Q2OyL1J
GiMxQ1blR/71Hr5hhqaQZ2+qOF6kuf1m9rLUtMUJwOKp/PjPDmy654ZGsFWFSZmy
eRpNzmGU+KJg0o+Qf+sm75a8gQZ8AsrqveW0S/8o+zAjD0SkPcd01QBmYzQhjbi/
LGGITrzbaB3ld9umJBIcXfnYPYisJfwSsT/jFwiXhrhpxNNaIaKlTzlQIt5l8bSs
HXzJBbuIg5Jb/SyIEpkCAwEAAaNQME4wHQYDVR0OBBYEFOfaQTUVAoNb6fc7qzzl
uKyHGrCYMB8GA1UdIwQYMBaAFOfaQTUVAoNb6fc7qzzluKyHGrCYMAwGA1UdEwQF
MAMBAf8wDQYJKoZIhvcNAQELBQADggEBALKKDtiV1YV8k0YsNdiIXRlS3jsuoGuI
VVBrvDGz5Hel0rH9YmmfPS/Yn08kk3DF8Uynr4Xo1Zt9hmhgoq3ZoWm7MIP1+a9s
WyACyEMhVQSCzQrexRvG5ElpHx/vNjbcwiBkE5urlIvMBVt+BRRNKMNWq6F9ae63
FxRp7CtNFSbibtLJuPgCs6qoNs0nlt2FPsNvs7jpPipj69o+egVckvQjAyppirWO
+jO5hCLy7EahLz2wCn90z0Xf9lhOZni9meaV1Vy3CHHg6jwIB8/XlRaHFrOGMGXg
h8eQqsmpk9/3o8pv00yj6Hkq+swVg7Rg9FZaUiOv/HO/J7stWU7qPbI=
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions iot/http_example/resources/rsa_private.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC+MAcR7hMmHW4s
Cog/Ml2O72KXMoHjkOP2XRIN8AgdjrmHGhUXArqi3EyzJ7s3XC/stxwY1pK0Vuuf
HINzRvd1Y9IsEq4GurtuAhgTWFfofDAb8Gg8ZPrrm53eTP0Njsi9SRojMUNW5Uf+
9R6+YYamkGdvqjhepLn9Zvay1LTFCcDiqfz4zw5suueGRrBVhUmZsnkaTc5hlPii
YNKPkH/rJu+WvIEGfALK6r3ltEv/KPswIw9EpD3HdNUAZmM0IY24vyxhiE6822gd
5XfbpiQSHF352D2IrCX8ErE/4xcIl4a4acTTWiGipU85UCLeZfG0rB18yQW7iIOS
W/0siBKZAgMBAAECggEAfwLmBdRfl2m6JNFX0hSZpJY72kuRsN8XTnUzVHmDgfHJ
9u61POvGpnLHCjIzdjIrk0NqETBjQup1aooJQ1gWdKAYQPSsobPc7geZ+nlaI9mj
61Su1/58EBKZ6Faz/HTpnHeQbAY/OW3fmeYrBOtumBgB6/HauWH7D77Oa/lfS+Ij
4f6OVAxevsi6PUtNmNtBwk5S0lZl9SFcKeHurVindquX9vWZjBEEFtNXazJttIJS
z9KX29VYwoLfflIKaUKckn8X+wYc+3u3BvH8zJpd60yQ6MSo7Sb1XkxT9549m+JW
Cb+i1K7MC/yQo4mvDtAQIVBh8p8qpd4VjpBwMuUbgQKBgQDexuAaLO3adSYFXGwW
nom6Mz/ImYcpxYo0ouAR1talbmF5/oKl9Tcwh7l1eDHfe70gfeP+g4uwAcc1hx3a
ZtXusrJFBktFezlFQnZXaE5ppryrFWeu0he0RYLAVxnL6IlP9dYQhVsTZm+7uX5d
UP7aZtmOU9ZTEsAoqvjJQXvaCQKBgQDajPebXOxIUj8ffGTeiPZczTwXux04caDC
eFKSCbAlHWgG7mR4P3fQONfEGWNHF0CxBSrew9CHmKdPyiISaExCfUaUWDDCPQCp
UE5VAHPdjSlb4lqi+cyNVlJxBJGONtyYkbQNd6N9GHMnBS8jZi7zf8VzIXpeExA4
n79Aml/YEQKBgDFrGId19AWD+z0xNWEHJjJB8CJFvHANvAzVHLOYXuEvzTvMs5qw
/N8tHHzsftO+lUPB6XOqJrCSlGhRYtPx//8FcPpS3Ru6rAerKKlXIB3buPqSsv9a
55s72DdmmvhayysLs8LSclOpY5vXGCsHLqGwMw6Zlm+zNyFOXAX5GspRAoGAaJMx
W68ABK8OM0OzhGQm9kriKTzIg5yjXspyQBzQo0HJ6B8kBgHgk8rPO68mOPsgYlPl
qogp/OgHjv9ahFJRwzLslckJM7g628loYfYAew+zrZrG4dsDjNG0Sw3zlAgeUAbQ
D+2iVhZf61josFiRuMP3t9paEi+vAFk4C3KSz/ECgYBpi1akpIzsYehW5uOL7Jhw
Hay5eshQ4vmHYuhDnn3gtT3h6J7TMwWs9pOygBG1I1b7GJ+tp4BZWJ2PmI7P8s45
jdI99WODHwv03lAzjLwigoqDUDduaYqXcGghcGht5Sknkl2uYDChwLtI5JdBZ9/x
8h9dE9oAiH/KTzhPmK1E1Q==
-----END PRIVATE KEY-----
Loading