From 2970b22ca5b4959ee1d3e1e883cdb00951e3917f Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri, 8 Oct 2021 14:04:24 +0000 Subject: [PATCH] feat: add CreateServiceTimeSeries RPC (#235) - [ ] Regenerate this pull request now. PiperOrigin-RevId: 401594069 Source-Link: https://github.com/googleapis/googleapis/commit/3b9c98eda3bded7bb01e2f5f5c7d20f4a5d3e121 Source-Link: https://github.com/googleapis/googleapis-gen/commit/b892911cf9f71d7bd154acd928d8a580dd519f71 Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiYjg5MjkxMWNmOWY3MWQ3YmQxNTRhY2Q5MjhkOGE1ODBkZDUxOWY3MSJ9 --- .../cloud/monitoring_v3/gapic_metadata.json | 10 + .../services/metric_service/async_client.py | 100 +++++++- .../services/metric_service/client.py | 102 +++++++- .../metric_service/transports/base.py | 14 ++ .../metric_service/transports/grpc.py | 40 ++- .../metric_service/transports/grpc_asyncio.py | 40 ++- scripts/fixup_monitoring_v3_keywords.py | 1 + .../monitoring_v3/test_metric_service.py | 233 ++++++++++++++++++ 8 files changed, 532 insertions(+), 8 deletions(-) diff --git a/google/cloud/monitoring_v3/gapic_metadata.json b/google/cloud/monitoring_v3/gapic_metadata.json index 0b3e214a..c4311709 100644 --- a/google/cloud/monitoring_v3/gapic_metadata.json +++ b/google/cloud/monitoring_v3/gapic_metadata.json @@ -153,6 +153,11 @@ "create_metric_descriptor" ] }, + "CreateServiceTimeSeries": { + "methods": [ + "create_service_time_series" + ] + }, "CreateTimeSeries": { "methods": [ "create_time_series" @@ -198,6 +203,11 @@ "create_metric_descriptor" ] }, + "CreateServiceTimeSeries": { + "methods": [ + "create_service_time_series" + ] + }, "CreateTimeSeries": { "methods": [ "create_time_series" diff --git a/google/cloud/monitoring_v3/services/metric_service/async_client.py b/google/cloud/monitoring_v3/services/metric_service/async_client.py index 81fdb43b..38e530ae 100644 --- a/google/cloud/monitoring_v3/services/metric_service/async_client.py +++ b/google/cloud/monitoring_v3/services/metric_service/async_client.py @@ -561,8 +561,10 @@ async def create_metric_descriptor( timeout: float = None, metadata: Sequence[Tuple[str, str]] = (), ) -> metric_pb2.MetricDescriptor: - r"""Creates a new metric descriptor. User-created metric descriptors - define `custom + r"""Creates a new metric descriptor. The creation is executed + asynchronously and callers may check the returned operation to + track its progress. User-created metric descriptors define + `custom metrics `__. Args: @@ -950,6 +952,100 @@ async def create_time_series( request, retry=retry, timeout=timeout, metadata=metadata, ) + async def create_service_time_series( + self, + request: metric_service.CreateTimeSeriesRequest = None, + *, + name: str = None, + time_series: Sequence[gm_metric.TimeSeries] = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + r"""Creates or adds data to one or more service time series. A + service time series is a time series for a metric from a Google + Cloud service. The response is empty if all time series in the + request were written. If any time series could not be written, a + corresponding failure message is included in the error response. + This endpoint rejects writes to user-defined metrics. This + method is only for use by Google Cloud services. Use + [projects.timeSeries.create][google.monitoring.v3.MetricService.CreateTimeSeries] + instead. + + Args: + request (:class:`google.cloud.monitoring_v3.types.CreateTimeSeriesRequest`): + The request object. The `CreateTimeSeries` request. + name (:class:`str`): + Required. The + `project `__ + on which to execute the request. The format is: + + :: + + projects/[PROJECT_ID_OR_NUMBER] + + This corresponds to the ``name`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + time_series (:class:`Sequence[google.cloud.monitoring_v3.types.TimeSeries]`): + Required. The new data to be added to a list of time + series. Adds at most one data point to each of several + time series. The new data point must be more recent than + any other point in its time series. Each ``TimeSeries`` + value must fully specify a unique time series by + supplying all label values for the metric and the + monitored resource. + + The maximum number of ``TimeSeries`` objects per + ``Create`` request is 200. + + This corresponds to the ``time_series`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([name, time_series]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = metric_service.CreateTimeSeriesRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + if name is not None: + request.name = name + if time_series: + request.time_series.extend(time_series) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.create_service_time_series, + default_timeout=None, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Send the request. + await rpc( + request, retry=retry, timeout=timeout, metadata=metadata, + ) + async def __aenter__(self): return self diff --git a/google/cloud/monitoring_v3/services/metric_service/client.py b/google/cloud/monitoring_v3/services/metric_service/client.py index 92db8888..db91a598 100644 --- a/google/cloud/monitoring_v3/services/metric_service/client.py +++ b/google/cloud/monitoring_v3/services/metric_service/client.py @@ -742,8 +742,10 @@ def create_metric_descriptor( timeout: float = None, metadata: Sequence[Tuple[str, str]] = (), ) -> metric_pb2.MetricDescriptor: - r"""Creates a new metric descriptor. User-created metric descriptors - define `custom + r"""Creates a new metric descriptor. The creation is executed + asynchronously and callers may check the returned operation to + track its progress. User-created metric descriptors define + `custom metrics `__. Args: @@ -1113,6 +1115,102 @@ def create_time_series( request, retry=retry, timeout=timeout, metadata=metadata, ) + def create_service_time_series( + self, + request: Union[metric_service.CreateTimeSeriesRequest, dict] = None, + *, + name: str = None, + time_series: Sequence[gm_metric.TimeSeries] = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + r"""Creates or adds data to one or more service time series. A + service time series is a time series for a metric from a Google + Cloud service. The response is empty if all time series in the + request were written. If any time series could not be written, a + corresponding failure message is included in the error response. + This endpoint rejects writes to user-defined metrics. This + method is only for use by Google Cloud services. Use + [projects.timeSeries.create][google.monitoring.v3.MetricService.CreateTimeSeries] + instead. + + Args: + request (Union[google.cloud.monitoring_v3.types.CreateTimeSeriesRequest, dict]): + The request object. The `CreateTimeSeries` request. + name (str): + Required. The + `project `__ + on which to execute the request. The format is: + + :: + + projects/[PROJECT_ID_OR_NUMBER] + + This corresponds to the ``name`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + time_series (Sequence[google.cloud.monitoring_v3.types.TimeSeries]): + Required. The new data to be added to a list of time + series. Adds at most one data point to each of several + time series. The new data point must be more recent than + any other point in its time series. Each ``TimeSeries`` + value must fully specify a unique time series by + supplying all label values for the metric and the + monitored resource. + + The maximum number of ``TimeSeries`` objects per + ``Create`` request is 200. + + This corresponds to the ``time_series`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([name, time_series]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a metric_service.CreateTimeSeriesRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, metric_service.CreateTimeSeriesRequest): + request = metric_service.CreateTimeSeriesRequest(request) + # If we have keyword arguments corresponding to fields on the + # request, apply these. + if name is not None: + request.name = name + if time_series is not None: + request.time_series = time_series + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[ + self._transport.create_service_time_series + ] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Send the request. + rpc( + request, retry=retry, timeout=timeout, metadata=metadata, + ) + def __enter__(self): return self diff --git a/google/cloud/monitoring_v3/services/metric_service/transports/base.py b/google/cloud/monitoring_v3/services/metric_service/transports/base.py index 8e2f966e..7bc3cd98 100644 --- a/google/cloud/monitoring_v3/services/metric_service/transports/base.py +++ b/google/cloud/monitoring_v3/services/metric_service/transports/base.py @@ -254,6 +254,11 @@ def _prep_wrapped_messages(self, client_info): self.create_time_series: gapic_v1.method.wrap_method( self.create_time_series, default_timeout=12.0, client_info=client_info, ), + self.create_service_time_series: gapic_v1.method.wrap_method( + self.create_service_time_series, + default_timeout=None, + client_info=client_info, + ), } def close(self): @@ -349,5 +354,14 @@ def create_time_series( ]: raise NotImplementedError() + @property + def create_service_time_series( + self, + ) -> Callable[ + [metric_service.CreateTimeSeriesRequest], + Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]], + ]: + raise NotImplementedError() + __all__ = ("MetricServiceTransport",) diff --git a/google/cloud/monitoring_v3/services/metric_service/transports/grpc.py b/google/cloud/monitoring_v3/services/metric_service/transports/grpc.py index 17271a52..37cdb725 100644 --- a/google/cloud/monitoring_v3/services/metric_service/transports/grpc.py +++ b/google/cloud/monitoring_v3/services/metric_service/transports/grpc.py @@ -362,8 +362,10 @@ def create_metric_descriptor( ]: r"""Return a callable for the create metric descriptor method over gRPC. - Creates a new metric descriptor. User-created metric descriptors - define `custom + Creates a new metric descriptor. The creation is executed + asynchronously and callers may check the returned operation to + track its progress. User-created metric descriptors define + `custom metrics `__. Returns: @@ -471,6 +473,40 @@ def create_time_series( ) return self._stubs["create_time_series"] + @property + def create_service_time_series( + self, + ) -> Callable[[metric_service.CreateTimeSeriesRequest], empty_pb2.Empty]: + r"""Return a callable for the create service time series method over gRPC. + + Creates or adds data to one or more service time series. A + service time series is a time series for a metric from a Google + Cloud service. The response is empty if all time series in the + request were written. If any time series could not be written, a + corresponding failure message is included in the error response. + This endpoint rejects writes to user-defined metrics. This + method is only for use by Google Cloud services. Use + [projects.timeSeries.create][google.monitoring.v3.MetricService.CreateTimeSeries] + instead. + + Returns: + Callable[[~.CreateTimeSeriesRequest], + ~.Empty]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "create_service_time_series" not in self._stubs: + self._stubs["create_service_time_series"] = self.grpc_channel.unary_unary( + "/google.monitoring.v3.MetricService/CreateServiceTimeSeries", + request_serializer=metric_service.CreateTimeSeriesRequest.serialize, + response_deserializer=empty_pb2.Empty.FromString, + ) + return self._stubs["create_service_time_series"] + def close(self): self.grpc_channel.close() diff --git a/google/cloud/monitoring_v3/services/metric_service/transports/grpc_asyncio.py b/google/cloud/monitoring_v3/services/metric_service/transports/grpc_asyncio.py index 8d192a68..67fa535c 100644 --- a/google/cloud/monitoring_v3/services/metric_service/transports/grpc_asyncio.py +++ b/google/cloud/monitoring_v3/services/metric_service/transports/grpc_asyncio.py @@ -367,8 +367,10 @@ def create_metric_descriptor( ]: r"""Return a callable for the create metric descriptor method over gRPC. - Creates a new metric descriptor. User-created metric descriptors - define `custom + Creates a new metric descriptor. The creation is executed + asynchronously and callers may check the returned operation to + track its progress. User-created metric descriptors define + `custom metrics `__. Returns: @@ -479,6 +481,40 @@ def create_time_series( ) return self._stubs["create_time_series"] + @property + def create_service_time_series( + self, + ) -> Callable[[metric_service.CreateTimeSeriesRequest], Awaitable[empty_pb2.Empty]]: + r"""Return a callable for the create service time series method over gRPC. + + Creates or adds data to one or more service time series. A + service time series is a time series for a metric from a Google + Cloud service. The response is empty if all time series in the + request were written. If any time series could not be written, a + corresponding failure message is included in the error response. + This endpoint rejects writes to user-defined metrics. This + method is only for use by Google Cloud services. Use + [projects.timeSeries.create][google.monitoring.v3.MetricService.CreateTimeSeries] + instead. + + Returns: + Callable[[~.CreateTimeSeriesRequest], + Awaitable[~.Empty]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "create_service_time_series" not in self._stubs: + self._stubs["create_service_time_series"] = self.grpc_channel.unary_unary( + "/google.monitoring.v3.MetricService/CreateServiceTimeSeries", + request_serializer=metric_service.CreateTimeSeriesRequest.serialize, + response_deserializer=empty_pb2.Empty.FromString, + ) + return self._stubs["create_service_time_series"] + def close(self): return self.grpc_channel.close() diff --git a/scripts/fixup_monitoring_v3_keywords.py b/scripts/fixup_monitoring_v3_keywords.py index 806357f6..00b2177c 100644 --- a/scripts/fixup_monitoring_v3_keywords.py +++ b/scripts/fixup_monitoring_v3_keywords.py @@ -45,6 +45,7 @@ class monitoringCallTransformer(cst.CSTTransformer): 'create_notification_channel': ('name', 'notification_channel', ), 'create_service': ('parent', 'service', 'service_id', ), 'create_service_level_objective': ('parent', 'service_level_objective', 'service_level_objective_id', ), + 'create_service_time_series': ('name', 'time_series', ), 'create_time_series': ('name', 'time_series', ), 'create_uptime_check_config': ('parent', 'uptime_check_config', ), 'delete_alert_policy': ('name', ), diff --git a/tests/unit/gapic/monitoring_v3/test_metric_service.py b/tests/unit/gapic/monitoring_v3/test_metric_service.py index 0451f291..9e0600b2 100644 --- a/tests/unit/gapic/monitoring_v3/test_metric_service.py +++ b/tests/unit/gapic/monitoring_v3/test_metric_service.py @@ -2872,6 +2872,238 @@ async def test_create_time_series_flattened_error_async(): ) +def test_create_service_time_series( + transport: str = "grpc", request_type=metric_service.CreateTimeSeriesRequest +): + client = MetricServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.create_service_time_series), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = None + response = client.create_service_time_series(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == metric_service.CreateTimeSeriesRequest() + + # Establish that the response is the type that we expect. + assert response is None + + +def test_create_service_time_series_from_dict(): + test_create_service_time_series(request_type=dict) + + +def test_create_service_time_series_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = MetricServiceClient( + credentials=ga_credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.create_service_time_series), "__call__" + ) as call: + client.create_service_time_series() + call.assert_called() + _, args, _ = call.mock_calls[0] + assert args[0] == metric_service.CreateTimeSeriesRequest() + + +@pytest.mark.asyncio +async def test_create_service_time_series_async( + transport: str = "grpc_asyncio", request_type=metric_service.CreateTimeSeriesRequest +): + client = MetricServiceAsyncClient( + credentials=ga_credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.create_service_time_series), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) + response = await client.create_service_time_series(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == metric_service.CreateTimeSeriesRequest() + + # Establish that the response is the type that we expect. + assert response is None + + +@pytest.mark.asyncio +async def test_create_service_time_series_async_from_dict(): + await test_create_service_time_series_async(request_type=dict) + + +def test_create_service_time_series_field_headers(): + client = MetricServiceClient(credentials=ga_credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = metric_service.CreateTimeSeriesRequest() + + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.create_service_time_series), "__call__" + ) as call: + call.return_value = None + client.create_service_time_series(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_create_service_time_series_field_headers_async(): + client = MetricServiceAsyncClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = metric_service.CreateTimeSeriesRequest() + + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.create_service_time_series), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) + await client.create_service_time_series(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value",) in kw["metadata"] + + +def test_create_service_time_series_flattened(): + client = MetricServiceClient(credentials=ga_credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.create_service_time_series), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = None + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.create_service_time_series( + name="name_value", + time_series=[ + gm_metric.TimeSeries(metric=metric_pb2.Metric(type="type_value")) + ], + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0].name == "name_value" + assert args[0].time_series == [ + gm_metric.TimeSeries(metric=metric_pb2.Metric(type="type_value")) + ] + + +def test_create_service_time_series_flattened_error(): + client = MetricServiceClient(credentials=ga_credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_service_time_series( + metric_service.CreateTimeSeriesRequest(), + name="name_value", + time_series=[ + gm_metric.TimeSeries(metric=metric_pb2.Metric(type="type_value")) + ], + ) + + +@pytest.mark.asyncio +async def test_create_service_time_series_flattened_async(): + client = MetricServiceAsyncClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.create_service_time_series), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = None + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.create_service_time_series( + name="name_value", + time_series=[ + gm_metric.TimeSeries(metric=metric_pb2.Metric(type="type_value")) + ], + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0].name == "name_value" + assert args[0].time_series == [ + gm_metric.TimeSeries(metric=metric_pb2.Metric(type="type_value")) + ] + + +@pytest.mark.asyncio +async def test_create_service_time_series_flattened_error_async(): + client = MetricServiceAsyncClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.create_service_time_series( + metric_service.CreateTimeSeriesRequest(), + name="name_value", + time_series=[ + gm_metric.TimeSeries(metric=metric_pb2.Metric(type="type_value")) + ], + ) + + def test_credentials_transport_error(): # It is an error to provide credentials and a transport instance. transport = transports.MetricServiceGrpcTransport( @@ -2977,6 +3209,7 @@ def test_metric_service_base_transport(): "delete_metric_descriptor", "list_time_series", "create_time_series", + "create_service_time_series", ) for method in methods: with pytest.raises(NotImplementedError):