Skip to content

Commit

Permalink
functions/billing: Node 8 + system tests (#1361)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ace Nassri authored and fhinkel committed Jun 22, 2019
1 parent ad43645 commit 9040e72
Show file tree
Hide file tree
Showing 8 changed files with 410 additions and 150 deletions.
7 changes: 7 additions & 0 deletions .kokoro/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
# limitations under the License.

export GCLOUD_PROJECT=nodejs-docs-samples-tests
export GCP_PROJECT=$GCLOUD_PROJECT
export GOOGLE_CLOUD_PROJECT=$GCLOUD_PROJECT

export GCF_REGION=us-central1
export NODE_ENV=development
export BUCKET_NAME=$GCLOUD_PROJECT
Expand Down Expand Up @@ -47,6 +50,10 @@ export NODEJS_IOT_EC_PUBLIC_KEY=${KOKORO_GFILE_DIR}/ec_public.pem
export NODEJS_IOT_RSA_PRIVATE_KEY=${KOKORO_GFILE_DIR}/rsa_private.pem
export NODEJS_IOT_RSA_PUBLIC_CERT=${KOKORO_GFILE_DIR}/rsa_cert.pem

# Configure Slack variables (for functions/slack sample)
export BOT_ACCESS_TOKEN=${KOKORO_GFILE_DIR}/secrets-slack-bot-access-token.txt
export CHANNEL=${KOKORO_GFILE_DIR}/secrets-slack-channel-id.txt

cd github/nodejs-docs-samples/${PROJECT}

# Install dependencies
Expand Down
25 changes: 25 additions & 0 deletions .kokoro/functions/billing-periodic.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Format: //devtools/kokoro/config/proto/build.proto

# Set the folder in which the tests are run
env_vars: {
key: "PROJECT"
value: "functions/billing"
}

# Set the test command to run (only for functions/billing)
env_vars: {
key: "TEST_CMD"
value: "compute-test"
}

# Configure the docker image for kokoro-trampoline.
env_vars: {
key: "TRAMPOLINE_IMAGE"
value: "gcr.io/cloud-devrel-kokoro-resources/node:8-user"
}

# Tell the trampoline which build file to use.
env_vars: {
key: "TRAMPOLINE_BUILD_FILE"
value: "github/nodejs-docs-samples/.kokoro/functions/functions-billing.sh"
}
8 changes: 4 additions & 4 deletions .kokoro/functions/billing.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ env_vars: {
value: "functions/billing"
}

# Configure the docker image for kokoro-trampoline.
# Set the test command to run (only for functions/billing)
env_vars: {
key: "TRAMPOLINE_IMAGE"
value: "gcr.io/cloud-devrel-kokoro-resources/node:8-user"
key: "TEST_CMD"
value: "test"
}

# Tell the trampoline which build file to use.
env_vars: {
key: "TRAMPOLINE_BUILD_FILE"
value: "github/nodejs-docs-samples/.kokoro/build.sh"
value: "github/nodejs-docs-samples/.kokoro/functions/functions-billing.sh"
}
41 changes: 41 additions & 0 deletions .kokoro/functions/functions-billing.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash

# Copyright 2019, Google LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

export GCLOUD_PROJECT=cdpe-functions-billing-test
export GCP_PROJECT=$GCLOUD_PROJECT
export GOOGLE_CLOUD_PROJECT=$GCLOUD_PROJECT

export GCF_REGION=us-central1
export NODE_ENV=development

# Configure Slack variables
export BOT_ACCESS_TOKEN=$(cat ${KOKORO_GFILE_DIR}/secrets-slack-bot-access-token.txt)
export CHANNEL=$(cat ${KOKORO_GFILE_DIR}/secrets-slack-channel-id.txt)
export BILLING_ACCOUNT=$(cat ${KOKORO_GFILE_DIR}/secrets-billing-account-id.txt)

cd github/nodejs-docs-samples/${PROJECT}

# Install dependencies
npm install

# Configure gcloud
export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/secrets-key.json
gcloud auth activate-service-account --key-file "$GOOGLE_APPLICATION_CREDENTIALS"
gcloud config set project $GCLOUD_PROJECT

npm run ${TEST_CMD}

exit $?
121 changes: 104 additions & 17 deletions functions/billing/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2018, Google LLC.
* Copyright 2019, Google LLC.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
Expand All @@ -13,6 +13,8 @@
* limitations under the License.
*/

/* eslint-disable no-warning-comments */

// [START functions_billing_limit]
// [START functions_billing_stop]
const {google} = require('googleapis');
Expand All @@ -26,29 +28,33 @@ const PROJECT_NAME = `projects/${PROJECT_ID}`;
// [START functions_billing_slack]
const slack = require('slack');

const BOT_ACCESS_TOKEN = 'xxxx-111111111111-abcdefghidklmnopq';
const CHANNEL = 'general';
// TODO(developer) replace these with your own values
const BOT_ACCESS_TOKEN =
process.env.BOT_ACCESS_TOKEN || 'xxxx-111111111111-abcdefghidklmnopq';
const CHANNEL = process.env.SLACK_CHANNEL || 'general';

exports.notifySlack = async (data, context) => {
const pubsubMessage = data;
const pubsubAttrs = JSON.stringify(pubsubMessage.attributes);
const pubsubData = Buffer.from(pubsubMessage.data, 'base64').toString();
exports.notifySlack = async (pubsubEvent, context) => {
const pubsubAttrs = pubsubEvent.attributes;
const pubsubData = Buffer.from(pubsubEvent.data, 'base64').toString();
const budgetNotificationText = `${pubsubAttrs}, ${pubsubData}`;

const res = await slack.chat.postMessage({
await slack.chat.postMessage({
token: BOT_ACCESS_TOKEN,
channel: CHANNEL,
text: budgetNotificationText,
});
console.log(res);

return 'Slack notification sent successfully';
};
// [END functions_billing_slack]

// [START functions_billing_stop]
const billing = google.cloudbilling('v1').projects;

exports.stopBilling = async (data, context) => {
const pubsubData = JSON.parse(Buffer.from(data.data, 'base64').toString());
exports.stopBilling = async (pubsubEvent, context) => {
const pubsubData = JSON.parse(
Buffer.from(pubsubEvent.data, 'base64').toString()
);
if (pubsubData.costAmount <= pubsubData.budgetAmount) {
return `No action necessary. (Current cost: ${pubsubData.costAmount})`;
}
Expand Down Expand Up @@ -105,19 +111,50 @@ const _disableBillingForProject = async projectName => {
};
// [END functions_billing_stop]

// Helper function to restart billing (used in tests)
exports.startBilling = async (pubsubEvent, context) => {
const pubsubData = JSON.parse(
Buffer.from(pubsubEvent.data, 'base64').toString()
);

await _setAuthCredential();
if (!(await _isBillingEnabled(PROJECT_NAME))) {
// Enable billing

const res = await billing.updateBillingInfo({
name: pubsubData.projectName,
resource: {
billingAccountName: pubsubData.billingAccountName,
billingEnabled: true,
},
});
return `Billing enabled: ${JSON.stringify(res.data)}`;
} else {
return 'Billing already enabled';
}
};

// [START functions_billing_limit]
const compute = google.compute('v1');
const ZONE = 'us-west1-b';
const ZONE = 'us-west1-a';

exports.limitUse = async (data, context) => {
const pubsubData = JSON.parse(Buffer.from(data.data, 'base64').toString());
exports.limitUse = async (pubsubEvent, context) => {
const pubsubData = JSON.parse(
Buffer.from(pubsubEvent.data, 'base64').toString()
);
if (pubsubData.costAmount <= pubsubData.budgetAmount) {
return `No action necessary. (Current cost: ${pubsubData.costAmount})`;
}

await _setAuthCredential();

const instanceNames = await _listRunningInstances(PROJECT_ID, ZONE);
if (!instanceNames.length) {
return 'No running instances were found.';
}

await _stopInstances(PROJECT_ID, ZONE, instanceNames);
return `${instanceNames.length} instance(s) stopped successfully.`;
};

/**
Expand All @@ -139,9 +176,6 @@ const _listRunningInstances = async (projectId, zone) => {
* @return {Promise} Response from stopping instances
*/
const _stopInstances = async (projectId, zone, instanceNames) => {
if (!instanceNames.length) {
return 'No running instances were found.';
}
await Promise.all(
instanceNames.map(instanceName => {
return compute.instances
Expand All @@ -158,3 +192,56 @@ const _stopInstances = async (projectId, zone, instanceNames) => {
);
};
// [END functions_billing_limit]

// Helper function to restart instances (used in tests)
exports.startInstances = async (pubsubEvent, context) => {
await _setAuthCredential();
const instanceNames = await _listStoppedInstances(PROJECT_ID, ZONE);

if (!instanceNames.length) {
return 'No stopped instances were found.';
}

await _startInstances(PROJECT_ID, ZONE, instanceNames);
return `${instanceNames.length} instance(s) started successfully.`;
};

/**
* @return {Promise} Array of names of running instances
*/
const _listStoppedInstances = async (projectId, zone) => {
const res = await compute.instances.list({
project: projectId,
zone: zone,
});

const instances = res.data.items || [];
const stoppedInstances = instances.filter(item => item.status !== 'RUNNING');
return stoppedInstances.map(item => item.name);
};

/**
* @param {Array} instanceNames Names of instance to stop
* @return {Promise} Response from stopping instances
*/
const _startInstances = async (projectId, zone, instanceNames) => {
if (!instanceNames.length) {
return 'No stopped instances were found.';
}
await Promise.all(
instanceNames.map(instanceName => {
return compute.instances.start({
project: projectId,
zone: zone,
instance: instanceName,
});
})
);
};

// Helper function used in tests
exports.listRunningInstances = async (pubsubEvent, context) => {
await _setAuthCredential();
console.log(PROJECT_ID, ZONE);
return _listRunningInstances(PROJECT_ID, ZONE);
};
10 changes: 8 additions & 2 deletions functions/billing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"node": ">=8.0.0"
},
"scripts": {
"test": "mocha test/*.test.js --timeout=20000"
"compute-test": "mocha test/periodic.test.js --timeout=600000",
"test": "mocha test/index.test.js --timeout=5000"
},
"author": "Ace Nassri <anassri@google.com>",
"license": "Apache-2.0",
Expand All @@ -17,9 +18,14 @@
"slack": "^11.0.1"
},
"devDependencies": {
"@google-cloud/functions-framework": "^1.1.1",
"@google-cloud/nodejs-repo-tools": "^3.3.0",
"child-process-promise": "^2.2.1",
"mocha": "^6.0.0",
"promise-retry": "^1.1.1",
"proxyquire": "^2.1.0",
"sinon": "^7.0.0"
"request": "^2.88.0",
"requestretry": "^4.0.0",
"sinon": "^7.3.2"
}
}
Loading

0 comments on commit 9040e72

Please sign in to comment.