0
votes

On GCP, I created a CloudFunction which is trigged by billing events from Pub/Sub and is publishing some messages on Slack. I am using node.js 10.I have the following dependencies:

{
  "name": "google-container-slack",
  "version": "0.0.1",
  "description": "Slack integration for Google Cloud Build, using Google Cloud Functions",
  "main": "index.js",
  "dependencies": {
    "@slack/webhook": "5.0.3",
    "@google-cloud/bigquery": "4.7.0",
    "@google-cloud/pubsub": "1.6.0"
  }
}

I have some issue when I add a function to write new budget info in BigQuery which is based on some official example: https://github.com/googleapis/nodejs-bigquery/blob/master/samples/insertRowsAsStream.js

// writeInBigQuery update BigQuery table
async function writeInBigQuery(pubsubdata, createdAt, project, threshold) {
  const bigquery = new BigQuery({projectId: billing_project});

  const rows = [{createdAt: createdAt},
                {budgetAmount:pubsubdata.budgetAmount},
                {projectName: project},
                {thresholdValue: threshold}];
  console.log(rows);

  console.log('start insert row in bigquery');
  await bigquery
    .dataset(dataset)
    .table(table)
    .insert(rows);
  console.log('end insert row in bigquery');

  console.log(`Inserted ${rows.length} rows`);
}

My guess is that the issue is related to async and await. It is my first code with node.js and the error message is cryptic for me:

 Function execution took 266 ms, finished with status: 'ok' 
Unhandled rejection 
PartialFailureError
     at request (/layers/google.nodejs.npm/npm/node_modules/@google-cloud/bigquery/build/src/table.js:1550:23)
     at util.makeRequest.params (/layers/google.nodejs.npm/npm/node_modules/@google-cloud/common/build/src/util.js:367:25)
8     at Util.handleResp (/layers/google.nodejs.npm/npm/node_modules/@google-cloud/common/build/src/util.js:144:9)
8     at retryRequest (/layers/google.nodejs.npm/npm/node_modules/@google-cloud/common/build/src/util.js:432:22)
     at onResponse (/layers/google.nodejs.npm/npm/node_modules/retry-request/index.js:206:7)
8     at /layers/google.nodejs.npm/npm/node_modules/teeny-request/build/src/index.js:233:13 
     at process._tickCallback (internal/process/next_tick.js:68:7)
8 Error: Process exited with code 16
    at process.on.code (/layers/google.nodejs.functions-framework/functions-framework/node_modules/@google-cloud/functions-framework/build/src/invoker.js:393:29)
    at process.emit (events.js:198:13)
    at process.EventEmitter.emit (domain.js:448:20)
    at process.exit (internal/process/per_thread.js:168:15)
    at logAndSendError (/layers/google.nodejs.functions-framework/functions-framework/node_modules/@google-cloud/functions-framework/build/src/invoker.js:184:9)
    at process.on.err (/layers/google.nodejs.functions-framework/functions-framework/node_modules/@google-cloud/functions-framework/build/src/invoker.js:390:13)
    at process.emit (events.js:198:13)
    at process.EventEmitter.emit (domain.js:448:20)
    at emitPromiseRejectionWarnings (internal/process/promises.js:140:18)
    at process._tickCallback (internal/process/next_tick.js:69:34)

Maybe the issue is related to the overall structure of the code:

const { IncomingWebhook } = require('@slack/webhook');
const {BigQuery} = require('@google-cloud/bigquery');

const url = process.env.SLACK_WEBHOOK_URL;
const project =process.env.PROJECT_LIST.split(',');
const dataset = process.env.DATASET;
const table = process.env.TABLE;
const billing_project = process.env.PROJECT;

const webhook = new IncomingWebhook(url);

// subscribeSlack is the main function called by Cloud Functions.
module.exports.subscribeSlack= (pubSubEvent, context) => {
  const pubsubdata = eventToBuild(pubSubEvent.data);

  //select for which project to send budget alert
  if (project.indexOf(pubsubdata.budgetDisplayName) === -1) {
    console.log(`skip project: ${pubsubdata.budgetDisplayName.substr(0,pubsubdata.budgetDisplayName.indexOf(' '))}`);
    return;
  }
  console.log(`project: ${pubsubdata.budgetDisplayName.substr(0,pubsubdata.budgetDisplayName.indexOf(' '))}`);

  // Send message to Slack.
  const message = createSlackMessage(pubsubdata);
  webhook.send(message);
};

// eventToBuild transforms pubsub event message to a build object.
const eventToBuild = (data) => {
  return JSON.parse(Buffer.from(data, 'base64').toString());
}

// writeInBigQuery update BigQuery table
async function writeInBigQuery(pubsubdata, createdAt, project, threshold) {
  const bigquery = new BigQuery({projectId: billing_project});

  const rows = [{createdAt: createdAt},
                {budgetAmount:pubsubdata.budgetAmount},
                {projectName: project},
                {thresholdValue: threshold}];
  console.log(rows);

  console.log('start insert row in bigquery');
  await bigquery
    .dataset(dataset)
    .table(table)
    .insert(rows);
  console.log('end insert row in bigquery');

  console.log(`Inserted ${rows.length} rows`);
}

// createSlackMessage creates a message from a build object.
const createSlackMessage = (pubsubdata) => {

  const formatter = new Intl.NumberFormat('de-DE', {style: 'currency', currency: 'EUR', minimumFractionDigits: 2})
  const costAmount = formatter.format(pubsubdata.costAmount);
  const budgetAmount = formatter.format(pubsubdata.budgetAmount);
  const budgetName = pubsubdata.budgetDisplayName;
  const createdAt = new Date().toISOString();
  const project = budgetName.substr(0,budgetName.indexOf(' '))
  let threshold = (pubsubdata.alertThresholdExceeded*100).toFixed(0);
  if (!isFinite(threshold)){
   threshold = 0;
  }

  // write current budget info in BigQuery
  console.log('big query call start');
  writeInBigQuery(pubsubdata, createdAt, project, threshold);
  console.log('big query call end');

  // create Slack message
  const emoticon = threshold >= 90 ? ':fire:' : ':white_check_mark:';
  notification = `${emoticon} Project: ${project}\nOverall cost:  ${costAmount} \nTotal Budget: ${budgetAmount}\nThresold: ${threshold}%`
  const message = {
    text: notification
  };
  return message;
}
1
Are you using promises? If so, please make sure to add a catch on every promise that could be rejected. A similar scenario here stackoverflow.com/questions/48028366/… - Kevin Quinzel
it seems that async and await are shorthand for chaining promise: medium.com/better-programming/… - Dr. Fabien Tarrade

1 Answers

0
votes

The issue had nothing to do with the asyn ... wait but was a mistake in the in the way I was preparing the data to write in BigQuery:

const rows = [{createdAt: createdAt},
              {budgetAmount:pubsubdata.budgetAmount},
              {projectName: project},
              {thresholdValue: threshold}];

and it should be:

const rows = [{createdAt: createdAt,
              budgetAmount:pubsubdata.budgetAmount,
              projectName: project,
              thresholdValue: threshold}];

With this fix this is working fine.

The final and is working find can be find here: index.js