1818import re
1919from google .cloud .bigtable_admin_v2 .types import instance
2020from google .api_core .exceptions import NotFound
21+ from google .protobuf import field_mask_pb2
2122
2223
2324_CLUSTER_NAME_RE = re .compile (
@@ -36,6 +37,7 @@ class Cluster(object):
3637 * :meth:`create` itself
3738 * :meth:`update` itself
3839 * :meth:`delete` itself
40+ * :meth:`disable_autoscaling` itself
3941
4042 :type cluster_id: str
4143 :param cluster_id: The ID of the cluster.
@@ -52,7 +54,9 @@ class Cluster(object):
5254 https://cloud.google.com/bigtable/docs/locations
5355
5456 :type serve_nodes: int
55- :param serve_nodes: (Optional) The number of nodes in the cluster.
57+ :param serve_nodes: (Optional) The number of nodes in the cluster for manual scaling. If any of the
58+ autoscaling configuration are specified, then the autoscaling
59+ configuration will take precedent.
5660
5761 :type default_storage_type: int
5862 :param default_storage_type: (Optional) The type of storage
@@ -85,6 +89,27 @@ class Cluster(object):
8589 :data:`google.cloud.bigtable.enums.Cluster.State.CREATING`.
8690 :data:`google.cloud.bigtable.enums.Cluster.State.RESIZING`.
8791 :data:`google.cloud.bigtable.enums.Cluster.State.DISABLED`.
92+
93+ :type min_serve_nodes: int
94+ :param min_serve_nodes: (Optional) The minimum number of nodes to be set in the cluster for autoscaling.
95+ Must be 1 or greater.
96+ If specified, this configuration takes precedence over
97+ ``serve_nodes``.
98+ If specified, then
99+ ``max_serve_nodes`` and ``cpu_utilization_percent`` must be
100+ specified too.
101+
102+ :type max_serve_nodes: int
103+ :param max_serve_nodes: (Optional) The maximum number of nodes to be set in the cluster for autoscaling.
104+ If specified, this configuration
105+ takes precedence over ``serve_nodes``. If specified, then
106+ ``min_serve_nodes`` and ``cpu_utilization_percent`` must be
107+ specified too.
108+
109+ :param cpu_utilization_percent: (Optional) The CPU utilization target for the cluster's workload for autoscaling.
110+ If specified, this configuration takes precedence over ``serve_nodes``. If specified, then
111+ ``min_serve_nodes`` and ``max_serve_nodes`` must be
112+ specified too.
88113 """
89114
90115 def __init__ (
@@ -96,6 +121,9 @@ def __init__(
96121 default_storage_type = None ,
97122 kms_key_name = None ,
98123 _state = None ,
124+ min_serve_nodes = None ,
125+ max_serve_nodes = None ,
126+ cpu_utilization_percent = None ,
99127 ):
100128 self .cluster_id = cluster_id
101129 self ._instance = instance
@@ -104,10 +132,13 @@ def __init__(
104132 self .default_storage_type = default_storage_type
105133 self ._kms_key_name = kms_key_name
106134 self ._state = _state
135+ self .min_serve_nodes = min_serve_nodes
136+ self .max_serve_nodes = max_serve_nodes
137+ self .cpu_utilization_percent = cpu_utilization_percent
107138
108139 @classmethod
109140 def from_pb (cls , cluster_pb , instance ):
110- """Creates an cluster instance from a protobuf.
141+ """Creates a cluster instance from a protobuf.
111142
112143 For example:
113144
@@ -159,6 +190,17 @@ def _update_from_pb(self, cluster_pb):
159190
160191 self .location_id = cluster_pb .location .split ("/" )[- 1 ]
161192 self .serve_nodes = cluster_pb .serve_nodes
193+
194+ self .min_serve_nodes = (
195+ cluster_pb .cluster_config .cluster_autoscaling_config .autoscaling_limits .min_serve_nodes
196+ )
197+ self .max_serve_nodes = (
198+ cluster_pb .cluster_config .cluster_autoscaling_config .autoscaling_limits .max_serve_nodes
199+ )
200+ self .cpu_utilization_percent = (
201+ cluster_pb .cluster_config .cluster_autoscaling_config .autoscaling_targets .cpu_utilization_percent
202+ )
203+
162204 self .default_storage_type = cluster_pb .default_storage_type
163205 if cluster_pb .encryption_config :
164206 self ._kms_key_name = cluster_pb .encryption_config .kms_key_name
@@ -211,6 +253,42 @@ def kms_key_name(self):
211253 """str: Customer managed encryption key for the cluster."""
212254 return self ._kms_key_name
213255
256+ def _validate_scaling_config (self ):
257+ """Validate auto/manual scaling configuration before creating or updating."""
258+
259+ if (
260+ not self .serve_nodes
261+ and not self .min_serve_nodes
262+ and not self .max_serve_nodes
263+ and not self .cpu_utilization_percent
264+ ):
265+ raise ValueError (
266+ "Must specify either serve_nodes or all of the autoscaling configurations (min_serve_nodes, max_serve_nodes, and cpu_utilization_percent)."
267+ )
268+ if self .serve_nodes and (
269+ self .max_serve_nodes or self .min_serve_nodes or self .cpu_utilization_percent
270+ ):
271+ raise ValueError (
272+ "Cannot specify both serve_nodes and autoscaling configurations (min_serve_nodes, max_serve_nodes, and cpu_utilization_percent)."
273+ )
274+ if (
275+ (
276+ self .min_serve_nodes
277+ and (not self .max_serve_nodes or not self .cpu_utilization_percent )
278+ )
279+ or (
280+ self .max_serve_nodes
281+ and (not self .min_serve_nodes or not self .cpu_utilization_percent )
282+ )
283+ or (
284+ self .cpu_utilization_percent
285+ and (not self .min_serve_nodes or not self .max_serve_nodes )
286+ )
287+ ):
288+ raise ValueError (
289+ "All of autoscaling configurations must be specified at the same time (min_serve_nodes, max_serve_nodes, and cpu_utilization_percent)."
290+ )
291+
214292 def __eq__ (self , other ):
215293 if not isinstance (other , self .__class__ ):
216294 return NotImplemented
@@ -290,7 +368,15 @@ def create(self):
290368 :rtype: :class:`~google.api_core.operation.Operation`
291369 :returns: The long-running operation corresponding to the
292370 create operation.
371+
372+ :raises: :class:`ValueError <exceptions.ValueError>` if the both ``serve_nodes`` and autoscaling configurations
373+ are set at the same time or if none of the ``serve_nodes`` or autoscaling configurations are set
374+ or if the autoscaling configurations are only partially set.
375+
293376 """
377+
378+ self ._validate_scaling_config ()
379+
294380 client = self ._instance ._client
295381 cluster_pb = self ._to_pb ()
296382
@@ -323,20 +409,73 @@ def update(self):
323409
324410 before calling :meth:`update`.
325411
412+ If autoscaling is already enabled, manual scaling will be silently ignored.
413+ To disable autoscaling and enable manual scaling, use the :meth:`disable_autoscaling` instead.
414+
326415 :rtype: :class:`Operation`
327416 :returns: The long-running operation corresponding to the
328417 update operation.
418+
329419 """
420+
330421 client = self ._instance ._client
331- # We are passing `None` for third argument location.
332- # Location is set only at the time of creation of a cluster
333- # and can not be changed after cluster has been created.
334- return client .instance_admin_client .update_cluster (
335- request = {
336- "serve_nodes" : self .serve_nodes ,
337- "name" : self .name ,
338- "location" : None ,
339- }
422+
423+ update_mask_pb = field_mask_pb2 .FieldMask ()
424+
425+ if self .serve_nodes :
426+ update_mask_pb .paths .append ("serve_nodes" )
427+
428+ if self .min_serve_nodes :
429+ update_mask_pb .paths .append (
430+ "cluster_config.cluster_autoscaling_config.autoscaling_limits.min_serve_nodes"
431+ )
432+ if self .max_serve_nodes :
433+ update_mask_pb .paths .append (
434+ "cluster_config.cluster_autoscaling_config.autoscaling_limits.max_serve_nodes"
435+ )
436+ if self .cpu_utilization_percent :
437+ update_mask_pb .paths .append (
438+ "cluster_config.cluster_autoscaling_config.autoscaling_targets.cpu_utilization_percent"
439+ )
440+
441+ cluster_pb = self ._to_pb ()
442+ cluster_pb .name = self .name
443+
444+ return client .instance_admin_client .partial_update_cluster (
445+ request = {"cluster" : cluster_pb , "update_mask" : update_mask_pb }
446+ )
447+
448+ def disable_autoscaling (self , serve_nodes ):
449+ """
450+ Disable autoscaling by specifying the number of nodes.
451+
452+ For example:
453+
454+ .. literalinclude:: snippets.py
455+ :start-after: [START bigtable_api_cluster_disable_autoscaling]
456+ :end-before: [END bigtable_api_cluster_disable_autoscaling]
457+ :dedent: 4
458+
459+ :type serve_nodes: int
460+ :param serve_nodes: The number of nodes in the cluster.
461+ """
462+
463+ client = self ._instance ._client
464+
465+ update_mask_pb = field_mask_pb2 .FieldMask ()
466+
467+ self .serve_nodes = serve_nodes
468+ self .min_serve_nodes = 0
469+ self .max_serve_nodes = 0
470+ self .cpu_utilization_percent = 0
471+
472+ update_mask_pb .paths .append ("serve_nodes" )
473+ update_mask_pb .paths .append ("cluster_config.cluster_autoscaling_config" )
474+ cluster_pb = self ._to_pb ()
475+ cluster_pb .name = self .name
476+
477+ return client .instance_admin_client .partial_update_cluster (
478+ request = {"cluster" : cluster_pb , "update_mask" : update_mask_pb }
340479 )
341480
342481 def delete (self ):
@@ -375,6 +514,7 @@ def _to_pb(self):
375514 location = client .instance_admin_client .common_location_path (
376515 client .project , self .location_id
377516 )
517+
378518 cluster_pb = instance .Cluster (
379519 location = location ,
380520 serve_nodes = self .serve_nodes ,
@@ -384,4 +524,18 @@ def _to_pb(self):
384524 cluster_pb .encryption_config = instance .Cluster .EncryptionConfig (
385525 kms_key_name = self ._kms_key_name ,
386526 )
527+
528+ if self .min_serve_nodes :
529+ cluster_pb .cluster_config .cluster_autoscaling_config .autoscaling_limits .min_serve_nodes = (
530+ self .min_serve_nodes
531+ )
532+ if self .max_serve_nodes :
533+ cluster_pb .cluster_config .cluster_autoscaling_config .autoscaling_limits .max_serve_nodes = (
534+ self .max_serve_nodes
535+ )
536+ if self .cpu_utilization_percent :
537+ cluster_pb .cluster_config .cluster_autoscaling_config .autoscaling_targets .cpu_utilization_percent = (
538+ self .cpu_utilization_percent
539+ )
540+
387541 return cluster_pb
0 commit comments