From d0acc3703ea90ca97f36372cc2daa417911a78cc Mon Sep 17 00:00:00 2001 From: Jianping Zeng Date: Tue, 26 Jul 2022 19:38:24 -0700 Subject: [PATCH] [AKS] GA AKS Support for Azure Dedicated Host (#23324) --- .../azure/cli/command_modules/acs/_help.py | 11 +++++- .../azure/cli/command_modules/acs/_params.py | 5 ++- .../cli/command_modules/acs/_validators.py | 7 ++++ .../acs/agentpool_decorator.py | 27 ++++++++++++++ .../azure/cli/command_modules/acs/custom.py | 2 ++ .../tests/latest/test_agentpool_decorator.py | 36 +++++++++++++++++++ .../acs/tests/latest/test_validators.py | 15 ++++++++ 7 files changed, 101 insertions(+), 2 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/acs/_help.py b/src/azure-cli/azure/cli/command_modules/acs/_help.py index 4e75f024387..c87adbfce55 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/_help.py +++ b/src/azure-cli/azure/cli/command_modules/acs/_help.py @@ -528,7 +528,9 @@ - name: --defender-config type: string short-summary: Path to JSON file containing Microsoft Defender profile configurations. - + - name: --host-group-id + type: string + short-summary: The fully qualified dedicated host group id used to provision agent node pool. examples: - name: Create a Kubernetes cluster with an existing SSH public key. text: az aks create -g MyResourceGroup -n MyManagedCluster --ssh-key-value /path/to/publickey @@ -592,6 +594,8 @@ text: az aks create -g MyResourceGroup -n MyManagedCluster --load-balancer-sku Standard --network-plugin azure --windows-admin-username azure --windows-admin-password 'replacePassword1234$' --enable-windows-gmsa --gmsa-dns-server "10.240.0.4" --gmsa-root-domain-name "contoso.com" - name: create a kubernetes cluster with a snapshot id. text: az aks create -g MyResourceGroup -n MyManagedCluster --kubernetes-version 1.20.9 --snapshot-id "/subscriptions/00000/resourceGroups/AnotherResourceGroup/providers/Microsoft.ContainerService/snapshots/mysnapshot1" + - name: create a kubernetes cluster with support of hostgroup id. + text: az aks create -g MyResourceGroup -n MyMC --kubernetes-version 1.20.13 --location westus2 --host-group-id /subscriptions/00000/resourceGroups/AnotherResourceGroup/providers/Microsoft.ContainerService/hostGroups/myHostGroup --node-vm-size VMSize --enable-managed-identity --assign-identity """ helps['aks update'] = """ @@ -1054,6 +1058,9 @@ - name: --linux-os-config type: string short-summary: Path to JSON file containing OS configurations for Linux agent nodes. https://aka.ms/aks/custom-node-config + - name: --host-group-id + type: string + short-summary: The fully qualified dedicated host group id used to provision agent node pool. examples: - name: Create a nodepool in an existing AKS cluster with ephemeral os enabled. text: az aks nodepool add -g MyResourceGroup -n nodepool1 --cluster-name MyManagedCluster --node-osdisk-type Ephemeral --node-osdisk-size 48 @@ -1067,6 +1074,8 @@ text: az aks nodepool add -g MyResourceGroup -n nodepool1 --cluster-name MyManagedCluster --enable-fips-image - name: create a kubernetes cluster with a snapshot id. text: az aks nodepool add -g MyResourceGroup -n nodepool1 --cluster-name MyManagedCluster --kubernetes-version 1.20.9 --snapshot-id "/subscriptions/00000/resourceGroups/AnotherResourceGroup/providers/Microsoft.ContainerService/snapshots/mysnapshot1" + - name: create a nodepool in an existing AKS cluster with host group id + text: az aks nodepool add -g MyResourceGroup -n MyNodePool --cluster-name MyMC --host-group-id /subscriptions/00000/resourceGroups/AnotherResourceGroup/providers/Microsoft.ContainerService/hostGroups/myHostGroup --node-vm-size VMSize """ helps['aks nodepool delete'] = """ diff --git a/src/azure-cli/azure/cli/command_modules/acs/_params.py b/src/azure-cli/azure/cli/command_modules/acs/_params.py index 525b4e334f6..5386ed6b77b 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/_params.py +++ b/src/azure-cli/azure/cli/command_modules/acs/_params.py @@ -42,7 +42,8 @@ validate_spot_max_price, validate_ssh_key, validate_taints, validate_vm_set_type, validate_vnet_subnet_id, validate_keyvault_secrets_provider_disable_and_enable_parameters, - validate_defender_disable_and_enable_parameters, validate_defender_config_parameter) + validate_defender_disable_and_enable_parameters, validate_defender_config_parameter, + validate_host_group_id) from azure.cli.core.commands.parameters import ( edge_zone_type, file_type, get_enum_type, get_resource_name_completion_list, get_three_state_flag, name_type, @@ -315,6 +316,7 @@ def load_arguments(self, _): c.argument('kubelet_config') c.argument('linux_os_config') c.argument('yes', options_list=['--yes', '-y'], help='Do not prompt for confirmation.', action='store_true') + c.argument('host_group_id', validator=validate_host_group_id) with self.argument_context('aks update') as c: # managed cluster paramerters @@ -474,6 +476,7 @@ def load_arguments(self, _): c.argument('enable_fips_image', action='store_true') c.argument('kubelet_config') c.argument('linux_os_config') + c.argument('host_group_id', validator=validate_host_group_id) with self.argument_context('aks nodepool update', resource_type=ResourceType.MGMT_CONTAINERSERVICE, operation_group='agent_pools') as c: c.argument('enable_cluster_autoscaler', options_list=[ diff --git a/src/azure-cli/azure/cli/command_modules/acs/_validators.py b/src/azure-cli/azure/cli/command_modules/acs/_validators.py index 77a93943379..395fc8ef4ea 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/_validators.py +++ b/src/azure-cli/azure/cli/command_modules/acs/_validators.py @@ -442,6 +442,13 @@ def validate_snapshot_id(namespace): raise InvalidArgumentValueError("--snapshot-id is not a valid Azure resource ID.") +def validate_host_group_id(namespace): + if namespace.host_group_id: + from msrestazure.tools import is_valid_resource_id + if not is_valid_resource_id(namespace.host_group_id): + raise InvalidArgumentValueError("--host-group-id is not a valid Azure resource ID.") + + def extract_comma_separated_string( raw_string, enable_strip=False, diff --git a/src/azure-cli/azure/cli/command_modules/acs/agentpool_decorator.py b/src/azure-cli/azure/cli/command_modules/acs/agentpool_decorator.py index f30af1dcf6c..55f25ca2cad 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/agentpool_decorator.py +++ b/src/azure-cli/azure/cli/command_modules/acs/agentpool_decorator.py @@ -351,6 +351,21 @@ def get_snapshot(self) -> Union[Snapshot, None]: self.set_intermediate("snapshot", snapshot, overwrite_exists=True) return snapshot + def get_host_group_id(self) -> Union[str, None]: + return self._get_host_group_id() + + def _get_host_group_id(self) -> Union[str, None]: + raw_value = self.raw_param.get("host_group_id") + # try to read the property value corresponding to the parameter from the `agentpool` object + value_obtained_from_agentpool = None + if self.agentpool and hasattr(self.agentpool, "host_group_id"): + value_obtained_from_agentpool = self.agentpool.host_group_id + if value_obtained_from_agentpool is not None: + host_group_id = value_obtained_from_agentpool + else: + host_group_id = raw_value + return host_group_id + def _get_kubernetes_version(self, read_only: bool = False) -> str: """Internal function to dynamically obtain the value of kubernetes_version according to the context. @@ -1325,6 +1340,16 @@ def set_up_snapshot_properties(self, agentpool: AgentPool) -> AgentPool: agentpool.os_sku = self.context.get_os_sku() return agentpool + def set_up_host_group_properties(self, agentpool: AgentPool) -> AgentPool: + """Set up host group related properties for the AgentPool object. + + :return: the AgentPool object + """ + self._ensure_agentpool(agentpool) + + agentpool.host_group_id = self.context.get_host_group_id() + return agentpool + def set_up_node_network_properties(self, agentpool: AgentPool) -> AgentPool: """Set up priority related properties for the AgentPool object. @@ -1458,6 +1483,8 @@ def construct_agentpool_profile_default(self, bypass_restore_defaults: bool = Fa self._remove_defaults_in_agentpool(agentpool) # set up snapshot properties agentpool = self.set_up_snapshot_properties(agentpool) + # set up host group properties + agentpool = self.set_up_host_group_properties(agentpool) # set up node network properties agentpool = self.set_up_node_network_properties(agentpool) # set up auto scaler properties diff --git a/src/azure-cli/azure/cli/command_modules/acs/custom.py b/src/azure-cli/azure/cli/command_modules/acs/custom.py index 715cd2a14b8..7530963e467 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/custom.py +++ b/src/azure-cli/azure/cli/command_modules/acs/custom.py @@ -1575,6 +1575,7 @@ def aks_create( no_wait=False, yes=False, aks_custom_headers=None, + host_group_id=None, ): # DO NOT MOVE: get all the original parameters and save them as a dictionary raw_parameters = locals() @@ -2972,6 +2973,7 @@ def aks_agentpool_add( linux_os_config=None, no_wait=False, aks_custom_headers=None, + host_group_id=None, ): # DO NOT MOVE: get all the original parameters and save them as a dictionary raw_parameters = locals() diff --git a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_agentpool_decorator.py b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_agentpool_decorator.py index 500404fe645..3f485d769aa 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_agentpool_decorator.py +++ b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_agentpool_decorator.py @@ -254,6 +254,34 @@ def common_get_snapshot(self): # test cache self.assertEqual(ctx_1.get_snapshot(), mock_snapshot) + def common_get_host_group_id(self): + # default + ctx_1 = AKSAgentPoolContext( + self.cmd, + AKSAgentPoolParamDict( + { + "host_group_id": None, + } + ), + self.models, + DecoratorMode.CREATE, + self.agentpool_decorator_mode, + ) + self.assertEqual(ctx_1.get_host_group_id(), None) + # custom + ctx_1 = AKSAgentPoolContext( + self.cmd, + AKSAgentPoolParamDict( + { + "host_group_id": "test_host_group_id", + } + ), + self.models, + DecoratorMode.CREATE, + self.agentpool_decorator_mode, + ) + self.assertEqual(ctx_1.get_host_group_id(), "test_host_group_id") + def common_get_kubernetes_version(self): # default ctx_1 = AKSAgentPoolContext( @@ -1316,6 +1344,9 @@ def test_get_snapshot_id(self): def test_get_snapshot(self): self.common_get_snapshot() + def test_get_host_group_id(self): + self.common_get_host_group_id() + def test_get_kubernetes_version(self): self.common_get_kubernetes_version() @@ -1465,6 +1496,9 @@ def test_get_snapshot_id(self): def test_get_snapshot(self): self.common_get_snapshot() + def test_get_host_group_id(self): + self.common_get_host_group_id() + def test_get_kubernetes_version(self): self.common_get_kubernetes_version() @@ -2066,6 +2100,7 @@ def test_construct_agentpool_profile_default(self): enable_fips=False, mode=CONST_NODEPOOL_MODE_USER, scale_down_mode=CONST_SCALE_DOWN_MODE_DELETE, + host_group_id=None, ) self.assertEqual(dec_agentpool_1, ground_truth_agentpool_1) @@ -2207,6 +2242,7 @@ def test_construct_agentpool_profile_default(self): enable_ultra_ssd=False, enable_fips=False, mode=CONST_NODEPOOL_MODE_SYSTEM, + host_group_id=None, ) self.assertEqual(dec_agentpool_1, ground_truth_agentpool_1) diff --git a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_validators.py b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_validators.py index 31a4b8f3032..01d36008632 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_validators.py +++ b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_validators.py @@ -474,6 +474,21 @@ def test_valid_keyvault_secret_provider_parameters(self): ) validators.validate_keyvault_secrets_provider_disable_and_enable_parameters(namespace_3) +class HostGroupIDNamespace: + def __init__(self, host_group_id): + + self.host_group_id = host_group_id + +class TestValidateHostGroupID(unittest.TestCase): + def test_invalid_host_group_id(self): + invalid_host_group_id = "dummy group id" + namespace = HostGroupIDNamespace(host_group_id=invalid_host_group_id) + err = ("--host-group-id is not a valid Azure resource ID.") + + with self.assertRaises(CLIError) as cm: + validators.validate_host_group_id(namespace) + self.assertEqual(str(cm.exception), err) + if __name__ == "__main__": unittest.main()