From 323e1c8202ce4fc0097c7a72bfc7ffdff947a464 Mon Sep 17 00:00:00 2001 From: Erin Borders Date: Tue, 19 May 2026 16:41:20 -0400 Subject: [PATCH 1/7] initial node disruption flag --- src/aks-preview/azext_aks_preview/_consts.py | 5 +++ src/aks-preview/azext_aks_preview/_params.py | 15 ++++++++ src/aks-preview/azext_aks_preview/custom.py | 2 ++ .../managed_cluster_decorator.py | 34 +++++++++++++++++++ 4 files changed, 56 insertions(+) diff --git a/src/aks-preview/azext_aks_preview/_consts.py b/src/aks-preview/azext_aks_preview/_consts.py index 90408f8a294..875993e5003 100644 --- a/src/aks-preview/azext_aks_preview/_consts.py +++ b/src/aks-preview/azext_aks_preview/_consts.py @@ -418,3 +418,8 @@ CONST_K8S_EXTENSION_NAME = "k8s-extension" CONST_K8S_EXTENSION_ACTION_MOD_NAME = "azext_k8s_extension.action" CONST_K8S_EXTENSION_FORMAT_MOD_NAME = "azext_k8s_extension._format" + +# Node Disruption Policy Consts +CONST_NODE_DISRUPTION_POLICY_ALLOW = "Allow" +CONST_NODE_DISRUPTION_POLICY_BLOCK = "Block" +CONST_NODE_DISRUPTION_POLICY_ALLOW_DURING_MAINTENANCE_WINDOW = "AllowDuringMaintenanceWindow" \ No newline at end of file diff --git a/src/aks-preview/azext_aks_preview/_params.py b/src/aks-preview/azext_aks_preview/_params.py index f4deb15bca9..d681a62df84 100644 --- a/src/aks-preview/azext_aks_preview/_params.py +++ b/src/aks-preview/azext_aks_preview/_params.py @@ -78,6 +78,9 @@ CONST_NETWORK_PLUGIN_NONE, CONST_NETWORK_POD_IP_ALLOCATION_MODE_DYNAMIC_INDIVIDUAL, CONST_NETWORK_POD_IP_ALLOCATION_MODE_STATIC_BLOCK, + CONST_NODE_DISRUPTION_POLICY_ALLOW, + CONST_NODE_DISRUPTION_POLICY_BLOCK, + CONST_NODE_DISRUPTION_POLICY_ALLOW_DURING_MAINTENANCE_WINDOW, CONST_NODE_IMAGE_UPGRADE_CHANNEL, CONST_NODE_OS_CHANNEL_NODE_IMAGE, CONST_NODE_OS_CHANNEL_NONE, @@ -561,6 +564,12 @@ CONST_UPGRADE_STRATEGY_BLUE_GREEN, ] +node_disruption_policies = [ + CONST_NODE_DISRUPTION_POLICY_ALLOW, + CONST_NODE_DISRUPTION_POLICY_BLOCK, + CONST_NODE_DISRUPTION_POLICY_ALLOW_DURING_MAINTENANCE_WINDOW, +] + def load_arguments(self, _): acr_arg_type = CLIArgumentType(metavar="ACR_NAME_OR_RESOURCE_ID") @@ -1918,6 +1927,12 @@ def load_arguments(self, _): is_preview=True, help="Disable continuous control plane and addon monitor for the cluster.", ) + c.argument( + "node_disruption_policy", + arg_type=get_enum_type(node_disruption_policies), + is_preview=True, + help="Set the node disruption profile for the cluster.", + ) with self.argument_context("aks delete") as c: c.argument("if_match") diff --git a/src/aks-preview/azext_aks_preview/custom.py b/src/aks-preview/azext_aks_preview/custom.py index 77ab483c1ad..bf79e841f26 100644 --- a/src/aks-preview/azext_aks_preview/custom.py +++ b/src/aks-preview/azext_aks_preview/custom.py @@ -1439,6 +1439,8 @@ def aks_update( # health monitor enable_continuous_control_plane_and_addon_monitor=False, disable_continuous_control_plane_and_addon_monitor=False, + # node disruption policy + node_disruption_policy=None, ): # DO NOT MOVE: get all the original parameters and save them as a dictionary raw_parameters = locals() diff --git a/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py b/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py index 60f22700aaa..393181008ae 100644 --- a/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py +++ b/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py @@ -3762,6 +3762,11 @@ def get_node_provisioning_mode(self) -> Union[str, None]: """Obtain the value of node_provisioning_mode. """ return self.raw_param.get("node_provisioning_mode") + + def get_node_disruption_policy(self) -> Union[str, None]: + """Obtain the value of node_disruption_policy. + """ + return self.raw_param.get("node_disruption_policy") def get_node_provisioning_default_pools(self) -> Union[str, None]: """Obtain the value of node_provisioning_default_pools. @@ -7318,6 +7323,21 @@ def update_node_provisioning_mode(self, mc: ManagedCluster) -> ManagedCluster: mc.node_provisioning_profile.mode = mode return mc + + def update_node_disruption_policy(self, mc: ManagedCluster) -> ManagedCluster: + self._ensure_mc(mc) + + policy = self.context.get_node_disruption_policy() + if policy is not None: + if mc.node_disruption_profile is None: + mc.node_disruption_profile = ( + self.models.NodeDisruptionProfile() # pylint: disable=no-member + ) + + # set policy + mc.node_disruption_profile.policy = policy + + return mc def update_node_provisioning_default_pools(self, mc: ManagedCluster) -> ManagedCluster: self._ensure_mc(mc) @@ -7628,6 +7648,18 @@ def update_node_provisioning_profile(self, mc: ManagedCluster) -> ManagedCluster mc = self.update_node_provisioning_default_pools(mc) return mc + + def update_node_disruption_policy(self, mc: ManagedCluster) -> ManagedCluster: + """Updates the nodeDisruptionPolicy field of the managed cluster + + :return: the ManagedCluster object + """ + self._ensure_mc(mc) + + mc = self.update_node_provisioning_mode(mc) + mc = self.update_node_provisioning_default_pools(mc) + + return mc def update_ai_toolchain_operator(self, mc: ManagedCluster) -> ManagedCluster: """Updates the aiToolchainOperatorProfile field of the managed cluster @@ -8166,6 +8198,8 @@ def update_mc_profile_preview(self) -> ManagedCluster: mc = self.update_http_proxy_enabled(mc) # update user-defined scheduler configuration for kube-scheduler upstream mc = self.update_upstream_kubescheduler_user_configuration(mc) + # update node disruption policy + mc = self.update_node_disruption_policy(mc) # update ManagedSystem pools, must at end mc = self.update_managed_system_pools(mc) From 62e74497d1e3fabb1b01c5b0a5df5f2fb9197b32 Mon Sep 17 00:00:00 2001 From: Erin Borders Date: Tue, 19 May 2026 17:10:41 -0400 Subject: [PATCH 2/7] initial test --- .../tests/latest/test_aks_commands.py | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/aks-preview/azext_aks_preview/tests/latest/test_aks_commands.py b/src/aks-preview/azext_aks_preview/tests/latest/test_aks_commands.py index 99fd082c123..fe2d98f47af 100644 --- a/src/aks-preview/azext_aks_preview/tests/latest/test_aks_commands.py +++ b/src/aks-preview/azext_aks_preview/tests/latest/test_aks_commands.py @@ -24248,3 +24248,81 @@ def test_aks_nodepool_update_vmss_vm_size_resize( self.check("vmSize", "Standard_D4s_v3"), ], ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer( + random_name_length=17, name_prefix="clitest", location="westus2" + ) + def test_aks_update_node_disruption_policy(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("c", 6) + nodepool2_name = self.create_random_name("c", 6) + self.kwargs.update( + { + "resource_group": resource_group, + "name": aks_name, + "location": resource_group_location, + "ssh_key_value": self.generate_ssh_keys(), + "resource_type": "Microsoft.ContainerService/ManagedClusters", + "nodepool_name": nodepool_name, + "nodepool2_name": nodepool2_name, + "vm_size": "Standard_D4s_v3", + "network_plugin": "azure", + "network_plugin_mode": "overlay", + } + ) + + # create aks cluster with Azure network plugin + self.cmd( + "aks create " + "--resource-group={resource_group} " + "--name={name} " + "--ssh-key-value={ssh_key_value} " + "--network-plugin={network_plugin} " + "--network-plugin-mode={network_plugin_mode} " + "--node-count=3", + checks=[ + self.check("provisioningState", "Succeeded"), + ], + ) + + # add nodepool + self.cmd( + "aks nodepool add " + "--resource-group={resource_group}" + " --cluster-name={name} " + "--name={nodepool2_name}", + checks=[ + self.check("provisioningState", "Succeeded"), + ], + ) + + # update node disruption policy to "Block" + self.cmd( + "aks update --resource-group={resource_group} --name={name} --node-disruption-policy Block", + checks=[ + self.check("provisioningState", "Succeeded"), + ], + ) + + # attempt to change network policy which should be blocked by node disruption policy + self.cmd( + "aks update --resource-group={resource_group} --name={name} --network-policy azure", + checks=[ + self.check("provisioningState", "Failed"), + ], + ) + + # attempt to change ssh access which should be allowed despite node disruption policy + self.cmd( + "aks nodepool update --resource-group={resource_group} --cluster-name={name} --name={nodepool2_name} --ssh-access disabled", + checks=[ + self.check("provisioningState", "Succeeded"), + ], + ) + + # delete + self.cmd( + "aks delete -g {resource_group} -n {name} --yes --no-wait --if-match={if_match}", + checks=[self.is_empty()], + ) From fc9de2fd24c555d89d22cd95e32130e812c386e8 Mon Sep 17 00:00:00 2001 From: Erin Borders Date: Thu, 21 May 2026 14:22:34 -0400 Subject: [PATCH 3/7] add changelog --- src/aks-preview/HISTORY.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/src/aks-preview/HISTORY.rst b/src/aks-preview/HISTORY.rst index eedb4f39f93..864b4262ab0 100644 --- a/src/aks-preview/HISTORY.rst +++ b/src/aks-preview/HISTORY.rst @@ -12,6 +12,7 @@ To release a new version, please select a new version number (usually plus 1 to Pending +++++++ * `az aks create` and `az aks nodepool add`: Add `--enable-osdisk-full-caching` (preview) to enable the full-cache ephemeral OS disk feature for a node pool. Requires AFEC registration `Microsoft.ContainerService/FullCachePreview`. Property is immutable after node pool creation. +* `az aks update`: Add `--node-disruption-policy` (preview) to update the node disruption policy for a cluster. Requires AFEC registration `Microsoft.ContainerService/NodeDisruptionProfile`. This is a cluster-level property that applies to all node pools in the cluster. 21.0.0b1 ++++++ From 0093ded40c6b634f13369d190e74fd389bab525c Mon Sep 17 00:00:00 2001 From: Erin Borders Date: Thu, 21 May 2026 14:34:40 -0400 Subject: [PATCH 4/7] style fix --- src/aks-preview/azext_aks_preview/_consts.py | 2 +- .../azext_aks_preview/managed_cluster_decorator.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/aks-preview/azext_aks_preview/_consts.py b/src/aks-preview/azext_aks_preview/_consts.py index 875993e5003..9b278d768ff 100644 --- a/src/aks-preview/azext_aks_preview/_consts.py +++ b/src/aks-preview/azext_aks_preview/_consts.py @@ -422,4 +422,4 @@ # Node Disruption Policy Consts CONST_NODE_DISRUPTION_POLICY_ALLOW = "Allow" CONST_NODE_DISRUPTION_POLICY_BLOCK = "Block" -CONST_NODE_DISRUPTION_POLICY_ALLOW_DURING_MAINTENANCE_WINDOW = "AllowDuringMaintenanceWindow" \ No newline at end of file +CONST_NODE_DISRUPTION_POLICY_ALLOW_DURING_MAINTENANCE_WINDOW = "AllowDuringMaintenanceWindow" diff --git a/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py b/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py index 393181008ae..143b46f1bff 100644 --- a/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py +++ b/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py @@ -3762,7 +3762,7 @@ def get_node_provisioning_mode(self) -> Union[str, None]: """Obtain the value of node_provisioning_mode. """ return self.raw_param.get("node_provisioning_mode") - + def get_node_disruption_policy(self) -> Union[str, None]: """Obtain the value of node_disruption_policy. """ @@ -7323,7 +7323,7 @@ def update_node_provisioning_mode(self, mc: ManagedCluster) -> ManagedCluster: mc.node_provisioning_profile.mode = mode return mc - + def update_node_disruption_policy(self, mc: ManagedCluster) -> ManagedCluster: self._ensure_mc(mc) @@ -7648,7 +7648,7 @@ def update_node_provisioning_profile(self, mc: ManagedCluster) -> ManagedCluster mc = self.update_node_provisioning_default_pools(mc) return mc - + def update_node_disruption_policy(self, mc: ManagedCluster) -> ManagedCluster: """Updates the nodeDisruptionPolicy field of the managed cluster From 85a6b97db6b884d6f36951c7c9b4fee9fc069720 Mon Sep 17 00:00:00 2001 From: Erin Borders Date: Thu, 21 May 2026 14:43:46 -0400 Subject: [PATCH 5/7] duplicate fix --- .../managed_cluster_decorator.py | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py b/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py index 143b46f1bff..c6e037b9a36 100644 --- a/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py +++ b/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py @@ -7324,21 +7324,6 @@ def update_node_provisioning_mode(self, mc: ManagedCluster) -> ManagedCluster: return mc - def update_node_disruption_policy(self, mc: ManagedCluster) -> ManagedCluster: - self._ensure_mc(mc) - - policy = self.context.get_node_disruption_policy() - if policy is not None: - if mc.node_disruption_profile is None: - mc.node_disruption_profile = ( - self.models.NodeDisruptionProfile() # pylint: disable=no-member - ) - - # set policy - mc.node_disruption_profile.policy = policy - - return mc - def update_node_provisioning_default_pools(self, mc: ManagedCluster) -> ManagedCluster: self._ensure_mc(mc) @@ -7656,8 +7641,15 @@ def update_node_disruption_policy(self, mc: ManagedCluster) -> ManagedCluster: """ self._ensure_mc(mc) - mc = self.update_node_provisioning_mode(mc) - mc = self.update_node_provisioning_default_pools(mc) + policy = self.context.get_node_disruption_policy() + if policy is not None: + if mc.node_disruption_profile is None: + mc.node_disruption_profile = ( + self.models.NodeDisruptionProfile() # pylint: disable=no-member + ) + + # set policy + mc.node_disruption_profile.policy = policy return mc From bd9069b2fa468c54a4bd834c2bffa3a97260de69 Mon Sep 17 00:00:00 2001 From: Erin Borders Date: Thu, 21 May 2026 14:45:14 -0400 Subject: [PATCH 6/7] remove unneeded flag --- .../azext_aks_preview/tests/latest/test_aks_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aks-preview/azext_aks_preview/tests/latest/test_aks_commands.py b/src/aks-preview/azext_aks_preview/tests/latest/test_aks_commands.py index fe2d98f47af..3384fa9de5b 100644 --- a/src/aks-preview/azext_aks_preview/tests/latest/test_aks_commands.py +++ b/src/aks-preview/azext_aks_preview/tests/latest/test_aks_commands.py @@ -24323,6 +24323,6 @@ def test_aks_update_node_disruption_policy(self, resource_group, resource_group_ # delete self.cmd( - "aks delete -g {resource_group} -n {name} --yes --no-wait --if-match={if_match}", + "aks delete -g {resource_group} -n {name} --yes --no-wait", checks=[self.is_empty()], ) From fb20a9bcaed6ce0298be3757e146551c104f3361 Mon Sep 17 00:00:00 2001 From: Erin Borders Date: Thu, 21 May 2026 14:48:34 -0400 Subject: [PATCH 7/7] nits --- src/aks-preview/azext_aks_preview/_params.py | 2 +- .../azext_aks_preview/tests/latest/test_aks_commands.py | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/aks-preview/azext_aks_preview/_params.py b/src/aks-preview/azext_aks_preview/_params.py index d681a62df84..342adf1e14a 100644 --- a/src/aks-preview/azext_aks_preview/_params.py +++ b/src/aks-preview/azext_aks_preview/_params.py @@ -1931,7 +1931,7 @@ def load_arguments(self, _): "node_disruption_policy", arg_type=get_enum_type(node_disruption_policies), is_preview=True, - help="Set the node disruption profile for the cluster.", + help="Set the node disruption policy for the cluster.", ) with self.argument_context("aks delete") as c: diff --git a/src/aks-preview/azext_aks_preview/tests/latest/test_aks_commands.py b/src/aks-preview/azext_aks_preview/tests/latest/test_aks_commands.py index 3384fa9de5b..0e08f0e62a8 100644 --- a/src/aks-preview/azext_aks_preview/tests/latest/test_aks_commands.py +++ b/src/aks-preview/azext_aks_preview/tests/latest/test_aks_commands.py @@ -24256,7 +24256,6 @@ def test_aks_nodepool_update_vmss_vm_size_resize( def test_aks_update_node_disruption_policy(self, resource_group, resource_group_location): aks_name = self.create_random_name("cliakstest", 16) nodepool_name = self.create_random_name("c", 6) - nodepool2_name = self.create_random_name("c", 6) self.kwargs.update( { "resource_group": resource_group, @@ -24265,14 +24264,13 @@ def test_aks_update_node_disruption_policy(self, resource_group, resource_group_ "ssh_key_value": self.generate_ssh_keys(), "resource_type": "Microsoft.ContainerService/ManagedClusters", "nodepool_name": nodepool_name, - "nodepool2_name": nodepool2_name, "vm_size": "Standard_D4s_v3", "network_plugin": "azure", "network_plugin_mode": "overlay", } ) - # create aks cluster with Azure network plugin + # create aks cluster with Azure network plugin self.cmd( "aks create " "--resource-group={resource_group} " @@ -24291,7 +24289,7 @@ def test_aks_update_node_disruption_policy(self, resource_group, resource_group_ "aks nodepool add " "--resource-group={resource_group}" " --cluster-name={name} " - "--name={nodepool2_name}", + "--name={nodepool_name}", checks=[ self.check("provisioningState", "Succeeded"), ], @@ -24315,7 +24313,7 @@ def test_aks_update_node_disruption_policy(self, resource_group, resource_group_ # attempt to change ssh access which should be allowed despite node disruption policy self.cmd( - "aks nodepool update --resource-group={resource_group} --cluster-name={name} --name={nodepool2_name} --ssh-access disabled", + "aks nodepool update --resource-group={resource_group} --cluster-name={name} --name={nodepool_name} --ssh-access disabled", checks=[ self.check("provisioningState", "Succeeded"), ],