diff --git a/src/aks-preview/HISTORY.rst b/src/aks-preview/HISTORY.rst index 4461fd9c887..c6b1074e8d8 100644 --- a/src/aks-preview/HISTORY.rst +++ b/src/aks-preview/HISTORY.rst @@ -19,6 +19,7 @@ Pending * Add blue-green upgrade strategy support for AKS node pools: - `az aks nodepool add/update/upgrade`: Add `--upgrade-strategy` parameter to switch between rolling and blue-green nodepool upgrades. - `az aks nodepool add/update/upgrade`: Add `--drain-batch-size`, `--drain-timeout-bg`, `--batch-soak-duration`, `--final-soak-duration` parameters to configure blue-green upgrade settings. +* Fix `--localdns-config` parameter to handle null values in JSON configuration files gracefully, preventing crashes when DNS override sections are null. 18.0.0b38 +++++++ diff --git a/src/aks-preview/azext_aks_preview/_helpers.py b/src/aks-preview/azext_aks_preview/_helpers.py index 0e791b4aecf..9b3fdd3ed01 100644 --- a/src/aks-preview/azext_aks_preview/_helpers.py +++ b/src/aks-preview/azext_aks_preview/_helpers.py @@ -448,3 +448,19 @@ def get_extension_in_allow_list(result): if _check_if_extension_type_is_in_allow_list(result.extension_type.lower()): return result return None + + +def process_dns_overrides(overrides_dict, target_dict, build_override_func): + """Helper function to safely process DNS overrides with null checks. + + Processes DNS override dictionaries from LocalDNS configuration, + filtering out null values and applying the build function to valid entries. + + :param overrides_dict: Dictionary containing DNS overrides (can be None) + :param target_dict: Target dictionary to populate with processed overrides + :param build_override_func: Function to build override objects from dict values + """ + if overrides_dict is not None: + for key, value in overrides_dict.items(): + if value is not None: + target_dict[key] = build_override_func(value) diff --git a/src/aks-preview/azext_aks_preview/agentpool_decorator.py b/src/aks-preview/azext_aks_preview/agentpool_decorator.py index 6ec1fa1f3ea..2f627f13fac 100644 --- a/src/aks-preview/azext_aks_preview/agentpool_decorator.py +++ b/src/aks-preview/azext_aks_preview/agentpool_decorator.py @@ -50,6 +50,7 @@ from azext_aks_preview._helpers import ( get_nodepool_snapshot_by_snapshot_id, filter_hard_taints, + process_dns_overrides, ) logger = get_logger(__name__) @@ -1477,13 +1478,16 @@ def build_override(override_dict): return self.models.LocalDNSOverride(**filtered) # Build kubeDNSOverrides and vnetDNSOverrides from the localdns_profile - kube_overrides = localdns_profile.get("kubeDNSOverrides") - for key, value in kube_overrides.items(): - kube_dns_overrides[key] = build_override(value) - - vnet_overrides = localdns_profile.get("vnetDNSOverrides") - for key, value in vnet_overrides.items(): - vnet_dns_overrides[key] = build_override(value) + process_dns_overrides( + localdns_profile.get("kubeDNSOverrides"), + kube_dns_overrides, + build_override + ) + process_dns_overrides( + localdns_profile.get("vnetDNSOverrides"), + vnet_dns_overrides, + build_override + ) agentpool.local_dns_profile = self.models.LocalDNSProfile( mode=localdns_profile.get("mode"), @@ -1816,13 +1820,16 @@ def build_override(override_dict): return self.models.LocalDNSOverride(**filtered) # Build kubeDNSOverrides and vnetDNSOverrides from the localdns_profile - kube_overrides = localdns_profile.get("kubeDNSOverrides") - for key, value in kube_overrides.items(): - kube_dns_overrides[key] = build_override(value) - - vnet_overrides = localdns_profile.get("vnetDNSOverrides") - for key, value in vnet_overrides.items(): - vnet_dns_overrides[key] = build_override(value) + process_dns_overrides( + localdns_profile.get("kubeDNSOverrides"), + kube_dns_overrides, + build_override + ) + process_dns_overrides( + localdns_profile.get("vnetDNSOverrides"), + vnet_dns_overrides, + build_override + ) agentpool.local_dns_profile = self.models.LocalDNSProfile( mode=localdns_profile.get("mode"), diff --git a/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/disabled_mode_only.json b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/disabled_mode_only.json new file mode 100644 index 00000000000..cbea7560aaf --- /dev/null +++ b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/disabled_mode_only.json @@ -0,0 +1,3 @@ +{ + "mode": "Disabled" +} \ No newline at end of file diff --git a/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/empty.json b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/empty.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/empty.json @@ -0,0 +1 @@ +{} diff --git a/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/empty_mode.json b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/empty_mode.json new file mode 100644 index 00000000000..b12fbfe5f68 --- /dev/null +++ b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/empty_mode.json @@ -0,0 +1,3 @@ +{ + "mode": "" +} diff --git a/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/invalid_mode.json b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/invalid_mode.json new file mode 100644 index 00000000000..2dbe61552c6 --- /dev/null +++ b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/invalid_mode.json @@ -0,0 +1,3 @@ +{ + "mode": "InvalidMode" +} diff --git a/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig.json b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/localdnsconfig.json similarity index 100% rename from src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig.json rename to src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/localdnsconfig.json diff --git a/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/localdnsconfig_with_extra_property.json b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/localdnsconfig_with_extra_property.json new file mode 100644 index 00000000000..83e1822cce1 --- /dev/null +++ b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/localdnsconfig_with_extra_property.json @@ -0,0 +1,48 @@ +{ + "mode": "Required", + "extraProperty": "unexpected", + "kubeDNSOverrides": { + ".": { + "cacheDurationInSeconds": 3600, + "forwardDestination": "ClusterCoreDNS", + "forwardPolicy": "Sequential", + "maxConcurrent": 1000, + "protocol": "PreferUDP", + "queryLogging": "Error", + "serveStale": "Verify", + "serveStaleDurationInSeconds": 3600 + }, + "cluster.local": { + "cacheDurationInSeconds": 3600, + "forwardDestination": "ClusterCoreDNS", + "forwardPolicy": "Sequential", + "maxConcurrent": 1000, + "protocol": "ForceTCP", + "queryLogging": "Error", + "serveStale": "Immediate", + "serveStaleDurationInSeconds": 3600 + } + }, + "vnetDNSOverrides": { + ".": { + "cacheDurationInSeconds": 3600, + "forwardDestination": "VnetDNS", + "forwardPolicy": "Sequential", + "maxConcurrent": 1000, + "protocol": "PreferUDP", + "queryLogging": "Error", + "serveStale": "Verify", + "serveStaleDurationInSeconds": 3600 + }, + "cluster.local": { + "cacheDurationInSeconds": 3600, + "forwardDestination": "ClusterCoreDNS", + "forwardPolicy": "Sequential", + "maxConcurrent": 1000, + "protocol": "ForceTCP", + "queryLogging": "Error", + "serveStale": "Immediate", + "serveStaleDurationInSeconds": 3600 + } + } +} diff --git a/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/localdnsconfig_with_extra_property_in_dnsOverrides.json b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/localdnsconfig_with_extra_property_in_dnsOverrides.json new file mode 100644 index 00000000000..a70eb30fb34 --- /dev/null +++ b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/localdnsconfig_with_extra_property_in_dnsOverrides.json @@ -0,0 +1,52 @@ +{ + "mode": "Required", + "extraProperty": "unexpected", + "kubeDNSOverrides": { + ".": { + "extraProperty": "unexpected", + "cacheDurationInSeconds": 3600, + "forwardDestination": "ClusterCoreDNS", + "forwardPolicy": "Sequential", + "maxConcurrent": 1000, + "protocol": "PreferUDP", + "queryLogging": "Error", + "serveStale": "Verify", + "serveStaleDurationInSeconds": 3600 + }, + "cluster.local": { + "extraProperty": "unexpected", + "cacheDurationInSeconds": 3600, + "forwardDestination": "ClusterCoreDNS", + "forwardPolicy": "Sequential", + "maxConcurrent": 1000, + "protocol": "ForceTCP", + "queryLogging": "Error", + "serveStale": "Immediate", + "serveStaleDurationInSeconds": 3600 + } + }, + "vnetDNSOverrides": { + ".": { + "extraProperty": "unexpected", + "cacheDurationInSeconds": 3600, + "forwardDestination": "VnetDNS", + "forwardPolicy": "Sequential", + "maxConcurrent": 1000, + "protocol": "PreferUDP", + "queryLogging": "Error", + "serveStale": "Verify", + "serveStaleDurationInSeconds": 3600 + }, + "cluster.local": { + "extraProperty": "unexpected", + "cacheDurationInSeconds": 3600, + "forwardDestination": "ClusterCoreDNS", + "forwardPolicy": "Sequential", + "maxConcurrent": 1000, + "protocol": "ForceTCP", + "queryLogging": "Error", + "serveStale": "Immediate", + "serveStaleDurationInSeconds": 3600 + } + } +} diff --git a/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/missing_mode.json b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/missing_mode.json new file mode 100644 index 00000000000..e1318790539 --- /dev/null +++ b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/missing_mode.json @@ -0,0 +1,26 @@ +{ + "kubeDnsOverrides": { + ".": { + "cacheDurationInSeconds": 3600, + "forwardDestination": "ClusterCoreDNS", + "forwardPolicy": "Sequential", + "maxConcurrent": 1000, + "protocol": "PreferUDP", + "queryLogging": "Error", + "serveStale": "Verify", + "serveStaleDurationInSeconds": 3600 + } + }, + "vnetDnsOverrides": { + ".": { + "cacheDurationInSeconds": 3600, + "forwardDestination": "VnetDNS", + "forwardPolicy": "Sequential", + "maxConcurrent": 1000, + "protocol": "PreferUDP", + "queryLogging": "Error", + "serveStale": "Verify", + "serveStaleDurationInSeconds": 3600 + } + } +} diff --git a/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/null_mode.json b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/null_mode.json new file mode 100644 index 00000000000..d5062c886e2 --- /dev/null +++ b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/null_mode.json @@ -0,0 +1,3 @@ +{ + "mode": null +} diff --git a/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_empty_overrides.json b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_empty_overrides.json new file mode 100644 index 00000000000..5ca13a1f6d3 --- /dev/null +++ b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_empty_overrides.json @@ -0,0 +1,5 @@ +{ + "mode": "Required", + "kubeDnsOverrides": {}, + "vnetDnsOverrides": {} +} diff --git a/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_extra_property.json b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_extra_property.json new file mode 100644 index 00000000000..2b7a342ab6f --- /dev/null +++ b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_extra_property.json @@ -0,0 +1,16 @@ +{ + "mode": "Required", + "extraProperty": "unexpected", + "kubeDnsOverrides": { + ".": { + "cacheDurationInSeconds": 3600, + "forwardDestination": "ClusterCoreDNS", + "forwardPolicy": "Sequential", + "maxConcurrent": 1000, + "protocol": "PreferUDP", + "queryLogging": "Error", + "serveStale": "Verify", + "serveStaleDurationInSeconds": 3600 + } + } +} diff --git a/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_invalid_kubedns.json b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_invalid_kubedns.json new file mode 100644 index 00000000000..e35a1e0107f --- /dev/null +++ b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_invalid_kubedns.json @@ -0,0 +1,8 @@ +{ + "mode": "Required", + "kubeDnsOverrides": { + ".": { + "invalidField": 123 + } + } +} diff --git a/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_invalid_vnetdns.json b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_invalid_vnetdns.json new file mode 100644 index 00000000000..8db6ca61076 --- /dev/null +++ b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_invalid_vnetdns.json @@ -0,0 +1,8 @@ +{ + "mode": "Required", + "vnetDnsOverrides": { + ".": { + "invalidField": 456 + } + } +} diff --git a/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_only.json b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_only.json new file mode 100644 index 00000000000..81adb8f0432 --- /dev/null +++ b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_only.json @@ -0,0 +1,3 @@ +{ + "mode": "Required" +} \ No newline at end of file diff --git a/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_partial_invalid.json b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_partial_invalid.json new file mode 100644 index 00000000000..3c3af53e6ff --- /dev/null +++ b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_partial_invalid.json @@ -0,0 +1,18 @@ +{ + "mode": "Required", + "kubeDnsOverrides": { + ".": { + "cacheDurationInSeconds": 3600, + "forwardDestination": "ClusterCoreDNS", + "forwardPolicy": "Sequential", + "maxConcurrent": 1000, + "protocol": "PreferUDP", + "queryLogging": "Error", + "serveStale": "Verify", + "serveStaleDurationInSeconds": 3600 + } + }, + "vnetDnsOverrides": { + ".": "invalid" + } +} diff --git a/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_with_valid_kubedns.json b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_with_valid_kubedns.json new file mode 100644 index 00000000000..e6b8644adfa --- /dev/null +++ b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_with_valid_kubedns.json @@ -0,0 +1,25 @@ +{ + "mode": "Required", + "kubeDnsOverrides": { + ".": { + "cacheDurationInSeconds": 3600, + "forwardDestination": "ClusterCoreDNS", + "forwardPolicy": "Sequential", + "maxConcurrent": 1000, + "protocol": "PreferUDP", + "queryLogging": "Error", + "serveStale": "Verify", + "serveStaleDurationInSeconds": 3600 + }, + "cluster.local": { + "cacheDurationInSeconds": 3600, + "forwardDestination": "ClusterCoreDNS", + "forwardPolicy": "Sequential", + "maxConcurrent": 1000, + "protocol": "ForceTCP", + "queryLogging": "Error", + "serveStale": "Immediate", + "serveStaleDurationInSeconds": 3600 + } + } +} diff --git a/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_with_valid_vnetdns.json b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_with_valid_vnetdns.json new file mode 100644 index 00000000000..63278c22aaf --- /dev/null +++ b/src/aks-preview/azext_aks_preview/tests/latest/data/localdnsconfig/required_mode_with_valid_vnetdns.json @@ -0,0 +1,25 @@ +{ + "mode": "Required", + "vnetDnsOverrides": { + ".": { + "cacheDurationInSeconds": 3600, + "forwardDestination": "VnetDNS", + "forwardPolicy": "Sequential", + "maxConcurrent": 1000, + "protocol": "PreferUDP", + "queryLogging": "Error", + "serveStale": "Verify", + "serveStaleDurationInSeconds": 3600 + }, + "cluster.local": { + "cacheDurationInSeconds": 3600, + "forwardDestination": "ClusterCoreDNS", + "forwardPolicy": "Sequential", + "maxConcurrent": 1000, + "protocol": "ForceTCP", + "queryLogging": "Error", + "serveStale": "Immediate", + "serveStaleDurationInSeconds": 3600 + } + } +} diff --git a/src/aks-preview/azext_aks_preview/tests/latest/test_agentpool_decorator.py b/src/aks-preview/azext_aks_preview/tests/latest/test_agentpool_decorator.py index 08b4de4d0a7..9591c00d1b6 100644 --- a/src/aks-preview/azext_aks_preview/tests/latest/test_agentpool_decorator.py +++ b/src/aks-preview/azext_aks_preview/tests/latest/test_agentpool_decorator.py @@ -2684,6 +2684,151 @@ def common_update_blue_green_upgrade_settings(self): ) self.assertEqual(dec_agentpool_3, ground_truth_agentpool_3) + def common_update_localdns_profile(self): + import tempfile + import json + import os + + # Test case 1: LocalDNS config provided - verify method is called + localdns_config = { + "mode": "Required", + "kubeDNSOverrides": { + ".": { + "cacheDurationInSeconds": 3600, + "protocol": "PreferUDP" + } + } + } + + with tempfile.NamedTemporaryFile(mode="w+", delete=False, suffix=".json") as f: + json.dump(localdns_config, f) + f.flush() + config_file_path = f.name + + try: + dec_1 = AKSPreviewAgentPoolUpdateDecorator( + self.cmd, + self.client, + {"localdns_config": config_file_path}, + self.resource_type, + self.agentpool_decorator_mode, + ) + + agentpool_1 = self.create_initialized_agentpool_instance() + dec_1.context.attach_agentpool(agentpool_1) + dec_agentpool_1 = dec_1.update_localdns_profile(agentpool_1) + + # Verify that LocalDNS profile was created and assigned + self.assertIsNotNone(dec_agentpool_1.local_dns_profile) + self.assertEqual(dec_agentpool_1.local_dns_profile.mode, "Required") + + finally: + os.unlink(config_file_path) + + # Test case 2: No LocalDNS config provided - no change + dec_2 = AKSPreviewAgentPoolUpdateDecorator( + self.cmd, + self.client, + {}, + self.resource_type, + self.agentpool_decorator_mode, + ) + + agentpool_2 = self.create_initialized_agentpool_instance() + original_local_dns_profile = agentpool_2.local_dns_profile + dec_2.context.attach_agentpool(agentpool_2) + dec_agentpool_2 = dec_2.update_localdns_profile(agentpool_2) + + # Verify LocalDNS profile wasn't changed + self.assertEqual(dec_agentpool_2.local_dns_profile, original_local_dns_profile) + + # Test case 3: LocalDNS config with null values + localdns_config_with_nulls = { + "mode": "Required", + "kubeDNSOverrides": None, + "vnetDNSOverrides": { + ".": { + "cacheDurationInSeconds": 1800, + "protocol": "ForceTCP" + } + } + } + + with tempfile.NamedTemporaryFile(mode="w+", delete=False, suffix=".json") as f: + json.dump(localdns_config_with_nulls, f) + f.flush() + config_file_path = f.name + + try: + dec_3 = AKSPreviewAgentPoolUpdateDecorator( + self.cmd, + self.client, + {"localdns_config": config_file_path}, + self.resource_type, + self.agentpool_decorator_mode, + ) + + agentpool_3 = self.create_initialized_agentpool_instance() + dec_3.context.attach_agentpool(agentpool_3) + dec_agentpool_3 = dec_3.update_localdns_profile(agentpool_3) + + # Verify that LocalDNS profile was created with null handling + self.assertIsNotNone(dec_agentpool_3.local_dns_profile) + self.assertEqual(dec_agentpool_3.local_dns_profile.mode, "Required") + # kubeDNSOverrides should be empty dict due to null input + self.assertEqual(len(dec_agentpool_3.local_dns_profile.kube_dns_overrides), 0) + # vnetDNSOverrides should have one entry + self.assertEqual(len(dec_agentpool_3.local_dns_profile.vnet_dns_overrides), 1) + + finally: + os.unlink(config_file_path) + + def common_test_process_dns_overrides_helper(self): + from azext_aks_preview._helpers import process_dns_overrides + + # Test the process_dns_overrides utility function functionality + + # Test case 1: Valid DNS overrides without nulls + dns_overrides = { + ".": { + "cacheDurationInSeconds": 3600, + "protocol": "PreferUDP" + } + } + target_dict = {} + + def mock_build_override(override_dict): + return self.models.LocalDNSOverride( + cache_duration_in_seconds=override_dict.get("cacheDurationInSeconds"), + protocol=override_dict.get("protocol") + ) + + process_dns_overrides(dns_overrides, target_dict, mock_build_override) + self.assertEqual(len(target_dict), 1) + self.assertIn(".", target_dict) + + # Test case 2: DNS overrides with null values (should handle gracefully) + dns_overrides_with_nulls = { + ".": { + "cacheDurationInSeconds": 1800, + "protocol": None + } + } + target_dict_2 = {} + + process_dns_overrides(dns_overrides_with_nulls, target_dict_2, mock_build_override) + self.assertEqual(len(target_dict_2), 1) + + # Test case 3: None input (should handle gracefully) + target_dict_3 = {} + process_dns_overrides(None, target_dict_3, mock_build_override) + self.assertEqual(len(target_dict_3), 0) + + # Test case 4: Empty input (should handle gracefully) + target_dict_4 = {} + process_dns_overrides({}, target_dict_4, mock_build_override) + self.assertEqual(len(target_dict_4), 0) + class AKSPreviewAgentPoolUpdateDecoratorStandaloneModeTestCase( AKSPreviewAgentPoolUpdateDecoratorCommonTestCase @@ -2721,6 +2866,12 @@ def test_update_upgrade_strategy(self): def test_update_blue_green_upgrade_settings(self): self.common_update_blue_green_upgrade_settings() + def test_update_localdns_profile(self): + self.common_update_localdns_profile() + + def test_process_dns_overrides_helper(self): + self.common_test_process_dns_overrides_helper() + def test_update_agentpool_profile_preview(self): import inspect @@ -2808,6 +2959,12 @@ def test_update_upgrade_strategy(self): def test_update_blue_green_upgrade_settings(self): self.common_update_blue_green_upgrade_settings() + def test_update_localdns_profile(self): + self.common_update_localdns_profile() + + def test_process_dns_overrides_helper(self): + self.common_test_process_dns_overrides_helper() + def test_update_agentpool_profile_preview(self): import inspect 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 7e4041d897f..9dfef3811fb 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 @@ -28,7 +28,7 @@ from azure.core.exceptions import HttpResponseError from knack.util import CLIError -from .test_localdns_profile import assert_dns_overrides_equal, vnetDnsOverridesExpected, kubeDnsOverridesExpected +from .test_localdns_profile import assert_dns_overrides_equal, vnetDnsOverridesExpected, kubeDnsOverridesExpected, vnetDnsOverridesExpectedDefault, kubeDnsOverridesExpectedDefault def _get_test_data_file(filename): curr_dir = os.path.dirname(os.path.realpath(__file__)) @@ -3346,7 +3346,7 @@ def test_aks_create_add_nodepool_with_custom_ca_trust_certificates( def test_aks_nodepool_add_with_localdns_config(self, resource_group, resource_group_location): aks_name = self.create_random_name("cliakstest", 16) nodepool_name = self.create_random_name("np", 6) - localdns_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig.json") + localdns_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "localdnsconfig.json") self.kwargs.update({ "resource_group": resource_group, "name": aks_name, @@ -3387,12 +3387,280 @@ def test_aks_nodepool_add_with_localdns_config(self, resource_group, resource_gr checks=[self.is_empty()], ) + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_add_with_localdns_required_mode(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + required_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "required_mode_only.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "required_config": required_config_path + }) + + # Create AKS cluster + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Add nodepool with required mode localdns config + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={required_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + self.cmd(add_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Show nodepool and check localDNSProfile + show_cmd = ( + "aks nodepool show --resource-group={resource_group} --cluster-name={name} --name={nodepool_name}" + ) + result = self.cmd(show_cmd).get_output_in_json() + assert result["localDnsProfile"]["mode"] == "Required" + + assert_dns_overrides_equal(result["localDnsProfile"]["kubeDnsOverrides"], kubeDnsOverridesExpectedDefault) + assert_dns_overrides_equal(result["localDnsProfile"]["vnetDnsOverrides"], vnetDnsOverridesExpectedDefault) + # Clean up + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_add_with_localdns_required_mode_single_vnetdns(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + vnetdns_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "required_mode_with_valid_vnetdns.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "vnetdns_config": vnetdns_config_path + }) + + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={vnetdns_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + self.cmd(add_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + show_cmd = ( + "aks nodepool show --resource-group={resource_group} --cluster-name={name} --name={nodepool_name}" + ) + result = self.cmd(show_cmd).get_output_in_json() + assert result["localDnsProfile"]["mode"] == "Required" + assert "kubeDnsOverrides" not in result["localDnsProfile"] or result["localDnsProfile"]["kubeDnsOverrides"] is None + assert_dns_overrides_equal(result["localDnsProfile"]["vnetDnsOverrides"], vnetDnsOverridesExpected) + + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_add_with_localdns_required_mode_single_kubedns(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + kubedns_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "required_mode_with_valid_kubedns.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "kubedns_config": kubedns_config_path + }) + + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={kubedns_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + self.cmd(add_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + show_cmd = ( + "aks nodepool show --resource-group={resource_group} --cluster-name={name} --name={nodepool_name}" + ) + result = self.cmd(show_cmd).get_output_in_json() + assert result["localDnsProfile"]["mode"] == "Required" + assert "vnetDnsOverrides" not in result["localDnsProfile"] or result["localDnsProfile"]["vnetDnsOverrides"] is None + assert_dns_overrides_equal(result["localDnsProfile"]["kubeDnsOverrides"], kubeDnsOverridesExpected) + + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_add_with_localdns_required_mode_with_extra_property(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + required_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "required_mode_extra_property.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "required_config": required_config_path + }) + + # Create AKS cluster + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Add nodepool with required mode localdns config + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={required_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + self.cmd(add_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Show nodepool and check localDNSProfile + show_cmd = ( + "aks nodepool show --resource-group={resource_group} --cluster-name={name} --name={nodepool_name}" + ) + result = self.cmd(show_cmd).get_output_in_json() + assert result["localDnsProfile"]["mode"] == "Required" + + assert_dns_overrides_equal(result["localDnsProfile"]["kubeDnsOverrides"], kubeDnsOverridesExpected) + assert_dns_overrides_equal(result["localDnsProfile"]["vnetDnsOverrides"], vnetDnsOverridesExpected) + # Clean up + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_add_with_localdns_config_with_extra_property(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + required_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "localdnsconfig_with_extra_property.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "required_config": required_config_path + }) + + # Create AKS cluster + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Add nodepool with required mode localdns config + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={required_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + self.cmd(add_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Show nodepool and check localDNSProfile + show_cmd = ( + "aks nodepool show --resource-group={resource_group} --cluster-name={name} --name={nodepool_name}" + ) + result = self.cmd(show_cmd).get_output_in_json() + assert result["localDnsProfile"]["mode"] == "Required" + + assert_dns_overrides_equal(result["localDnsProfile"]["kubeDnsOverrides"], kubeDnsOverridesExpected) + assert_dns_overrides_equal(result["localDnsProfile"]["vnetDnsOverrides"], vnetDnsOverridesExpected) + # Clean up + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_add_with_localdns_config_with_extra_property_in_dnsOverrides(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + required_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "localdnsconfig_with_extra_property_in_dnsOverrides.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "required_config": required_config_path + }) + + # Create AKS cluster + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Add nodepool with required mode localdns config + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={required_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + self.cmd(add_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Show nodepool and check localDNSProfile + show_cmd = ( + "aks nodepool show --resource-group={resource_group} --cluster-name={name} --name={nodepool_name}" + ) + result = self.cmd(show_cmd).get_output_in_json() + assert result["localDnsProfile"]["mode"] == "Required" + + assert_dns_overrides_equal(result["localDnsProfile"]["kubeDnsOverrides"], kubeDnsOverridesExpected) + assert_dns_overrides_equal(result["localDnsProfile"]["vnetDnsOverrides"], vnetDnsOverridesExpected) + # Clean up + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + @AllowLargeResponse() @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") def test_aks_nodepool_update_with_localdns_config(self, resource_group, resource_group_location): aks_name = self.create_random_name("cliakstest", 16) nodepool_name = self.create_random_name("np", 6) - localdns_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig.json") + localdns_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "localdnsconfig.json") self.kwargs.update({ "resource_group": resource_group, "name": aks_name, @@ -3440,6 +3708,328 @@ def test_aks_nodepool_update_with_localdns_config(self, resource_group, resource checks=[self.is_empty()], ) + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_update_with_localdns_required_mode_one_valid_override_each(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + valid_dns_overrides_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "localdnsconfig.json") + kubedns_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig","required_mode_with_valid_kubedns.json") + vnetdns_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig","required_mode_with_valid_vnetdns.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "valid_dns_overrides": valid_dns_overrides_path, + "kubedns_config": kubedns_config_path, + "vnetdns_config": vnetdns_config_path + }) + + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Add nodepool with valid DNS overrides + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={valid_dns_overrides} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + self.cmd(add_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + show_cmd = ( + "aks nodepool show --resource-group={resource_group} --cluster-name={name} --name={nodepool_name}" + ) + result = self.cmd(show_cmd).get_output_in_json() + assert result["localDnsProfile"]["mode"] == "Required" + # Should have both kubeDnsOverrides and vnetDnsOverrides + assert_dns_overrides_equal(result["localDnsProfile"].get("kubeDnsOverrides", {}), kubeDnsOverridesExpected) + assert_dns_overrides_equal(result["localDnsProfile"].get("vnetDnsOverrides", {}), vnetDnsOverridesExpected) + + # Update with only kubeDnsOverrides + update_kubedns_cmd = ( + "aks nodepool update --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --localdns-config={kubedns_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + ) + self.cmd(update_kubedns_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + result = self.cmd(show_cmd).get_output_in_json() + assert result["localDnsProfile"]["mode"] == "Required" + assert "vnetDnsOverrides" not in result["localDnsProfile"] or result["localDnsProfile"]["vnetDnsOverrides"] is None + assert_dns_overrides_equal(result["localDnsProfile"]["kubeDnsOverrides"], kubeDnsOverridesExpected) + + # Update with only vnetDnsOverrides + update_vnetdns_cmd = ( + "aks nodepool update --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --localdns-config={vnetdns_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + ) + self.cmd(update_vnetdns_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + result = self.cmd(show_cmd).get_output_in_json() + assert result["localDnsProfile"]["mode"] == "Required" + assert "kubeDnsOverrides" not in result["localDnsProfile"] or result["localDnsProfile"]["kubeDnsOverrides"] is None + assert_dns_overrides_equal(result["localDnsProfile"]["vnetDnsOverrides"], vnetDnsOverridesExpected) + + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_update_localdns_required_to_disabled(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + required_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "localdnsconfig.json") + disabled_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "disabled_mode_only.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "required_config": required_config_path, + "disabled_config": disabled_config_path + }) + + # Create AKS cluster + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" # k8s version > 1.33 to support localDNS + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Add nodepool with required mode localdns config + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={required_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" # k8s version > 1.33 to support localDNS + ) + self.cmd(add_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Verify nodepool has required mode and DNS overrides + show_cmd = ( + "aks nodepool show --resource-group={resource_group} --cluster-name={name} --name={nodepool_name}" + ) + result = self.cmd(show_cmd).get_output_in_json() + assert result["localDnsProfile"]["mode"] == "Required" + assert_dns_overrides_equal(result["localDnsProfile"]["kubeDnsOverrides"], kubeDnsOverridesExpected) + assert_dns_overrides_equal(result["localDnsProfile"]["vnetDnsOverrides"], vnetDnsOverridesExpected) + + # Update nodepool to disabled mode + update_cmd = ( + "aks nodepool update --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --localdns-config={disabled_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + ) + self.cmd(update_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Verify nodepool has disabled mode and no DNS overrides + result = self.cmd(show_cmd).get_output_in_json() + assert result["localDnsProfile"]["mode"] == "Disabled" + assert "kubeDnsOverrides" not in result["localDnsProfile"] or result["localDnsProfile"]["kubeDnsOverrides"] is None + assert "vnetDnsOverrides" not in result["localDnsProfile"] or result["localDnsProfile"]["vnetDnsOverrides"] is None + + # Clean up + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_update_localdns_disabled_to_required(self, resource_group): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + disabled_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "disabled_mode_only.json") + required_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "required_mode_only.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "disabled_config": disabled_config_path, + "required_config": required_config_path + }) + + # Create AKS cluster + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" # k8s version > 1.33 to support localDNS + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Add nodepool with disabled mode localdns config + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={disabled_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" # k8s version > 1.33 to support localDNS + ) + self.cmd(add_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Verify nodepool has disabled mode and no DNS overrides + show_cmd = ( + "aks nodepool show --resource-group={resource_group} --cluster-name={name} --name={nodepool_name}" + ) + result = self.cmd(show_cmd).get_output_in_json() + assert result["localDnsProfile"]["mode"] == "Disabled" + assert "kubeDnsOverrides" not in result["localDnsProfile"] or result["localDnsProfile"]["kubeDnsOverrides"] is None + assert "vnetDnsOverrides" not in result["localDnsProfile"] or result["localDnsProfile"]["vnetDnsOverrides"] is None + + # Update nodepool to required mode + update_cmd = ( + "aks nodepool update --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --localdns-config={required_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + ) + self.cmd(update_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Verify nodepool has required mode and DNS overrides + result = self.cmd(show_cmd).get_output_in_json() + assert result["localDnsProfile"]["mode"] == "Required" + assert_dns_overrides_equal(result["localDnsProfile"]["kubeDnsOverrides"], kubeDnsOverridesExpectedDefault) + assert_dns_overrides_equal(result["localDnsProfile"]["vnetDnsOverrides"], vnetDnsOverridesExpectedDefault) + + # Clean up + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_update_localdns_required_to_localdnsconfig_with_extra_property(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + required_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "localdnsconfig.json") + extra_property_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "localdnsconfig_with_extra_property.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "required_config": required_config_path, + "extra_property_config": extra_property_config_path + }) + + # Create AKS cluster + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" # k8s version > 1.33 to support localDNS + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Add nodepool with required mode localdns config + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={required_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" # k8s version > 1.33 to support localDNS + ) + self.cmd(add_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Verify nodepool has required mode and DNS overrides + show_cmd = ( + "aks nodepool show --resource-group={resource_group} --cluster-name={name} --name={nodepool_name}" + ) + result = self.cmd(show_cmd).get_output_in_json() + assert result["localDnsProfile"]["mode"] == "Required" + assert_dns_overrides_equal(result["localDnsProfile"]["kubeDnsOverrides"], kubeDnsOverridesExpected) + assert_dns_overrides_equal(result["localDnsProfile"]["vnetDnsOverrides"], vnetDnsOverridesExpected) + + # Update nodepool with extra property config + update_cmd = ( + "aks nodepool update --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --localdns-config={extra_property_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + ) + self.cmd(update_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Verify nodepool still has required mode and DNS overrides (extra property should be ignored) + result = self.cmd(show_cmd).get_output_in_json() + assert result["localDnsProfile"]["mode"] == "Required" + assert_dns_overrides_equal(result["localDnsProfile"]["kubeDnsOverrides"], kubeDnsOverridesExpected) + assert_dns_overrides_equal(result["localDnsProfile"]["vnetDnsOverrides"], vnetDnsOverridesExpected) + + # Clean up + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_update_localdns_required_to_localdnsconfig_with_extra_property_in_dnsOverrides(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + required_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "localdnsconfig.json") + extra_property_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "localdnsconfig_with_extra_property_in_dnsOverrides.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "required_config": required_config_path, + "extra_property_config": extra_property_config_path + }) + + # Create AKS cluster + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" # k8s version > 1.33 to support localDNS + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Add nodepool with required mode localdns config + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={required_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" # k8s version > 1.33 to support localDNS + ) + self.cmd(add_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Verify nodepool has required mode and DNS overrides + show_cmd = ( + "aks nodepool show --resource-group={resource_group} --cluster-name={name} --name={nodepool_name}" + ) + result = self.cmd(show_cmd).get_output_in_json() + assert result["localDnsProfile"]["mode"] == "Required" + assert_dns_overrides_equal(result["localDnsProfile"]["kubeDnsOverrides"], kubeDnsOverridesExpected) + assert_dns_overrides_equal(result["localDnsProfile"]["vnetDnsOverrides"], vnetDnsOverridesExpected) + + # Update nodepool with extra property config + update_cmd = ( + "aks nodepool update --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --localdns-config={extra_property_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + ) + self.cmd(update_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + # Verify nodepool still has required mode and DNS overrides (extra property should be ignored) + result = self.cmd(show_cmd).get_output_in_json() + assert result["localDnsProfile"]["mode"] == "Required" + assert_dns_overrides_equal(result["localDnsProfile"]["kubeDnsOverrides"], kubeDnsOverridesExpected) + assert_dns_overrides_equal(result["localDnsProfile"]["vnetDnsOverrides"], vnetDnsOverridesExpected) + + # Clean up + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + @AllowLargeResponse() @AKSCustomResourceGroupPreparer( random_name_length=17, name_prefix="clitest", location="westus2" diff --git a/src/aks-preview/azext_aks_preview/tests/latest/test_aks_invalid_localdns_cases.py b/src/aks-preview/azext_aks_preview/tests/latest/test_aks_invalid_localdns_cases.py new file mode 100644 index 00000000000..1946bcedc78 --- /dev/null +++ b/src/aks-preview/azext_aks_preview/tests/latest/test_aks_invalid_localdns_cases.py @@ -0,0 +1,603 @@ +# -------------------------------------------------------------------------------------------- +# INVALID CASES FOR LOCAL DNS CONFIG TESTS +# These tests cover invalid, error, and edge-case scenarios for local DNS config handling. +# -------------------------------------------------------------------------------------------- + +import os +from .test_localdns_profile import assert_dns_overrides_equal, vnetDnsOverridesExpected, kubeDnsOverridesExpected +from azext_aks_preview.tests.latest.custom_preparers import AKSCustomResourceGroupPreparer +from azure.cli.testsdk.scenario_tests import AllowLargeResponse + +class InvalidLocalDnsConfigTests: + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_update_with_localdns_invalid_mode(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + valid_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "localdnsconfig.json") + invalid_mode_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "invalid_mode.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "valid_config": valid_config_path, + "invalid_mode_config": invalid_mode_config_path + }) + + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={valid_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + self.cmd(add_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + update_cmd = ( + "aks nodepool update --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --localdns-config={invalid_mode_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + ) + try: + self.cmd(update_cmd) + assert False, "Expected failure for invalid mode, but command succeeded." + except Exception as ex: + assert "invalid" in str(ex).lower() or "error" in str(ex).lower() or "mode" in str(ex).lower(), f"Unexpected error: {ex}" + + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_update_with_localdns_empty_config(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + empty_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "empty_config.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "empty_config": empty_config_path + }) + + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={empty_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + self.cmd(add_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + update_cmd = ( + "aks nodepool update --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --localdns-config={empty_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + ) + try: + self.cmd(update_cmd) + assert False, "Expected failure for empty config, but command succeeded." + except Exception as ex: + assert "invalid" in str(ex).lower() or "error" in str(ex).lower() or "config" in str(ex).lower(), f"Unexpected error: {ex}" + + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_update_with_localdns_required_mode_invalid_vnetdns(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + valid_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "localdnsconfig.json") + invalid_vnetdns_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "invalid_vnetdns.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "valid_config": valid_config_path, + "invalid_vnetdns_config": invalid_vnetdns_config_path + }) + + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={valid_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + self.cmd(add_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + update_cmd = ( + "aks nodepool update --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --localdns-config={invalid_vnetdns_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + ) + try: + self.cmd(update_cmd) + assert False, "Expected failure for invalid vnetdns, but command succeeded." + except Exception as ex: + assert "invalid" in str(ex).lower() or "error" in str(ex).lower() or "vnetdns" in str(ex).lower(), f"Unexpected error: {ex}" + + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_update_with_localdns_required_mode_invalid_kubedns(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + valid_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "localdnsconfig.json") + invalid_kubedns_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "invalid_kubedns.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "valid_config": valid_config_path, + "invalid_kubedns_config": invalid_kubedns_config_path + }) + + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={valid_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + self.cmd(add_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + update_cmd = ( + "aks nodepool update --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --localdns-config={invalid_kubedns_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + ) + try: + self.cmd(update_cmd) + assert False, "Expected failure for invalid kubedns, but command succeeded." + except Exception as ex: + assert "invalid" in str(ex).lower() or "error" in str(ex).lower() or "kubedns" in str(ex).lower(), f"Unexpected error: {ex}" + + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_add_with_localdns_missing_mode(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + missing_mode_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "missing_mode.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "missing_mode_config": missing_mode_config_path + }) + + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={missing_mode_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + try: + self.cmd(add_cmd) + assert False, "Expected failure for missing mode, but command succeeded." + except Exception as ex: + assert "invalid" in str(ex).lower() or "error" in str(ex).lower() or "mode" in str(ex).lower(), f"Unexpected error: {ex}" + + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_add_with_localdns_required_mode_empty_overrides(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + empty_overrides_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "required_mode_empty_overrides.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "empty_overrides_config": empty_overrides_config_path + }) + + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={empty_overrides_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + try: + self.cmd(add_cmd) + assert False, "Expected failure for empty overrides in required mode, but command succeeded." + except Exception as ex: + assert "invalid" in str(ex).lower() or "error" in str(ex).lower() or "overrides" in str(ex).lower(), f"Unexpected error: {ex}" + + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_add_with_localdns_required_mode_partial_invalid(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + partial_invalid_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "required_mode_partial_invalid.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "partial_invalid_config": partial_invalid_config_path + }) + + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={partial_invalid_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + try: + self.cmd(add_cmd) + assert False, "Expected failure for partial invalid config in required mode, but command succeeded." + except Exception as ex: + assert "invalid" in str(ex).lower() or "error" in str(ex).lower() or "config" in str(ex).lower(), f"Unexpected error: {ex}" + + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_add_with_localdns_empty_mode(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + empty_mode_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "empty_mode.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "empty_mode_config": empty_mode_config_path + }) + + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={empty_mode_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + try: + self.cmd(add_cmd) + assert False, "Expected failure for empty mode, but command succeeded." + except Exception as ex: + assert "invalid" in str(ex).lower() or "error" in str(ex).lower() or "mode" in str(ex).lower(), f"Unexpected error: {ex}" + + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_add_with_localdns_null_mode(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + null_mode_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "null_mode.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "null_mode_config": null_mode_config_path + }) + + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={null_mode_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + try: + self.cmd(add_cmd) + assert False, "Expected failure for null mode, but command succeeded." + except Exception as ex: + assert "invalid" in str(ex).lower() or "error" in str(ex).lower() or "mode" in str(ex).lower(), f"Unexpected error: {ex}" + + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_add_with_localdns_empty_config(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + empty_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "empty_config.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "empty_config": empty_config_path + }) + + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={empty_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + try: + self.cmd(add_cmd) + assert False, "Expected failure for empty config, but command succeeded." + except Exception as ex: + assert "invalid" in str(ex).lower() or "error" in str(ex).lower() or "config" in str(ex).lower(), f"Unexpected error: {ex}" + + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_add_with_localdns_null_config(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + null_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "null_config.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "null_config": null_config_path + }) + + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={null_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + try: + self.cmd(add_cmd) + assert False, "Expected failure for null config, but command succeeded." + except Exception as ex: + assert "invalid" in str(ex).lower() or "error" in str(ex).lower() or "config" in str(ex).lower(), f"Unexpected error: {ex}" + + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_add_with_localdns_invalid_mode(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + valid_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "localdnsconfig.json") + invalid_mode_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "invalid_mode.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "valid_config": valid_config_path, + "invalid_mode_config": invalid_mode_config_path + }) + + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={valid_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + self.cmd(add_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + update_cmd = ( + "aks nodepool update --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --localdns-config={invalid_mode_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + ) + try: + self.cmd(update_cmd) + assert False, "Expected failure for invalid mode, but command succeeded." + except Exception as ex: + assert "invalid" in str(ex).lower() or "error" in str(ex).lower() or "mode" in str(ex).lower(), f"Unexpected error: {ex}" + + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_add_with_localdns_required_mode_invalid_vnetdns(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + valid_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "localdnsconfig.json") + invalid_vnetdns_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "invalid_vnetdns.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "valid_config": valid_config_path, + "invalid_vnetdns_config": invalid_vnetdns_config_path + }) + + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={valid_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + self.cmd(add_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + update_cmd = ( + "aks nodepool update --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --localdns-config={invalid_vnetdns_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + ) + try: + self.cmd(update_cmd) + assert False, "Expected failure for invalid vnetdns, but command succeeded." + except Exception as ex: + assert "invalid" in str(ex).lower() or "error" in str(ex).lower() or "vnetdns" in str(ex).lower(), f"Unexpected error: {ex}" + + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) + + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix="clitest", location="westus2") + def test_aks_nodepool_add_with_localdns_required_mode_invalid_kubedns(self, resource_group, resource_group_location): + aks_name = self.create_random_name("cliakstest", 16) + nodepool_name = self.create_random_name("np", 6) + valid_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "localdnsconfig.json") + invalid_kubedns_config_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", "localdnsconfig", "invalid_kubedns.json") + self.kwargs.update({ + "resource_group": resource_group, + "name": aks_name, + "nodepool_name": nodepool_name, + "ssh_key_value": self.generate_ssh_keys(), + "valid_config": valid_config_path, + "invalid_kubedns_config": invalid_kubedns_config_path + }) + + create_cmd = ( + "aks create --resource-group={resource_group} --name={name} " + "--node-count 1 --ssh-key-value={ssh_key_value} --generate-ssh-keys " + "--kubernetes-version 1.33.0" + ) + self.cmd(create_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + add_cmd = ( + "aks nodepool add --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --node-count 1 --localdns-config={valid_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + "--kubernetes-version 1.33.0" + ) + self.cmd(add_cmd, checks=[self.check("provisioningState", "Succeeded")]) + + update_cmd = ( + "aks nodepool update --resource-group={resource_group} --cluster-name={name} " + "--name={nodepool_name} --localdns-config={invalid_kubedns_config} " + "--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/LocalDNSPreview " + ) + try: + self.cmd(update_cmd) + assert False, "Expected failure for invalid kubedns, but command succeeded." + except Exception as ex: + assert "invalid" in str(ex).lower() or "error" in str(ex).lower() or "kubedns" in str(ex).lower(), f"Unexpected error: {ex}" + + self.cmd( + "aks delete --resource-group={resource_group} --name={name} --yes --no-wait", + checks=[self.is_empty()], + ) diff --git a/src/aks-preview/azext_aks_preview/tests/latest/test_localdns_profile.py b/src/aks-preview/azext_aks_preview/tests/latest/test_localdns_profile.py index 5f5b8a41c35..c6c1c06d84e 100644 --- a/src/aks-preview/azext_aks_preview/tests/latest/test_localdns_profile.py +++ b/src/aks-preview/azext_aks_preview/tests/latest/test_localdns_profile.py @@ -36,6 +36,29 @@ } } +kubeDnsOverridesExpectedDefault = { + ".": { + "cacheDurationInSeconds": 3600, + "forwardDestination": "ClusterCoreDNS", + "forwardPolicy": "Sequential", + "maxConcurrent": 1000, + "protocol": "PreferUDP", + "queryLogging": "Error", + "serveStale": "Immediate", + "serveStaleDurationInSeconds": 3600 + }, + "cluster.local": { + "cacheDurationInSeconds": 3600, + "forwardDestination": "ClusterCoreDNS", + "forwardPolicy": "Sequential", + "maxConcurrent": 1000, + "protocol": "ForceTCP", + "queryLogging": "Error", + "serveStale": "Immediate", + "serveStaleDurationInSeconds": 3600 + } +} + vnetDnsOverridesExpected = { ".": { "cacheDurationInSeconds": 3600, @@ -59,6 +82,29 @@ } } +vnetDnsOverridesExpectedDefault = { + ".": { + "cacheDurationInSeconds": 3600, + "forwardDestination": "VnetDNS", + "forwardPolicy": "Sequential", + "maxConcurrent": 1000, + "protocol": "PreferUDP", + "queryLogging": "Error", + "serveStale": "Immediate", + "serveStaleDurationInSeconds": 3600 + }, + "cluster.local": { + "cacheDurationInSeconds": 3600, + "forwardDestination": "ClusterCoreDNS", + "forwardPolicy": "Sequential", + "maxConcurrent": 1000, + "protocol": "ForceTCP", + "queryLogging": "Error", + "serveStale": "Immediate", + "serveStaleDurationInSeconds": 3600 + } +} + def assert_dns_overrides_equal(actual, expected): """Assert that all keys and subkeys in expected are present and equal in actual, case-insensitive for keys.""" # Lowercase all keys in actual and expected for comparison