From 3bf42d62dd8ba1365fc7c9a11de4cfc6c13d93aa Mon Sep 17 00:00:00 2001 From: Jeffrey Rennie Date: Thu, 10 May 2018 09:07:07 -0700 Subject: [PATCH 1/6] Stackdriver monitoring alerts sample. --- monitoring/api/v3/alerts-client/.gitignore | 1 + monitoring/api/v3/alerts-client/README.rst | 117 ++++++++ monitoring/api/v3/alerts-client/README.rst.in | 26 ++ .../api/v3/alerts-client/requirements.txt | 2 + monitoring/api/v3/alerts-client/snippets.py | 270 ++++++++++++++++++ .../api/v3/alerts-client/snippets_test.py | 111 +++++++ .../v3/alerts-client/test_alert_policy.json | 31 ++ .../test_notification_channel.json | 15 + 8 files changed, 573 insertions(+) create mode 100644 monitoring/api/v3/alerts-client/.gitignore create mode 100644 monitoring/api/v3/alerts-client/README.rst create mode 100644 monitoring/api/v3/alerts-client/README.rst.in create mode 100644 monitoring/api/v3/alerts-client/requirements.txt create mode 100644 monitoring/api/v3/alerts-client/snippets.py create mode 100644 monitoring/api/v3/alerts-client/snippets_test.py create mode 100644 monitoring/api/v3/alerts-client/test_alert_policy.json create mode 100644 monitoring/api/v3/alerts-client/test_notification_channel.json diff --git a/monitoring/api/v3/alerts-client/.gitignore b/monitoring/api/v3/alerts-client/.gitignore new file mode 100644 index 000000000000..de0a466d79c3 --- /dev/null +++ b/monitoring/api/v3/alerts-client/.gitignore @@ -0,0 +1 @@ +backup.json diff --git a/monitoring/api/v3/alerts-client/README.rst b/monitoring/api/v3/alerts-client/README.rst new file mode 100644 index 000000000000..68eba2344eb6 --- /dev/null +++ b/monitoring/api/v3/alerts-client/README.rst @@ -0,0 +1,117 @@ +.. This file is automatically generated. Do not edit this file directly. + +Google Stackdriver Alerting API Python Samples +=============================================================================== + +.. image:: https://gstatic.com/cloudssh/images/open-btn.png + :target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=monitoring/api/v3/alerts-client/README.rst + + +This directory contains samples for Google Stackdriver Alerting API. Stackdriver Monitoring collects metrics, events, and metadata from Google Cloud Platform, Amazon Web Services (AWS), hosted uptime probes, application instrumentation, and a variety of common application components including Cassandra, Nginx, Apache Web Server, Elasticsearch and many others. Stackdriver's Alerting API allows you to create, delete, and make back up copies of your alert policies. + + + + +.. _Google Stackdriver Alerting API: https://cloud.google.com/monitoring/alerts/ + +Setup +------------------------------------------------------------------------------- + + +Authentication +++++++++++++++ + +This sample requires you to have authentication setup. Refer to the +`Authentication Getting Started Guide`_ for instructions on setting up +credentials for applications. + +.. _Authentication Getting Started Guide: + https://cloud.google.com/docs/authentication/getting-started + +Install Dependencies +++++++++++++++++++++ + +#. Clone python-docs-samples and change directory to the sample directory you want to use. + + .. code-block:: bash + + $ git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git + +#. Install `pip`_ and `virtualenv`_ if you do not already have them. You may want to refer to the `Python Development Environment Setup Guide`_ for Google Cloud Platform for instructions. + + .. _Python Development Environment Setup Guide: + https://cloud.google.com/python/setup + +#. Create a virtualenv. Samples are compatible with Python 2.7 and 3.4+. + + .. code-block:: bash + + $ virtualenv env + $ source env/bin/activate + +#. Install the dependencies needed to run the samples. + + .. code-block:: bash + + $ pip install -r requirements.txt + +.. _pip: https://pip.pypa.io/ +.. _virtualenv: https://virtualenv.pypa.io/ + +Samples +------------------------------------------------------------------------------- + +Snippets ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +.. image:: https://gstatic.com/cloudssh/images/open-btn.png + :target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=monitoring/api/v3/alerts-client/snippets.py,monitoring/api/v3/alerts-client/README.rst + + + + +To run this sample: + +.. code-block:: bash + + $ python snippets.py + + usage: snippets.py [-h] + {list-alert-policies,list-notification-channels,enable-alert-policies,disable-alert-policies,replace-notification-channels,backup,restore} + ... + + Demonstrates AlertPolicy API operations. + + positional arguments: + {list-alert-policies,list-notification-channels,enable-alert-policies,disable-alert-policies,replace-notification-channels,backup,restore} + list-alert-policies + list-notification-channels + enable-alert-policies + disable-alert-policies + replace-notification-channels + backup + restore + + optional arguments: + -h, --help show this help message and exit + + + + + +The client library +------------------------------------------------------------------------------- + +This sample uses the `Google Cloud Client Library for Python`_. +You can read the documentation for more details on API usage and use GitHub +to `browse the source`_ and `report issues`_. + +.. _Google Cloud Client Library for Python: + https://googlecloudplatform.github.io/google-cloud-python/ +.. _browse the source: + https://github.com/GoogleCloudPlatform/google-cloud-python +.. _report issues: + https://github.com/GoogleCloudPlatform/google-cloud-python/issues + + +.. _Google Cloud SDK: https://cloud.google.com/sdk/ \ No newline at end of file diff --git a/monitoring/api/v3/alerts-client/README.rst.in b/monitoring/api/v3/alerts-client/README.rst.in new file mode 100644 index 000000000000..ed7f6a3bcf1e --- /dev/null +++ b/monitoring/api/v3/alerts-client/README.rst.in @@ -0,0 +1,26 @@ +# This file is used to generate README.rst + +product: + name: Google Stackdriver Alerting API + short_name: Stackdriver Alerting API + url: https://cloud.google.com/monitoring/alerts/ + description: > + Stackdriver Monitoring collects metrics, events, and metadata from Google + Cloud Platform, Amazon Web Services (AWS), hosted uptime probes, + application instrumentation, and a variety of common application + components including Cassandra, Nginx, Apache Web Server, Elasticsearch + and many others. Stackdriver's Alerting API allows you to create, + delete, and make back up copies of your alert policies. + +setup: +- auth +- install_deps + +samples: +- name: Snippets + file: snippets.py + show_help: true + +cloud_client_library: true + +folder: monitoring/api/v3/alerts-client \ No newline at end of file diff --git a/monitoring/api/v3/alerts-client/requirements.txt b/monitoring/api/v3/alerts-client/requirements.txt new file mode 100644 index 000000000000..09d8759373e9 --- /dev/null +++ b/monitoring/api/v3/alerts-client/requirements.txt @@ -0,0 +1,2 @@ +google-cloud-monitoring==0.29.0 +tabulate==0.8.2 diff --git a/monitoring/api/v3/alerts-client/snippets.py b/monitoring/api/v3/alerts-client/snippets.py new file mode 100644 index 000000000000..f3dc73ac6eec --- /dev/null +++ b/monitoring/api/v3/alerts-client/snippets.py @@ -0,0 +1,270 @@ +# 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. + +from google.cloud import monitoring_v3 +from tabulate import tabulate +import argparse +import google.protobuf.json_format +import json +import os +import typing + + +def list_alert_policies(project_name: str): + client = monitoring_v3.AlertPolicyServiceClient() + policies = client.list_alert_policies(project_name) + print(tabulate([(policy.name, policy.display_name) for policy in policies], + ('name', 'display_name'))) + + +# [START monitoring_alert_list_channels] +def list_notification_channels(project_name: str): + client = monitoring_v3.NotificationChannelServiceClient() + channels = client.list_notification_channels(project_name) + print(tabulate([(channel.name, channel.display_name) for channel in channels], + ('name', 'display_name'))) +# [END monitoring_alert_list_channels] + + +# [START monitoring_alert_enable_policies] +def enable_alert_policies(project_name: str, enable, filter_: str = None): + client = monitoring_v3.AlertPolicyServiceClient() + policies = client.list_alert_policies(project_name, filter_=filter_) + for policy in policies: + if bool(enable) == policy.enabled.value: + print('Policy', policy.name, 'is already', + 'enabled' if policy.enabled.value else 'disabled') + else: + policy.enabled.value = bool(enable) + mask = monitoring_v3.types.field_mask_pb2.FieldMask() + mask.paths.append('enabled') + client.update_alert_policy(policy, mask) + print('Enabled' if enable else 'Disabled', policy.name) +# [END monitoring_alert_enable_policies] + +# [START monitoring_alert_replace_channels] +def replace_notification_channels(project_name: str, alert_policy_id: str, + channel_ids: typing.Sequence[str]): + _, project_id = project_name.split('/') + alert_client = monitoring_v3.AlertPolicyServiceClient() + channel_client = monitoring_v3.NotificationChannelServiceClient() + policy = monitoring_v3.types.alert_pb2.AlertPolicy() + policy.name = alert_client.alert_policy_path(project_id, alert_policy_id) + for channel_id in channel_ids: + policy.notification_channels.append( + channel_client.notification_channel_path(project_id, channel_id)) + mask = monitoring_v3.types.field_mask_pb2.FieldMask() + mask.paths.append('notification_channels') + updated_policy = alert_client.update_alert_policy(policy, mask) + print('Updated', updated_policy.name) +# [END monitoring_alert_replace_channels] + + +# [START monitoring_alert_backup_policies] +def backup(project_name: str): + alert_client = monitoring_v3.AlertPolicyServiceClient() + channel_client = monitoring_v3.NotificationChannelServiceClient() + record = {'project_name': project_name, + 'policies': list(alert_client.list_alert_policies(project_name)), + 'channels': list(channel_client.list_notification_channels(project_name))} + json.dump(record, open('backup.json', 'wt'), cls=ProtoEncoder, indent=2) + print('Backed up alert policies and notification channels to backup.json.') + + +class ProtoEncoder(json.JSONEncoder): + """Uses google.protobuf.json_format to encode protobufs as json.""" + def default(self, obj): + if type(obj) in (monitoring_v3.types.alert_pb2.AlertPolicy, + monitoring_v3.types.notification_pb2.NotificationChannel): + text = google.protobuf.json_format.MessageToJson(obj) + return json.loads(text) + return super(ProtoEncoder, self).default(obj) +# [END monitoring_alert_backup_policies] + + +# [START monitoring_alert_restore_policies] +def restore(project_name: str): + print('Loading alert policies and notification channels from backup.json.') + record = json.load(open('backup.json', 'rt')) + is_same_project = project_name == record['project_name'] + # Convert dicts to AlertPolicies. + policies_json = [json.dumps(policy) for policy in record['policies']] + policies = [google.protobuf.json_format.Parse( + policy_json, monitoring_v3.types.alert_pb2.AlertPolicy()) + for policy_json in policies_json] + # Convert dicts to NotificationChannels + channels_json = [json.dumps(channel) for channel in record['channels']] + channels = [google.protobuf.json_format.Parse( + channel_json, monitoring_v3.types.notification_pb2.NotificationChannel()) + for channel_json in channels_json] + + # Restore the channels. + channel_client = monitoring_v3.NotificationChannelServiceClient() + channel_name_map = {} + for channel in channels: + updated = False + print('Updating channel', channel.display_name) + # This field is immutable and it is illegal to specify a + # non-default value (UNVERIFIED or VERIFIED) in the + # Create() or Update() operations. + channel.verification_status = monitoring_v3.enums.NotificationChannel.VerificationStatus.VERIFICATION_STATUS_UNSPECIFIED + if is_same_project: + try: + channel_client.update_notification_channel(channel) + updated = True + except google.api_core.exceptions.NotFound: + pass + if not updated: + # The channel no longer exists. Recreate it. + old_name = channel.name + channel.ClearField("name") + new_channel = channel_client.create_notification_channel(project_name, channel) + channel_name_map[old_name] = new_channel.name + + # Restore the alerts + alert_client = monitoring_v3.AlertPolicyServiceClient() + for policy in policies: + print('Updating policy', policy.display_name) + # These two fields cannot be set directly, so clear them. + policy.ClearField('creation_record') + policy.ClearField('mutation_record') + # Update old channel names with new channel names. + for i, channel in enumerate(policy.notification_channels): + new_channel = channel_name_map.get(channel) + if new_channel: + policy.notification_channels[i] = new_channel + updated = False + if is_same_project: + try: + alert_client.update_alert_policy(policy) + updated = True + except google.api_core.exceptions.NotFound: + pass + except google.api_core.exceptions.InvalidArgument: + pass + if not updated: + # The policy no longer exists. Recreate it. + old_name = policy.name + policy.ClearField("name") + for condition in policy.conditions: + condition.ClearField("name") + policy = alert_client.create_alert_policy(project_name, policy) + print('Updated', policy.name) +# [END monitoring_alert_restore_policies] + + +class MissingProjectIdError(Exception): + pass + + +def project_id(): + """Retreieves the project id from the environment variable. + + Raises: + MissingProjectIdError -- When not set. + + Returns: + str -- the project name + """ + project_id = os.environ['GCLOUD_PROJECT'] + if not project_id: + raise MissingProjectIdError('Set the environment variable ' + + 'GCLOUD_PROJECT to your Google Cloud Project Id.') + return project_id + + +def project_name(): + return 'projects/' + project_id() + + +if __name__ == '__main__': + + parser = argparse.ArgumentParser( + description='Demonstrates AlertPolicy API operations.') + + subparsers = parser.add_subparsers(dest='command') + + list_alert_policies_parser = subparsers.add_parser( + 'list-alert-policies', + help=list_alert_policies.__doc__ + ) + + list_notification_channels_parser = subparsers.add_parser( + 'list-notification-channels', + help=list_alert_policies.__doc__ + ) + + enable_alert_policies_parser = subparsers.add_parser( + 'enable-alert-policies', + help=enable_alert_policies.__doc__ + ) + enable_alert_policies_parser.add_argument( + '--filter', + ) + + disable_alert_policies_parser = subparsers.add_parser( + 'disable-alert-policies', + help=enable_alert_policies.__doc__ + ) + disable_alert_policies_parser.add_argument( + '--filter', + ) + + replace_notification_channels_parser = subparsers.add_parser( + 'replace-notification-channels', + help=replace_notification_channels.__doc__ + ) + replace_notification_channels_parser.add_argument( + '-p', '--alert_policy_id', + required=True + ) + replace_notification_channels_parser.add_argument( + '-c', '--notification_channel_id', + required=True, + action='append' + ) + + backup_parser = subparsers.add_parser( + 'backup', + help=backup.__doc__ + ) + + restore_parser = subparsers.add_parser( + 'restore', + help=restore.__doc__ + ) + + args = parser.parse_args() + + if args.command == 'list-alert-policies': + list_alert_policies(project_name()) + + elif args.command == 'list-notification-channels': + list_notification_channels(project_name()) + + elif args.command == 'enable-alert-policies': + enable_alert_policies(project_name(), enable=True, filter_=args.filter) + + elif args.command == 'disable-alert-policies': + enable_alert_policies(project_name(), enable=False, filter_=args.filter) + + elif args.command == 'replace-notification-channels': + replace_notification_channels(project_name(), args.alert_policy_id, + args.notification_channel_id) + + elif args.command == 'backup': + backup(project_name()) + + elif args.command == 'restore': + restore(project_name()) diff --git a/monitoring/api/v3/alerts-client/snippets_test.py b/monitoring/api/v3/alerts-client/snippets_test.py new file mode 100644 index 000000000000..30009673b631 --- /dev/null +++ b/monitoring/api/v3/alerts-client/snippets_test.py @@ -0,0 +1,111 @@ +# 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. + +from gcp_devrel.testing import eventually_consistent + +import snippets +import pytest +from google.cloud import monitoring_v3 +import google.protobuf.json_format +import random +import string + +def random_name(length): + return ''.join([random.choice(string.ascii_lowercase) for i in range(length)]) + +class PochanFixture: + """A test fixture that creates an alert POlicy and a notification CHANnel, + hence the name, pochan. + """ + + def __init__(self): + self.project_id = snippets.project_id() + self.project_name = snippets.project_name() + self.alert_policy_client = monitoring_v3.AlertPolicyServiceClient() + self.notification_channel_client = ( + monitoring_v3.NotificationChannelServiceClient()) + + def __enter__(self): + # Create a policy. + policy = monitoring_v3.types.alert_pb2.AlertPolicy() + json = open('test_alert_policy.json').read() + google.protobuf.json_format.Parse(json, policy) + policy.display_name = 'snippets-test-' + random_name(10) + self.alert_policy = self.alert_policy_client.create_alert_policy( + self.project_name, policy) + # Create a notification channel. + notification_channel = ( + monitoring_v3.types.notification_pb2.NotificationChannel()) + json = open('test_notification_channel.json').read() + google.protobuf.json_format.Parse(json, notification_channel) + notification_channel.display_name = 'snippets-test-' + random_name(10) + self.notification_channel = ( + self.notification_channel_client.create_notification_channel( + self.project_name, notification_channel)) + return self + + def __exit__(self, type, value, traceback): + # Delete the policy and channel we created. + self.alert_policy_client.delete_alert_policy(self.alert_policy.name) + self.notification_channel_client.delete_notification_channel( + self.notification_channel.name) + + +@pytest.fixture(scope='session') +def pochan(): + with PochanFixture() as pochan: + yield pochan + + +def test_list_alert_policies(capsys, pochan: PochanFixture): + snippets.list_alert_policies(pochan.project_name) + out, _ = capsys.readouterr() + assert pochan.alert_policy.display_name in out + + +def test_enable_alert_policies(capsys, pochan: PochanFixture): + snippets.enable_alert_policies(pochan.project_name, False) + out, _ = capsys.readouterr() + + snippets.enable_alert_policies(pochan.project_name, False) + out, _ = capsys.readouterr() + assert "already disabled" in out + + snippets.enable_alert_policies(pochan.project_name, True) + out, _ = capsys.readouterr() + assert "Enabled {0}".format(pochan.project_name) in out + + snippets.enable_alert_policies(pochan.project_name, True) + out, _ = capsys.readouterr() + assert "already enabled" in out + + +def test_replace_channels(capsys, pochan: PochanFixture): + alert_policy_id = pochan.alert_policy.name.split('/')[-1] + notification_channel_id = pochan.notification_channel.name.split('/')[-1] + snippets.replace_notification_channels(pochan.project_name, alert_policy_id, + [notification_channel_id]) + out, _ = capsys.readouterr() + assert "Updated {0}".format(pochan.alert_policy.name) in out + + +def test_backup_and_restore(capsys, pochan: PochanFixture): + snippets.backup(pochan.project_name) + out, _ = capsys.readouterr() + + snippets.restore(pochan.project_name) + out, _ = capsys.readouterr() + assert "Updated {0}".format(pochan.alert_policy.name) in out + assert "Updating channel {0}".format(pochan.notification_channel.display_name) in out + diff --git a/monitoring/api/v3/alerts-client/test_alert_policy.json b/monitoring/api/v3/alerts-client/test_alert_policy.json new file mode 100644 index 000000000000..d728949f9bb3 --- /dev/null +++ b/monitoring/api/v3/alerts-client/test_alert_policy.json @@ -0,0 +1,31 @@ +{ + "displayName": "test_alert_policy.json", + "combiner": "OR", + "conditions": [ + { + "conditionThreshold": { + "filter": "metric.label.state=\"blocked\" AND metric.type=\"agent.googleapis.com/processes/count_by_state\" AND resource.type=\"gce_instance\"", + "comparison": "COMPARISON_GT", + "thresholdValue": 100, + "duration": "900s", + "trigger": { + "percent": 0 + }, + "aggregations": [ + { + "alignmentPeriod": "60s", + "perSeriesAligner": "ALIGN_MEAN", + "crossSeriesReducer": "REDUCE_MEAN", + "groupByFields": [ + "project", + "resource.label.instance_id", + "resource.label.zone" + ] + } + ] + }, + "displayName": "test_alert_policy.json" + } + ], + "enabled": false +} \ No newline at end of file diff --git a/monitoring/api/v3/alerts-client/test_notification_channel.json b/monitoring/api/v3/alerts-client/test_notification_channel.json new file mode 100644 index 000000000000..6a0d53c00cdd --- /dev/null +++ b/monitoring/api/v3/alerts-client/test_notification_channel.json @@ -0,0 +1,15 @@ +{ + "type": "email", + "displayName": "Email joe.", + "description": "test_notification_channel.json", + "labels": { + "email_address": "joe@example.com" + }, + "userLabels": { + "office": "california_westcoast_usa", + "division": "fulfillment", + "role": "operations", + "level": "5" + }, + "enabled": true +} \ No newline at end of file From 4ae52e90de40dc37918ae76ebd34ce6f21708cb8 Mon Sep 17 00:00:00 2001 From: Jeffrey Rennie Date: Thu, 10 May 2018 14:00:58 -0700 Subject: [PATCH 2/6] Lint --- monitoring/api/v3/alerts-client/snippets.py | 58 +++++++++++-------- .../api/v3/alerts-client/snippets_test.py | 29 +++++----- 2 files changed, 50 insertions(+), 37 deletions(-) diff --git a/monitoring/api/v3/alerts-client/snippets.py b/monitoring/api/v3/alerts-client/snippets.py index f3dc73ac6eec..c1c1896c2e8c 100644 --- a/monitoring/api/v3/alerts-client/snippets.py +++ b/monitoring/api/v3/alerts-client/snippets.py @@ -12,19 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -from google.cloud import monitoring_v3 -from tabulate import tabulate import argparse -import google.protobuf.json_format import json import os import typing +from google.cloud import monitoring_v3 +import google.protobuf.json_format +import tabulate + def list_alert_policies(project_name: str): client = monitoring_v3.AlertPolicyServiceClient() policies = client.list_alert_policies(project_name) - print(tabulate([(policy.name, policy.display_name) for policy in policies], + print(tabulate.tabulate( + [(policy.name, policy.display_name) for policy in policies], ('name', 'display_name'))) @@ -32,7 +34,8 @@ def list_alert_policies(project_name: str): def list_notification_channels(project_name: str): client = monitoring_v3.NotificationChannelServiceClient() channels = client.list_notification_channels(project_name) - print(tabulate([(channel.name, channel.display_name) for channel in channels], + print(tabulate.tabulate( + [(channel.name, channel.display_name) for channel in channels], ('name', 'display_name'))) # [END monitoring_alert_list_channels] @@ -44,7 +47,7 @@ def enable_alert_policies(project_name: str, enable, filter_: str = None): for policy in policies: if bool(enable) == policy.enabled.value: print('Policy', policy.name, 'is already', - 'enabled' if policy.enabled.value else 'disabled') + 'enabled' if policy.enabled.value else 'disabled') else: policy.enabled.value = bool(enable) mask = monitoring_v3.types.field_mask_pb2.FieldMask() @@ -53,9 +56,10 @@ def enable_alert_policies(project_name: str, enable, filter_: str = None): print('Enabled' if enable else 'Disabled', policy.name) # [END monitoring_alert_enable_policies] + # [START monitoring_alert_replace_channels] def replace_notification_channels(project_name: str, alert_policy_id: str, - channel_ids: typing.Sequence[str]): + channel_ids: typing.Sequence[str]): _, project_id = project_name.split('/') alert_client = monitoring_v3.AlertPolicyServiceClient() channel_client = monitoring_v3.NotificationChannelServiceClient() @@ -77,7 +81,8 @@ def backup(project_name: str): channel_client = monitoring_v3.NotificationChannelServiceClient() record = {'project_name': project_name, 'policies': list(alert_client.list_alert_policies(project_name)), - 'channels': list(channel_client.list_notification_channels(project_name))} + 'channels': list(channel_client.list_notification_channels( + project_name))} json.dump(record, open('backup.json', 'wt'), cls=ProtoEncoder, indent=2) print('Backed up alert policies and notification channels to backup.json.') @@ -86,12 +91,13 @@ class ProtoEncoder(json.JSONEncoder): """Uses google.protobuf.json_format to encode protobufs as json.""" def default(self, obj): if type(obj) in (monitoring_v3.types.alert_pb2.AlertPolicy, - monitoring_v3.types.notification_pb2.NotificationChannel): - text = google.protobuf.json_format.MessageToJson(obj) + monitoring_v3.types.notification_pb2. + NotificationChannel): + text = google.protobuf.json_format.MessageToJson(obj) return json.loads(text) return super(ProtoEncoder, self).default(obj) # [END monitoring_alert_backup_policies] - + # [START monitoring_alert_restore_policies] def restore(project_name: str): @@ -106,8 +112,8 @@ def restore(project_name: str): # Convert dicts to NotificationChannels channels_json = [json.dumps(channel) for channel in record['channels']] channels = [google.protobuf.json_format.Parse( - channel_json, monitoring_v3.types.notification_pb2.NotificationChannel()) - for channel_json in channels_json] + channel_json, monitoring_v3.types.notification_pb2. + NotificationChannel()) for channel_json in channels_json] # Restore the channels. channel_client = monitoring_v3.NotificationChannelServiceClient() @@ -118,7 +124,8 @@ def restore(project_name: str): # This field is immutable and it is illegal to specify a # non-default value (UNVERIFIED or VERIFIED) in the # Create() or Update() operations. - channel.verification_status = monitoring_v3.enums.NotificationChannel.VerificationStatus.VERIFICATION_STATUS_UNSPECIFIED + channel.verification_status = monitoring_v3.enums.NotificationChannel.\ + VerificationStatus.VERIFICATION_STATUS_UNSPECIFIED if is_same_project: try: channel_client.update_notification_channel(channel) @@ -129,7 +136,8 @@ def restore(project_name: str): # The channel no longer exists. Recreate it. old_name = channel.name channel.ClearField("name") - new_channel = channel_client.create_notification_channel(project_name, channel) + new_channel = channel_client.create_notification_channel( + project_name, channel) channel_name_map[old_name] = new_channel.name # Restore the alerts @@ -160,7 +168,7 @@ def restore(project_name: str): for condition in policy.conditions: condition.ClearField("name") policy = alert_client.create_alert_policy(project_name, policy) - print('Updated', policy.name) + print('Updated', policy.name) # [END monitoring_alert_restore_policies] @@ -170,16 +178,17 @@ class MissingProjectIdError(Exception): def project_id(): """Retreieves the project id from the environment variable. - + Raises: MissingProjectIdError -- When not set. - + Returns: str -- the project name """ project_id = os.environ['GCLOUD_PROJECT'] if not project_id: - raise MissingProjectIdError('Set the environment variable ' + + raise MissingProjectIdError( + 'Set the environment variable ' + 'GCLOUD_PROJECT to your Google Cloud Project Id.') return project_id @@ -227,7 +236,7 @@ def project_name(): ) replace_notification_channels_parser.add_argument( '-p', '--alert_policy_id', - required=True + required=True ) replace_notification_channels_parser.add_argument( '-c', '--notification_channel_id', @@ -257,14 +266,15 @@ def project_name(): enable_alert_policies(project_name(), enable=True, filter_=args.filter) elif args.command == 'disable-alert-policies': - enable_alert_policies(project_name(), enable=False, filter_=args.filter) + enable_alert_policies(project_name(), enable=False, + filter_=args.filter) elif args.command == 'replace-notification-channels': - replace_notification_channels(project_name(), args.alert_policy_id, - args.notification_channel_id) + replace_notification_channels(project_name(), args.alert_policy_id, + args.notification_channel_id) elif args.command == 'backup': backup(project_name()) - + elif args.command == 'restore': restore(project_name()) diff --git a/monitoring/api/v3/alerts-client/snippets_test.py b/monitoring/api/v3/alerts-client/snippets_test.py index 30009673b631..c04e4595ae37 100644 --- a/monitoring/api/v3/alerts-client/snippets_test.py +++ b/monitoring/api/v3/alerts-client/snippets_test.py @@ -12,17 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -from gcp_devrel.testing import eventually_consistent +import random +import string -import snippets -import pytest from google.cloud import monitoring_v3 import google.protobuf.json_format -import random -import string +import pytest + +import snippets + def random_name(length): - return ''.join([random.choice(string.ascii_lowercase) for i in range(length)]) + return ''.join( + [random.choice(string.ascii_lowercase) for i in range(length)]) + class PochanFixture: """A test fixture that creates an alert POlicy and a notification CHANnel, @@ -35,7 +38,7 @@ def __init__(self): self.alert_policy_client = monitoring_v3.AlertPolicyServiceClient() self.notification_channel_client = ( monitoring_v3.NotificationChannelServiceClient()) - + def __enter__(self): # Create a policy. policy = monitoring_v3.types.alert_pb2.AlertPolicy() @@ -72,7 +75,7 @@ def test_list_alert_policies(capsys, pochan: PochanFixture): snippets.list_alert_policies(pochan.project_name) out, _ = capsys.readouterr() assert pochan.alert_policy.display_name in out - + def test_enable_alert_policies(capsys, pochan: PochanFixture): snippets.enable_alert_policies(pochan.project_name, False) @@ -94,8 +97,8 @@ def test_enable_alert_policies(capsys, pochan: PochanFixture): def test_replace_channels(capsys, pochan: PochanFixture): alert_policy_id = pochan.alert_policy.name.split('/')[-1] notification_channel_id = pochan.notification_channel.name.split('/')[-1] - snippets.replace_notification_channels(pochan.project_name, alert_policy_id, - [notification_channel_id]) + snippets.replace_notification_channels( + pochan.project_name, alert_policy_id, [notification_channel_id]) out, _ = capsys.readouterr() assert "Updated {0}".format(pochan.alert_policy.name) in out @@ -103,9 +106,9 @@ def test_replace_channels(capsys, pochan: PochanFixture): def test_backup_and_restore(capsys, pochan: PochanFixture): snippets.backup(pochan.project_name) out, _ = capsys.readouterr() - + snippets.restore(pochan.project_name) out, _ = capsys.readouterr() assert "Updated {0}".format(pochan.alert_policy.name) in out - assert "Updating channel {0}".format(pochan.notification_channel.display_name) in out - + assert "Updating channel {0}".format( + pochan.notification_channel.display_name) in out From 1ee2fb2b04d85d9830935ec9d05d4433cbe99dfa Mon Sep 17 00:00:00 2001 From: Jeffrey Rennie Date: Thu, 10 May 2018 14:47:05 -0700 Subject: [PATCH 3/6] py27 tests pass --- monitoring/api/v3/alerts-client/snippets.py | 18 +++++++++--------- .../api/v3/alerts-client/snippets_test.py | 12 +++++++----- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/monitoring/api/v3/alerts-client/snippets.py b/monitoring/api/v3/alerts-client/snippets.py index c1c1896c2e8c..e3e5bee669d3 100644 --- a/monitoring/api/v3/alerts-client/snippets.py +++ b/monitoring/api/v3/alerts-client/snippets.py @@ -1,4 +1,4 @@ -# Copyright 2017 Google Inc. +# 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. @@ -12,17 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import print_function + import argparse import json import os -import typing from google.cloud import monitoring_v3 import google.protobuf.json_format import tabulate -def list_alert_policies(project_name: str): +def list_alert_policies(project_name): client = monitoring_v3.AlertPolicyServiceClient() policies = client.list_alert_policies(project_name) print(tabulate.tabulate( @@ -31,7 +32,7 @@ def list_alert_policies(project_name: str): # [START monitoring_alert_list_channels] -def list_notification_channels(project_name: str): +def list_notification_channels(project_name): client = monitoring_v3.NotificationChannelServiceClient() channels = client.list_notification_channels(project_name) print(tabulate.tabulate( @@ -41,7 +42,7 @@ def list_notification_channels(project_name: str): # [START monitoring_alert_enable_policies] -def enable_alert_policies(project_name: str, enable, filter_: str = None): +def enable_alert_policies(project_name, enable, filter_=None): client = monitoring_v3.AlertPolicyServiceClient() policies = client.list_alert_policies(project_name, filter_=filter_) for policy in policies: @@ -58,8 +59,7 @@ def enable_alert_policies(project_name: str, enable, filter_: str = None): # [START monitoring_alert_replace_channels] -def replace_notification_channels(project_name: str, alert_policy_id: str, - channel_ids: typing.Sequence[str]): +def replace_notification_channels(project_name, alert_policy_id, channel_ids): _, project_id = project_name.split('/') alert_client = monitoring_v3.AlertPolicyServiceClient() channel_client = monitoring_v3.NotificationChannelServiceClient() @@ -76,7 +76,7 @@ def replace_notification_channels(project_name: str, alert_policy_id: str, # [START monitoring_alert_backup_policies] -def backup(project_name: str): +def backup(project_name): alert_client = monitoring_v3.AlertPolicyServiceClient() channel_client = monitoring_v3.NotificationChannelServiceClient() record = {'project_name': project_name, @@ -100,7 +100,7 @@ def default(self, obj): # [START monitoring_alert_restore_policies] -def restore(project_name: str): +def restore(project_name): print('Loading alert policies and notification channels from backup.json.') record = json.load(open('backup.json', 'rt')) is_same_project = project_name == record['project_name'] diff --git a/monitoring/api/v3/alerts-client/snippets_test.py b/monitoring/api/v3/alerts-client/snippets_test.py index c04e4595ae37..4ae8aaba0a5b 100644 --- a/monitoring/api/v3/alerts-client/snippets_test.py +++ b/monitoring/api/v3/alerts-client/snippets_test.py @@ -1,4 +1,4 @@ -# Copyright 2017 Google Inc. +# 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. @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import print_function + import random import string @@ -71,13 +73,13 @@ def pochan(): yield pochan -def test_list_alert_policies(capsys, pochan: PochanFixture): +def test_list_alert_policies(capsys, pochan): snippets.list_alert_policies(pochan.project_name) out, _ = capsys.readouterr() assert pochan.alert_policy.display_name in out -def test_enable_alert_policies(capsys, pochan: PochanFixture): +def test_enable_alert_policies(capsys, pochan): snippets.enable_alert_policies(pochan.project_name, False) out, _ = capsys.readouterr() @@ -94,7 +96,7 @@ def test_enable_alert_policies(capsys, pochan: PochanFixture): assert "already enabled" in out -def test_replace_channels(capsys, pochan: PochanFixture): +def test_replace_channels(capsys, pochan): alert_policy_id = pochan.alert_policy.name.split('/')[-1] notification_channel_id = pochan.notification_channel.name.split('/')[-1] snippets.replace_notification_channels( @@ -103,7 +105,7 @@ def test_replace_channels(capsys, pochan: PochanFixture): assert "Updated {0}".format(pochan.alert_policy.name) in out -def test_backup_and_restore(capsys, pochan: PochanFixture): +def test_backup_and_restore(capsys, pochan): snippets.backup(pochan.project_name) out, _ = capsys.readouterr() From 943c04f0bd2a4ea42de6c11badb999c35b562b64 Mon Sep 17 00:00:00 2001 From: Jeffrey Rennie Date: Thu, 10 May 2018 14:51:58 -0700 Subject: [PATCH 4/6] Accomodate reviewer's comments. --- monitoring/api/v3/alerts-client/snippets.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/monitoring/api/v3/alerts-client/snippets.py b/monitoring/api/v3/alerts-client/snippets.py index e3e5bee669d3..edb00dfa6125 100644 --- a/monitoring/api/v3/alerts-client/snippets.py +++ b/monitoring/api/v3/alerts-client/snippets.py @@ -131,7 +131,7 @@ def restore(project_name): channel_client.update_notification_channel(channel) updated = True except google.api_core.exceptions.NotFound: - pass + pass # The channel was deleted. Create it below. if not updated: # The channel no longer exists. Recreate it. old_name = channel.name @@ -158,9 +158,11 @@ def restore(project_name): alert_client.update_alert_policy(policy) updated = True except google.api_core.exceptions.NotFound: - pass + pass # The policy was deleted. Create it below. except google.api_core.exceptions.InvalidArgument: - pass + # Annoying that API throws InvalidArgument when the policy + # does not exist. Seems like it should throw NotFound. + pass # The policy was deleted. Create it below. if not updated: # The policy no longer exists. Recreate it. old_name = policy.name From aaa697375234ada828506cbf123a08e0a2d2fe35 Mon Sep 17 00:00:00 2001 From: Jeffrey Rennie Date: Fri, 11 May 2018 08:36:38 -0700 Subject: [PATCH 5/6] Add spaces around code blocks, Inc => LLC, and add docstring. --- monitoring/api/v3/alerts-client/snippets.py | 27 ++++++++++++++++++- .../api/v3/alerts-client/snippets_test.py | 2 +- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/monitoring/api/v3/alerts-client/snippets.py b/monitoring/api/v3/alerts-client/snippets.py index edb00dfa6125..3a01a0810b6d 100644 --- a/monitoring/api/v3/alerts-client/snippets.py +++ b/monitoring/api/v3/alerts-client/snippets.py @@ -1,4 +1,4 @@ -# Copyright 2018 Google Inc. +# 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. @@ -43,8 +43,22 @@ def list_notification_channels(project_name): # [START monitoring_alert_enable_policies] def enable_alert_policies(project_name, enable, filter_=None): + """Enable or disable alert policies in a project. + + Arguments: + project_name {str} + enable {bool} -- Enable or disable the policies. + + Keyword Arguments: + filter_ {str} -- Only enable/disable alert policies that match this + filter_. See + https://cloud.google.com/monitoring/api/v3/sorting-and-filtering + (default: {None}) + """ + client = monitoring_v3.AlertPolicyServiceClient() policies = client.list_alert_policies(project_name, filter_=filter_) + for policy in policies: if bool(enable) == policy.enabled.value: print('Policy', policy.name, 'is already', @@ -65,9 +79,11 @@ def replace_notification_channels(project_name, alert_policy_id, channel_ids): channel_client = monitoring_v3.NotificationChannelServiceClient() policy = monitoring_v3.types.alert_pb2.AlertPolicy() policy.name = alert_client.alert_policy_path(project_id, alert_policy_id) + for channel_id in channel_ids: policy.notification_channels.append( channel_client.notification_channel_path(project_id, channel_id)) + mask = monitoring_v3.types.field_mask_pb2.FieldMask() mask.paths.append('notification_channels') updated_policy = alert_client.update_alert_policy(policy, mask) @@ -118,6 +134,7 @@ def restore(project_name): # Restore the channels. channel_client = monitoring_v3.NotificationChannelServiceClient() channel_name_map = {} + for channel in channels: updated = False print('Updating channel', channel.display_name) @@ -126,12 +143,14 @@ def restore(project_name): # Create() or Update() operations. channel.verification_status = monitoring_v3.enums.NotificationChannel.\ VerificationStatus.VERIFICATION_STATUS_UNSPECIFIED + if is_same_project: try: channel_client.update_notification_channel(channel) updated = True except google.api_core.exceptions.NotFound: pass # The channel was deleted. Create it below. + if not updated: # The channel no longer exists. Recreate it. old_name = channel.name @@ -142,17 +161,21 @@ def restore(project_name): # Restore the alerts alert_client = monitoring_v3.AlertPolicyServiceClient() + for policy in policies: print('Updating policy', policy.display_name) # These two fields cannot be set directly, so clear them. policy.ClearField('creation_record') policy.ClearField('mutation_record') + # Update old channel names with new channel names. for i, channel in enumerate(policy.notification_channels): new_channel = channel_name_map.get(channel) if new_channel: policy.notification_channels[i] = new_channel + updated = False + if is_same_project: try: alert_client.update_alert_policy(policy) @@ -163,6 +186,7 @@ def restore(project_name): # Annoying that API throws InvalidArgument when the policy # does not exist. Seems like it should throw NotFound. pass # The policy was deleted. Create it below. + if not updated: # The policy no longer exists. Recreate it. old_name = policy.name @@ -188,6 +212,7 @@ def project_id(): str -- the project name """ project_id = os.environ['GCLOUD_PROJECT'] + if not project_id: raise MissingProjectIdError( 'Set the environment variable ' + diff --git a/monitoring/api/v3/alerts-client/snippets_test.py b/monitoring/api/v3/alerts-client/snippets_test.py index 4ae8aaba0a5b..e58dc39858a4 100644 --- a/monitoring/api/v3/alerts-client/snippets_test.py +++ b/monitoring/api/v3/alerts-client/snippets_test.py @@ -1,4 +1,4 @@ -# Copyright 2018 Google Inc. +# 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. From 1c93dd4dfeeedff120e58fd411a5de2721b5a80a Mon Sep 17 00:00:00 2001 From: Jeffrey Rennie Date: Mon, 14 May 2018 16:44:34 -0700 Subject: [PATCH 6/6] Reformat doc comments to look like Google doc comments. --- monitoring/api/v3/alerts-client/snippets.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/monitoring/api/v3/alerts-client/snippets.py b/monitoring/api/v3/alerts-client/snippets.py index 3a01a0810b6d..24c2bf8411eb 100644 --- a/monitoring/api/v3/alerts-client/snippets.py +++ b/monitoring/api/v3/alerts-client/snippets.py @@ -46,14 +46,11 @@ def enable_alert_policies(project_name, enable, filter_=None): """Enable or disable alert policies in a project. Arguments: - project_name {str} - enable {bool} -- Enable or disable the policies. - - Keyword Arguments: - filter_ {str} -- Only enable/disable alert policies that match this - filter_. See + project_name (str) + enable (bool): Enable or disable the policies. + filter_ (str, optional): Only enable/disable alert policies that match + this filter_. See https://cloud.google.com/monitoring/api/v3/sorting-and-filtering - (default: {None}) """ client = monitoring_v3.AlertPolicyServiceClient()