From bd545e51a0d18c224a3a28a6f9720904d7884a58 Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Fri, 4 May 2018 13:37:05 -0700 Subject: [PATCH] Add Alerting samples. (#41) --- monitoring/snippets/.gitignore | 1 + monitoring/snippets/README.md | 72 +++- monitoring/snippets/alerts.js | 361 ++++++++++++++++++ monitoring/snippets/package-lock.json | 15 +- monitoring/snippets/package.json | 2 +- .../snippets/system-test/alerts.test.js | 190 +++++++++ 6 files changed, 622 insertions(+), 19 deletions(-) create mode 100644 monitoring/snippets/.gitignore create mode 100644 monitoring/snippets/alerts.js create mode 100644 monitoring/snippets/system-test/alerts.test.js diff --git a/monitoring/snippets/.gitignore b/monitoring/snippets/.gitignore new file mode 100644 index 0000000000..e3fd3ac19d --- /dev/null +++ b/monitoring/snippets/.gitignore @@ -0,0 +1 @@ +policies_backup.json \ No newline at end of file diff --git a/monitoring/snippets/README.md b/monitoring/snippets/README.md index 37d70aa3ed..c959947849 100644 --- a/monitoring/snippets/README.md +++ b/monitoring/snippets/README.md @@ -1,3 +1,5 @@ +[//]: # "This README.md file is auto-generated, all changes to this file will be lost." +[//]: # "To regenerate it, use `npm run generate-scaffolding`." Google Cloud Platform logo # Stackdriver Monitoring: Node.js Samples @@ -10,6 +12,7 @@ * [Before you begin](#before-you-begin) * [Samples](#samples) + * [Alert Policies](#alert-policies) * [Metrics](#metrics) * [Uptime Config](#uptime-config) @@ -21,9 +24,56 @@ library's README. ## Samples +### Alert Policies + +View the [source code][alerts_0_code]. + +[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-monitoring&page=editor&open_in_editor=samples/alerts.js,samples/README.md) + +__Usage:__ `node alerts.js --help` + +``` +alerts.js + +Commands: + alerts.js backup Save alert policies to a ./policies_backup.json file. + alerts.js restore Restore alert policies from a ./policies_backup.json file. + alerts.js replace Replace the notification channels of the specified alert policy. + alerts.js disable [filter] Disables policies that match the given filter. + alerts.js enable [filter] Enables policies that match the given filter. + +Options: + --version Show version number [boolean] + --alertPolicyName [string] + --help Show help [boolean] + +Examples: + node alerts.js backup my-project-id Backup policies. + node alerts.js restore my-project-id Restore policies. + node alerts.js replace Replace the notification channels of the specified alert + projects/my-project-id/alertPolicies/12345 channel-1 policy. + channel-2 channel-3 + node alerts.js disable my-project-id "(NOT Disables policies that match the given filter. + display_name.empty OR NOT description.empty) AND + user_labels='active'" + node alerts.js disable my-project-id "description:'cloud'" Disables policies that match the given filter. + node alerts.js disable my-project-id Disables policies that match the given filter. + "display_name=monitoring.regex.full_match('Temp \d{4}')" + node alerts.js enable my-project-id "(NOT display_name.empty Enables policies that match the given filter. + OR NOT description.empty) AND user_labels='active'" + node alerts.js enable my-project-id "description:'cloud'" Enables policies that match the given filter. + node alerts.js enable my-project-id Enables policies that match the given filter. + "display_name=monitoring.regex.full_match('Temp \d{4}')" + +For more information, see https://cloud.google.com/monitoring/docs/ +``` + +[alerts_0_docs]: https://cloud.google.com/monitoring/docs +[alerts_0_code]: alerts.js + ### Metrics -View the [source code][metrics_0_code]. +View the [source code][metrics_1_code]. [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-monitoring&page=editor&open_in_editor=samples/metrics.js,samples/README.md) @@ -52,7 +102,7 @@ Commands: Options: --version Show version number [boolean] - --projectId, -p [string] + --projectId, -p [string] [default: "nodejs-docs-samples"] --help Show help [boolean] Examples: @@ -71,12 +121,12 @@ Examples: For more information, see https://cloud.google.com/monitoring/docs ``` -[metrics_0_docs]: https://cloud.google.com/monitoring/docs -[metrics_0_code]: metrics.js +[metrics_1_docs]: https://cloud.google.com/monitoring/docs +[metrics_1_code]: metrics.js ### Uptime Config -View the [source code][uptime_1_code]. +View the [source code][uptime_2_code]. [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-monitoring&page=editor&open_in_editor=samples/uptime.js,samples/README.md) @@ -86,7 +136,7 @@ __Usage:__ `node uptime.js --help` uptime.js Commands: - uptime.js create [projectId] Creates an uptime check config. + uptime.js create [projectId] Creates an uptime check config. uptime.js list [projectId] Lists uptime check configs. uptime.js list-ips Lists uptime check config IPs. uptime.js get [projectId] Gets an uptime check config. @@ -94,11 +144,11 @@ Commands: Options: --version Show version number [boolean] - --projectId, -p [string] + --projectId, -p [string] [default: "nodejs-docs-samples"] --help Show help [boolean] Examples: - node uptime.js create my-instance Create an uptime check for a "my-instance" GCE instance. + node uptime.js create mydomain.com Create an uptime check. node uptime.js list List all uptime check configs. node uptime.js list "resource.type = gce_instance AND List all uptime check configs for a specific GCE resource.label.instance_id = mongodb" instance. @@ -109,8 +159,8 @@ Examples: For more information, see https://cloud.google.com/monitoring/uptime-checks/ ``` -[uptime_1_docs]: https://cloud.google.com/monitoring/docs -[uptime_1_code]: uptime.js +[uptime_2_docs]: https://cloud.google.com/monitoring/docs +[uptime_2_code]: uptime.js -[shell_img]: //gstatic.com/cloudssh/images/open-btn.png +[shell_img]: https://gstatic.com/cloudssh/images/open-btn.png [shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-monitoring&page=editor&open_in_editor=samples/README.md diff --git a/monitoring/snippets/alerts.js b/monitoring/snippets/alerts.js new file mode 100644 index 0000000000..90dd4b642a --- /dev/null +++ b/monitoring/snippets/alerts.js @@ -0,0 +1,361 @@ +/** + * Copyright 2018, Google, Inc. + * 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. + */ + +/** + * This application demonstrates how to perform basic operations on alerting + * policies with the Google Stackdriver Monitoring API. + * + * For more information, see https://cloud.google.com/monitoring/docs/. + */ + +'use strict'; + +function backupPolicies(projectId) { + // [START monitoring_alert_backup_policies] + const fs = require('fs'); + + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.AlertPolicyServiceClient(); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'YOUR_PROJECT_ID'; + + const listAlertPoliciesRequest = { + name: client.projectPath(projectId), + }; + + client + .listAlertPolicies(listAlertPoliciesRequest) + .then(results => { + const policies = results[0]; + + fs.writeFileSync( + './policies_backup.json', + JSON.stringify(policies, null, 2), + 'utf-8' + ); + + console.log('Saved policies to ./policies_backup.json'); + }) + .catch(err => { + console.error('ERROR:', err); + }); + // [START monitoring_alert_backup_policies] +} + +function restorePolicies(projectId) { + // Note: The policies are restored one at a time because I get 'service + // unavailable' when I try to create multiple alerts simultaneously. + // [START monitoring_alert_restore_policies] + const fs = require('fs'); + + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.AlertPolicyServiceClient(); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'YOUR_PROJECT_ID'; + + console.log('Loading policies from ./policies_backup.json'); + const fileContent = fs.readFileSync('./policies_backup.json', 'utf-8'); + const policies = JSON.parse(fileContent); + + let promise = Promise.resolve(); + + policies.forEach(policy => { + // Restore each policy one at a time + promise = promise + .then(() => doesAlertPolicyExist(policy.name)) + .then(exists => { + if (exists) { + return client.updateAlertPolicy({alertPolicy: policy}); + } + + // Clear away output-only fields + delete policy.name; + delete policy.creationRecord; + delete policy.mutationRecord; + policy.conditions.forEach(condition => delete condition.name); + + return client.createAlertPolicy({ + name: client.projectPath(projectId), + alertPolicy: policy, + }); + }) + .then(response => { + const policy = response[0]; + console.log(`Restored ${policy.name}.`); + }); + }); + + promise.catch(err => { + console.error('ERROR:', err); + }); + + function doesAlertPolicyExist(name) { + return client + .getAlertPolicy({name: name}) + .then(() => true) + .catch(err => { + if (err && err.code === 5) { + // Error code 5 comes from the google.rpc.code.NOT_FOUND protobuf + return false; + } + return Promise.reject(err); + }); + } + // [START monitoring_alert_restore_policies] +} + +function replaceChannels(projectId, alertPolicyId, channelIds) { + // [START monitoring_alert_replace_channels] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates clients + const alertClient = new monitoring.AlertPolicyServiceClient(); + const notificationClient = new monitoring.NotificationChannelServiceClient(); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'YOUR_PROJECT_ID'; + // const alertPolicyId = '123456789012314'; + // const channelIds = [ + // 'channel-1', + // 'channel-2', + // 'channel-3', + // ]; + + const notificationChannels = channelIds.map(id => + notificationClient.notificationChannelPath(projectId, id) + ); + + const updateAlertPolicyRequest = { + updateMask: {paths: ['notification_channels']}, + alertPolicy: { + name: alertClient.alertPolicyPath(projectId, alertPolicyId), + notificationChannels: notificationChannels, + }, + }; + + alertClient + .updateAlertPolicy(updateAlertPolicyRequest) + .then(results => { + const alertPolicy = results[0]; + console.log(`Updated ${alertPolicy.name}.`); + }) + .catch(err => { + console.error('ERROR:', err); + }); + // [END monitoring_alert_replace_channels] +} + +function disablePolicies(projectId, filter) { + // [START monitoring_alert_disable_policies] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.AlertPolicyServiceClient(); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'YOUR_PROJECT_ID'; + // const filter = 'A filter for selecting policies, e.g. user_labels="active"'; + + const listAlertPoliciesRequest = { + name: client.projectPath(projectId), + // See https://cloud.google.com/monitoring/alerting/docs/sorting-and-filtering + filter: filter, + }; + + client + .listAlertPolicies(listAlertPoliciesRequest) + .then(results => { + const policies = results[0]; + + const tasks = policies + .map(policy => { + return { + updateMask: {paths: ['disabled']}, + alertPolicy: { + name: policy.name, + disabled: true, + }, + }; + }) + .map(updateAlertPolicyRequest => { + return client.updateAlertPolicy(updateAlertPolicyRequest); + }); + + // Wait for all policies to be disabled + return Promise.all(tasks); + }) + .then(responses => { + responses.forEach(response => { + const alertPolicy = response[0]; + console.log(`Disabled ${alertPolicy.name}.`); + }); + }) + .catch(err => { + console.error('ERROR:', err); + }); + // [END monitoring_alert_disable_policies] +} + +function enablePolicies(projectId, filter) { + // [START monitoring_alert_enable_policies] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.AlertPolicyServiceClient(); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'YOUR_PROJECT_ID'; + // const filter = 'A filter for selecting policies, e.g. description:"cloud"'; + + const listAlertPoliciesRequest = { + name: client.projectPath(projectId), + // See https://cloud.google.com/monitoring/alerting/docs/sorting-and-filtering + filter: filter, + }; + + client + .listAlertPolicies(listAlertPoliciesRequest) + .then(results => { + const policies = results[0]; + + const tasks = policies + .map(policy => { + return { + updateMask: {paths: ['disabled']}, + alertPolicy: { + name: policy.name, + disabled: false, + }, + }; + }) + .map(updateAlertPolicyRequest => + client.updateAlertPolicy(updateAlertPolicyRequest) + ); + + // Wait for all policies to be enabled + return Promise.all(tasks); + }) + .then(responses => { + responses.forEach(response => { + const alertPolicy = response[0]; + console.log(`Enabled ${alertPolicy.name}.`); + }); + }) + .catch(err => { + console.error('ERROR:', err); + }); + // [END monitoring_alert_enable_policies] +} + +require(`yargs`) + .demand(1) + .command( + `backup `, + `Save alert policies to a ./policies_backup.json file.`, + {}, + opts => backupPolicies(opts.projectId, opts.filter || '') + ) + .command( + `restore `, + `Restore alert policies from a ./policies_backup.json file.`, + {}, + opts => restorePolicies(opts.projectId, opts.filter || '') + ) + .command( + `replace `, + `Replace the notification channels of the specified alert policy.`, + {}, + opts => { + const parts = opts.alertPolicyName.split('/'); + const channelIds = opts.channelNames.map(name => name.split('/')[3]); + replaceChannels(parts[1], parts[3], channelIds); + } + ) + .command( + `disable [filter]`, + `Disables policies that match the given filter.`, + {}, + opts => disablePolicies(opts.projectId, opts.filter || ``) + ) + .command( + `enable [filter]`, + `Enables policies that match the given filter.`, + {}, + opts => enablePolicies(opts.projectId, opts.filter || ``) + ) + .options({ + alertPolicyName: { + type: 'string', + requiresArg: true, + }, + }) + .example(`node $0 backup my-project-id`, `Backup policies.`) + .example(`node $0 restore my-project-id`, `Restore policies.`) + .example( + `node $0 replace projects/my-project-id/alertPolicies/12345 channel-1 channel-2 channel-3`, + `Replace the notification channels of the specified alert policy.` + ) + .example( + `node $0 disable my-project-id "(NOT display_name.empty OR NOT description.empty) AND user_labels='active'"`, + `Disables policies that match the given filter.` + ) + .example( + `node $0 disable my-project-id "description:'cloud'"`, + `Disables policies that match the given filter.` + ) + .example( + `node $0 disable my-project-id "display_name=monitoring.regex.full_match('Temp \\d{4}')"`, + `Disables policies that match the given filter.` + ) + .example( + `node $0 enable my-project-id "(NOT display_name.empty OR NOT description.empty) AND user_labels='active'"`, + `Enables policies that match the given filter.` + ) + .example( + `node $0 enable my-project-id "description:'cloud'"`, + `Enables policies that match the given filter.` + ) + .example( + `node $0 enable my-project-id "display_name=monitoring.regex.full_match('Temp \\d{4}')"`, + `Enables policies that match the given filter.` + ) + .wrap(120) + .recommendCommands() + .epilogue( + `For more information, see https://cloud.google.com/monitoring/docs/` + ) + .help() + .strict().argv; diff --git a/monitoring/snippets/package-lock.json b/monitoring/snippets/package-lock.json index f14797e682..74021cdcdf 100644 --- a/monitoring/snippets/package-lock.json +++ b/monitoring/snippets/package-lock.json @@ -10769,9 +10769,9 @@ } }, "@google-cloud/nodejs-repo-tools": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@google-cloud/nodejs-repo-tools/-/nodejs-repo-tools-2.2.6.tgz", - "integrity": "sha512-bDEgNBAJ8kfYWrpifg+D7rXs8NUBUUeu2I6NWkcL9IOXg86rblu7xnhFHRxBMcvytJ+7WY4pvkCLxO1v3QBsXg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@google-cloud/nodejs-repo-tools/-/nodejs-repo-tools-2.3.0.tgz", + "integrity": "sha512-c8dIGESnNkmM88duFxGHvMQP5QKPgp/sfJq0QhC6+gOcJC7/PKjqd0PkmgPPeIgVl6SXy5Zf/KLbxnJUVgNT1Q==", "dev": true, "requires": { "ava": "0.25.0", @@ -10782,6 +10782,7 @@ "lodash": "4.17.5", "nyc": "11.4.1", "proxyquire": "1.8.0", + "semver": "5.5.0", "sinon": "4.3.0", "string": "3.3.3", "supertest": "3.0.0", @@ -16133,9 +16134,9 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true }, "query-string": { @@ -16709,7 +16710,7 @@ "formidable": "1.2.1", "methods": "1.1.2", "mime": "1.6.0", - "qs": "6.5.1", + "qs": "6.5.2", "readable-stream": "2.3.6" } }, diff --git a/monitoring/snippets/package.json b/monitoring/snippets/package.json index c764674ce4..1ae7629638 100644 --- a/monitoring/snippets/package.json +++ b/monitoring/snippets/package.json @@ -16,7 +16,7 @@ "yargs": "11.1.0" }, "devDependencies": { - "@google-cloud/nodejs-repo-tools": "2.2.6", + "@google-cloud/nodejs-repo-tools": "2.3.0", "ava": "0.25.0", "proxyquire": "2.0.1", "sinon": "4.5.0" diff --git a/monitoring/snippets/system-test/alerts.test.js b/monitoring/snippets/system-test/alerts.test.js new file mode 100644 index 0000000000..5a04d34fcc --- /dev/null +++ b/monitoring/snippets/system-test/alerts.test.js @@ -0,0 +1,190 @@ +/** + * Copyright 2017, Google, Inc. + * 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. + */ + +'use strict'; + +const fs = require(`fs`); +const monitoring = require(`@google-cloud/monitoring`); +const path = require(`path`); +const test = require(`ava`); +const tools = require(`@google-cloud/nodejs-repo-tools`); + +const client = new monitoring.AlertPolicyServiceClient(); +const channelClient = new monitoring.NotificationChannelServiceClient(); +const cwd = path.join(__dirname, `..`); +const projectId = process.env.GCLOUD_PROJECT; + +let policyOneName, policyTwoName, channelName; + +test.before(tools.checkCredentials); + +test.before(async () => { + let results = await client.createAlertPolicy({ + name: client.projectPath(projectId), + alertPolicy: { + displayName: 'first_policy', + combiner: 1, + conditions: [ + { + displayName: 'Condition 1', + conditionAbsent: { + filter: `metric.type = "cloudfunctions.googleapis.com/function/execution_count"`, + aggregations: [ + { + alignmentPeriod: { + seconds: 60, + }, + perSeriesAligner: 1, + crossSeriesReducer: 0, + }, + ], + duration: { + seconds: 60, + }, + trigger: { + count: 1, + }, + }, + }, + ], + }, + }); + policyOneName = results[0].name; + results = await client.createAlertPolicy({ + name: client.projectPath(projectId), + alertPolicy: { + displayName: 'second', + combiner: 1, + conditions: [ + { + displayName: 'Condition 2', + conditionAbsent: { + filter: `metric.type = "cloudfunctions.googleapis.com/function/execution_count"`, + aggregations: [ + { + alignmentPeriod: { + seconds: 60, + }, + perSeriesAligner: 1, + crossSeriesReducer: 0, + }, + ], + duration: { + seconds: 60, + }, + trigger: { + count: 1, + }, + }, + }, + ], + }, + }); + policyTwoName = results[0].name; + results = await channelClient.createNotificationChannel({ + name: channelClient.projectPath(projectId), + notificationChannel: { + displayName: 'Channel 1', + type: 'email', + labels: { + email_address: 'test@test.com', + }, + }, + }); + channelName = results[0].name; +}); + +function deletePolicies() { + return Promise.all([ + client.deleteAlertPolicy({ + name: policyOneName, + }), + client.deleteAlertPolicy({ + name: policyTwoName, + }), + ]); +} + +function deleteChannels() { + return Promise.all([ + channelClient.deleteNotificationChannel({ + name: channelName, + }), + ]); +} + +test.after.always(async () => { + await deletePolicies(); + // has to be done after policies are deleted + await deleteChannels(); +}); + +test.serial(`should backup all policies`, async t => { + const results = await tools.spawnAsyncWithIO( + `node`, + [`alerts.js`, `backup`, projectId], + cwd + ); + t.regex(results.output, /Saved policies to \.\/policies_backup.json/); + t.true(fs.existsSync(path.join(cwd, `policies_backup.json`))); + await client.deleteAlertPolicy({name: policyOneName}); +}); + +test.serial(`should restore policies`, async t => { + const results = await tools.spawnAsyncWithIO( + `node`, + [`alerts.js`, `restore`, projectId], + cwd + ); + t.regex(results.output, /Loading policies from .\/policies_backup.json/); + const nameRegexp = /projects\/[A-Za-z0-9-]+\/alertPolicies\/([\d]+)/gi; + const matches = results.output.match(nameRegexp); + t.true(Array.isArray(matches)); + t.is(matches.length, 2); + policyOneName = matches[0]; + policyTwoName = matches[1]; +}); + +test.serial(`should replace notification channels`, async t => { + const results = await tools.spawnAsyncWithIO( + `node`, + [`alerts.js`, `replace`, policyOneName, channelName], + cwd + ); + t.regex(results.output, /Updated projects\//); + t.true(results.output.includes(policyOneName)); +}); + +test.serial(`should disable policies`, async t => { + const results = await tools.spawnAsyncWithIO( + `node`, + [`alerts.js`, `disable`, projectId, `'display_name.size < 7'`], + cwd + ); + t.regex(results.output, /Disabled projects\//); + t.false(results.output.includes(policyOneName)); + t.true(results.output.includes(policyTwoName)); +}); + +test.serial(`should enable policies`, async t => { + const results = await tools.spawnAsyncWithIO( + `node`, + [`alerts.js`, `enable`, projectId, `'display_name.size < 7'`], + cwd + ); + t.regex(results.output, /Enabled projects\//); + t.false(results.output.includes(policyOneName)); + t.true(results.output.includes(policyTwoName)); +});