diff --git a/src/k8s-extension/HISTORY.rst b/src/k8s-extension/HISTORY.rst index 58a0fb647e0..6934bf610c9 100644 --- a/src/k8s-extension/HISTORY.rst +++ b/src/k8s-extension/HISTORY.rst @@ -3,6 +3,13 @@ Release History =============== +1.2.0 +++++++++++++++++++ +* microsoft.azureml.kubernetes: Update AzureMLKubernetes install parameters on inferenceRouterServiceType and internalLoadBalancerProvider +* microsoft.openservicemesh: Change extension validation logic osm-arc +* microsoft.azuremonitor.containers: Add Managed Identity Auth support for ContainerInsights Extension +* microsoft.azuremonitor.containers: Bring back containerInsights solution addition in MSI mode + 1.1.0 ++++++++++++++++++ * Migrate Extensions api-version to 2022-03-01 diff --git a/src/k8s-extension/azext_k8s_extension/partner_extensions/AzureMLKubernetes.py b/src/k8s-extension/azext_k8s_extension/partner_extensions/AzureMLKubernetes.py index 6334877f59e..34eb3218de2 100644 --- a/src/k8s-extension/azext_k8s_extension/partner_extensions/AzureMLKubernetes.py +++ b/src/k8s-extension/azext_k8s_extension/partner_extensions/AzureMLKubernetes.py @@ -78,10 +78,12 @@ def __init__(self): self.sslKeyPemFile = 'sslKeyPemFile' self.sslCertPemFile = 'sslCertPemFile' self.allowInsecureConnections = 'allowInsecureConnections' - self.privateEndpointILB = 'privateEndpointILB' - self.privateEndpointNodeport = 'privateEndpointNodeport' - self.inferenceLoadBalancerHA = 'inferenceLoadBalancerHA' self.SSL_SECRET = 'sslSecret' + self.SSL_Cname = 'sslCname' + + self.inferenceRouterServiceType = 'inferenceRouterServiceType' + self.internalLoadBalancerProvider = 'internalLoadBalancerProvider' + self.inferenceLoadBalancerHA = 'inferenceLoadBalancerHA' # constants for existing AKS to AMLARC migration self.IS_AKS_MIGRATION = 'isAKSMigration' @@ -96,12 +98,14 @@ def __init__(self): 'cluster_name': ['clusterId', 'prometheus.prometheusSpec.externalLabels.cluster_name'], } + self.OPEN_SHIFT = 'openshift' + def Create(self, cmd, client, resource_group_name, cluster_name, name, cluster_type, extension_type, scope, auto_upgrade_minor_version, release_train, version, target_namespace, release_namespace, configuration_settings, configuration_protected_settings, configuration_settings_file, configuration_protected_settings_file): if scope == 'namespace': - raise InvalidArgumentValueError("Invalid scope '{}'. This extension can be installed " + raise InvalidArgumentValueError("Invalid scope '{}'. This extension can't be installed " "only at 'cluster' scope.".format(scope)) if not release_namespace: release_namespace = self.DEFAULT_RELEASE_NAMESPACE @@ -122,6 +126,18 @@ def Create(self, cmd, client, resource_group_name, cluster_name, name, cluster_t resource = resources.get_by_id( cluster_resource_id, parent_api_version) cluster_location = resource.location.lower() + try: + if cluster_type.lower() == 'connectedclusters': + if resource.properties['totalNodeCount'] < 3: + configuration_settings['clusterPurpose'] = 'DevTest' + if cluster_type.lower() == 'managedclusters': + nodeCount = 0 + for agent in resource.properties['agentPoolProfiles']: + nodeCount += agent['count'] + if nodeCount < 3: + configuration_settings['clusterPurpose'] = 'DevTest' + except: + pass except CloudError as ex: raise ex @@ -181,8 +197,9 @@ def Update(self, cmd, resource_group_name, cluster_name, auto_upgrade_minor_vers disableInference = False disableNvidiaDevicePlugin = False hasAllowInsecureConnections = False - hasPrivateEndpointNodeport = False - hasPrivateEndpointILB = False + hasInferenceRouterServiceType = False + hasInternalLoadBalancerProvider = False + hasSslCname = False hasNodeSelector = False enableLogAnalyticsWS = False @@ -209,15 +226,20 @@ def Update(self, cmd, resource_group_name, cluster_name, auto_upgrade_minor_vers hasAllowInsecureConnections = True messageBody = messageBody + "allowInsecureConnections\n" - privateEndpointNodeport = _get_value_from_config_protected_config(self.privateEndpointNodeport, configuration_settings, configuration_protected_settings) - if privateEndpointNodeport is not None: - hasPrivateEndpointNodeport = True - messageBody = messageBody + "privateEndpointNodeport\n" + inferenceRouterServiceType = _get_value_from_config_protected_config(self.inferenceRouterServiceType, configuration_settings, configuration_protected_settings) + if inferenceRouterServiceType is not None: + hasInferenceRouterServiceType = True + messageBody = messageBody + "inferenceRouterServiceType\n" + + internalLoadBalancerProvider = _get_value_from_config_protected_config(self.internalLoadBalancerProvider, configuration_settings, configuration_protected_settings) + if internalLoadBalancerProvider is not None: + hasInternalLoadBalancerProvider = True + messageBody = messageBody + "internalLoadBalancerProvider\n" - privateEndpointILB = _get_value_from_config_protected_config(self.privateEndpointILB, configuration_settings, configuration_protected_settings) - if privateEndpointILB is not None: - hasPrivateEndpointILB = True - messageBody = messageBody + "privateEndpointILB\n" + sslCname = _get_value_from_config_protected_config(self.SSL_Cname, configuration_settings, configuration_protected_settings) + if sslCname is not None: + hasSslCname = True + messageBody = messageBody + "sslCname\n" hasNodeSelector = _check_nodeselector_existed(configuration_settings, configuration_protected_settings) if hasNodeSelector: @@ -232,7 +254,7 @@ def Update(self, cmd, resource_group_name, cluster_name, auto_upgrade_minor_vers if disableTraining or disableNvidiaDevicePlugin or hasNodeSelector: impactScenario = "jobs" - if disableInference or disableNvidiaDevicePlugin or hasAllowInsecureConnections or hasPrivateEndpointNodeport or hasPrivateEndpointILB or hasNodeSelector: + if disableInference or disableNvidiaDevicePlugin or hasAllowInsecureConnections or hasInferenceRouterServiceType or hasInternalLoadBalancerProvider or hasNodeSelector or hasSslCname: if impactScenario == "": impactScenario = "online endpoints and deployments" else: @@ -286,7 +308,11 @@ def Update(self, cmd, resource_group_name, cluster_name, auto_upgrade_minor_vers if self.sslKeyPemFile in configuration_protected_settings and \ self.sslCertPemFile in configuration_protected_settings: logger.info(f"Both {self.sslKeyPemFile} and {self.sslCertPemFile} are set, update ssl key.") - self.__set_inference_ssl_from_file(configuration_protected_settings, self.sslCertPemFile, self.sslKeyPemFile) + fe_ssl_cert_file = configuration_protected_settings.get(self.sslCertPemFile) + fe_ssl_key_file = configuration_protected_settings.get(self.sslKeyPemFile) + + if fe_ssl_cert_file and fe_ssl_key_file: + self.__set_inference_ssl_from_file(configuration_protected_settings, fe_ssl_cert_file, fe_ssl_key_file) return PatchExtension(auto_upgrade_minor_version=auto_upgrade_minor_version, release_train=release_train, @@ -305,16 +331,21 @@ def __normalize_config(self, configuration_settings, configuration_protected_set else: configuration_settings['clusterPurpose'] = 'FastProd' - feIsNodePort = _get_value_from_config_protected_config( - self.privateEndpointNodeport, configuration_settings, configuration_protected_settings) - if feIsNodePort is not None: - feIsNodePort = str(feIsNodePort).lower() == 'true' + inferenceRouterServiceType = _get_value_from_config_protected_config( + self.inferenceRouterServiceType, configuration_settings, configuration_protected_settings) + if inferenceRouterServiceType: + if inferenceRouterServiceType.lower() != 'nodeport' and inferenceRouterServiceType.lower() != 'loadbalancer': + raise InvalidArgumentValueError( + "inferenceRouterServiceType only supports nodePort or loadBalancer." + "Check https://aka.ms/arcmltsg for more information.") + + feIsNodePort = str(inferenceRouterServiceType).lower() == 'nodeport' configuration_settings['scoringFe.serviceType.nodePort'] = feIsNodePort - feIsInternalLoadBalancer = _get_value_from_config_protected_config( - self.privateEndpointILB, configuration_settings, configuration_protected_settings) - if feIsInternalLoadBalancer is not None: - feIsInternalLoadBalancer = str(feIsInternalLoadBalancer).lower() == 'true' + internalLoadBalancerProvider = _get_value_from_config_protected_config( + self.internalLoadBalancerProvider, configuration_settings, configuration_protected_settings) + if internalLoadBalancerProvider: + feIsInternalLoadBalancer = str(internalLoadBalancerProvider).lower() == 'azure' configuration_settings['scoringFe.serviceType.internalLoadBalancer'] = feIsInternalLoadBalancer logger.warning( 'Internal load balancer only supported on AKS and AKS Engine Clusters.') @@ -345,7 +376,8 @@ def __validate_config(self, configuration_settings, configuration_protected_sett raise InvalidArgumentValueError( "To create Microsoft.AzureML.Kubernetes extension, either " "enable Machine Learning training or inference by specifying " - f"'--configuration-settings {self.ENABLE_TRAINING}=true' or '--configuration-settings {self.ENABLE_INFERENCE}=true'") + f"'--configuration-settings {self.ENABLE_TRAINING}=true' or '--configuration-settings {self.ENABLE_INFERENCE}=true'." + "Please check https://aka.ms/arcmltsg for more information.") configuration_settings[self.ENABLE_TRAINING] = configuration_settings.get(self.ENABLE_TRAINING, enable_training) configuration_settings[self.ENABLE_INFERENCE] = configuration_settings.get( @@ -378,20 +410,34 @@ def __validate_scoring_fe_settings(self, configuration_settings, configuration_p if not sslEnabled and not allowInsecureConnections: raise InvalidArgumentValueError( "To enable HTTPs endpoint, " - "either provide sslCertPemFile and sslKeyPemFile to config protected settings, " - f"or provide sslSecret (kubernetes secret name) containing both ssl cert and ssl key under {release_namespace} namespace. " + "either provide sslCertPemFile and sslKeyPemFile to --configuration-protected-settings, " + f"or provide sslSecret(kubernetes secret name) in --configuration-settings containing both ssl cert and ssl key under {release_namespace} namespace. " "Otherwise, to enable HTTP endpoint, explicitly set allowInsecureConnections=true.") - feIsNodePort = _get_value_from_config_protected_config( - self.privateEndpointNodeport, configuration_settings, configuration_protected_settings) - feIsNodePort = str(feIsNodePort).lower() == 'true' - feIsInternalLoadBalancer = _get_value_from_config_protected_config( - self.privateEndpointILB, configuration_settings, configuration_protected_settings) - feIsInternalLoadBalancer = str(feIsInternalLoadBalancer).lower() == 'true' + if sslEnabled: + sslCname = _get_value_from_config_protected_config( + self.SSL_Cname, configuration_settings, configuration_protected_settings) + if not sslCname: + raise InvalidArgumentValueError( + "To enable HTTPs endpoint, " + "please specify sslCname parameter in --configuration-settings. Check https://aka.ms/arcmltsg for more information.") + + inferenceRouterServiceType = _get_value_from_config_protected_config( + self.inferenceRouterServiceType, configuration_settings, configuration_protected_settings) + if not inferenceRouterServiceType or (inferenceRouterServiceType.lower() != 'nodeport' and inferenceRouterServiceType.lower() != 'loadbalancer'): + raise InvalidArgumentValueError( + "To use inference, " + "please specify inferenceRouterServiceType=nodePort or inferenceRouterServiceType=loadBalancer in --configuration-settings and also set internalLoadBalancerProvider=azure if your aks only supports internal load balancer." + "Check https://aka.ms/arcmltsg for more information.") + + feIsNodePort = str(inferenceRouterServiceType).lower() == 'nodeport' + internalLoadBalancerProvider = _get_value_from_config_protected_config( + self.internalLoadBalancerProvider, configuration_settings, configuration_protected_settings) + feIsInternalLoadBalancer = str(internalLoadBalancerProvider).lower() == 'azure' if feIsNodePort and feIsInternalLoadBalancer: raise MutuallyExclusiveArgumentError( - "Specify either privateEndpointNodeport=true or privateEndpointILB=true, but not both.") + "When using nodePort as inferenceRouterServiceType, no need to specify internalLoadBalancerProvider.") if feIsNodePort: configuration_settings['scoringFe.serviceType.nodePort'] = feIsNodePort elif feIsInternalLoadBalancer: diff --git a/src/k8s-extension/azext_k8s_extension/partner_extensions/ContainerInsights.py b/src/k8s-extension/azext_k8s_extension/partner_extensions/ContainerInsights.py index f152b2e2ca8..b2eecf32f89 100644 --- a/src/k8s-extension/azext_k8s_extension/partner_extensions/ContainerInsights.py +++ b/src/k8s-extension/azext_k8s_extension/partner_extensions/ContainerInsights.py @@ -8,13 +8,16 @@ import datetime import json +from ..utils import get_cluster_rp_api_version + from knack.log import get_logger -from azure.cli.core.azclierror import InvalidArgumentValueError +from azure.cli.core.azclierror import AzCLIError, CLIError, InvalidArgumentValueError, ClientRequestError from azure.cli.core.commands import LongRunningOperation from azure.cli.core.commands.client_factory import get_mgmt_service_client, get_subscription_id -from azure.cli.core.util import sdk_no_wait +from azure.cli.core.util import sdk_no_wait, send_raw_request from msrestazure.tools import parse_resource_id, is_valid_resource_id +from azure.core.exceptions import HttpResponseError from ..vendored_sdks.models import Extension from ..vendored_sdks.models import ScopeCluster @@ -33,7 +36,6 @@ def Create(self, cmd, client, resource_group_name, cluster_name, name, cluster_t scope, auto_upgrade_minor_version, release_train, version, target_namespace, release_namespace, configuration_settings, configuration_protected_settings, configuration_settings_file, configuration_protected_settings_file): - """ExtensionType 'microsoft.azuremonitor.containers' specific validations & defaults for Create Must create and return a valid 'Extension' object. @@ -70,6 +72,48 @@ def Create(self, cmd, client, resource_group_name, cluster_name, name, cluster_t ) return extension, name, create_identity + def Delete(self, cmd, client, resource_group_name, cluster_name, name, cluster_type, yes): + # Delete DCR-A if it exists incase of MSI Auth + useAADAuth = False + isDCRAExists = False + cluster_rp, _ = get_cluster_rp_api_version(cluster_type) + try: + extension = client.get(resource_group_name, cluster_rp, cluster_type, cluster_name, name) + except Exception: + pass # its OK to ignore the exception since MSI auth in preview + + subscription_id = get_subscription_id(cmd.cli_ctx) + # handle cluster type here + cluster_resource_id = '/subscriptions/{0}/resourceGroups/{1}/providers/{2}/{3}/{4}'.format(subscription_id, resource_group_name, cluster_rp, cluster_type, cluster_name) + if (extension is not None) and (extension.configuration_settings is not None): + configSettings = extension.configuration_settings + if 'omsagent.useAADAuth' in configSettings: + useAADAuthSetting = configSettings['omsagent.useAADAuth'] + if (isinstance(useAADAuthSetting, str) and str(useAADAuthSetting).lower() == "true") or (isinstance(useAADAuthSetting, bool) and useAADAuthSetting): + useAADAuth = True + if useAADAuth: + association_url = cmd.cli_ctx.cloud.endpoints.resource_manager + f"{cluster_resource_id}/providers/Microsoft.Insights/dataCollectionRuleAssociations/ContainerInsightsExtension?api-version=2019-11-01-preview" + for _ in range(3): + try: + send_raw_request(cmd.cli_ctx, "GET", association_url,) + isDCRAExists = True + break + except HttpResponseError as ex: + # Customize the error message for resources not found + if ex.response.status_code == 404: + isDCRAExists = False + except Exception: + pass # its OK to ignore the exception since MSI auth in preview + + if isDCRAExists: + association_url = cmd.cli_ctx.cloud.endpoints.resource_manager + f"{cluster_resource_id}/providers/Microsoft.Insights/dataCollectionRuleAssociations/ContainerInsightsExtension?api-version=2019-11-01-preview" + for _ in range(3): + try: + send_raw_request(cmd.cli_ctx, "DELETE", association_url,) + break + except Exception: + pass # its OK to ignore the exception since MSI auth in preview + # Custom Validation Logic for Container Insights @@ -159,7 +203,8 @@ def _ensure_default_log_analytics_workspace_for_monitoring(cmd, subscription_id, "westeurope": "westeurope", "westindia": "centralindia", "westus": "westus", - "westus2": "westus2" + "westus2": "westus2", + "eastus2euap": "eastus2euap" } # mapping for azure china cloud @@ -271,6 +316,28 @@ def _ensure_default_log_analytics_workspace_for_monitoring(cmd, subscription_id, return ws_resource_id +def _is_container_insights_solution_exists(cmd, workspace_resource_id): + # extract subscription ID and resource group from workspace_resource_id URL + is_exists = False + _MAX_RETRY_TIMES = 3 + parsed = parse_resource_id(workspace_resource_id) + subscription_id, resource_group, workspace_name = parsed["subscription"], parsed["resource_group"], parsed["name"] + solution_resource_id = "/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.OperationsManagement/solutions/ContainerInsights({2})".format(subscription_id, resource_group, workspace_name) + resources = cf_resources(cmd.cli_ctx, subscription_id) + for retry_count in range(0, _MAX_RETRY_TIMES): + try: + resources.get_by_id(solution_resource_id, '2015-11-01-preview') + is_exists = True + break + except HttpResponseError as ex: + if ex.status_code == 404: + is_exists = False + break + if retry_count >= (_MAX_RETRY_TIMES - 1): + raise ex + return is_exists + + def _ensure_container_insights_for_monitoring(cmd, workspace_resource_id): # extract subscription ID and resource group from workspace_resource_id URL parsed = parse_resource_id(workspace_resource_id) @@ -376,6 +443,7 @@ def _get_container_insights_settings(cmd, cluster_resource_group_name, cluster_n subscription_id = get_subscription_id(cmd.cli_ctx) workspace_resource_id = '' + useAADAuth = False if configuration_settings is not None: if 'loganalyticsworkspaceresourceid' in configuration_settings: @@ -385,6 +453,12 @@ def _get_container_insights_settings(cmd, cluster_resource_group_name, cluster_n if 'logAnalyticsWorkspaceResourceID' in configuration_settings: workspace_resource_id = configuration_settings['logAnalyticsWorkspaceResourceID'] + if 'omsagent.useAADAuth' in configuration_settings: + useAADAuthSetting = configuration_settings['omsagent.useAADAuth'] + logger.info("provided useAADAuth flag is : %s", useAADAuthSetting) + if (isinstance(useAADAuthSetting, str) and str(useAADAuthSetting).lower() == "true") or (isinstance(useAADAuthSetting, bool) and useAADAuthSetting): + useAADAuth = True + workspace_resource_id = workspace_resource_id.strip() if configuration_protected_settings is not None: @@ -409,7 +483,12 @@ def _get_container_insights_settings(cmd, cluster_resource_group_name, cluster_n raise InvalidArgumentValueError('{} is not a valid Azure resource ID.'.format(workspace_resource_id)) if is_ci_extension_type: - _ensure_container_insights_for_monitoring(cmd, workspace_resource_id).result() + if not _is_container_insights_solution_exists(cmd, workspace_resource_id): + logger.info("creating containerinsights solution resource since it doesnt exist") + _ensure_container_insights_for_monitoring(cmd, workspace_resource_id).result() + if useAADAuth: + logger.info("creating data collection rule and association") + _ensure_container_insights_dcr_for_monitoring(cmd, subscription_id, cluster_resource_group_name, cluster_name, workspace_resource_id) # extract subscription ID and resource group from workspace_resource_id URL parsed = parse_resource_id(workspace_resource_id) @@ -440,3 +519,188 @@ def _get_container_insights_settings(cmd, cluster_resource_group_name, cluster_n configuration_settings['omsagent.domain'] = 'opinsights.azure.eaglex.ic.gov' elif cloud_name.lower() == 'ussec': configuration_settings['omsagent.domain'] = 'opinsights.azure.microsoft.scloud' + + +def get_existing_container_insights_extension_dcr_tags(cmd, dcr_url): + tags = {} + _MAX_RETRY_TIMES = 3 + for retry_count in range(0, _MAX_RETRY_TIMES): + try: + resp = send_raw_request( + cmd.cli_ctx, "GET", dcr_url + ) + json_response = json.loads(resp.text) + if ("tags" in json_response) and (json_response["tags"] is not None): + tags = json_response["tags"] + break + except CLIError as e: + if "ResourceNotFound" in str(e): + break + if retry_count >= (_MAX_RETRY_TIMES - 1): + raise e + return tags + + +def _ensure_container_insights_dcr_for_monitoring(cmd, subscription_id, cluster_resource_group_name, cluster_name, workspace_resource_id): + from azure.core.exceptions import HttpResponseError + + cluster_region = '' + resources = cf_resources(cmd.cli_ctx, subscription_id) + cluster_resource_id = '/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Kubernetes' \ + '/connectedClusters/{2}'.format(subscription_id, cluster_resource_group_name, cluster_name) + try: + resource = resources.get_by_id(cluster_resource_id, '2020-01-01-preview') + cluster_region = resource.location.lower() + except HttpResponseError as ex: + raise ex + + # extract subscription ID and resource group from workspace_resource_id URL + parsed = parse_resource_id(workspace_resource_id) + workspace_subscription_id, workspace_resource_group = parsed["subscription"], parsed["resource_group"] + workspace_region = '' + resources = cf_resources(cmd.cli_ctx, workspace_subscription_id) + try: + resource = resources.get_by_id(workspace_resource_id, '2015-11-01-preview') + workspace_region = resource.location + except HttpResponseError as ex: + raise ex + + dataCollectionRuleName = f"MSCI-{cluster_name}-{cluster_region}" + dcr_resource_id = f"/subscriptions/{workspace_subscription_id}/resourceGroups/{workspace_resource_group}/providers/Microsoft.Insights/dataCollectionRules/{dataCollectionRuleName}" + + # first get the association between region display names and region IDs (because for some reason + # the "which RPs are available in which regions" check returns region display names) + region_names_to_id = {} + # retry the request up to two times + for _ in range(3): + try: + location_list_url = cmd.cli_ctx.cloud.endpoints.resource_manager + f"/subscriptions/{subscription_id}/locations?api-version=2019-11-01" + r = send_raw_request(cmd.cli_ctx, "GET", location_list_url) + # this is required to fool the static analyzer. The else statement will only run if an exception + # is thrown, but flake8 will complain that e is undefined if we don't also define it here. + error = None + break + except AzCLIError as e: + error = e + else: + # This will run if the above for loop was not broken out of. This means all three requests failed + raise error + json_response = json.loads(r.text) + for region_data in json_response["value"]: + region_names_to_id[region_data["displayName"]] = region_data["name"] + + # check if region supports DCR and DCR-A + for _ in range(3): + try: + feature_check_url = cmd.cli_ctx.cloud.endpoints.resource_manager + f"/subscriptions/{subscription_id}/providers/Microsoft.Insights?api-version=2020-10-01" + r = send_raw_request(cmd.cli_ctx, "GET", feature_check_url) + error = None + break + except AzCLIError as e: + error = e + else: + raise error + + json_response = json.loads(r.text) + for resource in json_response["resourceTypes"]: + if (resource["resourceType"].lower() == "datacollectionrules"): + region_ids = map(lambda x: region_names_to_id[x], resource["locations"]) # dcr supported regions + if (workspace_region not in region_ids): + raise ClientRequestError(f"Data Collection Rules are not supported for LA workspace region {workspace_region}") + if (resource["resourceType"].lower() == "datacollectionruleassociations"): + region_ids = map(lambda x: region_names_to_id[x], resource["locations"]) # dcr-a supported regions + if (cluster_region not in region_ids): + raise ClientRequestError(f"Data Collection Rule Associations are not supported for cluster region {cluster_region}") + + dcr_url = cmd.cli_ctx.cloud.endpoints.resource_manager + f"{dcr_resource_id}?api-version=2019-11-01-preview" + # get existing tags on the container insights extension DCR if the customer added any + existing_tags = get_existing_container_insights_extension_dcr_tags(cmd, dcr_url) + + # create the DCR + dcr_creation_body = json.dumps( + { + "location": workspace_region, + "tags": existing_tags, + "properties": { + "dataSources": { + "extensions": [ + { + "name": "ContainerInsightsExtension", + "streams": [ + "Microsoft-Perf", + "Microsoft-ContainerInventory", + "Microsoft-ContainerLog", + "Microsoft-ContainerLogV2", + "Microsoft-ContainerNodeInventory", + "Microsoft-KubeEvents", + "Microsoft-KubeMonAgentEvents", + "Microsoft-KubeNodeInventory", + "Microsoft-KubePodInventory", + "Microsoft-KubePVInventory", + "Microsoft-KubeServices", + "Microsoft-InsightsMetrics", + ], + "extensionName": "ContainerInsights", + } + ] + }, + "dataFlows": [ + { + "streams": [ + "Microsoft-Perf", + "Microsoft-ContainerInventory", + "Microsoft-ContainerLog", + "Microsoft-ContainerLogV2", + "Microsoft-ContainerNodeInventory", + "Microsoft-KubeEvents", + "Microsoft-KubeMonAgentEvents", + "Microsoft-KubeNodeInventory", + "Microsoft-KubePodInventory", + "Microsoft-KubePVInventory", + "Microsoft-KubeServices", + "Microsoft-InsightsMetrics", + ], + "destinations": ["la-workspace"], + } + ], + "destinations": { + "logAnalytics": [ + { + "workspaceResourceId": workspace_resource_id, + "name": "la-workspace", + } + ] + }, + }, + } + ) + + for _ in range(3): + try: + send_raw_request(cmd.cli_ctx, "PUT", dcr_url, body=dcr_creation_body) + error = None + break + except AzCLIError as e: + error = e + else: + raise error + + association_body = json.dumps( + { + "location": cluster_region, + "properties": { + "dataCollectionRuleId": dcr_resource_id, + "description": "routes monitoring data to a Log Analytics workspace", + }, + } + ) + association_url = cmd.cli_ctx.cloud.endpoints.resource_manager + f"{cluster_resource_id}/providers/Microsoft.Insights/dataCollectionRuleAssociations/ContainerInsightsExtension?api-version=2019-11-01-preview" + for _ in range(3): + try: + send_raw_request(cmd.cli_ctx, "PUT", association_url, body=association_body,) + error = None + break + except AzCLIError as e: + error = e + else: + raise error diff --git a/src/k8s-extension/azext_k8s_extension/partner_extensions/OpenServiceMesh.py b/src/k8s-extension/azext_k8s_extension/partner_extensions/OpenServiceMesh.py index f3ad45bb177..b1ad6694530 100644 --- a/src/k8s-extension/azext_k8s_extension/partner_extensions/OpenServiceMesh.py +++ b/src/k8s-extension/azext_k8s_extension/partner_extensions/OpenServiceMesh.py @@ -7,6 +7,7 @@ # pylint: disable=redefined-outer-name # pylint: disable=no-member +import json from knack.log import get_logger from azure.cli.core.azclierror import InvalidArgumentValueError @@ -51,7 +52,7 @@ def Create(self, cmd, client, resource_group_name, cluster_name, name, cluster_t # NOTE-2: Return a valid Extension object, Instance name and flag for Identity create_identity = True - _validate_tested_distro(cmd, resource_group_name, cluster_name, version) + _validate_tested_distro(cmd, resource_group_name, cluster_name, version, release_train) extension = Extension( extension_type=extension_type, @@ -67,15 +68,12 @@ def Create(self, cmd, client, resource_group_name, cluster_name, name, cluster_t return extension, name, create_identity -def _validate_tested_distro(cmd, cluster_resource_group_name, cluster_name, extension_version): +def _validate_tested_distro(cmd, cluster_resource_group_name, cluster_name, extension_version, extension_release_train): field_unavailable_error = '\"testedDistros\" field unavailable for version {0} of microsoft.openservicemesh, ' \ 'cannot determine if this Kubernetes distribution has been properly tested'.format(extension_version) - logger.debug('Input version: %s', version) - if version.parse(str(extension_version)) <= version.parse("0.8.3"): - logger.warning(field_unavailable_error) - return + logger.debug('Input version: %s', extension_version) subscription_id = get_subscription_id(cmd.cli_ctx) resources = cf_resources(cmd.cli_ctx, subscription_id) @@ -84,8 +82,30 @@ def _validate_tested_distro(cmd, cluster_resource_group_name, cluster_name, exte '/connectedClusters/{2}'.format(subscription_id, cluster_resource_group_name, cluster_name) resource = resources.get_by_id(cluster_resource_id, '2021-10-01') + cluster_location = resource.location cluster_distro = resource.properties['distribution'].lower() + if extension_version is None and extension_release_train != "staging": + if str(cluster_location) == "eastus2euap": + ring = "canary" + else: + ring = "batch1" + + if extension_release_train is None: + extension_release_train = "stable" + + req_url = 'https://mcr.microsoft.com/v2/oss/openservicemesh/{0}/{1}/osm-arc/tags/list'\ + .format(ring, extension_release_train) + req = requests.get(url=req_url) + req_json = json.loads(req.text) + tags = req_json['tags'] + + extension_version = tags[len(tags) - 1] + + if version.parse(str(extension_version)) <= version.parse("0.8.3"): + logger.warning(field_unavailable_error) + return + if cluster_distro == "general": logger.warning('Unable to determine if distro has been tested for microsoft.openservicemesh, ' 'kubernetes distro: \"general\"') diff --git a/src/k8s-extension/azext_k8s_extension/tests/latest/test_open_service_mesh.py b/src/k8s-extension/azext_k8s_extension/tests/latest/test_open_service_mesh.py index b087be716d8..a27e1adcef9 100644 --- a/src/k8s-extension/azext_k8s_extension/tests/latest/test_open_service_mesh.py +++ b/src/k8s-extension/azext_k8s_extension/tests/latest/test_open_service_mesh.py @@ -5,7 +5,6 @@ # pylint: disable=protected-access -import os import unittest from azure.cli.core.azclierror import InvalidArgumentValueError diff --git a/src/k8s-extension/setup.py b/src/k8s-extension/setup.py index c1ca69b66fa..24c14f3cc99 100644 --- a/src/k8s-extension/setup.py +++ b/src/k8s-extension/setup.py @@ -33,7 +33,7 @@ # TODO: Add any additional SDK dependencies here DEPENDENCIES = [] -VERSION = "1.1.0" +VERSION = "1.2.0" with open("README.rst", "r", encoding="utf-8") as f: README = f.read()