From f4a13b3242fdfb5e06e4868c0676bec5f6f1c02a Mon Sep 17 00:00:00 2001 From: Luke Sneeringer Date: Thu, 23 Mar 2017 14:49:26 -0700 Subject: [PATCH] CI Rehash (#3146) --- packages/google-cloud-monitoring/.flake8 | 6 + packages/google-cloud-monitoring/LICENSE | 202 ++++++++++ packages/google-cloud-monitoring/MANIFEST.in | 8 +- packages/google-cloud-monitoring/nox.py | 87 ++++ packages/google-cloud-monitoring/setup.py | 2 +- .../google-cloud-monitoring/tests/__init__.py | 0 .../google-cloud-monitoring/tests/system.py | 372 ++++++++++++++++++ .../{unit_tests => tests/unit}/__init__.py | 0 .../unit}/test__dataframe.py | 0 .../{unit_tests => tests/unit}/test__http.py | 0 .../{unit_tests => tests/unit}/test_client.py | 0 .../{unit_tests => tests/unit}/test_group.py | 0 .../{unit_tests => tests/unit}/test_label.py | 0 .../{unit_tests => tests/unit}/test_metric.py | 0 .../{unit_tests => tests/unit}/test_query.py | 0 .../unit}/test_resource.py | 0 .../unit}/test_timeseries.py | 0 packages/google-cloud-monitoring/tox.ini | 36 -- 18 files changed, 672 insertions(+), 41 deletions(-) create mode 100644 packages/google-cloud-monitoring/.flake8 create mode 100644 packages/google-cloud-monitoring/LICENSE create mode 100644 packages/google-cloud-monitoring/nox.py create mode 100644 packages/google-cloud-monitoring/tests/__init__.py create mode 100644 packages/google-cloud-monitoring/tests/system.py rename packages/google-cloud-monitoring/{unit_tests => tests/unit}/__init__.py (100%) rename packages/google-cloud-monitoring/{unit_tests => tests/unit}/test__dataframe.py (100%) rename packages/google-cloud-monitoring/{unit_tests => tests/unit}/test__http.py (100%) rename packages/google-cloud-monitoring/{unit_tests => tests/unit}/test_client.py (100%) rename packages/google-cloud-monitoring/{unit_tests => tests/unit}/test_group.py (100%) rename packages/google-cloud-monitoring/{unit_tests => tests/unit}/test_label.py (100%) rename packages/google-cloud-monitoring/{unit_tests => tests/unit}/test_metric.py (100%) rename packages/google-cloud-monitoring/{unit_tests => tests/unit}/test_query.py (100%) rename packages/google-cloud-monitoring/{unit_tests => tests/unit}/test_resource.py (100%) rename packages/google-cloud-monitoring/{unit_tests => tests/unit}/test_timeseries.py (100%) delete mode 100644 packages/google-cloud-monitoring/tox.ini diff --git a/packages/google-cloud-monitoring/.flake8 b/packages/google-cloud-monitoring/.flake8 new file mode 100644 index 000000000000..25168dc87605 --- /dev/null +++ b/packages/google-cloud-monitoring/.flake8 @@ -0,0 +1,6 @@ +[flake8] +exclude = + __pycache__, + .git, + *.pyc, + conf.py diff --git a/packages/google-cloud-monitoring/LICENSE b/packages/google-cloud-monitoring/LICENSE new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/packages/google-cloud-monitoring/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/packages/google-cloud-monitoring/MANIFEST.in b/packages/google-cloud-monitoring/MANIFEST.in index cb3a2b9ef4fa..9f7100c9528a 100644 --- a/packages/google-cloud-monitoring/MANIFEST.in +++ b/packages/google-cloud-monitoring/MANIFEST.in @@ -1,4 +1,4 @@ -include README.rst -graft google -graft unit_tests -global-exclude *.pyc +include README.rst LICENSE +recursive-include google *.json *.proto +recursive-include unit_tests * +global-exclude *.pyc __pycache__ diff --git a/packages/google-cloud-monitoring/nox.py b/packages/google-cloud-monitoring/nox.py new file mode 100644 index 000000000000..75eba7590c78 --- /dev/null +++ b/packages/google-cloud-monitoring/nox.py @@ -0,0 +1,87 @@ +# Copyright 2016 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 __future__ import absolute_import + +import os + +import nox + + +@nox.session +@nox.parametrize('python_version', ['2.7', '3.4', '3.5', '3.6']) +def unit_tests(session, python_version): + """Run the unit test suite.""" + + # Run unit tests against all supported versions of Python. + session.interpreter = 'python%s' % python_version + + # Install all test dependencies, then install this package in-place. + session.install('mock', 'pytest', 'pytest-cov', '../core/') + session.install('-e', '.') + + # Run py.test against the unit tests. + session.run('py.test', '--quiet', + '--cov=google.cloud.monitoring', '--cov=tests.unit', '--cov-append', + '--cov-config=.coveragerc', '--cov-report=', '--cov-fail-under=97', + 'tests/unit', + ) + + +@nox.session +@nox.parametrize('python_version', ['2.7', '3.6']) +def system_tests(session, python_version): + """Run the system test suite.""" + + # Sanity check: Only run system tests if the environment variable is set. + if not os.environ.get('GOOGLE_APPLICATION_CREDENTIALS', ''): + return + + # Run the system tests against latest Python 2 and Python 3 only. + session.interpreter = 'python%s' % python_version + + # Install all test dependencies, then install this package into the + # virutalenv's dist-packages. + session.install('mock', 'pytest', + '../core/', '../test_utils/') + session.install('.') + + # Run py.test against the system tests. + session.run('py.test', '--quiet', 'tests/system.py') + + +@nox.session +def lint(session): + """Run flake8. + + Returns a failure if flake8 finds linting errors or sufficiently + serious code quality issues. + """ + session.interpreter = 'python3.6' + session.install('flake8') + session.install('.') + session.run('flake8', 'google/cloud/monitoring') + + +@nox.session +def cover(session): + """Run the final coverage report. + + This outputs the coverage report aggregating coverage from the unit + test runs (not system test runs), and then erases coverage data. + """ + session.interpreter = 'python3.6' + session.install('coverage', 'pytest-cov') + session.run('coverage', 'report', '--show-missing', '--fail-under=100') + session.run('coverage', 'erase') diff --git a/packages/google-cloud-monitoring/setup.py b/packages/google-cloud-monitoring/setup.py index 5f98c9884b6e..a89408fb51b8 100644 --- a/packages/google-cloud-monitoring/setup.py +++ b/packages/google-cloud-monitoring/setup.py @@ -62,7 +62,7 @@ 'google', 'google.cloud', ], - packages=find_packages(), + packages=find_packages(exclude=('unit_tests*',)), install_requires=REQUIREMENTS, **SETUP_BASE ) diff --git a/packages/google-cloud-monitoring/tests/__init__.py b/packages/google-cloud-monitoring/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/google-cloud-monitoring/tests/system.py b/packages/google-cloud-monitoring/tests/system.py new file mode 100644 index 000000000000..46eb5a40f72c --- /dev/null +++ b/packages/google-cloud-monitoring/tests/system.py @@ -0,0 +1,372 @@ +# Copyright 2016 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. + +import unittest + +from google.cloud.exceptions import BadRequest +from google.cloud.exceptions import InternalServerError +from google.cloud.exceptions import NotFound +from google.cloud.exceptions import ServiceUnavailable +from google.cloud import monitoring + +from test_utils.retry import RetryErrors +from test_utils.retry import RetryResult +from test_utils.system import unique_resource_id + +retry_404 = RetryErrors(NotFound, max_tries=5) +retry_404_500 = RetryErrors((NotFound, InternalServerError)) +retry_500 = RetryErrors(InternalServerError) +retry_503 = RetryErrors(ServiceUnavailable) + + +class TestMonitoring(unittest.TestCase): + + def test_fetch_metric_descriptor(self): + METRIC_TYPE = ( + 'pubsub.googleapis.com/topic/send_message_operation_count') + METRIC_KIND = monitoring.MetricKind.DELTA + VALUE_TYPE = monitoring.ValueType.INT64 + + client = monitoring.Client() + descriptor = client.fetch_metric_descriptor(METRIC_TYPE) + + expected_name = 'projects/{project}/metricDescriptors/{type}'.format( + project=client.project, + type=METRIC_TYPE, + ) + self.assertEqual(descriptor.name, expected_name) + self.assertEqual(descriptor.type, METRIC_TYPE) + self.assertEqual(descriptor.metric_kind, METRIC_KIND) + self.assertEqual(descriptor.value_type, VALUE_TYPE) + self.assertTrue(descriptor.description) + + self.assertTrue(descriptor.labels) + for label in descriptor.labels: + self.assertTrue(label.key) + self.assertTrue(label.value_type) + self.assertTrue(label.description) + + def test_list_metric_descriptors(self): + METRIC_TYPE = ( + 'pubsub.googleapis.com/topic/send_message_operation_count') + METRIC_KIND = monitoring.MetricKind.DELTA + VALUE_TYPE = monitoring.ValueType.INT64 + + client = monitoring.Client() + + descriptor = None + for item in client.list_metric_descriptors(): + if item.type == METRIC_TYPE: + descriptor = item + + self.assertIsNotNone(descriptor) + + expected_name = 'projects/{project}/{what}/{type}'.format( + project=client.project, + what='metricDescriptors', + type=METRIC_TYPE, + ) + self.assertEqual(descriptor.name, expected_name) + self.assertEqual(descriptor.type, METRIC_TYPE) + self.assertEqual(descriptor.metric_kind, METRIC_KIND) + self.assertEqual(descriptor.value_type, VALUE_TYPE) + self.assertTrue(descriptor.description) + + self.assertTrue(descriptor.labels) + for label in descriptor.labels: + self.assertTrue(label.key) + self.assertTrue(label.value_type) + self.assertTrue(label.description) + + def test_list_metric_descriptors_filtered(self): + client = monitoring.Client() + + PREFIX = 'compute.googleapis.com/' + descriptors = client.list_metric_descriptors(type_prefix=PREFIX) + + # There are currently 18 types with this prefix, but that may change. + self.assertGreater(len(descriptors), 10) + + for descriptor in descriptors: + self.assertTrue(descriptor.type.startswith(PREFIX)) + + def test_fetch_resource_descriptor(self): + RESOURCE_TYPE = 'pubsub_topic' + + client = monitoring.Client() + descriptor = client.fetch_resource_descriptor(RESOURCE_TYPE) + + expected_name = 'projects/{project}/{what}/{type}'.format( + project=client.project, + what='monitoredResourceDescriptors', + type=RESOURCE_TYPE, + ) + self.assertEqual(descriptor.name, expected_name) + self.assertEqual(descriptor.type, RESOURCE_TYPE) + self.assertTrue(descriptor.display_name) + self.assertTrue(descriptor.description) + + self.assertTrue(descriptor.labels) + for label in descriptor.labels: + self.assertTrue(label.key) + self.assertTrue(label.value_type) + self.assertTrue(label.description) + + def test_list_resource_descriptors(self): + RESOURCE_TYPE = 'pubsub_topic' + + client = monitoring.Client() + + descriptor = None + for item in client.list_resource_descriptors(): + if item.type == RESOURCE_TYPE: + descriptor = item + + self.assertIsNotNone(descriptor) + + expected_name = 'projects/{project}/{what}/{type}'.format( + project=client.project, + what='monitoredResourceDescriptors', + type=RESOURCE_TYPE, + ) + self.assertEqual(descriptor.name, expected_name) + self.assertEqual(descriptor.type, RESOURCE_TYPE) + self.assertTrue(descriptor.display_name) + self.assertTrue(descriptor.description) + + self.assertTrue(descriptor.labels) + for label in descriptor.labels: + self.assertTrue(label.key) + self.assertTrue(label.value_type) + self.assertTrue(label.description) + + def test_query(self): + METRIC_TYPE = ( + 'pubsub.googleapis.com/topic/send_message_operation_count') + client = monitoring.Client() + query = client.query(METRIC_TYPE, hours=1) + # There may be no data, but we can ask anyway. + for _ in query: + pass # Not necessarily reached. + + def test_create_and_delete_metric_descriptor(self): + METRIC_TYPE = ('custom.googleapis.com/tmp/system_test_example' + + unique_resource_id()) + METRIC_KIND = monitoring.MetricKind.GAUGE + VALUE_TYPE = monitoring.ValueType.DOUBLE + DESCRIPTION = 'System test example -- DELETE ME!' + + client = monitoring.Client() + descriptor = client.metric_descriptor( + METRIC_TYPE, + metric_kind=METRIC_KIND, + value_type=VALUE_TYPE, + description=DESCRIPTION, + ) + + retry_500(descriptor.create)() + retry_404_500(descriptor.delete)() + + def test_write_point(self): + METRIC_TYPE = ('custom.googleapis.com/tmp/system_test_example' + + unique_resource_id()) + METRIC_KIND = monitoring.MetricKind.GAUGE + VALUE_TYPE = monitoring.ValueType.DOUBLE + DESCRIPTION = 'System test example -- DELETE ME!' + VALUE = 3.14 + + client = monitoring.Client() + descriptor = client.metric_descriptor( + METRIC_TYPE, + metric_kind=METRIC_KIND, + value_type=VALUE_TYPE, + description=DESCRIPTION, + ) + + descriptor.create() + + metric = client.metric(METRIC_TYPE, {}) + resource = client.resource('global', {}) + + retry_500(client.write_point)(metric, resource, VALUE) + + def _query_timeseries_with_retries(): + MAX_RETRIES = 10 + + def _has_timeseries(result): + return len(list(result)) > 0 + + retry_result = RetryResult(_has_timeseries, + max_tries=MAX_RETRIES)(client.query) + return RetryErrors(BadRequest, max_tries=MAX_RETRIES)(retry_result) + + query = _query_timeseries_with_retries()(METRIC_TYPE, minutes=5) + timeseries_list = list(query) + self.assertEqual(len(timeseries_list), 1) + timeseries = timeseries_list[0] + self.assertEqual(timeseries.metric, metric) + # project_id label only exists on output. + del timeseries.resource.labels['project_id'] + self.assertEqual(timeseries.resource, resource) + + descriptor.delete() + + with self.assertRaises(NotFound): + descriptor.delete() + + +class TestMonitoringGroups(unittest.TestCase): + + def setUp(self): + self.to_delete = [] + self.DISPLAY_NAME = 'Testing: New group' + self.FILTER = 'resource.type = "gce_instance"' + self.IS_CLUSTER = True + + def tearDown(self): + for group in self.to_delete: + retry_404(group.delete)() + + def test_create_group(self): + client = monitoring.Client() + group = client.group( + display_name=self.DISPLAY_NAME, + filter_string=self.FILTER, + is_cluster=self.IS_CLUSTER, + ) + + retry_503(group.create)() + self.to_delete.append(group) + + self.assertTrue(group.exists()) + + def test_list_groups(self): + client = monitoring.Client() + new_group = client.group( + display_name=self.DISPLAY_NAME, + filter_string=self.FILTER, + is_cluster=self.IS_CLUSTER, + ) + before_groups = client.list_groups() + before_names = set(group.name for group in before_groups) + + retry_503(new_group.create)() + self.to_delete.append(new_group) + + self.assertTrue(new_group.exists()) + after_groups = client.list_groups() + after_names = set(group.name for group in after_groups) + self.assertEqual(after_names - before_names, + set([new_group.name])) + + def test_reload_group(self): + client = monitoring.Client() + group = client.group( + display_name=self.DISPLAY_NAME, + filter_string=self.FILTER, + is_cluster=self.IS_CLUSTER, + ) + + retry_503(group.create)() + self.to_delete.append(group) + + group.filter = 'resource.type = "aws_ec2_instance"' + group.display_name = 'locally changed name' + group.reload() + self.assertEqual(group.filter, self.FILTER) + self.assertEqual(group.display_name, self.DISPLAY_NAME) + + def test_update_group(self): + NEW_FILTER = 'resource.type = "aws_ec2_instance"' + NEW_DISPLAY_NAME = 'updated' + + client = monitoring.Client() + group = client.group( + display_name=self.DISPLAY_NAME, + filter_string=self.FILTER, + is_cluster=self.IS_CLUSTER, + ) + + retry_503(group.create)() + self.to_delete.append(group) + + group.filter = NEW_FILTER + group.display_name = NEW_DISPLAY_NAME + group.update() + + after = client.fetch_group(group.id) + self.assertEqual(after.filter, NEW_FILTER) + self.assertEqual(after.display_name, NEW_DISPLAY_NAME) + + def test_list_group_members(self): + client = monitoring.Client() + group = client.group( + display_name=self.DISPLAY_NAME, + filter_string=self.FILTER, + is_cluster=self.IS_CLUSTER, + ) + + retry_503(group.create)() + self.to_delete.append(group) + + for member in group.list_members(): + self.assertIsInstance(member, monitoring.Resource) + + def test_group_hierarchy(self): + client = monitoring.Client() + root_group = client.group( + display_name='Testing: Root group', + filter_string=self.FILTER, + ) + + retry_503(root_group.create)() + self.to_delete.insert(0, root_group) + + middle_group = client.group( + display_name='Testing: Middle group', + filter_string=self.FILTER, + parent_id=root_group.id, + ) + + retry_503(middle_group.create)() + self.to_delete.insert(0, middle_group) + + leaf_group = client.group( + display_name='Testing: Leaf group', + filter_string=self.FILTER, + parent_id=middle_group.id, + ) + + retry_503(leaf_group.create)() + self.to_delete.insert(0, leaf_group) + + # Test for parent. + actual_parent = middle_group.fetch_parent() + self.assertTrue(actual_parent.name, root_group.name) + + # Test for children. + actual_children = middle_group.list_children() + children_names = [group.name for group in actual_children] + self.assertEqual(children_names, [leaf_group.name]) + + # Test for descendants. + actual_descendants = root_group.list_descendants() + descendant_names = {group.name for group in actual_descendants} + self.assertEqual(descendant_names, + set([middle_group.name, leaf_group.name])) + + # Test for ancestors. + actual_ancestors = leaf_group.list_ancestors() + ancestor_names = [group.name for group in actual_ancestors] + self.assertEqual(ancestor_names, [middle_group.name, root_group.name]) diff --git a/packages/google-cloud-monitoring/unit_tests/__init__.py b/packages/google-cloud-monitoring/tests/unit/__init__.py similarity index 100% rename from packages/google-cloud-monitoring/unit_tests/__init__.py rename to packages/google-cloud-monitoring/tests/unit/__init__.py diff --git a/packages/google-cloud-monitoring/unit_tests/test__dataframe.py b/packages/google-cloud-monitoring/tests/unit/test__dataframe.py similarity index 100% rename from packages/google-cloud-monitoring/unit_tests/test__dataframe.py rename to packages/google-cloud-monitoring/tests/unit/test__dataframe.py diff --git a/packages/google-cloud-monitoring/unit_tests/test__http.py b/packages/google-cloud-monitoring/tests/unit/test__http.py similarity index 100% rename from packages/google-cloud-monitoring/unit_tests/test__http.py rename to packages/google-cloud-monitoring/tests/unit/test__http.py diff --git a/packages/google-cloud-monitoring/unit_tests/test_client.py b/packages/google-cloud-monitoring/tests/unit/test_client.py similarity index 100% rename from packages/google-cloud-monitoring/unit_tests/test_client.py rename to packages/google-cloud-monitoring/tests/unit/test_client.py diff --git a/packages/google-cloud-monitoring/unit_tests/test_group.py b/packages/google-cloud-monitoring/tests/unit/test_group.py similarity index 100% rename from packages/google-cloud-monitoring/unit_tests/test_group.py rename to packages/google-cloud-monitoring/tests/unit/test_group.py diff --git a/packages/google-cloud-monitoring/unit_tests/test_label.py b/packages/google-cloud-monitoring/tests/unit/test_label.py similarity index 100% rename from packages/google-cloud-monitoring/unit_tests/test_label.py rename to packages/google-cloud-monitoring/tests/unit/test_label.py diff --git a/packages/google-cloud-monitoring/unit_tests/test_metric.py b/packages/google-cloud-monitoring/tests/unit/test_metric.py similarity index 100% rename from packages/google-cloud-monitoring/unit_tests/test_metric.py rename to packages/google-cloud-monitoring/tests/unit/test_metric.py diff --git a/packages/google-cloud-monitoring/unit_tests/test_query.py b/packages/google-cloud-monitoring/tests/unit/test_query.py similarity index 100% rename from packages/google-cloud-monitoring/unit_tests/test_query.py rename to packages/google-cloud-monitoring/tests/unit/test_query.py diff --git a/packages/google-cloud-monitoring/unit_tests/test_resource.py b/packages/google-cloud-monitoring/tests/unit/test_resource.py similarity index 100% rename from packages/google-cloud-monitoring/unit_tests/test_resource.py rename to packages/google-cloud-monitoring/tests/unit/test_resource.py diff --git a/packages/google-cloud-monitoring/unit_tests/test_timeseries.py b/packages/google-cloud-monitoring/tests/unit/test_timeseries.py similarity index 100% rename from packages/google-cloud-monitoring/unit_tests/test_timeseries.py rename to packages/google-cloud-monitoring/tests/unit/test_timeseries.py diff --git a/packages/google-cloud-monitoring/tox.ini b/packages/google-cloud-monitoring/tox.ini deleted file mode 100644 index f9ba521efeed..000000000000 --- a/packages/google-cloud-monitoring/tox.ini +++ /dev/null @@ -1,36 +0,0 @@ -[tox] -envlist = - py27,py34,py35,cover,{py27,py34,py35}-{pandas} - -[testing] -localdeps = - pip install --quiet --upgrade {toxinidir}/../core -deps = - {toxinidir}/../core - pytest - mock -covercmd = - py.test --quiet \ - --cov=google.cloud.monitoring \ - --cov=unit_tests \ - --cov-config {toxinidir}/.coveragerc \ - unit_tests - -[testenv] -commands = - {[testing]localdeps} - py.test --quiet {posargs} unit_tests -deps = - {[testing]deps} - pandas: pandas - -[testenv:cover] -basepython = - python2.7 -commands = - {[testing]localdeps} - {[testing]covercmd} -deps = - {[testing]deps} - coverage - pytest-cov