2020 CONST_SPOT_EVICTION_POLICY_DELETE ,
2121 CONST_VIRTUAL_MACHINE_SCALE_SETS ,
2222 AgentPoolDecoratorMode ,
23+ DecoratorEarlyExitException ,
2324 DecoratorMode ,
2425)
2526from azure .cli .command_modules .acs ._helpers import get_snapshot_by_snapshot_id
2627from azure .cli .command_modules .acs ._validators import extract_comma_separated_string
2728from azure .cli .command_modules .acs .base_decorator import BaseAKSContext , BaseAKSModels , BaseAKSParamDict
29+ from azure .cli .command_modules .acs .decorator import safe_list_get
2830from azure .cli .core import AzCommandsLoader
29- from azure .cli .core .azclierror import CLIInternalError , InvalidArgumentValueError , RequiredArgumentMissingError
31+ from azure .cli .core .azclierror import (
32+ ArgumentUsageError ,
33+ CLIInternalError ,
34+ InvalidArgumentValueError ,
35+ MutuallyExclusiveArgumentError ,
36+ RequiredArgumentMissingError ,
37+ )
3038from azure .cli .core .commands import AzCliCommand
3139from azure .cli .core .profiles import ResourceType
3240from azure .cli .core .util import get_file_json , sdk_no_wait
@@ -93,6 +101,7 @@ def __init__(
93101 super ().__init__ (cmd , raw_parameters , models , decorator_mode )
94102 self .agentpool_decorator_mode = agentpool_decorator_mode
95103 self .agentpool = None
104+ self ._agentpools = []
96105
97106 # pylint: disable=no-self-use
98107 def __validate_counts_in_autoscaler (
@@ -218,7 +227,10 @@ def _get_nodepool_name(self, enable_validation: bool = False) -> str:
218227 # this parameter does not need dynamic completion
219228 # validation
220229 if enable_validation :
221- if self .agentpool_decorator_mode == AgentPoolDecoratorMode .STANDALONE :
230+ if (
231+ self .agentpool_decorator_mode == AgentPoolDecoratorMode .STANDALONE and
232+ self .decorator_mode == DecoratorMode .CREATE
233+ ):
222234 agentpool_client = cf_agent_pools (self .cmd .cli_ctx )
223235 instances = agentpool_client .list (self .get_resource_group_name (), self .get_cluster_name ())
224236 for agentpool_profile in instances :
@@ -265,7 +277,6 @@ def get_max_surge(self):
265277 # this parameter does not need validation
266278 return max_surge
267279
268- # pylint: disable=too-many-branches
269280 def get_node_count_and_enable_cluster_autoscaler_min_max_count (
270281 self ,
271282 ) -> Tuple [int , bool , Union [int , None ], Union [int , None ]]:
@@ -316,6 +327,93 @@ def get_node_count_and_enable_cluster_autoscaler_min_max_count(
316327 )
317328 return node_count , enable_cluster_autoscaler , min_count , max_count
318329
330+ def get_update_enable_disable_cluster_autoscaler_and_min_max_count (
331+ self ,
332+ ) -> Tuple [bool , bool , bool , Union [int , None ], Union [int , None ]]:
333+ """Obtain the value of update_cluster_autoscaler, enable_cluster_autoscaler, disable_cluster_autoscaler,
334+ min_count and max_count.
335+
336+ This function will verify the parameters through function "__validate_counts_in_autoscaler" by default. Besides
337+ if both enable_cluster_autoscaler and update_cluster_autoscaler are specified, a MutuallyExclusiveArgumentError
338+ will be raised. If enable_cluster_autoscaler or update_cluster_autoscaler is specified and there are multiple
339+ agent pool profiles, an ArgumentUsageError will be raised. If enable_cluster_autoscaler is specified and
340+ autoscaler is already enabled in `mc`, it will output warning messages and exit with code 0. If
341+ update_cluster_autoscaler is specified and autoscaler is not enabled in `mc`, it will raise an
342+ InvalidArgumentValueError. If disable_cluster_autoscaler is specified and autoscaler is not enabled in `mc`,
343+ it will output warning messages and exit with code 0.
344+
345+ :return: a tuple containing four elements: update_cluster_autoscaler of bool type, enable_cluster_autoscaler
346+ of bool type, disable_cluster_autoscaler of bool type, min_count of int type or None and max_count of int type
347+ or None
348+ """
349+ # update_cluster_autoscaler
350+ # read the original value passed by the command
351+ update_cluster_autoscaler = self .raw_param .get ("update_cluster_autoscaler" , False )
352+
353+ # enable_cluster_autoscaler
354+ # read the original value passed by the command
355+ enable_cluster_autoscaler = self .raw_param .get ("enable_cluster_autoscaler" , False )
356+
357+ # disable_cluster_autoscaler
358+ # read the original value passed by the command
359+ disable_cluster_autoscaler = self .raw_param .get ("disable_cluster_autoscaler" , False )
360+
361+ # min_count
362+ # read the original value passed by the command
363+ min_count = self .raw_param .get ("min_count" )
364+
365+ # max_count
366+ # read the original value passed by the command
367+ max_count = self .raw_param .get ("max_count" )
368+
369+ # these parameters do not need dynamic completion
370+
371+ # validation
372+ if self .agentpool_decorator_mode == AgentPoolDecoratorMode .MANAGED_CLUSTER :
373+ # For multi-agent pool, use the az aks nodepool command
374+ if (enable_cluster_autoscaler or update_cluster_autoscaler ) and len (self ._agentpools ) > 1 :
375+ raise ArgumentUsageError (
376+ 'There are more than one node pool in the cluster. Please use "az aks nodepool" command '
377+ "to update per node pool auto scaler settings"
378+ )
379+
380+ if enable_cluster_autoscaler + update_cluster_autoscaler + disable_cluster_autoscaler > 1 :
381+ raise MutuallyExclusiveArgumentError (
382+ "Can only specify one of --enable-cluster-autoscaler, --update-cluster-autoscaler and "
383+ "--disable-cluster-autoscaler"
384+ )
385+
386+ self .__validate_counts_in_autoscaler (
387+ None ,
388+ enable_cluster_autoscaler or update_cluster_autoscaler ,
389+ min_count ,
390+ max_count ,
391+ decorator_mode = DecoratorMode .UPDATE ,
392+ )
393+
394+ if enable_cluster_autoscaler and self .agentpool .enable_auto_scaling :
395+ logger .warning (
396+ "Cluster autoscaler is already enabled for this node pool.\n "
397+ 'Please run "az aks --update-cluster-autoscaler" '
398+ "if you want to update min-count or max-count."
399+ )
400+ raise DecoratorEarlyExitException ()
401+
402+ if update_cluster_autoscaler and not self .agentpool .enable_auto_scaling :
403+ raise InvalidArgumentValueError (
404+ "Cluster autoscaler is not enabled for this node pool.\n "
405+ 'Run "az aks nodepool update --enable-cluster-autoscaler" '
406+ "to enable cluster with min-count and max-count."
407+ )
408+
409+ if disable_cluster_autoscaler and not self .agentpool .enable_auto_scaling :
410+ logger .warning (
411+ "Cluster autoscaler is already disabled for this node pool."
412+ )
413+ raise DecoratorEarlyExitException ()
414+
415+ return update_cluster_autoscaler , enable_cluster_autoscaler , disable_cluster_autoscaler , min_count , max_count
416+
319417 def get_node_osdisk_size (self ) -> Union [int , None ]:
320418 """Obtain the value of node_osdisk_size.
321419
@@ -583,9 +681,10 @@ def get_nodepool_labels(self) -> Union[Dict[str, str], None]:
583681 else :
584682 nodepool_labels = self .raw_param .get ("labels" )
585683
586- # try to read the property value corresponding to the parameter from the `agentpool` object
587- if self .agentpool and self .agentpool .node_labels is not None :
588- nodepool_labels = self .agentpool .node_labels
684+ # In create mode, try to read the property value corresponding to the parameter from the `agentpool` object
685+ if self .decorator_mode == DecoratorMode .CREATE :
686+ if self .agentpool and self .agentpool .node_labels is not None :
687+ nodepool_labels = self .agentpool .node_labels
589688
590689 # this parameter does not need dynamic completion
591690 # this parameter does not need validation
@@ -602,9 +701,10 @@ def get_nodepool_tags(self) -> Union[Dict[str, str], None]:
602701 else :
603702 nodepool_tags = self .raw_param .get ("tags" )
604703
605- # try to read the property value corresponding to the parameter from the `agentpool` object
606- if self .agentpool and self .agentpool .tags is not None :
607- nodepool_tags = self .agentpool .tags
704+ # In create mode, try to read the property value corresponding to the parameter from the `agentpool` object
705+ if self .decorator_mode == DecoratorMode .CREATE :
706+ if self .agentpool and self .agentpool .tags is not None :
707+ nodepool_tags = self .agentpool .tags
608708
609709 # this parameter does not need dynamic completion
610710 # this parameter does not need validation
@@ -617,13 +717,17 @@ def get_node_taints(self) -> Union[List[str], None]:
617717 """
618718 # read the original value passed by the command
619719 node_taints = self .raw_param .get ("node_taints" )
620- # normalize, keep None as None
720+ # normalize, default is an empty list
621721 if node_taints is not None :
622722 node_taints = [x .strip () for x in (node_taints .split ("," ) if node_taints else [])]
723+ # keep None as None for update mode
724+ if node_taints is None and self .decorator_mode == DecoratorMode .CREATE :
725+ node_taints = []
623726
624- # try to read the property value corresponding to the parameter from the `agentpool` object
625- if self .agentpool and self .agentpool .node_taints is not None :
626- node_taints = self .agentpool .node_taints
727+ # In create mode, try to read the property value corresponding to the parameter from the `agentpool` object
728+ if self .decorator_mode == DecoratorMode .CREATE :
729+ if self .agentpool and self .agentpool .node_taints is not None :
730+ node_taints = self .agentpool .node_taints
627731
628732 # this parameter does not need validation
629733 return node_taints
@@ -1228,8 +1332,8 @@ def set_up_custom_node_config(self, agentpool: AgentPool) -> AgentPool:
12281332 agentpool .linux_os_config = self .context .get_linux_os_config ()
12291333 return agentpool
12301334
1231- def construct_default_agentpool_profile (self ) -> AgentPool :
1232- """The overall controller used to construct the default AgentPool profile.
1335+ def construct_agentpool_profile_default (self ) -> AgentPool :
1336+ """The overall controller used to construct the AgentPool profile by default .
12331337
12341338 The completely constructed AgentPool object will later be passed as a parameter to the underlying SDK
12351339 (mgmt-containerservice) to send the actual request.
@@ -1266,8 +1370,8 @@ def construct_default_agentpool_profile(self) -> AgentPool:
12661370 def add_agentpool (self , agentpool : AgentPool ) -> AgentPool :
12671371 """Send request to add a new agentpool.
12681372
1269- The function "sdk_no_wait" will be called to use the ContainerServiceClient to send a reqeust to add a new agent
1270- pool to the cluster.
1373+ The function "sdk_no_wait" will be called to use the Agentpool operations of ContainerServiceClient to send a
1374+ reqeust to add a new agent pool to the cluster.
12711375
12721376 :return: the AgentPool object
12731377 """
@@ -1292,6 +1396,7 @@ def __init__(
12921396 client : AgentPoolsOperations ,
12931397 raw_parameters : Dict ,
12941398 resource_type : ResourceType ,
1399+ agentpool_decorator_mode : AgentPoolDecoratorMode ,
12951400 ):
12961401 """Internal controller of aks_agentpool_update.
12971402
@@ -1303,8 +1408,135 @@ def __init__(
13031408 """
13041409 self .cmd = cmd
13051410 self .client = client
1306- self .models = AKSAgentPoolModels (cmd , resource_type )
1411+ self .agentpool_decorator_mode = agentpool_decorator_mode
1412+ self .models = AKSAgentPoolModels (cmd , resource_type , agentpool_decorator_mode )
13071413 # store the context in the process of assemble the AgentPool object
13081414 self .context = AKSAgentPoolContext (
1309- cmd , AKSAgentPoolParamDict (raw_parameters ), self .models , decorator_mode = DecoratorMode .UPDATE
1415+ cmd , AKSAgentPoolParamDict (raw_parameters ), self .models , DecoratorMode .UPDATE , agentpool_decorator_mode
1416+ )
1417+
1418+ def _ensure_agentpool (self , agentpool : AgentPool ) -> None :
1419+ """Internal function to ensure that the incoming `agentpool` object is valid and the same as the attached
1420+ `agentpool` object in the context.
1421+
1422+ If the incoming `agentpool` is not valid or is inconsistent with the `agentpool` in the context, raise a
1423+ CLIInternalError.
1424+
1425+ :return: None
1426+ """
1427+ if not isinstance (agentpool , self .models .UnifiedAgentPoolModel ):
1428+ raise CLIInternalError (
1429+ "Unexpected agentpool object with type '{}'." .format (type (agentpool ))
1430+ )
1431+
1432+ if self .context .agentpool != agentpool :
1433+ raise CLIInternalError (
1434+ "Inconsistent state detected. The incoming `agentpool` "
1435+ "is not the same as the `agentpool` in the context."
1436+ )
1437+
1438+ # pylint: disable=protected-access
1439+ def fetch_agentpool (self , agentpools : List [AgentPool ] = None ) -> AgentPool :
1440+ """Get the AgentPool object currently in use and attach it to internal context.
1441+
1442+ Internally send request using Agentpool operations of ContainerServiceClient.
1443+
1444+ :return: the AgentPool object
1445+ """
1446+ if self .agentpool_decorator_mode == AgentPoolDecoratorMode .MANAGED_CLUSTER :
1447+ self .context ._agentpools = agentpools
1448+ agentpool = safe_list_get (agentpools , 0 )
1449+ else :
1450+ agentpool = self .client .get (
1451+ self .context .get_resource_group_name (),
1452+ self .context .get_cluster_name (),
1453+ self .context .get_nodepool_name (),
1454+ )
1455+
1456+ # attach agentpool to AKSAgentPoolContext
1457+ self .context .attach_agentpool (agentpool )
1458+ return agentpool
1459+
1460+ def update_auto_scaler_properties (self , agentpool : AgentPool ) -> AgentPool :
1461+ """Update auto scaler related properties for the Agentpool object.
1462+
1463+ :return: the Agentpool object
1464+ """
1465+ self ._ensure_agentpool (agentpool )
1466+
1467+ (
1468+ update_cluster_autoscaler ,
1469+ enable_cluster_autoscaler ,
1470+ disable_cluster_autoscaler ,
1471+ min_count ,
1472+ max_count ,
1473+ ) = (
1474+ self .context .get_update_enable_disable_cluster_autoscaler_and_min_max_count ()
1475+ )
1476+
1477+ if update_cluster_autoscaler or enable_cluster_autoscaler :
1478+ agentpool .enable_auto_scaling = True
1479+ agentpool .min_count = min_count
1480+ agentpool .max_count = max_count
1481+
1482+ if disable_cluster_autoscaler :
1483+ agentpool .enable_auto_scaling = False
1484+ agentpool .min_count = None
1485+ agentpool .max_count = None
1486+ return agentpool
1487+
1488+ def update_label_tag_taint (self , agentpool : AgentPool ) -> AgentPool :
1489+ """Set up label, tag, taint for the AgentPool object.
1490+
1491+ :return: the AgentPool object
1492+ """
1493+ self ._ensure_agentpool (agentpool )
1494+
1495+ labels = self .context .get_nodepool_labels ()
1496+ if labels is not None :
1497+ agentpool .node_labels = labels
1498+
1499+ tags = self .context .get_nodepool_tags ()
1500+ if tags is not None :
1501+ agentpool .tags = tags
1502+
1503+ node_taints = self .context .get_node_taints ()
1504+ if node_taints is not None :
1505+ agentpool .node_taints = node_taints
1506+ return agentpool
1507+
1508+ def update_agentpool_profile_default (self , agentpools : List [AgentPool ] = None ) -> AgentPool :
1509+ """The overall controller used to update AgentPool profile by default.
1510+
1511+ The completely constructed AgentPool object will later be passed as a parameter to the underlying SDK
1512+ (mgmt-containerservice) to send the actual request.
1513+
1514+ :return: the AgentPool object
1515+ """
1516+ # fetch the Agentpool object
1517+ agentpool = self .fetch_agentpool (agentpools )
1518+ # update auto scaler properties
1519+ agentpool = self .update_auto_scaler_properties (agentpool )
1520+ # update label, tag, taint
1521+ agentpool = self .update_label_tag_taint (agentpool )
1522+ return agentpool
1523+
1524+ def update_agentpool (self , agentpool : AgentPool ) -> AgentPool :
1525+ """Send request to add a new agentpool.
1526+
1527+ The function "sdk_no_wait" will be called to use the Agentpool operations of ContainerServiceClient to send a
1528+ reqeust to update an existing agent pool of the cluster.
1529+
1530+ :return: the AgentPool object
1531+ """
1532+ self ._ensure_agentpool (agentpool )
1533+
1534+ return sdk_no_wait (
1535+ self .context .get_no_wait (),
1536+ self .client .begin_create_or_update ,
1537+ self .context .get_resource_group_name (),
1538+ self .context .get_cluster_name (),
1539+ self .context .get_nodepool_name (),
1540+ agentpool ,
1541+ headers = self .context .get_aks_custom_headers (),
13101542 )
0 commit comments