diff --git a/google/cloud/bigtable_admin_v2/__init__.py b/google/cloud/bigtable_admin_v2/__init__.py index 1b46d6215..3713dc1e8 100644 --- a/google/cloud/bigtable_admin_v2/__init__.py +++ b/google/cloud/bigtable_admin_v2/__init__.py @@ -34,6 +34,8 @@ from .types.bigtable_instance_admin import ListAppProfilesResponse from .types.bigtable_instance_admin import ListClustersRequest from .types.bigtable_instance_admin import ListClustersResponse +from .types.bigtable_instance_admin import ListHotTabletsRequest +from .types.bigtable_instance_admin import ListHotTabletsResponse from .types.bigtable_instance_admin import ListInstancesRequest from .types.bigtable_instance_admin import ListInstancesResponse from .types.bigtable_instance_admin import PartialUpdateClusterMetadata @@ -78,6 +80,7 @@ from .types.instance import AutoscalingLimits from .types.instance import AutoscalingTargets from .types.instance import Cluster +from .types.instance import HotTablet from .types.instance import Instance from .types.table import Backup from .types.table import BackupInfo @@ -130,6 +133,7 @@ "GetInstanceRequest", "GetSnapshotRequest", "GetTableRequest", + "HotTablet", "Instance", "ListAppProfilesRequest", "ListAppProfilesResponse", @@ -137,6 +141,8 @@ "ListBackupsResponse", "ListClustersRequest", "ListClustersResponse", + "ListHotTabletsRequest", + "ListHotTabletsResponse", "ListInstancesRequest", "ListInstancesResponse", "ListSnapshotsRequest", diff --git a/google/cloud/bigtable_admin_v2/gapic_metadata.json b/google/cloud/bigtable_admin_v2/gapic_metadata.json index c360e7712..a843c42e0 100644 --- a/google/cloud/bigtable_admin_v2/gapic_metadata.json +++ b/google/cloud/bigtable_admin_v2/gapic_metadata.json @@ -70,6 +70,11 @@ "list_clusters" ] }, + "ListHotTablets": { + "methods": [ + "list_hot_tablets" + ] + }, "ListInstances": { "methods": [ "list_instances" @@ -175,6 +180,11 @@ "list_clusters" ] }, + "ListHotTablets": { + "methods": [ + "list_hot_tablets" + ] + }, "ListInstances": { "methods": [ "list_instances" diff --git a/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/async_client.py b/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/async_client.py index 18b6541dc..af8e62857 100644 --- a/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/async_client.py +++ b/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/async_client.py @@ -69,8 +69,14 @@ class BigtableInstanceAdminAsyncClient: parse_crypto_key_path = staticmethod( BigtableInstanceAdminClient.parse_crypto_key_path ) + hot_tablet_path = staticmethod(BigtableInstanceAdminClient.hot_tablet_path) + parse_hot_tablet_path = staticmethod( + BigtableInstanceAdminClient.parse_hot_tablet_path + ) instance_path = staticmethod(BigtableInstanceAdminClient.instance_path) parse_instance_path = staticmethod(BigtableInstanceAdminClient.parse_instance_path) + table_path = staticmethod(BigtableInstanceAdminClient.table_path) + parse_table_path = staticmethod(BigtableInstanceAdminClient.parse_table_path) common_billing_account_path = staticmethod( BigtableInstanceAdminClient.common_billing_account_path ) @@ -2109,6 +2115,98 @@ async def test_iam_permissions( # Done; return the response. return response + async def list_hot_tablets( + self, + request: Union[bigtable_instance_admin.ListHotTabletsRequest, dict] = None, + *, + parent: str = None, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pagers.ListHotTabletsAsyncPager: + r"""Lists hot tablets in a cluster, within the time range + provided. Hot tablets are ordered based on CPU usage. + + Args: + request (Union[google.cloud.bigtable_admin_v2.types.ListHotTabletsRequest, dict]): + The request object. Request message for + BigtableInstanceAdmin.ListHotTablets. + parent (:class:`str`): + Required. The cluster name to list hot tablets. Value is + in the following form: + ``projects/{project}/instances/{instance}/clusters/{cluster}``. + + This corresponds to the ``parent`` 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. + + Returns: + google.cloud.bigtable_admin_v2.services.bigtable_instance_admin.pagers.ListHotTabletsAsyncPager: + Response message for + BigtableInstanceAdmin.ListHotTablets. + Iterating over this object will yield + results and resolve additional pages + automatically. + + """ + # Create or coerce a protobuf request object. + # Quick check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([parent]) + 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 = bigtable_instance_admin.ListHotTabletsRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + if parent is not None: + request.parent = parent + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.list_hot_tablets, + default_retry=retries.Retry( + initial=1.0, + maximum=60.0, + multiplier=2, + predicate=retries.if_exception_type( + core_exceptions.DeadlineExceeded, + core_exceptions.ServiceUnavailable, + ), + deadline=60.0, + ), + default_timeout=60.0, + 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((("parent", request.parent),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # This method is paged; wrap the response in a pager, which provides + # an `__aiter__` convenience method. + response = pagers.ListHotTabletsAsyncPager( + method=rpc, request=request, response=response, metadata=metadata, + ) + + # Done; return the response. + return response + async def __aenter__(self): return self diff --git a/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/client.py b/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/client.py index a13a4b794..f149ccf9d 100644 --- a/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/client.py +++ b/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/client.py @@ -227,6 +227,24 @@ def parse_crypto_key_path(path: str) -> Dict[str, str]: ) return m.groupdict() if m else {} + @staticmethod + def hot_tablet_path( + project: str, instance: str, cluster: str, hot_tablet: str, + ) -> str: + """Returns a fully-qualified hot_tablet string.""" + return "projects/{project}/instances/{instance}/clusters/{cluster}/hotTablets/{hot_tablet}".format( + project=project, instance=instance, cluster=cluster, hot_tablet=hot_tablet, + ) + + @staticmethod + def parse_hot_tablet_path(path: str) -> Dict[str, str]: + """Parses a hot_tablet path into its component segments.""" + m = re.match( + r"^projects/(?P.+?)/instances/(?P.+?)/clusters/(?P.+?)/hotTablets/(?P.+?)$", + path, + ) + return m.groupdict() if m else {} + @staticmethod def instance_path(project: str, instance: str,) -> str: """Returns a fully-qualified instance string.""" @@ -240,6 +258,22 @@ def parse_instance_path(path: str) -> Dict[str, str]: m = re.match(r"^projects/(?P.+?)/instances/(?P.+?)$", path) return m.groupdict() if m else {} + @staticmethod + def table_path(project: str, instance: str, table: str,) -> str: + """Returns a fully-qualified table string.""" + return "projects/{project}/instances/{instance}/tables/{table}".format( + project=project, instance=instance, table=table, + ) + + @staticmethod + def parse_table_path(path: str) -> Dict[str, str]: + """Parses a table path into its component segments.""" + m = re.match( + r"^projects/(?P.+?)/instances/(?P.+?)/tables/(?P.+?)$", + path, + ) + return m.groupdict() if m else {} + @staticmethod def common_billing_account_path(billing_account: str,) -> str: """Returns a fully-qualified billing_account string.""" @@ -2215,6 +2249,88 @@ def test_iam_permissions( # Done; return the response. return response + def list_hot_tablets( + self, + request: Union[bigtable_instance_admin.ListHotTabletsRequest, dict] = None, + *, + parent: str = None, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pagers.ListHotTabletsPager: + r"""Lists hot tablets in a cluster, within the time range + provided. Hot tablets are ordered based on CPU usage. + + Args: + request (Union[google.cloud.bigtable_admin_v2.types.ListHotTabletsRequest, dict]): + The request object. Request message for + BigtableInstanceAdmin.ListHotTablets. + parent (str): + Required. The cluster name to list hot tablets. Value is + in the following form: + ``projects/{project}/instances/{instance}/clusters/{cluster}``. + + This corresponds to the ``parent`` 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. + + Returns: + google.cloud.bigtable_admin_v2.services.bigtable_instance_admin.pagers.ListHotTabletsPager: + Response message for + BigtableInstanceAdmin.ListHotTablets. + Iterating over this object will yield + results and resolve additional pages + automatically. + + """ + # Create or coerce a protobuf request object. + # Quick check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([parent]) + 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 bigtable_instance_admin.ListHotTabletsRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, bigtable_instance_admin.ListHotTabletsRequest): + request = bigtable_instance_admin.ListHotTabletsRequest(request) + # If we have keyword arguments corresponding to fields on the + # request, apply these. + if parent is not None: + request.parent = parent + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.list_hot_tablets] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # This method is paged; wrap the response in a pager, which provides + # an `__iter__` convenience method. + response = pagers.ListHotTabletsPager( + method=rpc, request=request, response=response, metadata=metadata, + ) + + # Done; return the response. + return response + def __enter__(self): return self diff --git a/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/pagers.py b/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/pagers.py index 77bde77e4..bfcbbf23d 100644 --- a/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/pagers.py +++ b/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/pagers.py @@ -158,3 +158,135 @@ async def async_generator(): def __repr__(self) -> str: return "{0}<{1!r}>".format(self.__class__.__name__, self._response) + + +class ListHotTabletsPager: + """A pager for iterating through ``list_hot_tablets`` requests. + + This class thinly wraps an initial + :class:`google.cloud.bigtable_admin_v2.types.ListHotTabletsResponse` object, and + provides an ``__iter__`` method to iterate through its + ``hot_tablets`` field. + + If there are more pages, the ``__iter__`` method will make additional + ``ListHotTablets`` requests and continue to iterate + through the ``hot_tablets`` field on the + corresponding responses. + + All the usual :class:`google.cloud.bigtable_admin_v2.types.ListHotTabletsResponse` + attributes are available on the pager. If multiple requests are made, only + the most recent response is retained, and thus used for attribute lookup. + """ + + def __init__( + self, + method: Callable[..., bigtable_instance_admin.ListHotTabletsResponse], + request: bigtable_instance_admin.ListHotTabletsRequest, + response: bigtable_instance_admin.ListHotTabletsResponse, + *, + metadata: Sequence[Tuple[str, str]] = () + ): + """Instantiate the pager. + + Args: + method (Callable): The method that was originally called, and + which instantiated this pager. + request (google.cloud.bigtable_admin_v2.types.ListHotTabletsRequest): + The initial request object. + response (google.cloud.bigtable_admin_v2.types.ListHotTabletsResponse): + The initial response object. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + self._method = method + self._request = bigtable_instance_admin.ListHotTabletsRequest(request) + self._response = response + self._metadata = metadata + + def __getattr__(self, name: str) -> Any: + return getattr(self._response, name) + + @property + def pages(self) -> Iterator[bigtable_instance_admin.ListHotTabletsResponse]: + yield self._response + while self._response.next_page_token: + self._request.page_token = self._response.next_page_token + self._response = self._method(self._request, metadata=self._metadata) + yield self._response + + def __iter__(self) -> Iterator[instance.HotTablet]: + for page in self.pages: + yield from page.hot_tablets + + def __repr__(self) -> str: + return "{0}<{1!r}>".format(self.__class__.__name__, self._response) + + +class ListHotTabletsAsyncPager: + """A pager for iterating through ``list_hot_tablets`` requests. + + This class thinly wraps an initial + :class:`google.cloud.bigtable_admin_v2.types.ListHotTabletsResponse` object, and + provides an ``__aiter__`` method to iterate through its + ``hot_tablets`` field. + + If there are more pages, the ``__aiter__`` method will make additional + ``ListHotTablets`` requests and continue to iterate + through the ``hot_tablets`` field on the + corresponding responses. + + All the usual :class:`google.cloud.bigtable_admin_v2.types.ListHotTabletsResponse` + attributes are available on the pager. If multiple requests are made, only + the most recent response is retained, and thus used for attribute lookup. + """ + + def __init__( + self, + method: Callable[ + ..., Awaitable[bigtable_instance_admin.ListHotTabletsResponse] + ], + request: bigtable_instance_admin.ListHotTabletsRequest, + response: bigtable_instance_admin.ListHotTabletsResponse, + *, + metadata: Sequence[Tuple[str, str]] = () + ): + """Instantiates the pager. + + Args: + method (Callable): The method that was originally called, and + which instantiated this pager. + request (google.cloud.bigtable_admin_v2.types.ListHotTabletsRequest): + The initial request object. + response (google.cloud.bigtable_admin_v2.types.ListHotTabletsResponse): + The initial response object. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + self._method = method + self._request = bigtable_instance_admin.ListHotTabletsRequest(request) + self._response = response + self._metadata = metadata + + def __getattr__(self, name: str) -> Any: + return getattr(self._response, name) + + @property + async def pages( + self, + ) -> AsyncIterator[bigtable_instance_admin.ListHotTabletsResponse]: + yield self._response + while self._response.next_page_token: + self._request.page_token = self._response.next_page_token + self._response = await self._method(self._request, metadata=self._metadata) + yield self._response + + def __aiter__(self) -> AsyncIterator[instance.HotTablet]: + async def async_generator(): + async for page in self.pages: + for response in page.hot_tablets: + yield response + + return async_generator() + + def __repr__(self) -> str: + return "{0}<{1!r}>".format(self.__class__.__name__, self._response) diff --git a/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/base.py b/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/base.py index b32cba715..2330879c7 100644 --- a/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/base.py +++ b/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/base.py @@ -350,6 +350,21 @@ def _prep_wrapped_messages(self, client_info): default_timeout=60.0, client_info=client_info, ), + self.list_hot_tablets: gapic_v1.method.wrap_method( + self.list_hot_tablets, + default_retry=retries.Retry( + initial=1.0, + maximum=60.0, + multiplier=2, + predicate=retries.if_exception_type( + core_exceptions.DeadlineExceeded, + core_exceptions.ServiceUnavailable, + ), + deadline=60.0, + ), + default_timeout=60.0, + client_info=client_info, + ), } def close(self): @@ -557,5 +572,17 @@ def test_iam_permissions( ]: raise NotImplementedError() + @property + def list_hot_tablets( + self, + ) -> Callable[ + [bigtable_instance_admin.ListHotTabletsRequest], + Union[ + bigtable_instance_admin.ListHotTabletsResponse, + Awaitable[bigtable_instance_admin.ListHotTabletsResponse], + ], + ]: + raise NotImplementedError() + __all__ = ("BigtableInstanceAdminTransport",) diff --git a/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc.py b/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc.py index 8a0169ad0..e33cc473f 100644 --- a/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc.py +++ b/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc.py @@ -830,6 +830,36 @@ def test_iam_permissions( ) return self._stubs["test_iam_permissions"] + @property + def list_hot_tablets( + self, + ) -> Callable[ + [bigtable_instance_admin.ListHotTabletsRequest], + bigtable_instance_admin.ListHotTabletsResponse, + ]: + r"""Return a callable for the list hot tablets method over gRPC. + + Lists hot tablets in a cluster, within the time range + provided. Hot tablets are ordered based on CPU usage. + + Returns: + Callable[[~.ListHotTabletsRequest], + ~.ListHotTabletsResponse]: + 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 "list_hot_tablets" not in self._stubs: + self._stubs["list_hot_tablets"] = self.grpc_channel.unary_unary( + "/google.bigtable.admin.v2.BigtableInstanceAdmin/ListHotTablets", + request_serializer=bigtable_instance_admin.ListHotTabletsRequest.serialize, + response_deserializer=bigtable_instance_admin.ListHotTabletsResponse.deserialize, + ) + return self._stubs["list_hot_tablets"] + def close(self): self.grpc_channel.close() diff --git a/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc_asyncio.py b/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc_asyncio.py index c1be2101a..bf1b8a38e 100644 --- a/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc_asyncio.py +++ b/google/cloud/bigtable_admin_v2/services/bigtable_instance_admin/transports/grpc_asyncio.py @@ -856,6 +856,36 @@ def test_iam_permissions( ) return self._stubs["test_iam_permissions"] + @property + def list_hot_tablets( + self, + ) -> Callable[ + [bigtable_instance_admin.ListHotTabletsRequest], + Awaitable[bigtable_instance_admin.ListHotTabletsResponse], + ]: + r"""Return a callable for the list hot tablets method over gRPC. + + Lists hot tablets in a cluster, within the time range + provided. Hot tablets are ordered based on CPU usage. + + Returns: + Callable[[~.ListHotTabletsRequest], + Awaitable[~.ListHotTabletsResponse]]: + 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 "list_hot_tablets" not in self._stubs: + self._stubs["list_hot_tablets"] = self.grpc_channel.unary_unary( + "/google.bigtable.admin.v2.BigtableInstanceAdmin/ListHotTablets", + request_serializer=bigtable_instance_admin.ListHotTabletsRequest.serialize, + response_deserializer=bigtable_instance_admin.ListHotTabletsResponse.deserialize, + ) + return self._stubs["list_hot_tablets"] + def close(self): return self.grpc_channel.close() diff --git a/google/cloud/bigtable_admin_v2/types/__init__.py b/google/cloud/bigtable_admin_v2/types/__init__.py index a81a4b7ed..f35f0f4ab 100644 --- a/google/cloud/bigtable_admin_v2/types/__init__.py +++ b/google/cloud/bigtable_admin_v2/types/__init__.py @@ -29,6 +29,8 @@ ListAppProfilesResponse, ListClustersRequest, ListClustersResponse, + ListHotTabletsRequest, + ListHotTabletsResponse, ListInstancesRequest, ListInstancesResponse, PartialUpdateClusterMetadata, @@ -79,6 +81,7 @@ AutoscalingLimits, AutoscalingTargets, Cluster, + HotTablet, Instance, ) from .table import ( @@ -109,6 +112,8 @@ "ListAppProfilesResponse", "ListClustersRequest", "ListClustersResponse", + "ListHotTabletsRequest", + "ListHotTabletsResponse", "ListInstancesRequest", "ListInstancesResponse", "PartialUpdateClusterMetadata", @@ -153,6 +158,7 @@ "AutoscalingLimits", "AutoscalingTargets", "Cluster", + "HotTablet", "Instance", "Backup", "BackupInfo", diff --git a/google/cloud/bigtable_admin_v2/types/bigtable_instance_admin.py b/google/cloud/bigtable_admin_v2/types/bigtable_instance_admin.py index 131c28177..b4d7c55aa 100644 --- a/google/cloud/bigtable_admin_v2/types/bigtable_instance_admin.py +++ b/google/cloud/bigtable_admin_v2/types/bigtable_instance_admin.py @@ -47,6 +47,8 @@ "UpdateAppProfileRequest", "DeleteAppProfileRequest", "UpdateAppProfileMetadata", + "ListHotTabletsRequest", + "ListHotTabletsResponse", }, ) @@ -553,4 +555,78 @@ class UpdateAppProfileMetadata(proto.Message): """ +class ListHotTabletsRequest(proto.Message): + r"""Request message for BigtableInstanceAdmin.ListHotTablets. + + Attributes: + parent (str): + Required. The cluster name to list hot tablets. Value is in + the following form: + ``projects/{project}/instances/{instance}/clusters/{cluster}``. + start_time (google.protobuf.timestamp_pb2.Timestamp): + The start time to list hot tablets. The hot + tablets in the response will have start times + between the requested start time and end time. + Start time defaults to Now if it is unset, and + end time defaults to Now - 24 hours if it is + unset. The start time should be less than the + end time, and the maximum allowed time range + between start time and end time is 48 hours. + Start time and end time should have values + between Now and Now - 14 days. + end_time (google.protobuf.timestamp_pb2.Timestamp): + The end time to list hot tablets. + page_size (int): + Maximum number of results per page. + + A page_size that is empty or zero lets the server choose the + number of items to return. A page_size which is strictly + positive will return at most that many items. A negative + page_size will cause an error. + + Following the first request, subsequent paginated calls do + not need a page_size field. If a page_size is set in + subsequent calls, it must match the page_size given in the + first request. + page_token (str): + The value of ``next_page_token`` returned by a previous + call. + """ + + parent = proto.Field(proto.STRING, number=1,) + start_time = proto.Field(proto.MESSAGE, number=2, message=timestamp_pb2.Timestamp,) + end_time = proto.Field(proto.MESSAGE, number=3, message=timestamp_pb2.Timestamp,) + page_size = proto.Field(proto.INT32, number=4,) + page_token = proto.Field(proto.STRING, number=5,) + + +class ListHotTabletsResponse(proto.Message): + r"""Response message for BigtableInstanceAdmin.ListHotTablets. + + Attributes: + hot_tablets (Sequence[google.cloud.bigtable_admin_v2.types.HotTablet]): + List of hot tablets in the tables of the + requested cluster that fall within the requested + time range. Hot tablets are ordered by node cpu + usage percent. If there are multiple hot tablets + that correspond to the same tablet within a + 15-minute interval, only the hot tablet with the + highest node cpu usage will be included in the + response. + next_page_token (str): + Set if not all hot tablets could be returned in a single + response. Pass this value to ``page_token`` in another + request to get the next page of results. + """ + + @property + def raw_page(self): + return self + + hot_tablets = proto.RepeatedField( + proto.MESSAGE, number=1, message=gba_instance.HotTablet, + ) + next_page_token = proto.Field(proto.STRING, number=2,) + + __all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/bigtable_admin_v2/types/instance.py b/google/cloud/bigtable_admin_v2/types/instance.py index 22c73dfdd..961cba434 100644 --- a/google/cloud/bigtable_admin_v2/types/instance.py +++ b/google/cloud/bigtable_admin_v2/types/instance.py @@ -27,6 +27,7 @@ "AutoscalingLimits", "Cluster", "AppProfile", + "HotTablet", }, ) @@ -323,4 +324,46 @@ class SingleClusterRouting(proto.Message): ) +class HotTablet(proto.Message): + r"""A tablet is a defined by a start and end key and is explained + in https://cloud.google.com/bigtable/docs/overview#architecture + and + https://cloud.google.com/bigtable/docs/performance#optimization. + A Hot tablet is a tablet that exhibits high average cpu usage + during the time interval from start time to end time. + + Attributes: + name (str): + The unique name of the hot tablet. Values are of the form + ``projects/{project}/instances/{instance}/clusters/{cluster}/hotTablets/[a-zA-Z0-9_-]*``. + table_name (str): + Name of the table that contains the tablet. Values are of + the form + ``projects/{project}/instances/{instance}/tables/[_a-zA-Z0-9][-_.a-zA-Z0-9]*``. + start_time (google.protobuf.timestamp_pb2.Timestamp): + Output only. The start time of the hot + tablet. + end_time (google.protobuf.timestamp_pb2.Timestamp): + Output only. The end time of the hot tablet. + start_key (str): + Tablet Start Key (inclusive). + end_key (str): + Tablet End Key (inclusive). + node_cpu_usage_percent (float): + Output only. The average CPU usage spent by a node on this + tablet over the start_time to end_time time range. The + percentage is the amount of CPU used by the node to serve + the tablet, from 0% (tablet was not interacted with) to 100% + (the node spent all cycles serving the hot tablet). + """ + + name = proto.Field(proto.STRING, number=1,) + table_name = proto.Field(proto.STRING, number=2,) + start_time = proto.Field(proto.MESSAGE, number=3, message=timestamp_pb2.Timestamp,) + end_time = proto.Field(proto.MESSAGE, number=4, message=timestamp_pb2.Timestamp,) + start_key = proto.Field(proto.STRING, number=5,) + end_key = proto.Field(proto.STRING, number=6,) + node_cpu_usage_percent = proto.Field(proto.FLOAT, number=7,) + + __all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/scripts/fixup_bigtable_admin_v2_keywords.py b/scripts/fixup_bigtable_admin_v2_keywords.py index 6d5bc00f4..2079dd99b 100644 --- a/scripts/fixup_bigtable_admin_v2_keywords.py +++ b/scripts/fixup_bigtable_admin_v2_keywords.py @@ -64,6 +64,7 @@ class bigtable_adminCallTransformer(cst.CSTTransformer): 'list_app_profiles': ('parent', 'page_size', 'page_token', ), 'list_backups': ('parent', 'filter', 'order_by', 'page_size', 'page_token', ), 'list_clusters': ('parent', 'page_token', ), + 'list_hot_tablets': ('parent', 'start_time', 'end_time', 'page_size', 'page_token', ), 'list_instances': ('parent', 'page_token', ), 'list_snapshots': ('parent', 'page_size', 'page_token', ), 'list_tables': ('parent', 'view', 'page_size', 'page_token', ), diff --git a/tests/unit/gapic/bigtable_admin_v2/test_bigtable_instance_admin.py b/tests/unit/gapic/bigtable_admin_v2/test_bigtable_instance_admin.py index 76df253aa..226f5acf7 100644 --- a/tests/unit/gapic/bigtable_admin_v2/test_bigtable_instance_admin.py +++ b/tests/unit/gapic/bigtable_admin_v2/test_bigtable_instance_admin.py @@ -5242,6 +5242,374 @@ async def test_test_iam_permissions_flattened_error_async(): ) +@pytest.mark.parametrize( + "request_type", [bigtable_instance_admin.ListHotTabletsRequest, dict,] +) +def test_list_hot_tablets(request_type, transport: str = "grpc"): + client = BigtableInstanceAdminClient( + 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.list_hot_tablets), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = bigtable_instance_admin.ListHotTabletsResponse( + next_page_token="next_page_token_value", + ) + response = client.list_hot_tablets(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == bigtable_instance_admin.ListHotTabletsRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListHotTabletsPager) + assert response.next_page_token == "next_page_token_value" + + +def test_list_hot_tablets_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 = BigtableInstanceAdminClient( + 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.list_hot_tablets), "__call__") as call: + client.list_hot_tablets() + call.assert_called() + _, args, _ = call.mock_calls[0] + assert args[0] == bigtable_instance_admin.ListHotTabletsRequest() + + +@pytest.mark.asyncio +async def test_list_hot_tablets_async( + transport: str = "grpc_asyncio", + request_type=bigtable_instance_admin.ListHotTabletsRequest, +): + client = BigtableInstanceAdminAsyncClient( + 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.list_hot_tablets), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + bigtable_instance_admin.ListHotTabletsResponse( + next_page_token="next_page_token_value", + ) + ) + response = await client.list_hot_tablets(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == bigtable_instance_admin.ListHotTabletsRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListHotTabletsAsyncPager) + assert response.next_page_token == "next_page_token_value" + + +@pytest.mark.asyncio +async def test_list_hot_tablets_async_from_dict(): + await test_list_hot_tablets_async(request_type=dict) + + +def test_list_hot_tablets_field_headers(): + client = BigtableInstanceAdminClient( + 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 = bigtable_instance_admin.ListHotTabletsRequest() + + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_hot_tablets), "__call__") as call: + call.return_value = bigtable_instance_admin.ListHotTabletsResponse() + client.list_hot_tablets(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", "parent=parent/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_list_hot_tablets_field_headers_async(): + client = BigtableInstanceAdminAsyncClient( + 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 = bigtable_instance_admin.ListHotTabletsRequest() + + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_hot_tablets), "__call__") as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + bigtable_instance_admin.ListHotTabletsResponse() + ) + await client.list_hot_tablets(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", "parent=parent/value",) in kw["metadata"] + + +def test_list_hot_tablets_flattened(): + client = BigtableInstanceAdminClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_hot_tablets), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = bigtable_instance_admin.ListHotTabletsResponse() + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.list_hot_tablets(parent="parent_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] + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + + +def test_list_hot_tablets_flattened_error(): + client = BigtableInstanceAdminClient( + 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.list_hot_tablets( + bigtable_instance_admin.ListHotTabletsRequest(), parent="parent_value", + ) + + +@pytest.mark.asyncio +async def test_list_hot_tablets_flattened_async(): + client = BigtableInstanceAdminAsyncClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_hot_tablets), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = bigtable_instance_admin.ListHotTabletsResponse() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + bigtable_instance_admin.ListHotTabletsResponse() + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.list_hot_tablets(parent="parent_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + + +@pytest.mark.asyncio +async def test_list_hot_tablets_flattened_error_async(): + client = BigtableInstanceAdminAsyncClient( + 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.list_hot_tablets( + bigtable_instance_admin.ListHotTabletsRequest(), parent="parent_value", + ) + + +def test_list_hot_tablets_pager(transport_name: str = "grpc"): + client = BigtableInstanceAdminClient( + credentials=ga_credentials.AnonymousCredentials, transport=transport_name, + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_hot_tablets), "__call__") as call: + # Set the response to a series of pages. + call.side_effect = ( + bigtable_instance_admin.ListHotTabletsResponse( + hot_tablets=[ + instance.HotTablet(), + instance.HotTablet(), + instance.HotTablet(), + ], + next_page_token="abc", + ), + bigtable_instance_admin.ListHotTabletsResponse( + hot_tablets=[], next_page_token="def", + ), + bigtable_instance_admin.ListHotTabletsResponse( + hot_tablets=[instance.HotTablet(),], next_page_token="ghi", + ), + bigtable_instance_admin.ListHotTabletsResponse( + hot_tablets=[instance.HotTablet(), instance.HotTablet(),], + ), + RuntimeError, + ) + + metadata = () + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", ""),)), + ) + pager = client.list_hot_tablets(request={}) + + assert pager._metadata == metadata + + results = [i for i in pager] + assert len(results) == 6 + assert all(isinstance(i, instance.HotTablet) for i in results) + + +def test_list_hot_tablets_pages(transport_name: str = "grpc"): + client = BigtableInstanceAdminClient( + credentials=ga_credentials.AnonymousCredentials, transport=transport_name, + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_hot_tablets), "__call__") as call: + # Set the response to a series of pages. + call.side_effect = ( + bigtable_instance_admin.ListHotTabletsResponse( + hot_tablets=[ + instance.HotTablet(), + instance.HotTablet(), + instance.HotTablet(), + ], + next_page_token="abc", + ), + bigtable_instance_admin.ListHotTabletsResponse( + hot_tablets=[], next_page_token="def", + ), + bigtable_instance_admin.ListHotTabletsResponse( + hot_tablets=[instance.HotTablet(),], next_page_token="ghi", + ), + bigtable_instance_admin.ListHotTabletsResponse( + hot_tablets=[instance.HotTablet(), instance.HotTablet(),], + ), + RuntimeError, + ) + pages = list(client.list_hot_tablets(request={}).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.asyncio +async def test_list_hot_tablets_async_pager(): + client = BigtableInstanceAdminAsyncClient( + credentials=ga_credentials.AnonymousCredentials, + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_hot_tablets), "__call__", new_callable=mock.AsyncMock + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + bigtable_instance_admin.ListHotTabletsResponse( + hot_tablets=[ + instance.HotTablet(), + instance.HotTablet(), + instance.HotTablet(), + ], + next_page_token="abc", + ), + bigtable_instance_admin.ListHotTabletsResponse( + hot_tablets=[], next_page_token="def", + ), + bigtable_instance_admin.ListHotTabletsResponse( + hot_tablets=[instance.HotTablet(),], next_page_token="ghi", + ), + bigtable_instance_admin.ListHotTabletsResponse( + hot_tablets=[instance.HotTablet(), instance.HotTablet(),], + ), + RuntimeError, + ) + async_pager = await client.list_hot_tablets(request={},) + assert async_pager.next_page_token == "abc" + responses = [] + async for response in async_pager: + responses.append(response) + + assert len(responses) == 6 + assert all(isinstance(i, instance.HotTablet) for i in responses) + + +@pytest.mark.asyncio +async def test_list_hot_tablets_async_pages(): + client = BigtableInstanceAdminAsyncClient( + credentials=ga_credentials.AnonymousCredentials, + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_hot_tablets), "__call__", new_callable=mock.AsyncMock + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + bigtable_instance_admin.ListHotTabletsResponse( + hot_tablets=[ + instance.HotTablet(), + instance.HotTablet(), + instance.HotTablet(), + ], + next_page_token="abc", + ), + bigtable_instance_admin.ListHotTabletsResponse( + hot_tablets=[], next_page_token="def", + ), + bigtable_instance_admin.ListHotTabletsResponse( + hot_tablets=[instance.HotTablet(),], next_page_token="ghi", + ), + bigtable_instance_admin.ListHotTabletsResponse( + hot_tablets=[instance.HotTablet(), instance.HotTablet(),], + ), + RuntimeError, + ) + pages = [] + async for page_ in (await client.list_hot_tablets(request={})).pages: + pages.append(page_) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + def test_credentials_transport_error(): # It is an error to provide credentials and a transport instance. transport = transports.BigtableInstanceAdminGrpcTransport( @@ -5380,6 +5748,7 @@ def test_bigtable_instance_admin_base_transport(): "get_iam_policy", "set_iam_policy", "test_iam_permissions", + "list_hot_tablets", ) for method in methods: with pytest.raises(NotImplementedError): @@ -5821,9 +6190,37 @@ def test_parse_crypto_key_path(): assert expected == actual -def test_instance_path(): +def test_hot_tablet_path(): project = "winkle" instance = "nautilus" + cluster = "scallop" + hot_tablet = "abalone" + expected = "projects/{project}/instances/{instance}/clusters/{cluster}/hotTablets/{hot_tablet}".format( + project=project, instance=instance, cluster=cluster, hot_tablet=hot_tablet, + ) + actual = BigtableInstanceAdminClient.hot_tablet_path( + project, instance, cluster, hot_tablet + ) + assert expected == actual + + +def test_parse_hot_tablet_path(): + expected = { + "project": "squid", + "instance": "clam", + "cluster": "whelk", + "hot_tablet": "octopus", + } + path = BigtableInstanceAdminClient.hot_tablet_path(**expected) + + # Check that the path construction is reversible. + actual = BigtableInstanceAdminClient.parse_hot_tablet_path(path) + assert expected == actual + + +def test_instance_path(): + project = "oyster" + instance = "nudibranch" expected = "projects/{project}/instances/{instance}".format( project=project, instance=instance, ) @@ -5833,8 +6230,8 @@ def test_instance_path(): def test_parse_instance_path(): expected = { - "project": "scallop", - "instance": "abalone", + "project": "cuttlefish", + "instance": "mussel", } path = BigtableInstanceAdminClient.instance_path(**expected) @@ -5843,8 +6240,32 @@ def test_parse_instance_path(): assert expected == actual +def test_table_path(): + project = "winkle" + instance = "nautilus" + table = "scallop" + expected = "projects/{project}/instances/{instance}/tables/{table}".format( + project=project, instance=instance, table=table, + ) + actual = BigtableInstanceAdminClient.table_path(project, instance, table) + assert expected == actual + + +def test_parse_table_path(): + expected = { + "project": "abalone", + "instance": "squid", + "table": "clam", + } + path = BigtableInstanceAdminClient.table_path(**expected) + + # Check that the path construction is reversible. + actual = BigtableInstanceAdminClient.parse_table_path(path) + assert expected == actual + + def test_common_billing_account_path(): - billing_account = "squid" + billing_account = "whelk" expected = "billingAccounts/{billing_account}".format( billing_account=billing_account, ) @@ -5854,7 +6275,7 @@ def test_common_billing_account_path(): def test_parse_common_billing_account_path(): expected = { - "billing_account": "clam", + "billing_account": "octopus", } path = BigtableInstanceAdminClient.common_billing_account_path(**expected) @@ -5864,7 +6285,7 @@ def test_parse_common_billing_account_path(): def test_common_folder_path(): - folder = "whelk" + folder = "oyster" expected = "folders/{folder}".format(folder=folder,) actual = BigtableInstanceAdminClient.common_folder_path(folder) assert expected == actual @@ -5872,7 +6293,7 @@ def test_common_folder_path(): def test_parse_common_folder_path(): expected = { - "folder": "octopus", + "folder": "nudibranch", } path = BigtableInstanceAdminClient.common_folder_path(**expected) @@ -5882,7 +6303,7 @@ def test_parse_common_folder_path(): def test_common_organization_path(): - organization = "oyster" + organization = "cuttlefish" expected = "organizations/{organization}".format(organization=organization,) actual = BigtableInstanceAdminClient.common_organization_path(organization) assert expected == actual @@ -5890,7 +6311,7 @@ def test_common_organization_path(): def test_parse_common_organization_path(): expected = { - "organization": "nudibranch", + "organization": "mussel", } path = BigtableInstanceAdminClient.common_organization_path(**expected) @@ -5900,7 +6321,7 @@ def test_parse_common_organization_path(): def test_common_project_path(): - project = "cuttlefish" + project = "winkle" expected = "projects/{project}".format(project=project,) actual = BigtableInstanceAdminClient.common_project_path(project) assert expected == actual @@ -5908,7 +6329,7 @@ def test_common_project_path(): def test_parse_common_project_path(): expected = { - "project": "mussel", + "project": "nautilus", } path = BigtableInstanceAdminClient.common_project_path(**expected) @@ -5918,8 +6339,8 @@ def test_parse_common_project_path(): def test_common_location_path(): - project = "winkle" - location = "nautilus" + project = "scallop" + location = "abalone" expected = "projects/{project}/locations/{location}".format( project=project, location=location, ) @@ -5929,8 +6350,8 @@ def test_common_location_path(): def test_parse_common_location_path(): expected = { - "project": "scallop", - "location": "abalone", + "project": "squid", + "location": "clam", } path = BigtableInstanceAdminClient.common_location_path(**expected)