18
18
import re
19
19
from google .cloud .bigtable_admin_v2 .types import instance
20
20
from google .api_core .exceptions import NotFound
21
+ from google .protobuf import field_mask_pb2
21
22
22
23
23
24
_CLUSTER_NAME_RE = re .compile (
@@ -36,6 +37,7 @@ class Cluster(object):
36
37
* :meth:`create` itself
37
38
* :meth:`update` itself
38
39
* :meth:`delete` itself
40
+ * :meth:`disable_autoscaling` itself
39
41
40
42
:type cluster_id: str
41
43
:param cluster_id: The ID of the cluster.
@@ -52,7 +54,9 @@ class Cluster(object):
52
54
https://cloud.google.com/bigtable/docs/locations
53
55
54
56
: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.
56
60
57
61
:type default_storage_type: int
58
62
:param default_storage_type: (Optional) The type of storage
@@ -85,6 +89,27 @@ class Cluster(object):
85
89
:data:`google.cloud.bigtable.enums.Cluster.State.CREATING`.
86
90
:data:`google.cloud.bigtable.enums.Cluster.State.RESIZING`.
87
91
: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.
88
113
"""
89
114
90
115
def __init__ (
@@ -96,6 +121,9 @@ def __init__(
96
121
default_storage_type = None ,
97
122
kms_key_name = None ,
98
123
_state = None ,
124
+ min_serve_nodes = None ,
125
+ max_serve_nodes = None ,
126
+ cpu_utilization_percent = None ,
99
127
):
100
128
self .cluster_id = cluster_id
101
129
self ._instance = instance
@@ -104,10 +132,13 @@ def __init__(
104
132
self .default_storage_type = default_storage_type
105
133
self ._kms_key_name = kms_key_name
106
134
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
107
138
108
139
@classmethod
109
140
def from_pb (cls , cluster_pb , instance ):
110
- """Creates an cluster instance from a protobuf.
141
+ """Creates a cluster instance from a protobuf.
111
142
112
143
For example:
113
144
@@ -159,6 +190,17 @@ def _update_from_pb(self, cluster_pb):
159
190
160
191
self .location_id = cluster_pb .location .split ("/" )[- 1 ]
161
192
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
+
162
204
self .default_storage_type = cluster_pb .default_storage_type
163
205
if cluster_pb .encryption_config :
164
206
self ._kms_key_name = cluster_pb .encryption_config .kms_key_name
@@ -211,6 +253,42 @@ def kms_key_name(self):
211
253
"""str: Customer managed encryption key for the cluster."""
212
254
return self ._kms_key_name
213
255
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
+
214
292
def __eq__ (self , other ):
215
293
if not isinstance (other , self .__class__ ):
216
294
return NotImplemented
@@ -290,7 +368,15 @@ def create(self):
290
368
:rtype: :class:`~google.api_core.operation.Operation`
291
369
:returns: The long-running operation corresponding to the
292
370
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
+
293
376
"""
377
+
378
+ self ._validate_scaling_config ()
379
+
294
380
client = self ._instance ._client
295
381
cluster_pb = self ._to_pb ()
296
382
@@ -323,20 +409,73 @@ def update(self):
323
409
324
410
before calling :meth:`update`.
325
411
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
+
326
415
:rtype: :class:`Operation`
327
416
:returns: The long-running operation corresponding to the
328
417
update operation.
418
+
329
419
"""
420
+
330
421
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 }
340
479
)
341
480
342
481
def delete (self ):
@@ -375,6 +514,7 @@ def _to_pb(self):
375
514
location = client .instance_admin_client .common_location_path (
376
515
client .project , self .location_id
377
516
)
517
+
378
518
cluster_pb = instance .Cluster (
379
519
location = location ,
380
520
serve_nodes = self .serve_nodes ,
@@ -384,4 +524,18 @@ def _to_pb(self):
384
524
cluster_pb .encryption_config = instance .Cluster .EncryptionConfig (
385
525
kms_key_name = self ._kms_key_name ,
386
526
)
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
+
387
541
return cluster_pb
0 commit comments