diff --git a/.github/workflows/monitoring-snippets.yaml b/.github/workflows/monitoring-snippets.yaml new file mode 100644 index 0000000000..c847249853 --- /dev/null +++ b/.github/workflows/monitoring-snippets.yaml @@ -0,0 +1,67 @@ +name: monitoring-snippets +on: + push: + branches: + - main + paths: + - 'monitoring/snippets/**' + pull_request: + paths: + - 'monitoring/snippets/**' + pull_request_target: + types: [labeled] + schedule: + - cron: '0 0 * * 0' +jobs: + test: + if: ${{ github.event.action != 'labeled' || github.event.label.name == 'actions:force-run' }} + runs-on: ubuntu-latest + timeout-minutes: 60 + permissions: + contents: 'write' + pull-requests: 'write' + id-token: 'write' + steps: + - uses: actions/checkout@v3.1.0 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + - uses: 'google-github-actions/auth@v0.8.3' + with: + workload_identity_provider: 'projects/1046198160504/locations/global/workloadIdentityPools/github-actions-pool/providers/github-actions-provider' + service_account: 'kokoro-system-test@long-door-651.iam.gserviceaccount.com' + create_credentials_file: 'true' + access_token_lifetime: 600s + - uses: actions/setup-node@v3.5.1 + with: + node-version: 16 + - run: npm install + working-directory: monitoring/snippets + - run: npm test + working-directory: monitoring/snippets + env: + MOCHA_REPORTER_SUITENAME: monitoring_snippets + MOCHA_REPORTER_OUTPUT: monitoring_snippets_sponge_log.xml + MOCHA_REPORTER: xunit + - if: ${{ github.event.action == 'labeled' && github.event.label.name == 'actions:force-run' }} + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + try { + await github.rest.issues.removeLabel({ + name: 'actions:force-run', + owner: 'GoogleCloudPlatform', + repo: 'nodejs-docs-samples', + issue_number: context.payload.pull_request.number + }); + } catch (e) { + if (!e.message.includes('Label does not exist')) { + throw e; + } + } + - if: ${{ github.event_name == 'schedule'}} + run: | + curl https://github.com/googleapis/repo-automation-bots/releases/download/flakybot-1.1.0/flakybot -o flakybot -s -L + chmod +x ./flakybot + ./flakybot --repo GoogleCloudPlatform/nodejs-docs-samples --commit_hash ${{github.sha}} --build_url https://github.com/${{github.repository}}/actions/runs/${{github.run_id}} diff --git a/.github/workflows/workflows.json b/.github/workflows/workflows.json index c78d1861d8..93af217573 100644 --- a/.github/workflows/workflows.json +++ b/.github/workflows/workflows.json @@ -43,6 +43,7 @@ "mediatranslation", "monitoring/opencensus", "monitoring/prometheus", + "monitoring/snippets", "datacatalog/cloud-client", "datacatalog/quickstart", "datastore/functions", diff --git a/monitoring/snippets/.eslintrc.yml b/monitoring/snippets/.eslintrc.yml new file mode 100644 index 0000000000..282535f55f --- /dev/null +++ b/monitoring/snippets/.eslintrc.yml @@ -0,0 +1,3 @@ +--- +rules: + no-console: off diff --git a/monitoring/snippets/.gitignore b/monitoring/snippets/.gitignore new file mode 100644 index 0000000000..d2dc4ff2fe --- /dev/null +++ b/monitoring/snippets/.gitignore @@ -0,0 +1,2 @@ +policies_backup.json +package-lock.json diff --git a/monitoring/snippets/alerts.backupPolicies.js b/monitoring/snippets/alerts.backupPolicies.js new file mode 100644 index 0000000000..867341cbf0 --- /dev/null +++ b/monitoring/snippets/alerts.backupPolicies.js @@ -0,0 +1,60 @@ +// Copyright 2018 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. + +'use strict'; + +function main(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(); + + async function backupPolicies() { + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'YOUR_PROJECT_ID'; + + const listAlertPoliciesRequest = { + name: client.projectPath(projectId), + }; + + let [policies] = await client.listAlertPolicies(listAlertPoliciesRequest); + + // filter out any policies created by tests for this sample + policies = policies.filter(policy => { + return !policy.displayName.startsWith('gcloud-tests-'); + }); + + fs.writeFileSync( + './policies_backup.json', + JSON.stringify(policies, null, 2), + 'utf-8' + ); + + console.log('Saved policies to ./policies_backup.json'); + // [END monitoring_alert_backup_policies] + } + backupPolicies(); +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/monitoring/snippets/alerts.deleteChannels.js b/monitoring/snippets/alerts.deleteChannels.js new file mode 100644 index 0000000000..c773b81870 --- /dev/null +++ b/monitoring/snippets/alerts.deleteChannels.js @@ -0,0 +1,60 @@ +// Copyright 2018 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. + +'use strict'; + +function main(projectId, filter) { + // [START monitoring_alert_delete_channel] + // [START monitoring_alert_list_channels] + + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.NotificationChannelServiceClient(); + + async function deleteChannels() { + /** + * 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 request = { + name: client.projectPath(projectId), + filter, + }; + const channels = await client.listNotificationChannels(request); + console.log(channels); + for (const channel of channels[0]) { + console.log(`Deleting channel ${channel.displayName}`); + try { + await client.deleteNotificationChannel({ + name: channel.name, + }); + } catch (err) { + // ignore error + } + } + } + deleteChannels(); + // [END monitoring_alert_delete_channel] + // [END monitoring_alert_list_channels] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/monitoring/snippets/alerts.enablePolicies.js b/monitoring/snippets/alerts.enablePolicies.js new file mode 100644 index 0000000000..3c44c5d165 --- /dev/null +++ b/monitoring/snippets/alerts.enablePolicies.js @@ -0,0 +1,71 @@ +// Copyright 2018 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. + +'use strict'; + +function main(projectId, enabled, filter) { + enabled = enabled === 'true'; + + // [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(); + + async function enablePolicies() { + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'YOUR_PROJECT_ID'; + // const enabled = true; + // 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, + }; + + const [policies] = await client.listAlertPolicies(listAlertPoliciesRequest); + const responses = []; + for (const policy of policies) { + responses.push( + await client.updateAlertPolicy({ + updateMask: { + paths: ['enabled'], + }, + alertPolicy: { + name: policy.name, + enabled: { + value: enabled, + }, + }, + }) + ); + } + responses.forEach(response => { + const alertPolicy = response[0]; + console.log(`${enabled ? 'Enabled' : 'Disabled'} ${alertPolicy.name}.`); + }); + } + enablePolicies(); + // [END monitoring_alert_enable_policies] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/monitoring/snippets/alerts.listPolicies.js b/monitoring/snippets/alerts.listPolicies.js new file mode 100644 index 0000000000..09067eaf87 --- /dev/null +++ b/monitoring/snippets/alerts.listPolicies.js @@ -0,0 +1,51 @@ +// Copyright 2018 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. + +'use strict'; + +function main(projectId) { + // [START monitoring_alert_list_policies] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.AlertPolicyServiceClient(); + + async function listPolicies() { + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'YOUR_PROJECT_ID'; + + const listAlertPoliciesRequest = { + name: client.projectPath(projectId), + }; + const [policies] = await client.listAlertPolicies(listAlertPoliciesRequest); + console.log('Policies:'); + policies.forEach(policy => { + console.log(` Display name: ${policy.displayName}`); + if (policy.documentation && policy.documentation.content) { + console.log(` Documentation: ${policy.documentation.content}`); + } + }); + } + listPolicies(); + // [END monitoring_alert_list_policies] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/monitoring/snippets/alerts.replaceChannels.js b/monitoring/snippets/alerts.replaceChannels.js new file mode 100644 index 0000000000..654c78762d --- /dev/null +++ b/monitoring/snippets/alerts.replaceChannels.js @@ -0,0 +1,103 @@ +// Copyright 2018 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. + +'use strict'; + +function main(projectId, alertPolicyId, ...channelIds) { + // [START monitoring_alert_replace_channels] + // [START monitoring_alert_enable_channel] + // [START monitoring_alert_update_channel] + // [START monitoring_alert_create_channel] + + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates clients + const alertClient = new monitoring.AlertPolicyServiceClient(); + const notificationClient = new monitoring.NotificationChannelServiceClient(); + + async function replaceChannels() { + /** + * 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.projectNotificationChannelPath(projectId, id) + ); + + for (const channel of notificationChannels) { + const updateChannelRequest = { + updateMask: { + paths: ['enabled'], + }, + notificationChannel: { + name: channel, + enabled: { + value: true, + }, + }, + }; + try { + await notificationClient.updateNotificationChannel( + updateChannelRequest + ); + } catch (err) { + const createChannelRequest = { + notificationChannel: { + name: channel, + notificationChannel: { + type: 'email', + }, + }, + }; + const newChannel = await notificationClient.createNotificationChannel( + createChannelRequest + ); + notificationChannels.push(newChannel); + } + } + + const updateAlertPolicyRequest = { + updateMask: { + paths: ['notification_channels'], + }, + alertPolicy: { + name: alertClient.projectAlertPolicyPath(projectId, alertPolicyId), + notificationChannels: notificationChannels, + }, + }; + const [alertPolicy] = await alertClient.updateAlertPolicy( + updateAlertPolicyRequest + ); + console.log(`Updated ${alertPolicy.name}.`); + } + replaceChannels(); + // [END monitoring_alert_replace_channels] + // [END monitoring_alert_enable_channel] + // [END monitoring_alert_update_channel] + // [END monitoring_alert_create_channel] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/monitoring/snippets/alerts.restorePolicies.js b/monitoring/snippets/alerts.restorePolicies.js new file mode 100644 index 0000000000..5e8aceb830 --- /dev/null +++ b/monitoring/snippets/alerts.restorePolicies.js @@ -0,0 +1,88 @@ +// Copyright 2018 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. + +'use strict'; + +function main(projectId) { + // [START monitoring_alert_restore_policies] + // [START monitoring_alert_create_policy] + const fs = require('fs'); + + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.AlertPolicyServiceClient(); + + async function restorePolicies() { + // Note: The policies are restored one at a time due to limitations in + // the API. Otherwise, you may receive a 'service unavailable' error + // while trying to create multiple alerts simultaneously. + + /** + * 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); + + for (const index in policies) { + // Restore each policy one at a time + let policy = policies[index]; + if (await doesAlertPolicyExist(policy.name)) { + policy = await client.updateAlertPolicy({ + alertPolicy: policy, + }); + } else { + // Clear away output-only fields + delete policy.name; + delete policy.creationRecord; + delete policy.mutationRecord; + policy.conditions.forEach(condition => delete condition.name); + + policy = await client.createAlertPolicy({ + name: client.projectPath(projectId), + alertPolicy: policy, + }); + } + + console.log(`Restored ${policy[0].name}.`); + } + async function doesAlertPolicyExist(name) { + try { + const [policy] = await client.getAlertPolicy({ + name, + }); + return policy ? true : false; + } catch (err) { + if (err && err.code === 5) { + // Error code 5 comes from the google.rpc.code.NOT_FOUND protobuf + return false; + } + throw err; + } + } + } + restorePolicies(); + // [END monitoring_alert_create_policy] + // [END monitoring_alert_restore_policies] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/monitoring/snippets/metrics.createDescriptor.js b/monitoring/snippets/metrics.createDescriptor.js new file mode 100644 index 0000000000..fd80d8f205 --- /dev/null +++ b/monitoring/snippets/metrics.createDescriptor.js @@ -0,0 +1,76 @@ +// Copyright 2017 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. + +'use strict'; + +// sample-metadata: +// title: Create Metric Descriptor +// description: Creates an example 'custom.googleapis.com/stores/daily_sales' custom metric descriptor. +// usage: node metrics.createDescriptor.js your-project-id + +function main(projectId) { + // [START monitoring_create_metric] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.MetricServiceClient(); + + /** + * TODO(developer): Uncomment and edit the following lines of code. + */ + // const projectId = 'YOUR_PROJECT_ID'; + async function createMetricDescriptor() { + const request = { + name: client.projectPath(projectId), + metricDescriptor: { + description: 'Daily sales records from all branch stores.', + displayName: 'Daily Sales', + type: 'custom.googleapis.com/stores/daily_sales', + metricKind: 'GAUGE', + valueType: 'DOUBLE', + unit: '{USD}', + labels: [ + { + key: 'store_id', + valueType: 'STRING', + description: 'The ID of the store.', + }, + ], + }, + }; + + // Creates a custom metric descriptor + const [descriptor] = await client.createMetricDescriptor(request); + console.log('Created custom Metric:\n'); + console.log(`Name: ${descriptor.displayName}`); + console.log(`Description: ${descriptor.description}`); + console.log(`Type: ${descriptor.type}`); + console.log(`Kind: ${descriptor.metricKind}`); + console.log(`Value Type: ${descriptor.valueType}`); + console.log(`Unit: ${descriptor.unit}`); + console.log('Labels:'); + descriptor.labels.forEach(label => { + console.log(` ${label.key} (${label.valueType}) - ${label.description}`); + }); + } + createMetricDescriptor(); + // [END monitoring_create_metric] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/monitoring/snippets/metrics.deleteDescriptor.js b/monitoring/snippets/metrics.deleteDescriptor.js new file mode 100644 index 0000000000..54ac4c222b --- /dev/null +++ b/monitoring/snippets/metrics.deleteDescriptor.js @@ -0,0 +1,53 @@ +// Copyright 2017 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. + +'use strict'; + +// sample-metadata: +// title: Delete Metric Descriptor +// description: Deletes a custom metric descriptor. +// usage: node metrics.deleteDescriptor.js your-project-id custom.googleapis.com/stores/daily_sales + +function main(projectId, metricId) { + // [START monitoring_delete_metric] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.MetricServiceClient(); + + async function deleteMetricDescriptor() { + /** + * TODO(developer): Uncomment and edit the following lines of code. + */ + // const projectId = 'YOUR_PROJECT_ID'; + // const metricId = 'custom.googleapis.com/stores/daily_sales'; + + const request = { + name: client.projectMetricDescriptorPath(projectId, metricId), + }; + + // Deletes a metric descriptor + const [result] = await client.deleteMetricDescriptor(request); + console.log(`Deleted ${metricId}`, result); + } + deleteMetricDescriptor(); + // [END monitoring_delete_metric] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/monitoring/snippets/metrics.getDescriptor.js b/monitoring/snippets/metrics.getDescriptor.js new file mode 100644 index 0000000000..661a53492c --- /dev/null +++ b/monitoring/snippets/metrics.getDescriptor.js @@ -0,0 +1,62 @@ +// Copyright 2017 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. + +'use strict'; + +// sample-metadata: +// title: Get Metric Descriptor +// description: Gets a custom metric descriptor +// usage: node metrics.getDescriptor.js your-project-id custom.googleapis.com/your/id + +function main(projectId, metricId) { + // [START monitoring_get_descriptor] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.MetricServiceClient(); + + async function getMetricDescriptor() { + /** + * TODO(developer): Uncomment and edit the following lines of code. + */ + // const projectId = 'YOUR_PROJECT_ID'; + // const metricId = 'custom.googleapis.com/your/id'; + + const request = { + name: client.projectMetricDescriptorPath(projectId, metricId), + }; + + // Retrieves a metric descriptor + const [descriptor] = await client.getMetricDescriptor(request); + console.log(`Name: ${descriptor.displayName}`); + console.log(`Description: ${descriptor.description}`); + console.log(`Type: ${descriptor.type}`); + console.log(`Kind: ${descriptor.metricKind}`); + console.log(`Value Type: ${descriptor.valueType}`); + console.log(`Unit: ${descriptor.unit}`); + console.log('Labels:'); + descriptor.labels.forEach(label => { + console.log(` ${label.key} (${label.valueType}) - ${label.description}`); + }); + } + getMetricDescriptor(); + // [END monitoring_get_descriptor] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/monitoring/snippets/metrics.getMonitoredResourceDescriptor.js b/monitoring/snippets/metrics.getMonitoredResourceDescriptor.js new file mode 100644 index 0000000000..1c1bfe6825 --- /dev/null +++ b/monitoring/snippets/metrics.getMonitoredResourceDescriptor.js @@ -0,0 +1,62 @@ +// Copyright 2017 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. + +'use strict'; + +// sample-metadata: +// title: Get Monitored Resource Descriptor +// usage: node metrics.getMonitoredResourceDescriptor.js your-project-id some-resource-type + +function main(projectId, resourceType) { + // [START monitoring_get_resource] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.MetricServiceClient(); + + async function getMonitoredResourceDescriptor() { + /** + * TODO(developer): Uncomment and edit the following lines of code. + */ + // const projectId = 'YOUR_PROJECT_ID'; + // const resourceType = 'some_resource_type, e.g. cloudsql_database'; + + const request = { + name: client.projectMonitoredResourceDescriptorPath( + projectId, + resourceType + ), + }; + + // Lists monitored resource descriptors + const [descriptor] = await client.getMonitoredResourceDescriptor(request); + + console.log(`Name: ${descriptor.displayName}`); + console.log(`Description: ${descriptor.description}`); + console.log(`Type: ${descriptor.type}`); + console.log('Labels:'); + descriptor.labels.forEach(label => { + console.log(` ${label.key} (${label.valueType}) - ${label.description}`); + }); + } + getMonitoredResourceDescriptor(); + // [END monitoring_get_resource] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/monitoring/snippets/metrics.listDescriptors.js b/monitoring/snippets/metrics.listDescriptors.js new file mode 100644 index 0000000000..29a474a680 --- /dev/null +++ b/monitoring/snippets/metrics.listDescriptors.js @@ -0,0 +1,52 @@ +// Copyright 2017 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. + +'use strict'; + +// sample-metadata: +// title: List Metric Descriptors +// usage: node metrics.listDescriptors.js your-project-id + +function main(projectId) { + // [START monitoring_list_descriptors] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.MetricServiceClient(); + + async function listMetricDescriptors() { + /** + * TODO(developer): Uncomment and edit the following lines of code. + */ + // const projectId = 'YOUR_PROJECT_ID'; + + const request = { + name: client.projectPath(projectId), + }; + + // Lists metric descriptors + const [descriptors] = await client.listMetricDescriptors(request); + console.log('Metric Descriptors:'); + descriptors.forEach(descriptor => console.log(descriptor.name)); + } + listMetricDescriptors(); + // [END monitoring_list_descriptors] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/monitoring/snippets/metrics.listMonitoredResourceDescriptors.js b/monitoring/snippets/metrics.listMonitoredResourceDescriptors.js new file mode 100644 index 0000000000..c5ba628a4a --- /dev/null +++ b/monitoring/snippets/metrics.listMonitoredResourceDescriptors.js @@ -0,0 +1,66 @@ +// Copyright 2017 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. + +'use strict'; + +// sample-metadata: +// title: List Monitored Resource Descriptors +// usage: node metrics.listMonitoredResourceDescriptors.js your-project-id + +function main(projectId) { + // [START monitoring_list_resources] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.MetricServiceClient(); + + async function listMonitoredResourceDescriptors() { + /** + * TODO(developer): Uncomment and edit the following lines of code. + */ + // const projectId = 'YOUR_PROJECT_ID'; + + const request = { + name: client.projectPath(projectId), + }; + + // Lists monitored resource descriptors + const [descriptors] = await client.listMonitoredResourceDescriptors( + request + ); + console.log('Monitored Resource Descriptors:'); + descriptors.forEach(descriptor => { + console.log(descriptor.name); + console.log(` Type: ${descriptor.type}`); + if (descriptor.labels) { + console.log(' Labels:'); + descriptor.labels.forEach(label => { + console.log( + ` ${label.key} (${label.valueType}): ${label.description}` + ); + }); + } + console.log(); + }); + } + listMonitoredResourceDescriptors(); + // [END monitoring_list_resources] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/monitoring/snippets/metrics.readTimeSeriesAggregate.js b/monitoring/snippets/metrics.readTimeSeriesAggregate.js new file mode 100644 index 0000000000..03f775db78 --- /dev/null +++ b/monitoring/snippets/metrics.readTimeSeriesAggregate.js @@ -0,0 +1,77 @@ +// Copyright 2017 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. + +'use strict'; + +// sample-metadata: +// title: Read Time Series Aggregate +// description: Aggregates time series data that matches 'compute.googleapis.com/instance/cpu/utilization'. +// usage: node metrics.readTimeSeriesAggregate.js your-project-id + +function main(projectId) { + // [START monitoring_read_timeseries_align] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.MetricServiceClient(); + + async function readTimeSeriesAggregate() { + /** + * TODO(developer): Uncomment and edit the following lines of code. + */ + // const projectId = 'YOUR_PROJECT_ID'; + + const request = { + name: client.projectPath(projectId), + filter: 'metric.type="compute.googleapis.com/instance/cpu/utilization"', + interval: { + startTime: { + // Limit results to the last 20 minutes + seconds: Date.now() / 1000 - 60 * 20, + }, + endTime: { + seconds: Date.now() / 1000, + }, + }, + // Aggregate results per matching instance + aggregation: { + alignmentPeriod: { + seconds: 600, + }, + perSeriesAligner: 'ALIGN_MEAN', + }, + }; + + // Writes time series data + const [timeSeries] = await client.listTimeSeries(request); + console.log('CPU utilization:'); + timeSeries.forEach(data => { + console.log(data.metric.labels.instance_name); + console.log(` Now: ${data.points[0].value.doubleValue}`); + if (data.points.length > 1) { + console.log(` 10 min ago: ${data.points[1].value.doubleValue}`); + } + console.log('====='); + }); + } + readTimeSeriesAggregate(); + // [END monitoring_read_timeseries_align] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/monitoring/snippets/metrics.readTimeSeriesData.js b/monitoring/snippets/metrics.readTimeSeriesData.js new file mode 100644 index 0000000000..f21a3a87ff --- /dev/null +++ b/monitoring/snippets/metrics.readTimeSeriesData.js @@ -0,0 +1,68 @@ +// Copyright 2017 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. + +'use strict'; + +// sample-metadata: +// title: Read Time Series Data +// description: Reads time series data that matches the given filter. +// usage: node metrics.readTimeSeriesData.js your-project-id 'metric.type="compute.googleapis.com/instance/cpu/utilization"' + +function main(projectId, filter) { + // [START monitoring_read_timeseries_simple] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.MetricServiceClient(); + + async function readTimeSeriesData() { + /** + * TODO(developer): Uncomment and edit the following lines of code. + */ + // const projectId = 'YOUR_PROJECT_ID'; + // const filter = 'metric.type="compute.googleapis.com/instance/cpu/utilization"'; + + const request = { + name: client.projectPath(projectId), + filter: filter, + interval: { + startTime: { + // Limit results to the last 20 minutes + seconds: Date.now() / 1000 - 60 * 20, + }, + endTime: { + seconds: Date.now() / 1000, + }, + }, + }; + + // Writes time series data + const [timeSeries] = await client.listTimeSeries(request); + timeSeries.forEach(data => { + console.log(`${data.metric.labels.instance_name}:`); + data.points.forEach(point => { + console.log(JSON.stringify(point.value)); + }); + }); + } + readTimeSeriesData(); + // [END monitoring_read_timeseries_simple] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/monitoring/snippets/metrics.readTimeSeriesFields.js b/monitoring/snippets/metrics.readTimeSeriesFields.js new file mode 100644 index 0000000000..71dab2ebe9 --- /dev/null +++ b/monitoring/snippets/metrics.readTimeSeriesFields.js @@ -0,0 +1,68 @@ +// Copyright 2017 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. + +'use strict'; + +// sample-metadata: +// title: Read Time Series Fields +// description: Reads headers of time series data that matches 'compute.googleapis.com/instance/cpu/utilization'. +// usage: node metrics.readTimeSeriesFields.js your-project-id + +function main(projectId) { + // [START monitoring_read_timeseries_fields] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.MetricServiceClient(); + + async function readTimeSeriesFields() { + /** + * TODO(developer): Uncomment and edit the following lines of code. + */ + // const projectId = 'YOUR_PROJECT_ID'; + + const request = { + name: client.projectPath(projectId), + filter: 'metric.type="compute.googleapis.com/instance/cpu/utilization"', + interval: { + startTime: { + // Limit results to the last 20 minutes + seconds: Date.now() / 1000 - 60 * 20, + }, + endTime: { + seconds: Date.now() / 1000, + }, + }, + // Don't return time series data, instead just return information about + // the metrics that match the filter + view: 'HEADERS', + }; + + // Writes time series data + const [timeSeries] = await client.listTimeSeries(request); + console.log('Found data points for the following instances:'); + timeSeries.forEach(data => { + console.log(data.metric.labels.instance_name); + }); + } + readTimeSeriesFields(); + // [END monitoring_read_timeseries_fields] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/monitoring/snippets/metrics.readTimeSeriesReduce.js b/monitoring/snippets/metrics.readTimeSeriesReduce.js new file mode 100644 index 0000000000..65cc64a539 --- /dev/null +++ b/monitoring/snippets/metrics.readTimeSeriesReduce.js @@ -0,0 +1,78 @@ +// Copyright 2017 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. + +'use strict'; + +// sample-metadata: +// title: Read Time Series Reduce +// description: Reduces time series data that matches 'compute.googleapis.com/instance/cpu/utilization'. +// usage: node metrics.readTimeSeriesReduce.js your-project-id + +function main(projectId) { + // [START monitoring_read_timeseries_reduce] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.MetricServiceClient(); + + async function readTimeSeriesReduce() { + /** + * TODO(developer): Uncomment and edit the following lines of code. + */ + // const projectId = 'YOUR_PROJECT_ID'; + + const request = { + name: client.projectPath(projectId), + filter: 'metric.type="compute.googleapis.com/instance/cpu/utilization"', + interval: { + startTime: { + // Limit results to the last 20 minutes + seconds: Date.now() / 1000 - 60 * 20, + }, + endTime: { + seconds: Date.now() / 1000, + }, + }, + // Aggregate results per matching instance + aggregation: { + alignmentPeriod: { + seconds: 600, + }, + crossSeriesReducer: 'REDUCE_MEAN', + perSeriesAligner: 'ALIGN_MEAN', + }, + }; + + // Writes time series data + const [result] = await client.listTimeSeries(request); + if (result.length === 0) { + console.log('No data'); + return; + } + const reductions = result[0].points; + + console.log('Average CPU utilization across all GCE instances:'); + console.log(` Last 10 min: ${reductions[0].value.doubleValue}`); + console.log(` 10-20 min ago: ${reductions[0].value.doubleValue}`); + } + readTimeSeriesReduce(); + // [END monitoring_read_timeseries_reduce] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/monitoring/snippets/metrics.writeTimeSeriesData.js b/monitoring/snippets/metrics.writeTimeSeriesData.js new file mode 100644 index 0000000000..5ee37efa3c --- /dev/null +++ b/monitoring/snippets/metrics.writeTimeSeriesData.js @@ -0,0 +1,80 @@ +// Copyright 2017 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. + +'use strict'; + +// sample-metadata: +// title: Write Time Series Data +// description: Writes example time series data to 'custom.googleapis.com/stores/daily_sales'. +// usage: node metrics.writeTimeSeriesData.js your-project-id + +function main(projectId) { + // [START monitoring_write_timeseries] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.MetricServiceClient(); + + async function writeTimeSeriesData() { + /** + * TODO(developer): Uncomment and edit the following lines of code. + */ + // const projectId = 'YOUR_PROJECT_ID'; + + const dataPoint = { + interval: { + endTime: { + seconds: Date.now() / 1000, + }, + }, + value: { + doubleValue: 123.45, + }, + }; + + const timeSeriesData = { + metric: { + type: 'custom.googleapis.com/stores/daily_sales', + labels: { + store_id: 'Pittsburgh', + }, + }, + resource: { + type: 'global', + labels: { + project_id: projectId, + }, + }, + points: [dataPoint], + }; + + const request = { + name: client.projectPath(projectId), + timeSeries: [timeSeriesData], + }; + + // Writes time series data + const result = await client.createTimeSeries(request); + console.log('Done writing time series data.', result); + } + writeTimeSeriesData(); + // [END monitoring_write_timeseries] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/monitoring/snippets/package.json b/monitoring/snippets/package.json new file mode 100644 index 0000000000..14f5a0ae27 --- /dev/null +++ b/monitoring/snippets/package.json @@ -0,0 +1,25 @@ +{ + "name": "nodejs-docs-samples-monitoring", + "private": true, + "license": "Apache-2.0", + "author": "Google LLC", + "repository": "googleapis/nodejs-monitoring", + "files": [ + "*.js" + ], + "engines": { + "node": ">=12.0.0" + }, + "scripts": { + "test": "mocha --timeout 600000" + }, + "dependencies": { + "@google-cloud/monitoring": "^3.0.2", + "yargs": "^16.0.0" + }, + "devDependencies": { + "chai": "^4.2.0", + "mocha": "^8.0.0", + "uuid": "^9.0.0" + } +} \ No newline at end of file diff --git a/monitoring/snippets/test/alerts.test.js b/monitoring/snippets/test/alerts.test.js new file mode 100644 index 0000000000..caff2f836a --- /dev/null +++ b/monitoring/snippets/test/alerts.test.js @@ -0,0 +1,243 @@ +// Copyright 2017 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. + +'use strict'; + +const monitoring = require('@google-cloud/monitoring'); +const {assert} = require('chai'); +const {describe, it, before, after} = require('mocha'); +const cp = require('child_process'); +const uuid = require('uuid'); +const path = require('path'); +const fs = require('fs'); + +const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'}); + +const client = new monitoring.AlertPolicyServiceClient(); +const channelClient = new monitoring.NotificationChannelServiceClient(); +let projectId; +let policyOneName, policyTwoName, channelName; +const testPrefix = `gcloud-test-${uuid.v4().split('-')[0]}`; + +// Tests in this suite can trigger the error, +// "Too many concurrent edits to the project configuration. Please try again". +const delay = async test => { + const retries = test.currentRetry(); + if (retries === 0) return; // no retry on the first failure. + // see: https://cloud.google.com/storage/docs/exponential-backoff: + const ms = Math.pow(2, retries) * 500 + Math.random() * 2000; + return new Promise(done => { + console.info(`retrying "${test.title}" in ${ms}ms`); + setTimeout(done, ms); + }); +}; + +describe('alerts', () => { + before(async () => { + projectId = await client.getProjectId(); + await reapPolicies(); + let results = await client.createAlertPolicy({ + name: client.projectPath(projectId), + alertPolicy: { + displayName: `${testPrefix}-first-policy`, + combiner: 1, + documentation: { + content: 'Test', + mimeType: 'text/markdown', + }, + conditions: [ + { + displayName: 'Condition 1', + conditionAbsent: { + filter: + 'resource.type = "cloud_function" AND metric.type = "cloudfunctions.googleapis.com/function/execution_count"', + aggregations: [ + { + alignmentPeriod: { + seconds: 60, + }, + perSeriesAligner: 1, + crossSeriesReducer: 0, + }, + ], + duration: { + seconds: 120, + }, + trigger: { + count: 1, + }, + }, + }, + ], + }, + }); + policyOneName = results[0].name; + results = await client.createAlertPolicy({ + name: client.projectPath(projectId), + alertPolicy: { + displayName: `${testPrefix}-second`, + combiner: 1, + conditions: [ + { + displayName: 'Condition 2', + conditionAbsent: { + filter: + 'resource.type = "cloud_function" AND metric.type = "cloudfunctions.googleapis.com/function/execution_count"', + aggregations: [ + { + alignmentPeriod: { + seconds: 60, + }, + perSeriesAligner: 1, + crossSeriesReducer: 0, + }, + ], + duration: { + seconds: 120, + }, + 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; + }); + + /** + * Delete any policies created by a test that's older than 2 minutes. + */ + async function reapPolicies() { + const [policies] = await client.listAlertPolicies({ + name: client.projectPath(projectId), + }); + const crustyPolicies = policies + .filter(p => p.displayName.match(/^gcloud-test-/)) + .filter(p => { + const minutesOld = + (Date.now() - p.creationRecord.mutateTime.seconds * 1000) / 1000 / 60; + return minutesOld > 2; + }); + // This is serial on purpose. When trying to delete all alert policies in + // parallel, all of the promises return successful, but then only 2? + // get deleted. Super, super bizarre. + // https://github.com/googleapis/nodejs-monitoring/issues/192 + for (const p of crustyPolicies) { + console.log(`\tReaping ${p.name}...`); + await client.deleteAlertPolicy({name: p.name}); + } + } + + async function deletePolicies() { + await client.deleteAlertPolicy({ + name: policyOneName, + }); + await client.deleteAlertPolicy({ + name: policyTwoName, + }); + } + + async function deleteChannels() { + await channelClient.deleteNotificationChannel({ + name: channelName, + force: true, + }); + } + + after(async () => { + await deletePolicies(); + // has to be done after policies are deleted + await deleteChannels(); + }); + + it('should replace notification channels', async function () { + this.retries(8); + await delay(this.test); + const parts = policyOneName.split('/'); + const projectId = parts[1]; + const alertPolicyId = parts[3]; + const channelId = channelName.split('/')[3]; + const stdout = execSync( + `node alerts.replaceChannels.js ${projectId} ${alertPolicyId} ${channelId}` + ); + assert.include(stdout, 'Updated projects'); + assert.include(stdout, policyOneName); + }); + + it('should disable policies', async function () { + //this.retries(8); + await delay(this.test); + const stdout = execSync( + `node alerts.enablePolicies.js ${projectId} false 'display_name.size < 28'` + ); + assert.include(stdout, 'Disabled projects'); + assert.notInclude(stdout, policyOneName); + assert.include(stdout, policyTwoName); + }); + + it('should enable policies', async function () { + this.retries(8); + await delay(this.test); + const stdout = execSync( + `node alerts.enablePolicies.js ${projectId} true 'display_name.size < 28'` + ); + assert.include(stdout, 'Enabled projects'); + assert.notInclude(stdout, policyOneName); + assert.include(stdout, policyTwoName); + }); + + it('should list policies', () => { + const stdout = execSync(`node alerts.listPolicies.js ${projectId}`); + assert.include(stdout, 'Policies:'); + assert.include(stdout, 'first-policy'); + assert.include(stdout, 'Test'); + assert.include(stdout, 'second'); + }); + + it('should backup all policies', async function () { + this.retries(8); + await delay(this.test); + const output = execSync(`node alerts.backupPolicies.js ${projectId}`); + assert.include(output, 'Saved policies to ./policies_backup.json'); + assert.ok(fs.existsSync(path.join(__dirname, '../policies_backup.json'))); + await client.deleteAlertPolicy({name: policyOneName}); + }); + + it('should restore policies', async function () { + this.retries(8); + await delay(this.test); + const output = execSync(`node alerts.restorePolicies.js ${projectId}`); + assert.include(output, 'Loading policies from ./policies_backup.json'); + const matches = output.match( + /projects\/[A-Za-z0-9-]+\/alertPolicies\/([\d]+)/gi + ); + assert.ok(Array.isArray(matches)); + assert(matches.length > 1); + policyOneName = matches[0]; + policyTwoName = matches[1]; + }); +}); diff --git a/monitoring/snippets/test/metrics.test.js b/monitoring/snippets/test/metrics.test.js new file mode 100644 index 0000000000..65c94dc6ba --- /dev/null +++ b/monitoring/snippets/test/metrics.test.js @@ -0,0 +1,235 @@ +// Copyright 2017 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. + +'use strict'; + +const monitoring = require('@google-cloud/monitoring'); +const {assert} = require('chai'); +const {describe, it, before} = require('mocha'); +const cp = require('child_process'); + +const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'}); + +const client = new monitoring.MetricServiceClient(); +const customMetricId = 'custom.googleapis.com/stores/daily_sales'; +const computeMetricId = 'compute.googleapis.com/instance/cpu/utilization'; +const filter = `metric.type="${computeMetricId}"`; +const resourceId = 'cloudsql_database'; +let projectId; + +// A helper for delaying integration tests with an exponential backoff. +// See examples like: https://github.com/googleapis/nodejs-monitoring/issues/190, +// https://github.com/googleapis/nodejs-monitoring/issues/191. +const delay = async test => { + const retries = test.currentRetry(); + if (retries === 0) return; // no retry on the first failure. + // see: https://cloud.google.com/storage/docs/exponential-backoff: + const ms = Math.pow(2, retries) * 500 + Math.random() * 1000; + return new Promise(done => { + console.info(`retrying "${test.title}" in ${ms}ms`); + setTimeout(done, ms); + }); +}; + +describe('metrics', async () => { + before(async () => { + projectId = await client.getProjectId(); + }); + + it('should create a metric descriptors', async function () { + this.retries(8); + await delay(this.test); + const output = execSync(`node metrics.createDescriptor.js ${projectId}`); + assert.include(output, 'Created custom Metric'); + assert.include(output, `Type: ${customMetricId}`); + }); + + it('should list metric descriptors, including the new custom one', async function () { + this.retries(8); + await delay(this.test); + const output = execSync(`node metrics.listDescriptors.js ${projectId}`); + assert.include(output, customMetricId); + assert.include(output, computeMetricId); + }); + + it('should get a metric descriptor', () => { + const output = execSync( + `node metrics.getDescriptor.js ${projectId} ${customMetricId}` + ); + assert.include(output, `Type: ${customMetricId}`); + }); + + it('should write time series data', async function () { + this.retries(5); + await delay(this.test); + const output = execSync(`node metrics.writeTimeSeriesData.js ${projectId}`); + assert.include(output, 'Done writing time series data.'); + }); + + it('should delete a metric descriptor', async function () { + this.retries(5); + await delay(this.test); + const output = execSync( + `node metrics.deleteDescriptor.js ${projectId} ${customMetricId}` + ); + assert.include(output, `Deleted ${customMetricId}`); + }); + + it('should list monitored resource descriptors', () => { + const output = execSync( + `node metrics.listMonitoredResourceDescriptors.js ${projectId}` + ); + assert.include( + output, + `projects/${projectId}/monitoredResourceDescriptors/${resourceId}` + ); + }); + + it('should get a monitored resource descriptor', () => { + const output = execSync( + `node metrics.getMonitoredResourceDescriptor.js ${projectId} ${resourceId}` + ); + assert.include(output, `Type: ${resourceId}`); + }); + + it('should read time series data', async () => { + const [timeSeries] = await client.listTimeSeries({ + name: client.projectPath(projectId), + filter: filter, + interval: { + startTime: { + // Limit results to the last 20 minutes + seconds: Date.now() / 1000 - 60 * 20, + }, + endTime: { + seconds: Date.now() / 1000, + }, + }, + }); + const output = execSync( + `node metrics.readTimeSeriesData ${projectId} '${filter}'` + ); + //t.true(true); // Do not fail if there is simply no data to return. + timeSeries.forEach(data => { + assert.include(output, `${data.metric.labels.instance_name}:`); + data.points.forEach(point => { + assert.include(output, JSON.stringify(point.value)); + }); + }); + }); + + it('should read time series data fields', async () => { + const [timeSeries] = await client.listTimeSeries({ + name: client.projectPath(projectId), + filter: filter, + interval: { + startTime: { + // Limit results to the last 20 minutes + seconds: Date.now() / 1000 - 60 * 20, + }, + endTime: { + seconds: Date.now() / 1000, + }, + }, + // Don't return time series data, instead just return information about + // the metrics that match the filter + view: 'HEADERS', + }); + const output = execSync( + `node metrics.readTimeSeriesFields.js ${projectId}` + ); + assert.include(output, 'Found data points for the following instances'); + timeSeries.forEach(data => { + assert.include(output, data.metric.labels.instance_name); + }); + }); + + it('should read time series data aggregated', async function () { + this.retries(5); + await delay(this.test); + const [timeSeries] = await client.listTimeSeries({ + name: client.projectPath(projectId), + filter: filter, + interval: { + startTime: { + // Limit results to the last 20 minutes + seconds: Date.now() / 1000 - 60 * 20, + }, + endTime: { + seconds: Date.now() / 1000, + }, + }, + // Aggregate results per matching instance + aggregation: { + alignmentPeriod: { + seconds: 600, + }, + perSeriesAligner: 'ALIGN_MEAN', + }, + }); + let output; + try { + output = execSync(`node metrics.readTimeSeriesAggregate.js ${projectId}`); + } catch (e) { + console.error(e); + throw e; + } + assert.include(output, 'CPU utilization:'); + timeSeries.forEach(data => { + assert.include(output, data.metric.labels.instance_name); + assert.include(output, ' Now: 0.'); + // Don't assert against a value from 10 minutes ago, if none is + // being reported by stackdriver: + if (data.points.length > 1) { + assert.include(output, ' 10 min ago: 0.'); + } + }); + }); + + it('should read time series data reduced', async () => { + await client.listTimeSeries({ + name: client.projectPath(projectId), + filter: filter, + interval: { + startTime: { + // Limit results to the last 20 minutes + seconds: Date.now() / 1000 - 60 * 20, + }, + endTime: { + seconds: Date.now() / 1000, + }, + }, + // Aggregate results per matching instance + aggregation: { + alignmentPeriod: { + seconds: 600, + }, + crossSeriesReducer: 'REDUCE_MEAN', + perSeriesAligner: 'ALIGN_MEAN', + }, + }); + const output = execSync( + `node metrics.readTimeSeriesReduce.js ${projectId}` + ); + // Special case: No output. + if (output.indexOf('No data') < 0) { + assert.include( + output, + 'Average CPU utilization across all GCE instances:' + ); + assert.include(output, 'Last 10 min'); + assert.include(output, '10-20 min ago'); + } + }); +}); diff --git a/monitoring/snippets/test/uptime.test.js b/monitoring/snippets/test/uptime.test.js new file mode 100644 index 0000000000..ffa4961966 --- /dev/null +++ b/monitoring/snippets/test/uptime.test.js @@ -0,0 +1,107 @@ +// Copyright 2017 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. + +'use strict'; + +const {assert} = require('chai'); +const {describe, it} = require('mocha'); +const cp = require('child_process'); + +const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'}); + +const cmd = 'node uptime.js'; +const projectId = + process.env.GCLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT; +const hostname = 'mydomain.com'; + +function getResourceObjects(output) { + const regex = new RegExp(/^\s*Resource: (.*)$/gm); + const result = []; + let match; + while ((match = regex.exec(output)) !== null) { + result.push(JSON.parse(match[1])); + } + return result; +} + +describe('uptime', () => { + it('should list uptime-check ips', async () => { + const output = execSync(`${cmd} list-ips`); + assert.match(output, /USA/); + }); + + let id; + + it('should create an uptime check', async () => { + const output = execSync(`${cmd} create ${hostname}`); + const matches = output.match( + new RegExp(`ID: projects/${projectId}/uptimeCheckConfigs/(.+)`) + ); + id = matches[1]; + assert.match(output, /Uptime check created:/); + const resources = getResourceObjects(output); + assert.include(resources[0]['type'], 'uptime_url'); + assert.include(resources[0]['labels']['host'], hostname); + assert.match(output, /Display Name: My Uptime Check/); + }); + + it('should get an uptime check', async () => { + const output = execSync(`${cmd} get ${id}`); + assert.match( + output, + new RegExp(`Retrieving projects/${projectId}/uptimeCheckConfigs/${id}`) + ); + const resources = getResourceObjects(output); + assert.include(resources[0]['type'], 'uptime_url'); + assert.include(resources[0]['labels']['host'], hostname); + }); + + it('should list uptime checks', async () => { + const output = execSync(`${cmd} list`); + const resources = getResourceObjects(output); + const resourceCount = resources.filter( + resource => + resource['type'] === 'uptime_url' && + resource['labels']['host'] === hostname + ).length; + assert.isAbove(resourceCount, 0); + assert.match(output, /Display Name: My Uptime Check/); + }); + + it('should update an uptime check', async () => { + const newDisplayName = 'My New Display'; + const path = '/'; + const output = execSync(`${cmd} update ${id} "${newDisplayName}" ${path}`); + assert.include( + output, + `Updating projects/${projectId}/uptimeCheckConfigs/${id} to ${newDisplayName}` + ); + assert.include( + output, + `projects/${projectId}/uptimeCheckConfigs/${id} config updated.` + ); + }); + + it('should delete an uptime check', async () => { + const output = execSync(`${cmd} delete ${id}`); + assert.include( + output, + `Deleting projects/${projectId}/uptimeCheckConfigs/${id}` + ); + assert.include( + output, + `projects/${projectId}/uptimeCheckConfigs/${id} deleted.` + ); + }); +}); diff --git a/monitoring/snippets/uptime.js b/monitoring/snippets/uptime.js new file mode 100644 index 0000000000..addd5cb483 --- /dev/null +++ b/monitoring/snippets/uptime.js @@ -0,0 +1,331 @@ +// Copyright 2017 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. + +/** + * This application demonstrates how to perform basic operations on uptime check + * configs with the Google Stackdriver Monitoring API. + * + * For more information, see the README.md under /monitoring and the + * documentation at https://cloud.google.com/monitoring/uptime-checks/. + */ + +'use strict'; + +async function createUptimeCheckConfig(projectId, hostname) { + // [START monitoring_uptime_check_create] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.UptimeCheckServiceClient(); + + /** + * TODO(developer): Uncomment and edit the following lines of code. + */ + // const projectId = 'YOUR_PROJECT_ID'; + // const hostname = 'mydomain.com'; + + const request = { + // i.e. parent: 'projects/my-project-id' + parent: client.projectPath(projectId), + uptimeCheckConfig: { + displayName: 'My Uptime Check', + monitoredResource: { + // See the Uptime Check docs for supported MonitoredResource types + type: 'uptime_url', + labels: { + host: hostname, + }, + }, + httpCheck: { + path: '/', + port: 80, + }, + timeout: { + seconds: 10, + }, + period: { + seconds: 300, + }, + }, + }; + + // Creates an uptime check config for a GCE instance + const [uptimeCheckConfig] = await client.createUptimeCheckConfig(request); + console.log('Uptime check created:'); + console.log(`ID: ${uptimeCheckConfig.name}`); + console.log(`Display Name: ${uptimeCheckConfig.displayName}`); + console.log('Resource: %j', uptimeCheckConfig.monitoredResource); + console.log('Period: %j', uptimeCheckConfig.period); + console.log('Timeout: %j', uptimeCheckConfig.timeout); + console.log(`Check type: ${uptimeCheckConfig.check_request_type}`); + console.log( + 'Check: %j', + uptimeCheckConfig.httpCheck || uptimeCheckConfig.tcpCheck + ); + console.log( + `Content matchers: ${uptimeCheckConfig.contentMatchers + .map(matcher => matcher.content) + .join(', ')}` + ); + console.log(`Regions: ${uptimeCheckConfig.selectedRegions.join(', ')}`); + + // [END monitoring_uptime_check_create] +} + +async function listUptimeCheckConfigs(projectId) { + // [START monitoring_uptime_check_list_configs] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.UptimeCheckServiceClient(); + + /** + * TODO(developer): Uncomment and edit the following lines of code. + */ + // const projectId = 'YOUR_PROJECT_ID'; + + const request = { + parent: client.projectPath(projectId), + }; + + // Retrieves an uptime check config + const [uptimeCheckConfigs] = await client.listUptimeCheckConfigs(request); + + uptimeCheckConfigs.forEach(uptimeCheckConfig => { + console.log(`ID: ${uptimeCheckConfig.name}`); + console.log(` Display Name: ${uptimeCheckConfig.displayName}`); + console.log(' Resource: %j', uptimeCheckConfig.monitoredResource); + console.log(' Period: %j', uptimeCheckConfig.period); + console.log(' Timeout: %j', uptimeCheckConfig.timeout); + console.log(` Check type: ${uptimeCheckConfig.check_request_type}`); + console.log( + ' Check: %j', + uptimeCheckConfig.httpCheck || uptimeCheckConfig.tcpCheck + ); + console.log( + ` Content matchers: ${uptimeCheckConfig.contentMatchers + .map(matcher => matcher.content) + .join(', ')}` + ); + console.log(` Regions: ${uptimeCheckConfig.selectedRegions.join(', ')}`); + }); + + // [END monitoring_uptime_check_list_configs] +} + +async function listUptimeCheckIps() { + // [START monitoring_uptime_check_list_ips] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.UptimeCheckServiceClient(); + + // List uptime check IPs + const [uptimeCheckIps] = await client.listUptimeCheckIps(); + uptimeCheckIps.forEach(uptimeCheckIp => { + console.log( + uptimeCheckIp.region, + uptimeCheckIp.location, + uptimeCheckIp.ipAddress + ); + }); + + // [END monitoring_uptime_check_list_ips] +} + +async function getUptimeCheckConfig(projectId, uptimeCheckConfigId) { + // [START monitoring_uptime_check_get] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.UptimeCheckServiceClient(); + + /** + * TODO(developer): Uncomment and edit the following lines of code. + */ + // const projectId = 'YOUR_PROJECT_ID'; + // const uptimeCheckConfigId = 'YOUR_UPTIME_CHECK_CONFIG_ID'; + + const request = { + // i.e. name: 'projects/my-project-id/uptimeCheckConfigs/My-Uptime-Check + name: client.projectUptimeCheckConfigPath(projectId, uptimeCheckConfigId), + }; + + console.log(`Retrieving ${request.name}`); + + // Retrieves an uptime check config + const [uptimeCheckConfig] = await client.getUptimeCheckConfig(request); + console.log(`ID: ${uptimeCheckConfig.name}`); + console.log(`Display Name: ${uptimeCheckConfig.displayName}`); + console.log('Resource: %j', uptimeCheckConfig.monitoredResource); + console.log('Period: %j', uptimeCheckConfig.period); + console.log('Timeout: %j', uptimeCheckConfig.timeout); + console.log(`Check type: ${uptimeCheckConfig.check_request_type}`); + console.log( + 'Check: %j', + uptimeCheckConfig.httpCheck || uptimeCheckConfig.tcpCheck + ); + console.log( + `Content matchers: ${uptimeCheckConfig.contentMatchers + .map(matcher => matcher.content) + .join(', ')}` + ); + console.log(`Regions: ${uptimeCheckConfig.selectedRegions.join(', ')}`); + + // [END monitoring_uptime_check_get] +} + +async function deleteUptimeCheckConfig(projectId, uptimeCheckConfigId) { + // [START monitoring_uptime_check_delete] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.UptimeCheckServiceClient(); + + /** + * TODO(developer): Uncomment and edit the following lines of code. + */ + // const projectId = 'YOUR_PROJECT_ID'; + // const uptimeCheckConfigId = 'YOUR_UPTIME_CHECK_CONFIG_ID'; + + const request = { + // i.e. name: 'projects/my-project-id/uptimeCheckConfigs/My-Uptime-Check + name: client.projectUptimeCheckConfigPath(projectId, uptimeCheckConfigId), + }; + + console.log(`Deleting ${request.name}`); + + // Delete an uptime check config + await client.deleteUptimeCheckConfig(request); + console.log(`${request.name} deleted.`); + + // [END monitoring_uptime_check_delete] +} + +async function updateUptimeCheckConfigDisplayName( + projectId, + uptimeCheckConfigId, + displayName, + path +) { + // [START monitoring_uptime_check_update] + // Imports the Google Cloud client library + const monitoring = require('@google-cloud/monitoring'); + + // Creates a client + const client = new monitoring.UptimeCheckServiceClient(); + + /** + * TODO(developer): Uncomment and edit the following lines of code. + */ + // const projectId = 'YOUR_PROJECT_ID'; + // const uptimeCheckConfigId = 'YOUR_UPTIME_CHECK_CONFIG_ID'; + // const displayName = 'A New Display Name'; + // const path = '/some/path'; + + const request = { + // i.e. name: 'projects/my-project-id/uptimeCheckConfigs/My-Uptime-Check + name: client.projectUptimeCheckConfigPath(projectId, uptimeCheckConfigId), + }; + + console.log(`Updating ${request.name} to ${displayName}`); + + // Updates the display name and path on an uptime check config + request.uptimeCheckConfig = { + name: request.name, + displayName: displayName, + httpCheck: { + path: path, + }, + }; + + request.updateMask = { + paths: ['display_name', 'http_check.path'], + }; + + const [response] = await client.updateUptimeCheckConfig(request); + console.log(`${response.name} config updated.`); + + // [END monitoring_uptime_check_update] +} + +require('yargs') + .demand(1) + .command( + 'create [projectId]', + 'Creates an uptime check config.', + {}, + opts => createUptimeCheckConfig(opts.projectId, '' + opts.hostname) + ) + .command('list [projectId]', 'Lists uptime check configs.', {}, opts => + listUptimeCheckConfigs(opts.projectId) + ) + .command('list-ips', 'Lists uptime check config IPs.', {}, () => + listUptimeCheckIps() + ) + .command( + 'get [projectId]', + 'Gets an uptime check config.', + {}, + opts => getUptimeCheckConfig(opts.projectId, opts.uptimeCheckConfigId) + ) + .command( + 'delete [projectId]', + 'Deletes an uptime check config.', + {}, + opts => deleteUptimeCheckConfig(opts.projectId, opts.uptimeCheckConfigId) + ) + .command( + 'update [projectId]', + 'Update the display name of an uptime check config.', + {}, + opts => + updateUptimeCheckConfigDisplayName( + opts.projectId, + opts.uptimeCheckConfigId, + opts.displayName, + opts.path + ) + ) + .options({ + projectId: { + alias: 'p', + default: process.env.GCLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT, + global: true, + requiresArg: true, + type: 'string', + }, + }) + .example('node $0 create mydomain.com', 'Create an uptime check.') + .example('node $0 list', 'List all uptime check configs.') + .example( + 'node $0 list "resource.type = gce_instance AND resource.label.instance_id = mongodb"', + 'List all uptime check configs for a specific GCE instance.' + ) + .example('node $0 list-ips') + .example('node $0 get My-Uptime-Check') + .example('node $0 delete My-Uptime-Check') + .example('node $0 update My-Uptime-Check "My New Uptime Check" /some/path') + .wrap(120) + .recommendCommands() + .epilogue( + 'For more information, see https://cloud.google.com/monitoring/uptime-checks/' + ) + .help() + .strict().argv;