diff --git a/src/aks-preview/HISTORY.rst b/src/aks-preview/HISTORY.rst index cf28110468a..f9073cd3065 100644 --- a/src/aks-preview/HISTORY.rst +++ b/src/aks-preview/HISTORY.rst @@ -12,6 +12,12 @@ To release a new version, please select a new version number (usually plus 1 to Pending +++++++ +21.0.0b15 ++++++++++ +* `az aks create` and `az aks update`: Add `--opentelemetry-metrics-port-grpc` and `--opentelemetry-logs-traces-port-grpc` (preview) to configure the gRPC ports for OpenTelemetry metrics and logs/traces collection, in addition to the existing HTTP/protobuf ports. +* `az aks create` and `az aks update`: Rename the OpenTelemetry parameters, keeping the previous names as deprecated aliases: `--opentelemetry-metrics-port` -> `--opentelemetry-metrics-port-http`, `--opentelemetry-logs-port` -> `--opentelemetry-logs-traces-port-http`, `--enable-opentelemetry-logs` -> `--enable-opentelemetry-logs-traces`, `--disable-opentelemetry-logs` -> `--disable-opentelemetry-logs-traces`. +* `az aks update`: Fix the OpenTelemetry logs and traces parameters (`--enable-opentelemetry-logs-traces`, `--disable-opentelemetry-logs-traces`, `--opentelemetry-logs-traces-port-http`, `--opentelemetry-logs-traces-port-grpc`) incorrectly triggering Azure Monitor Workspace (Prometheus) onboarding. Only Azure Monitor metrics and OpenTelemetry metrics parameters do so now. + 21.0.0b14 +++++++++ * Skip SSH key generation/validation for `az aks create --sku automatic` so `--no-ssh-key` is no longer required for Automatic clusters. @@ -31,7 +37,7 @@ Pending * Vendor new SDK and bump API version to `2026-05-02-preview`. 21.0.0b10 -++++++++ ++++++++++ * `az aks create`: Add parameters `--system-node-subnet-id`, `--node-subnet-id` and `--enable-hosted-system` to support BYO VNet for Automatic Managed System Pool clusters. 21.0.0b9 diff --git a/src/aks-preview/azext_aks_preview/_help.py b/src/aks-preview/azext_aks_preview/_help.py index 1311d808081..582ba94f0c6 100644 --- a/src/aks-preview/azext_aks_preview/_help.py +++ b/src/aks-preview/azext_aks_preview/_help.py @@ -630,21 +630,27 @@ - name: --enable-opentelemetry-metrics type: bool short-summary: Enable OpenTelemetry metrics collection. Requires Azure Monitor metrics to be enabled. - - name: --opentelemetry-metrics-port + - name: --opentelemetry-metrics-port-http type: int - short-summary: Port for OpenTelemetry metrics collection (default port will be used if not specified) + short-summary: HTTP/protobuf port for OpenTelemetry metrics collection (default port will be used if not specified) + - name: --opentelemetry-metrics-port-grpc + type: int + short-summary: gRPC port for OpenTelemetry metrics collection (default port will be used if not specified) - name: --disable-opentelemetry-metrics type: bool short-summary: Disable OpenTelemetry metrics collection - - name: --enable-opentelemetry-logs + - name: --enable-opentelemetry-logs-traces type: bool - short-summary: Enable OpenTelemetry logs collection. Requires Azure Monitor logs to be enabled. - - name: --opentelemetry-logs-port + short-summary: Enable OpenTelemetry logs and traces collection. Requires Azure Monitor logs to be enabled. + - name: --opentelemetry-logs-traces-port-http + type: int + short-summary: HTTP/protobuf port for OpenTelemetry logs and traces collection (default port will be used if not specified) + - name: --opentelemetry-logs-traces-port-grpc type: int - short-summary: Port for OpenTelemetry logs collection (default port will be used if not specified) - - name: --disable-opentelemetry-logs + short-summary: gRPC port for OpenTelemetry logs and traces collection (default port will be used if not specified) + - name: --disable-opentelemetry-logs-traces type: bool - short-summary: Disable OpenTelemetry logs collection + short-summary: Disable OpenTelemetry logs and traces collection - name: --nodepool-labels type: string short-summary: The node labels for all node pools in this cluster. See https://aka.ms/node-labels for syntax of labels. @@ -839,19 +845,21 @@ - name: Create a kubernetes cluster with Azure Service Mesh enabled. text: az aks create -g MyResourceGroup -n MyManagedCluster --enable-azure-service-mesh - name: Create a kubernetes cluster with Azure Monitor Metrics enabled. - text: az aks create -g MyResourceGroup -n MyManagedCluster --enable-azuremonitormetrics + text: az aks create -g MyResourceGroup -n MyManagedCluster --enable-azure-monitor-metrics - name: Create a kubernetes cluster with Azure Monitor App Monitoring enabled text: az aks create -g MyResourceGroup -n MyManagedCluster --enable-azure-monitor-app-monitoring - name: Create a kubernetes cluster with OpenTelemetry metrics collection enabled - text: az aks create -g MyResourceGroup -n MyManagedCluster --enable-opentelemetry-metrics --enable-azuremonitormetrics - - name: Create a kubernetes cluster with OpenTelemetry logs collection enabled - text: az aks create -g MyResourceGroup -n MyManagedCluster --enable-opentelemetry-logs --enable-addons monitoring + text: az aks create -g MyResourceGroup -n MyManagedCluster --enable-opentelemetry-metrics --enable-azure-monitor-metrics + - name: Create a kubernetes cluster with OpenTelemetry logs and traces collection enabled + text: az aks create -g MyResourceGroup -n MyManagedCluster --enable-opentelemetry-logs-traces --enable-addons monitoring - name: Create a kubernetes cluster with Azure Monitor logs enabled (shorthand) text: az aks create -g MyResourceGroup -n MyManagedCluster --enable-azure-monitor-logs - name: Create a kubernetes cluster with OpenTelemetry metrics on custom port - text: az aks create -g MyResourceGroup -n MyManagedCluster --enable-opentelemetry-metrics --opentelemetry-metrics-port 8888 --enable-azuremonitormetrics - - name: Create a kubernetes cluster with OpenTelemetry logs on custom port - text: az aks create -g MyResourceGroup -n MyManagedCluster --enable-opentelemetry-logs --opentelemetry-logs-port 4317 --enable-azure-monitor-logs + text: az aks create -g MyResourceGroup -n MyManagedCluster --enable-opentelemetry-metrics --opentelemetry-metrics-port-http 8888 --enable-azure-monitor-metrics + - name: Create a kubernetes cluster with OpenTelemetry logs and traces on custom ports + text: az aks create -g MyResourceGroup -n MyManagedCluster --enable-opentelemetry-logs-traces --opentelemetry-logs-traces-port-http 8080 --opentelemetry-logs-traces-port-grpc 8082 --enable-azure-monitor-logs + - name: Create a kubernetes cluster with OpenTelemetry metrics on custom HTTP and gRPC ports + text: az aks create -g MyResourceGroup -n MyManagedCluster --enable-opentelemetry-metrics --opentelemetry-metrics-port-http 8888 --opentelemetry-metrics-port-grpc 8889 --enable-azure-monitor-metrics - name: Create a kubernetes cluster with a nodepool having ip allocation mode set to "StaticBlock" text: az aks create -g MyResourceGroup -n MyManagedCluster --os-sku Ubuntu --max-pods MaxPodsPerNode --network-plugin azure --vnet-subnet-id /subscriptions/00000/resourceGroups/AnotherResourceGroup/providers/Microsoft.Network/virtualNetworks/MyVnet/subnets/NodeSubnet --pod-subnet-id /subscriptions/00000/resourceGroups/AnotherResourceGroup/providers/Microsoft.Network/virtualNetworks/MyVnet/subnets/PodSubnet --pod-ip-allocation-mode StaticBlock - name: Create a kubernetes cluster with a VirtualMachines nodepool @@ -1398,21 +1406,27 @@ - name: --enable-opentelemetry-metrics type: bool short-summary: Enable OpenTelemetry metrics collection. Requires Azure Monitor metrics to be enabled. - - name: --opentelemetry-metrics-port + - name: --opentelemetry-metrics-port-http type: int - short-summary: Port for OpenTelemetry metrics collection (default port will be used if not specified) + short-summary: HTTP/protobuf port for OpenTelemetry metrics collection (default port will be used if not specified) + - name: --opentelemetry-metrics-port-grpc + type: int + short-summary: gRPC port for OpenTelemetry metrics collection (default port will be used if not specified) - name: --disable-opentelemetry-metrics type: bool short-summary: Disable OpenTelemetry metrics collection - - name: --enable-opentelemetry-logs + - name: --enable-opentelemetry-logs-traces type: bool - short-summary: Enable OpenTelemetry logs collection. Requires Azure Monitor logs to be enabled. - - name: --opentelemetry-logs-port + short-summary: Enable OpenTelemetry logs and traces collection. Requires Azure Monitor logs to be enabled. + - name: --opentelemetry-logs-traces-port-http + type: int + short-summary: HTTP/protobuf port for OpenTelemetry logs and traces collection (default port will be used if not specified) + - name: --opentelemetry-logs-traces-port-grpc type: int - short-summary: Port for OpenTelemetry logs collection (default port will be used if not specified) - - name: --disable-opentelemetry-logs + short-summary: gRPC port for OpenTelemetry logs and traces collection (default port will be used if not specified) + - name: --disable-opentelemetry-logs-traces type: bool - short-summary: Disable OpenTelemetry logs collection + short-summary: Disable OpenTelemetry logs and traces collection - name: --enable-private-cluster type: bool short-summary: Enable private cluster for apiserver vnet integration cluster. @@ -1654,16 +1668,18 @@ text: az aks update -g MyResourceGroup -n MyManagedCluster --disable-gateway-api - name: Enable OpenTelemetry metrics collection on an existing cluster text: az aks update -g MyResourceGroup -n MyManagedCluster --enable-opentelemetry-metrics - - name: Enable OpenTelemetry logs collection on an existing cluster - text: az aks update -g MyResourceGroup -n MyManagedCluster --enable-opentelemetry-logs + - name: Enable OpenTelemetry logs and traces collection on an existing cluster + text: az aks update -g MyResourceGroup -n MyManagedCluster --enable-opentelemetry-logs-traces - name: Configure OpenTelemetry metrics with custom port - text: az aks update -g MyResourceGroup -n MyManagedCluster --enable-opentelemetry-metrics --opentelemetry-metrics-port 8888 - - name: Configure OpenTelemetry logs with custom port - text: az aks update -g MyResourceGroup -n MyManagedCluster --enable-opentelemetry-logs --opentelemetry-logs-port 4317 + text: az aks update -g MyResourceGroup -n MyManagedCluster --enable-opentelemetry-metrics --opentelemetry-metrics-port-http 8888 + - name: Configure OpenTelemetry logs and traces with custom ports + text: az aks update -g MyResourceGroup -n MyManagedCluster --enable-opentelemetry-logs-traces --opentelemetry-logs-traces-port-http 8080 --opentelemetry-logs-traces-port-grpc 8082 + - name: Configure OpenTelemetry metrics with custom HTTP and gRPC ports + text: az aks update -g MyResourceGroup -n MyManagedCluster --enable-opentelemetry-metrics --opentelemetry-metrics-port-http 8888 --opentelemetry-metrics-port-grpc 8889 - name: Disable OpenTelemetry metrics collection on an existing cluster text: az aks update -g MyResourceGroup -n MyManagedCluster --disable-opentelemetry-metrics - - name: Disable OpenTelemetry logs collection on an existing cluster - text: az aks update -g MyResourceGroup -n MyManagedCluster --disable-opentelemetry-logs + - name: Disable OpenTelemetry logs and traces collection on an existing cluster + text: az aks update -g MyResourceGroup -n MyManagedCluster --disable-opentelemetry-logs-traces """ helps['aks kollect'] = """ diff --git a/src/aks-preview/azext_aks_preview/_params.py b/src/aks-preview/azext_aks_preview/_params.py index 686df5fadf9..507d033735c 100644 --- a/src/aks-preview/azext_aks_preview/_params.py +++ b/src/aks-preview/azext_aks_preview/_params.py @@ -285,6 +285,34 @@ ) from knack.arguments import CLIArgumentType +from knack.deprecation import Deprecated + + +class _SizedDeprecated(Deprecated): + """A Deprecated option that reports a length. + + knack computes the preview target with sorted(options_list, key=len). A plain Deprecated + has no __len__, so combining is_preview=True with a deprecated option name raises + TypeError. Reporting the length of the option name keeps both status tags usable and + makes the longest (current) option name the preview target. + """ + + def __len__(self): + return len(self.target) + + +def _deprecate_option(c, target, redirect): + """Deprecate a single option name and keep it compatible with is_preview. + + Builds the Deprecated object through c.deprecate() so that the message, the object type + and the applicability checks stay identical to every other deprecated option, then only + adds the __len__ behaviour that knack needs for the preview tag. + """ + deprecated = c.deprecate(target=target, redirect=redirect) + if deprecated is not None: + deprecated.__class__ = _SizedDeprecated + return deprecated + # candidates for enumeration # consts for AgentPool @@ -1164,9 +1192,19 @@ def load_arguments(self, _): validator=validate_azure_monitor_and_opentelemetry_for_create ) c.argument("opentelemetry_metrics_port", + options_list=[ + "--opentelemetry-metrics-port-http", + _deprecate_option(c, "--opentelemetry-metrics-port", "--opentelemetry-metrics-port-http"), + ], + is_preview=True, + type=int, + help="HTTP/protobuf port for OpenTelemetry metrics collection" + ) + c.argument("opentelemetry_metrics_port_grpc", + options_list=["--opentelemetry-metrics-port-grpc"], is_preview=True, type=int, - help="Port for OpenTelemetry metrics collection" + help="gRPC port for OpenTelemetry metrics collection" ) c.argument("disable_opentelemetry_metrics", is_preview=True, @@ -1174,20 +1212,37 @@ def load_arguments(self, _): help="Disable OpenTelemetry metrics collection" ) c.argument("enable_opentelemetry_logs", - options_list=["--enable-opentelemetry-logs"], + options_list=[ + "--enable-opentelemetry-logs-traces", + _deprecate_option(c, "--enable-opentelemetry-logs", "--enable-opentelemetry-logs-traces"), + ], is_preview=True, action="store_true", - help="Enable OpenTelemetry logs collection" + help="Enable OpenTelemetry logs and traces collection" ) c.argument("opentelemetry_logs_port", + options_list=[ + "--opentelemetry-logs-traces-port-http", + _deprecate_option(c, "--opentelemetry-logs-port", "--opentelemetry-logs-traces-port-http"), + ], is_preview=True, type=int, - help="Port for OpenTelemetry logs collection" + help="HTTP/protobuf port for OpenTelemetry logs and traces collection" + ) + c.argument("opentelemetry_logs_traces_port_grpc", + options_list=["--opentelemetry-logs-traces-port-grpc"], + is_preview=True, + type=int, + help="gRPC port for OpenTelemetry logs and traces collection" ) c.argument("disable_opentelemetry_logs", + options_list=[ + "--disable-opentelemetry-logs-traces", + _deprecate_option(c, "--disable-opentelemetry-logs", "--disable-opentelemetry-logs-traces"), + ], is_preview=True, action="store_true", - help="Disable OpenTelemetry logs collection" + help="Disable OpenTelemetry logs and traces collection" ) c.argument("enable_cost_analysis", action="store_true" @@ -1768,9 +1823,19 @@ def load_arguments(self, _): validator=validate_azure_monitor_and_opentelemetry_for_update ) c.argument("opentelemetry_metrics_port", + options_list=[ + "--opentelemetry-metrics-port-http", + _deprecate_option(c, "--opentelemetry-metrics-port", "--opentelemetry-metrics-port-http"), + ], + is_preview=True, + type=int, + help="HTTP/protobuf port for OpenTelemetry metrics collection" + ) + c.argument("opentelemetry_metrics_port_grpc", + options_list=["--opentelemetry-metrics-port-grpc"], is_preview=True, type=int, - help="Port for OpenTelemetry metrics collection" + help="gRPC port for OpenTelemetry metrics collection" ) c.argument("disable_opentelemetry_metrics", is_preview=True, @@ -1778,19 +1843,37 @@ def load_arguments(self, _): help="Disable OpenTelemetry metrics collection" ) c.argument("enable_opentelemetry_logs", + options_list=[ + "--enable-opentelemetry-logs-traces", + _deprecate_option(c, "--enable-opentelemetry-logs", "--enable-opentelemetry-logs-traces"), + ], is_preview=True, action="store_true", - help="Enable OpenTelemetry logs collection" + help="Enable OpenTelemetry logs and traces collection" ) c.argument("opentelemetry_logs_port", + options_list=[ + "--opentelemetry-logs-traces-port-http", + _deprecate_option(c, "--opentelemetry-logs-port", "--opentelemetry-logs-traces-port-http"), + ], + is_preview=True, + type=int, + help="HTTP/protobuf port for OpenTelemetry logs and traces collection" + ) + c.argument("opentelemetry_logs_traces_port_grpc", + options_list=["--opentelemetry-logs-traces-port-grpc"], is_preview=True, type=int, - help="Port for OpenTelemetry logs collection" + help="gRPC port for OpenTelemetry logs and traces collection" ) c.argument("disable_opentelemetry_logs", + options_list=[ + "--disable-opentelemetry-logs-traces", + _deprecate_option(c, "--disable-opentelemetry-logs", "--disable-opentelemetry-logs-traces"), + ], is_preview=True, action="store_true", - help="Disable OpenTelemetry logs collection" + help="Disable OpenTelemetry logs and traces collection" ) c.argument( "enable_vpa", diff --git a/src/aks-preview/azext_aks_preview/_validators.py b/src/aks-preview/azext_aks_preview/_validators.py index eb96ed86c8c..6689bbfa208 100644 --- a/src/aks-preview/azext_aks_preview/_validators.py +++ b/src/aks-preview/azext_aks_preview/_validators.py @@ -1103,24 +1103,31 @@ def validate_location_resource_group_cluster_parameters(namespace): def validate_opentelemetry_ports(namespace): - """Validate that OpenTelemetry metrics and logs ports don't conflict.""" - metrics_port = getattr(namespace, 'opentelemetry_metrics_port', None) - logs_port = getattr(namespace, 'opentelemetry_logs_port', None) - - # Check if both ports are specified and are the same - if metrics_port is not None and logs_port is not None and metrics_port == logs_port: - raise ArgumentUsageError( - "OpenTelemetry metrics port and logs port cannot be the same. " - "Please specify different ports for --opentelemetry-metrics-port and --opentelemetry-logs-port." - ) + """Validate that the OpenTelemetry HTTP and gRPC ports are in range and all distinct.""" + ports = [ + ("--opentelemetry-metrics-port-http", getattr(namespace, 'opentelemetry_metrics_port', None)), + ("--opentelemetry-metrics-port-grpc", getattr(namespace, 'opentelemetry_metrics_port_grpc', None)), + ("--opentelemetry-logs-traces-port-http", getattr(namespace, 'opentelemetry_logs_port', None)), + ("--opentelemetry-logs-traces-port-grpc", getattr(namespace, 'opentelemetry_logs_traces_port_grpc', None)), + ] # Validate port ranges - for port, port_name in [(metrics_port, 'metrics'), (logs_port, 'logs')]: + for flag, port in ports: if port is not None and not (1 <= port <= 65535): raise ArgumentUsageError( - f"OpenTelemetry {port_name} port must be between 1 and 65535, got {port}." + f"OpenTelemetry port {flag} must be between 1 and 65535, got {port}." ) + # All specified OpenTelemetry ports (HTTP and gRPC, metrics and logs/traces) must be distinct + specified = [(flag, port) for flag, port in ports if port is not None] + for i in range(len(specified)): + for j in range(i + 1, len(specified)): + if specified[i][1] == specified[j][1]: + raise ArgumentUsageError( + "OpenTelemetry ports must all be different. " + f"{specified[i][0]} and {specified[j][0]} cannot both be set to {specified[i][1]}." + ) + def validate_opentelemetry_metrics_dependencies(namespace): """Validate OpenTelemetry metrics dependencies for create operations.""" @@ -1172,7 +1179,7 @@ def validate_opentelemetry_logs_dependencies(namespace): # Check mutual exclusion if enable_otlp_logs and disable_otlp_logs: raise MutuallyExclusiveArgumentError( - "Cannot specify both --enable-opentelemetry-logs and --disable-opentelemetry-logs at the same time." + "Cannot specify both --enable-opentelemetry-logs-traces and --disable-opentelemetry-logs-traces at the same time." ) # Check if trying to enable OTLP logs without Azure Monitor @@ -1197,7 +1204,7 @@ def validate_opentelemetry_logs_dependencies_for_update(namespace): # Check mutual exclusion if enable_otlp_logs and disable_otlp_logs: raise MutuallyExclusiveArgumentError( - "Cannot specify both --enable-opentelemetry-logs and --disable-opentelemetry-logs at the same time." + "Cannot specify both --enable-opentelemetry-logs-traces and --disable-opentelemetry-logs-traces at the same time." ) # For update operations, validation is deferred to the decorator where we have access # to the cluster's Azure Monitor profile diff --git a/src/aks-preview/azext_aks_preview/custom.py b/src/aks-preview/azext_aks_preview/custom.py index 50b5210e40d..8f36c3a07b2 100644 --- a/src/aks-preview/azext_aks_preview/custom.py +++ b/src/aks-preview/azext_aks_preview/custom.py @@ -1398,9 +1398,11 @@ def aks_create( # opentelemetry parameters enable_opentelemetry_metrics=False, opentelemetry_metrics_port=None, + opentelemetry_metrics_port_grpc=None, disable_opentelemetry_metrics=False, enable_opentelemetry_logs=False, opentelemetry_logs_port=None, + opentelemetry_logs_traces_port_grpc=None, disable_opentelemetry_logs=False, # metrics profile enable_cost_analysis=False, @@ -1641,9 +1643,11 @@ def aks_update( # opentelemetry parameters enable_opentelemetry_metrics=False, opentelemetry_metrics_port=None, + opentelemetry_metrics_port_grpc=None, disable_opentelemetry_metrics=False, enable_opentelemetry_logs=False, opentelemetry_logs_port=None, + opentelemetry_logs_traces_port_grpc=None, disable_opentelemetry_logs=False, enable_vpa=False, disable_vpa=False, diff --git a/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py b/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py index 6dfff64bb88..ea3c7905043 100644 --- a/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py +++ b/src/aks-preview/azext_aks_preview/managed_cluster_decorator.py @@ -3012,19 +3012,19 @@ def get_opentelemetry_metrics_port(self) -> Union[int, None]: # Validate that port is not negative if opentelemetry_metrics_port < 0: raise InvalidArgumentValueError( - "--opentelemetry-metrics-port must be a non-negative integer." + "--opentelemetry-metrics-port-http must be a non-negative integer." ) # Check if disabling Azure Monitor metrics - port specification is invalid if self.get_disable_azure_monitor_metrics(): raise InvalidArgumentValueError( - "--opentelemetry-metrics-port cannot be specified when --disable-azure-monitor-metrics is used." + "--opentelemetry-metrics-port-http cannot be specified when --disable-azure-monitor-metrics is used." ) # For CREATE: --enable-opentelemetry-metrics must be explicitly specified if self.decorator_mode == DecoratorMode.CREATE: if not self.get_enable_opentelemetry_metrics(): raise InvalidArgumentValueError( - "--opentelemetry-metrics-port can only be specified when " + "--opentelemetry-metrics-port-http can only be specified when " "--enable-opentelemetry-metrics is also specified." ) # For UPDATE: allow if either explicitly enabling OR already enabled in cluster @@ -3039,13 +3039,58 @@ def get_opentelemetry_metrics_port(self) -> Union[int, None]: ) if not explicitly_enabling and not already_enabled: raise InvalidArgumentValueError( - "--opentelemetry-metrics-port can only be specified when " + "--opentelemetry-metrics-port-http can only be specified when " "--enable-opentelemetry-metrics is also specified or " "OpenTelemetry metrics are already enabled." ) return opentelemetry_metrics_port + def get_opentelemetry_metrics_port_grpc(self) -> Union[int, None]: + """Obtain the value of opentelemetry_metrics_port_grpc. + :return: int or None + """ + opentelemetry_metrics_port_grpc = self.raw_param.get("opentelemetry_metrics_port_grpc") + + # Validate that port is only specified when OpenTelemetry metrics are enabled + if opentelemetry_metrics_port_grpc is not None: + # Validate that port is not negative + if opentelemetry_metrics_port_grpc < 0: + raise InvalidArgumentValueError( + "--opentelemetry-metrics-port-grpc must be a non-negative integer." + ) + # Check if disabling Azure Monitor metrics - port specification is invalid + if self.get_disable_azure_monitor_metrics(): + raise InvalidArgumentValueError( + "--opentelemetry-metrics-port-grpc cannot be specified when --disable-azure-monitor-metrics is used." + ) + + # For CREATE: --enable-opentelemetry-metrics must be explicitly specified + if self.decorator_mode == DecoratorMode.CREATE: + if not self.get_enable_opentelemetry_metrics(): + raise InvalidArgumentValueError( + "--opentelemetry-metrics-port-grpc can only be specified when " + "--enable-opentelemetry-metrics is also specified." + ) + # For UPDATE: allow if either explicitly enabling OR already enabled in cluster + elif self.decorator_mode == DecoratorMode.UPDATE: + explicitly_enabling = self.get_enable_opentelemetry_metrics() + already_enabled = ( + self.mc and + self.mc.azure_monitor_profile and + self.mc.azure_monitor_profile.app_monitoring and + self.mc.azure_monitor_profile.app_monitoring.open_telemetry_metrics and + self.mc.azure_monitor_profile.app_monitoring.open_telemetry_metrics.enabled + ) + if not explicitly_enabling and not already_enabled: + raise InvalidArgumentValueError( + "--opentelemetry-metrics-port-grpc can only be specified when " + "--enable-opentelemetry-metrics is also specified or " + "OpenTelemetry metrics are already enabled." + ) + + return opentelemetry_metrics_port_grpc + def _get_enable_opentelemetry_logs(self, enable_validation: bool = False) -> bool: """Internal function to obtain the value of enable_opentelemetry_logs. This function supports the option of enable_validation. When enabled, if both enable_opentelemetry_logs and @@ -3061,8 +3106,8 @@ def _get_enable_opentelemetry_logs(self, enable_validation: bool = False) -> boo if enable_opentelemetry_logs and self._get_disable_opentelemetry_logs( enable_validation=False): raise MutuallyExclusiveArgumentError( - "Cannot specify --enable-opentelemetry-logs and " - "--disable-opentelemetry-logs at the same time." + "Cannot specify --enable-opentelemetry-logs-traces and " + "--disable-opentelemetry-logs-traces at the same time." ) # For update operations, validate that Azure Monitor logs is enabled @@ -3126,7 +3171,8 @@ def _get_disable_opentelemetry_logs(self, enable_validation: bool = False) -> bo if enable_validation: if disable_opentelemetry_logs and self._get_enable_opentelemetry_logs(enable_validation=False): raise MutuallyExclusiveArgumentError( - "Cannot specify --enable-opentelemetry-logs and --disable-opentelemetry-logs at the same time." + "Cannot specify --enable-opentelemetry-logs-traces and " + "--disable-opentelemetry-logs-traces at the same time." ) return disable_opentelemetry_logs if disable_opentelemetry_logs is not None else False @@ -3149,20 +3195,20 @@ def get_opentelemetry_logs_port(self) -> Union[int, None]: # Validate that port is not negative if opentelemetry_logs_port < 0: raise InvalidArgumentValueError( - "--opentelemetry-logs-port must be a non-negative integer." + "--opentelemetry-logs-traces-port-http must be a non-negative integer." ) # Check if disabling Azure Monitor logs - port specification is invalid if self.get_disable_azure_monitor_logs(): raise InvalidArgumentValueError( - "--opentelemetry-logs-port cannot be specified when --disable-azure-monitor-logs is used." + "--opentelemetry-logs-traces-port-http cannot be specified when --disable-azure-monitor-logs is used." ) - # For CREATE: --enable-opentelemetry-logs must be explicitly specified + # For CREATE: --enable-opentelemetry-logs-traces must be explicitly specified if self.decorator_mode == DecoratorMode.CREATE: if not self.get_enable_opentelemetry_logs(): raise InvalidArgumentValueError( - "--opentelemetry-logs-port can only be specified when " - "--enable-opentelemetry-logs is also specified." + "--opentelemetry-logs-traces-port-http can only be specified when " + "--enable-opentelemetry-logs-traces is also specified." ) # For UPDATE: allow if either explicitly enabling OR already enabled in cluster elif self.decorator_mode == DecoratorMode.UPDATE: @@ -3176,13 +3222,58 @@ def get_opentelemetry_logs_port(self) -> Union[int, None]: ) if not explicitly_enabling and not already_enabled: raise InvalidArgumentValueError( - "--opentelemetry-logs-port can only be specified when " - "--enable-opentelemetry-logs is also specified or " + "--opentelemetry-logs-traces-port-http can only be specified when " + "--enable-opentelemetry-logs-traces is also specified or " "OpenTelemetry logs are already enabled." ) return opentelemetry_logs_port + def get_opentelemetry_logs_traces_port_grpc(self) -> Union[int, None]: + """Obtain the value of opentelemetry_logs_traces_port_grpc. + :return: int or None + """ + opentelemetry_logs_traces_port_grpc = self.raw_param.get("opentelemetry_logs_traces_port_grpc") + + # Validate that port is only specified when OpenTelemetry logs are enabled + if opentelemetry_logs_traces_port_grpc is not None: + # Validate that port is not negative + if opentelemetry_logs_traces_port_grpc < 0: + raise InvalidArgumentValueError( + "--opentelemetry-logs-traces-port-grpc must be a non-negative integer." + ) + # Check if disabling Azure Monitor logs - port specification is invalid + if self.get_disable_azure_monitor_logs(): + raise InvalidArgumentValueError( + "--opentelemetry-logs-traces-port-grpc cannot be specified when --disable-azure-monitor-logs is used." + ) + + # For CREATE: --enable-opentelemetry-logs-traces must be explicitly specified + if self.decorator_mode == DecoratorMode.CREATE: + if not self.get_enable_opentelemetry_logs(): + raise InvalidArgumentValueError( + "--opentelemetry-logs-traces-port-grpc can only be specified when " + "--enable-opentelemetry-logs-traces is also specified." + ) + # For UPDATE: allow if either explicitly enabling OR already enabled in cluster + elif self.decorator_mode == DecoratorMode.UPDATE: + explicitly_enabling = self.get_enable_opentelemetry_logs() + already_enabled = ( + self.mc and + self.mc.azure_monitor_profile and + self.mc.azure_monitor_profile.app_monitoring and + self.mc.azure_monitor_profile.app_monitoring.open_telemetry_logs_and_traces and + self.mc.azure_monitor_profile.app_monitoring.open_telemetry_logs_and_traces.enabled + ) + if not explicitly_enabling and not already_enabled: + raise InvalidArgumentValueError( + "--opentelemetry-logs-traces-port-grpc can only be specified when " + "--enable-opentelemetry-logs-traces is also specified or " + "OpenTelemetry logs are already enabled." + ) + + return opentelemetry_logs_traces_port_grpc + def get_enable_high_log_scale_mode(self) -> Union[bool, None]: """Obtain the value of enable_high_log_scale_mode. @@ -5086,8 +5177,11 @@ def _setup_opentelemetry_metrics(self, mc: ManagedCluster) -> None: otlp_metrics_config = ( self.models.ManagedClusterAzureMonitorProfileAppMonitoringOpenTelemetryMetrics(enabled=True)) metrics_port = self.context.get_opentelemetry_metrics_port() - if metrics_port: + if metrics_port is not None: otlp_metrics_config.http_port = metrics_port + metrics_port_grpc = self.context.get_opentelemetry_metrics_port_grpc() + if metrics_port_grpc is not None: + otlp_metrics_config.grpc_port = metrics_port_grpc mc.azure_monitor_profile.app_monitoring.open_telemetry_metrics = otlp_metrics_config @@ -5099,8 +5193,9 @@ def _disable_opentelemetry_metrics(self, mc: ManagedCluster) -> None: self.models.ManagedClusterAzureMonitorProfileAppMonitoringOpenTelemetryMetrics(enabled=False)) else: mc.azure_monitor_profile.app_monitoring.open_telemetry_metrics.enabled = False - # Clear the port when disabling OpenTelemetry metrics + # Clear the ports when disabling OpenTelemetry metrics mc.azure_monitor_profile.app_monitoring.open_telemetry_metrics.http_port = None + mc.azure_monitor_profile.app_monitoring.open_telemetry_metrics.grpc_port = None def _setup_opentelemetry_logs(self, mc: ManagedCluster) -> None: """Set up OpenTelemetry logs configuration.""" @@ -5109,8 +5204,11 @@ def _setup_opentelemetry_logs(self, mc: ManagedCluster) -> None: otel_logs_cls = self.models.ManagedClusterAzureMonitorProfileAppMonitoringOpenTelemetryLogsAndTraces otlp_logs_config = otel_logs_cls(enabled=True) logs_port = self.context.get_opentelemetry_logs_port() - if logs_port: + if logs_port is not None: otlp_logs_config.http_port = logs_port + logs_port_grpc = self.context.get_opentelemetry_logs_traces_port_grpc() + if logs_port_grpc is not None: + otlp_logs_config.grpc_port = logs_port_grpc mc.azure_monitor_profile.app_monitoring.open_telemetry_logs_and_traces = otlp_logs_config @@ -5124,8 +5222,9 @@ def _disable_opentelemetry_logs(self, mc: ManagedCluster) -> None: ) else: mc.azure_monitor_profile.app_monitoring.open_telemetry_logs_and_traces.enabled = False - # Clear the port when disabling OpenTelemetry logs + # Clear the ports when disabling OpenTelemetry logs mc.azure_monitor_profile.app_monitoring.open_telemetry_logs_and_traces.http_port = None + mc.azure_monitor_profile.app_monitoring.open_telemetry_logs_and_traces.grpc_port = None def set_up_azure_monitor_profile(self, mc: ManagedCluster) -> ManagedCluster: """Set up azure monitor profile for the ManagedCluster object. @@ -7552,8 +7651,10 @@ def update_azure_monitor_profile(self, mc: ManagedCluster) -> ManagedCluster: otlp_metrics_config = ( self.models.ManagedClusterAzureMonitorProfileAppMonitoringOpenTelemetryMetrics(enabled=True) ) - if self.context.get_opentelemetry_metrics_port(): + if self.context.get_opentelemetry_metrics_port() is not None: otlp_metrics_config.http_port = self.context.get_opentelemetry_metrics_port() + if self.context.get_opentelemetry_metrics_port_grpc() is not None: + otlp_metrics_config.grpc_port = self.context.get_opentelemetry_metrics_port_grpc() mc.azure_monitor_profile.app_monitoring.open_telemetry_metrics = otlp_metrics_config @@ -7569,8 +7670,10 @@ def update_azure_monitor_profile(self, mc: ManagedCluster) -> ManagedCluster: # Configure OpenTelemetry logs with custom port if provided otel_logs_cls = self.models.ManagedClusterAzureMonitorProfileAppMonitoringOpenTelemetryLogsAndTraces otlp_logs_config = otel_logs_cls(enabled=True) - if self.context.get_opentelemetry_logs_port(): + if self.context.get_opentelemetry_logs_port() is not None: otlp_logs_config.http_port = self.context.get_opentelemetry_logs_port() + if self.context.get_opentelemetry_logs_traces_port_grpc() is not None: + otlp_logs_config.grpc_port = self.context.get_opentelemetry_logs_traces_port_grpc() mc.azure_monitor_profile.app_monitoring.open_telemetry_logs_and_traces = otlp_logs_config @@ -7605,6 +7708,7 @@ def update_azure_monitor_profile(self, mc: ManagedCluster) -> ManagedCluster: else: mc.azure_monitor_profile.app_monitoring.open_telemetry_metrics.enabled = False mc.azure_monitor_profile.app_monitoring.open_telemetry_metrics.http_port = None + mc.azure_monitor_profile.app_monitoring.open_telemetry_metrics.grpc_port = None # Handle disable OpenTelemetry logs updates if self.context.get_disable_opentelemetry_logs(): @@ -7623,9 +7727,10 @@ def update_azure_monitor_profile(self, mc: ManagedCluster) -> ManagedCluster: else: mc.azure_monitor_profile.app_monitoring.open_telemetry_logs_and_traces.enabled = False mc.azure_monitor_profile.app_monitoring.open_telemetry_logs_and_traces.http_port = None + mc.azure_monitor_profile.app_monitoring.open_telemetry_logs_and_traces.grpc_port = None # Handle standalone port updates for OpenTelemetry metrics - if (self.context.get_opentelemetry_metrics_port() and + if (self.context.get_opentelemetry_metrics_port() is not None and not self.context.get_enable_opentelemetry_metrics() and not self.context.get_disable_opentelemetry_metrics()): # Only update port if OpenTelemetry metrics is already enabled and we're not changing the enabled state @@ -7636,8 +7741,19 @@ def update_azure_monitor_profile(self, mc: ManagedCluster) -> ManagedCluster: metrics_port = self.context.get_opentelemetry_metrics_port() mc.azure_monitor_profile.app_monitoring.open_telemetry_metrics.http_port = metrics_port + # Handle standalone gRPC port updates for OpenTelemetry metrics + if (self.context.get_opentelemetry_metrics_port_grpc() is not None and + not self.context.get_enable_opentelemetry_metrics() and + not self.context.get_disable_opentelemetry_metrics()): + if (mc.azure_monitor_profile and + mc.azure_monitor_profile.app_monitoring and + mc.azure_monitor_profile.app_monitoring.open_telemetry_metrics and + mc.azure_monitor_profile.app_monitoring.open_telemetry_metrics.enabled): + metrics_port_grpc = self.context.get_opentelemetry_metrics_port_grpc() + mc.azure_monitor_profile.app_monitoring.open_telemetry_metrics.grpc_port = metrics_port_grpc + # Handle standalone port updates for OpenTelemetry logs - if (self.context.get_opentelemetry_logs_port() and + if (self.context.get_opentelemetry_logs_port() is not None and not self.context.get_enable_opentelemetry_logs() and not self.context.get_disable_opentelemetry_logs()): # Only update port if OpenTelemetry logs is already enabled and we're not changing the enabled state @@ -7648,19 +7764,33 @@ def update_azure_monitor_profile(self, mc: ManagedCluster) -> ManagedCluster: logs_port = self.context.get_opentelemetry_logs_port() mc.azure_monitor_profile.app_monitoring.open_telemetry_logs_and_traces.http_port = logs_port + # Handle standalone gRPC port updates for OpenTelemetry logs + if (self.context.get_opentelemetry_logs_traces_port_grpc() is not None and + not self.context.get_enable_opentelemetry_logs() and + not self.context.get_disable_opentelemetry_logs()): + if (mc.azure_monitor_profile and + mc.azure_monitor_profile.app_monitoring and + mc.azure_monitor_profile.app_monitoring.open_telemetry_logs_and_traces and + mc.azure_monitor_profile.app_monitoring.open_telemetry_logs_and_traces.enabled): + logs_port_grpc = self.context.get_opentelemetry_logs_traces_port_grpc() + mc.azure_monitor_profile.app_monitoring.open_telemetry_logs_and_traces.grpc_port = logs_port_grpc + # TODO: should remove get value from enable_azuremonitormetrics once the option is removed # TODO: should remove get value from disable_azuremonitormetrics once the option is removed azure_monitor_metrics = (self.context.raw_param.get("enable_azuremonitormetrics") or self.context.get_enable_azure_monitor_metrics() or self.context.raw_param.get("disable_azuremonitormetrics") or self.context.get_disable_azure_monitor_metrics()) - opentelemetry = (self.context.raw_param.get("enable_opentelemetry_metrics") or - self.context.raw_param.get("enable_opentelemetry_logs") or - self.context.raw_param.get("disable_opentelemetry_metrics") or - self.context.raw_param.get("disable_opentelemetry_logs") or - self.context.get_opentelemetry_metrics_port() or - self.context.get_opentelemetry_logs_port()) - if azure_monitor_metrics or opentelemetry: + opentelemetry_metrics = (self.context.raw_param.get("enable_opentelemetry_metrics") or + self.context.raw_param.get("disable_opentelemetry_metrics") or + self.context.get_opentelemetry_metrics_port() is not None or + self.context.get_opentelemetry_metrics_port_grpc() is not None) + # NOTE: only Azure Monitor metrics and OpenTelemetry *metrics* may require the Prometheus + # artifacts (Azure Monitor Workspace, DCE, DCR, DCRA, Grafana link, recording rules) that + # ensure_azure_monitor_profile_prerequisites creates. OpenTelemetry *logs and traces* ride + # on the Container Insights pipeline and must NOT trigger Prometheus onboarding, otherwise + # a command that only sets an OTLP logs/traces port silently provisions an AMW. + if azure_monitor_metrics or opentelemetry_metrics: ensure_azure_monitor_profile_prerequisites( self.cmd, self.context.get_subscription_id(), @@ -8644,8 +8774,9 @@ def _disable_azure_monitor_logs(self, mc: ManagedCluster) -> None: # Also disable OpenTelemetry logs when disabling Azure Monitor logs if opentelemetry_logs_enabled: mc.azure_monitor_profile.app_monitoring.open_telemetry_logs_and_traces.enabled = False - # Clear the port when disabling OpenTelemetry logs + # Clear the ports when disabling OpenTelemetry logs mc.azure_monitor_profile.app_monitoring.open_telemetry_logs_and_traces.http_port = None + mc.azure_monitor_profile.app_monitoring.open_telemetry_logs_and_traces.grpc_port = None def _disable_azure_monitor_metrics(self, mc: ManagedCluster) -> None: """Disable Azure Monitor metrics configuration.""" @@ -8686,8 +8817,9 @@ def _disable_azure_monitor_metrics(self, mc: ManagedCluster) -> None: # Also disable OpenTelemetry metrics when disabling Azure Monitor metrics if opentelemetry_metrics_enabled: mc.azure_monitor_profile.app_monitoring.open_telemetry_metrics.enabled = False - # Clear the port when disabling OpenTelemetry metrics + # Clear the ports when disabling OpenTelemetry metrics mc.azure_monitor_profile.app_monitoring.open_telemetry_metrics.http_port = None + mc.azure_monitor_profile.app_monitoring.open_telemetry_metrics.grpc_port = None def update_addon_profiles(self, mc: ManagedCluster) -> ManagedCluster: """Update addon profiles for the ManagedCluster object. 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 7da7157acba..488bbf82fa5 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 @@ -59,6 +59,28 @@ def _create_log_analytics_workspace(self, resource_group_location): "--location {workspace_location} --query id -o tsv" ).output.strip() + def _create_azure_monitor_workspace(self, resource_group_location): + """Create a dedicated Azure Monitor workspace (Prometheus) for a test. + + Without --azure-monitor-workspace-resource-id the metrics onboarding falls back to the + subscription's shared DefaultAzureMonitorWorkspace-, which races when live tests + run in parallel. + """ + amw_name = self.create_random_name("cliamw", 16) + amw_location = ( + "eastus2" if resource_group_location.lower().endswith("euap") else resource_group_location + ) + self.kwargs.update( + { + "amw_name": amw_name, + "amw_location": amw_location, + } + ) + return self.cmd( + "monitor account create -g {resource_group} -n {amw_name} " + "--location {amw_location} --query id -o tsv" + ).output.strip() + def _get_versions(self, location): """Return the previous and current Kubernetes minor release versions, such as ("1.11.6", "1.12.4").""" supported_versions = self.cmd( @@ -16562,19 +16584,31 @@ def test_aks_create_with_azuremonitorlogs_and_opentelemetry(self, resource_group 'ssh_key_value': self.generate_ssh_keys(), 'node_vm_size': node_vm_size, }) + # Use a dedicated workspace: without --workspace-resource-id the monitoring addon falls back + # to the subscription's shared default workspace, which conflicts when live tests run in parallel. + self.kwargs.update({ + 'workspace_resource_id': self._create_log_analytics_workspace(resource_group_location), + }) create_cmd = ( 'aks create --resource-group={resource_group} --name={name} --location={location} --ssh-key-value={ssh_key_value} --node-vm-size={node_vm_size} ' - '--enable-managed-identity --enable-azure-monitor-logs --enable-opentelemetry-logs --opentelemetry-logs-port=8080 ' - '--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/AzureMonitorAppMonitoringPreview --output=json' + '--enable-managed-identity --enable-azure-monitor-logs --workspace-resource-id={workspace_resource_id} ' + '--enable-opentelemetry-logs-traces --opentelemetry-logs-traces-port-http=8080 --opentelemetry-logs-traces-port-grpc=8082 ' + '--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/AzureMonitorAppMonitoringPreview ' + '--output=json' ) self.cmd(create_cmd, checks=[ self.check('provisioningState', 'Succeeded'), self.check('addonProfiles.omsagent.enabled', True), - self.exists('addonProfiles.omsagent.config.logAnalyticsWorkspaceResourceID'), + self.check( + 'addonProfiles.omsagent.config.logAnalyticsWorkspaceResourceID', + self.kwargs['workspace_resource_id'], + case_sensitive=False, + ), self.check('addonProfiles.omsagent.config.useAADAuth', 'true'), - self.check('azureMonitorProfile.appMonitoring.openTelemetryLogs.enabled', True), - self.check('azureMonitorProfile.appMonitoring.openTelemetryLogs.port', 8080), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.enabled', True), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.httpPort', 8080), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.grpcPort', 8082), ]) # delete @@ -16596,6 +16630,11 @@ def test_aks_update_with_azuremonitorlogs_and_opentelemetry(self, resource_group 'ssh_key_value': self.generate_ssh_keys(), 'node_vm_size': node_vm_size, }) + # Use a dedicated workspace: without --workspace-resource-id the monitoring addon falls back + # to the subscription's shared default workspace, which conflicts when live tests run in parallel. + self.kwargs.update({ + 'workspace_resource_id': self._create_log_analytics_workspace(resource_group_location), + }) # create: without enable-azure-monitor-logs create_cmd = 'aks create --resource-group={resource_group} --name={name} --location={location} --ssh-key-value={ssh_key_value} --node-vm-size={node_vm_size} --enable-managed-identity --output=json' @@ -16607,28 +16646,49 @@ def test_aks_update_with_azuremonitorlogs_and_opentelemetry(self, resource_group # update: enable-azure-monitor-logs with OpenTelemetry logs update_cmd = ( 'aks update --resource-group={resource_group} --name={name} --yes --output=json ' - '--enable-azure-monitor-logs --enable-opentelemetry-logs --opentelemetry-logs-port=9090 ' - '--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/AzureMonitorAppMonitoringPreview' + '--enable-azure-monitor-logs --workspace-resource-id={workspace_resource_id} ' + '--enable-opentelemetry-logs-traces --opentelemetry-logs-traces-port-http=9090 --opentelemetry-logs-traces-port-grpc=9092 ' + '--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/AzureMonitorAppMonitoringPreview ' ) self.cmd(update_cmd, checks=[ self.check('provisioningState', 'Succeeded'), self.check('addonProfiles.omsagent.enabled', True), - self.exists('addonProfiles.omsagent.config.logAnalyticsWorkspaceResourceID'), + self.check( + 'addonProfiles.omsagent.config.logAnalyticsWorkspaceResourceID', + self.kwargs['workspace_resource_id'], + case_sensitive=False, + ), self.check('addonProfiles.omsagent.config.useAADAuth', 'true'), - self.check('azureMonitorProfile.appMonitoring.openTelemetryLogs.enabled', True), - self.check('azureMonitorProfile.appMonitoring.openTelemetryLogs.port', 9090), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.enabled', True), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.httpPort', 9090), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.grpcPort', 9092), + ]) + + # The monitoring cluster extension keeps provisioning after the update call returns. + # Without this wait the next update collides with it and fails with + # CreateOrUpdateExtensionFailed ("conflicting operation in progress"). + wait_cmd = ' '.join([ + 'aks', 'wait', '--resource-group={resource_group}', '--name={name}', '--updated', + '--interval 60', '--timeout 1800', + ]) + self.cmd(wait_cmd, checks=[ + self.is_empty(), ]) # update: disable OpenTelemetry logs but keep Azure Monitor logs update_cmd = ( 'aks update --resource-group={resource_group} --name={name} --yes --output=json ' - '--disable-opentelemetry-logs' + '--disable-opentelemetry-logs-traces' ) self.cmd(update_cmd, checks=[ self.check('provisioningState', 'Succeeded'), self.check('addonProfiles.omsagent.enabled', True), # Still enabled self.check('addonProfiles.omsagent.config.useAADAuth', 'true'), - self.check('azureMonitorProfile.appMonitoring.openTelemetryLogs.enabled', False), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.enabled', False), + ]) + + self.cmd(wait_cmd, checks=[ + self.is_empty(), ]) # update: disable-azure-monitor-logs (should also disable OpenTelemetry logs) @@ -16799,11 +16859,18 @@ def test_aks_create_with_azuremonitormetrics_and_opentelemetry(self, resource_gr 'ssh_key_value': self.generate_ssh_keys(), 'node_vm_size': node_vm_size, }) + # Use a dedicated Azure Monitor workspace: without --azure-monitor-workspace-resource-id + # metrics onboarding falls back to the shared default AMW, which races under parallelism. + self.kwargs.update({ + 'amw_resource_id': self._create_azure_monitor_workspace(resource_group_location), + }) create_cmd = ( 'aks create --resource-group={resource_group} --name={name} --location={location} --ssh-key-value={ssh_key_value} --node-vm-size={node_vm_size} ' - '--enable-managed-identity --enable-azure-monitor-metrics --enable-opentelemetry-metrics --opentelemetry-metrics-port=8080 ' - '--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/AzureMonitorAppMonitoringPreview --output=json' + '--enable-managed-identity --enable-azure-monitor-metrics --azure-monitor-workspace-resource-id={amw_resource_id} ' + '--enable-opentelemetry-metrics --opentelemetry-metrics-port-http=8080 --opentelemetry-metrics-port-grpc=8082 ' + '--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/AzureMonitorAppMonitoringPreview ' + '--output=json' ) self.cmd(create_cmd, checks=[ self.check('provisioningState', 'Succeeded'), @@ -16825,7 +16892,8 @@ def test_aks_create_with_azuremonitormetrics_and_opentelemetry(self, resource_gr self.check('provisioningState', 'Succeeded'), self.check('azureMonitorProfile.metrics.enabled', True), self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.enabled', True), - self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.port', 8080), + self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.httpPort', 8080), + self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.grpcPort', 8082), ]) # delete @@ -16847,6 +16915,11 @@ def test_aks_update_with_azuremonitormetrics_and_opentelemetry(self, resource_gr 'ssh_key_value': self.generate_ssh_keys(), 'node_vm_size': node_vm_size, }) + # Use a dedicated Azure Monitor workspace: without --azure-monitor-workspace-resource-id + # metrics onboarding falls back to the shared default AMW, which races under parallelism. + self.kwargs.update({ + 'amw_resource_id': self._create_azure_monitor_workspace(resource_group_location), + }) # create: without enable-azure-monitor-metrics create_cmd = 'aks create --resource-group={resource_group} --name={name} --location={location} --ssh-key-value={ssh_key_value} --node-vm-size={node_vm_size} --enable-managed-identity --output=json' @@ -16858,7 +16931,8 @@ def test_aks_update_with_azuremonitormetrics_and_opentelemetry(self, resource_gr # update: enable-azure-monitor-metrics with OpenTelemetry metrics update_cmd = ( 'aks update --resource-group={resource_group} --name={name} --yes ' - '--enable-azure-monitor-metrics --enable-opentelemetry-metrics --opentelemetry-metrics-port=9090 ' + '--enable-azure-monitor-metrics --azure-monitor-workspace-resource-id={amw_resource_id} ' + '--enable-opentelemetry-metrics --opentelemetry-metrics-port-http=9090 ' '--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/AzureMonitorAppMonitoringPreview ' '--output=json' ) @@ -16957,6 +17031,83 @@ def test_aks_update_with_azuremonitormetrics_and_opentelemetry(self, resource_gr self.is_empty(), ]) + @live_only() + @AllowLargeResponse() + @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix='clitest', location='westus2') + def test_aks_create_with_opentelemetry_deprecated_flag_aliases(self, resource_group, resource_group_location): + # Verifies the deprecated OTLP flag aliases still work and redirect to the + # renamed parameters (i.e. --enable-opentelemetry-logs -> --enable-opentelemetry-logs-traces, + # --opentelemetry-logs-port -> --opentelemetry-logs-traces-port-http, + # --opentelemetry-metrics-port -> --opentelemetry-metrics-port-http). + # reset the count so in replay mode the random names will start with 0 + self.test_resources_count = 0 + aks_name = self.create_random_name('cliakstest', 16) + node_vm_size = 'standard_d2s_v3' + self.kwargs.update({ + 'resource_group': resource_group, + 'name': aks_name, + 'location': resource_group_location, + 'resource_type': 'Microsoft.ContainerService/ManagedClusters', + 'ssh_key_value': self.generate_ssh_keys(), + 'node_vm_size': node_vm_size, + }) + # Use dedicated workspaces: without explicit ids the monitoring addons fall back to the + # subscription's shared default workspaces, which conflict when live tests run in parallel. + self.kwargs.update({ + 'workspace_resource_id': self._create_log_analytics_workspace(resource_group_location), + 'amw_resource_id': self._create_azure_monitor_workspace(resource_group_location), + }) + + # Intentionally use the DEPRECATED flag names; they must still bind the same + # destinations and configure OpenTelemetry on the new API surface. + create_cmd = ( + 'aks create --resource-group={resource_group} --name={name} --location={location} --ssh-key-value={ssh_key_value} --node-vm-size={node_vm_size} ' + '--enable-managed-identity --enable-azure-monitor-logs --workspace-resource-id={workspace_resource_id} ' + '--enable-azure-monitor-metrics --azure-monitor-workspace-resource-id={amw_resource_id} ' + '--enable-opentelemetry-logs --opentelemetry-logs-port=8080 ' + '--enable-opentelemetry-metrics --opentelemetry-metrics-port=8081 ' + '--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/AzureMonitorAppMonitoringPreview ' + '--output=json' + ) + self.cmd(create_cmd, checks=[ + self.check('provisioningState', 'Succeeded'), + ]) + + # azuremonitor metrics is enabled in a postprocessing update; wait for it to settle. + wait_cmd = ' '.join([ + 'aks', 'wait', '--resource-group={resource_group}', '--name={name}', '--updated', + '--interval 60', '--timeout 1800', + ]) + self.cmd(wait_cmd, checks=[ + self.is_empty(), + ]) + + self.cmd('aks show -g {resource_group} -n {name} --output=json', checks=[ + self.check('provisioningState', 'Succeeded'), + self.check('addonProfiles.omsagent.enabled', True), + self.check('azureMonitorProfile.metrics.enabled', True), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.enabled', True), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.httpPort', 8080), + self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.enabled', True), + self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.httpPort', 8081), + ]) + + # Disable using the deprecated disable alias. + update_cmd = ( + 'aks update --resource-group={resource_group} --name={name} --yes --output=json ' + '--disable-opentelemetry-logs' + ) + self.cmd(update_cmd, checks=[ + self.check('provisioningState', 'Succeeded'), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.enabled', False), + ]) + + # delete + cmd = 'aks delete --resource-group={resource_group} --name={name} --yes --no-wait' + self.cmd(cmd, checks=[ + self.is_empty(), + ]) + @live_only() @AllowLargeResponse(999999) @AKSCustomResourceGroupPreparer(random_name_length=17, name_prefix='clitest', location='swedencentral') @@ -17087,15 +17238,23 @@ def test_aks_comprehensive_monitoring_integration(self, resource_group, resource 'ssh_key_value': self.generate_ssh_keys(), 'node_vm_size': node_vm_size, }) + # Use dedicated workspaces: without explicit ids the monitoring addons fall back to the + # subscription's shared default workspaces, which conflict when live tests run in parallel. + self.kwargs.update({ + 'workspace_resource_id': self._create_log_analytics_workspace(resource_group_location), + 'amw_resource_id': self._create_azure_monitor_workspace(resource_group_location), + }) # Phase 1: Create cluster with all monitoring features enabled create_cmd = ( 'aks create --resource-group={resource_group} --name={name} --location={location} --ssh-key-value={ssh_key_value} --node-vm-size={node_vm_size} ' - '--enable-managed-identity --enable-azure-monitor-logs --enable-azure-monitor-metrics --enable-azure-monitor-app-monitoring ' - '--enable-opentelemetry-logs --opentelemetry-logs-port=8080 ' - '--enable-opentelemetry-metrics --opentelemetry-metrics-port=8081 ' + '--enable-managed-identity --enable-azure-monitor-logs --workspace-resource-id={workspace_resource_id} ' + '--enable-azure-monitor-metrics --azure-monitor-workspace-resource-id={amw_resource_id} --enable-azure-monitor-app-monitoring ' + '--enable-opentelemetry-logs-traces --opentelemetry-logs-traces-port-http=8080 --opentelemetry-logs-traces-port-grpc=8082 ' + '--enable-opentelemetry-metrics --opentelemetry-metrics-port-http=8081 --opentelemetry-metrics-port-grpc=8083 ' '--enable-windows-recording-rules ' - '--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/AzureMonitorAppMonitoringPreview --output=json' + '--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/AzureMonitorAppMonitoringPreview ' + '--output=json' ) self.cmd(create_cmd, checks=[ self.check('provisioningState', 'Succeeded'), @@ -17117,24 +17276,30 @@ def test_aks_comprehensive_monitoring_integration(self, resource_group, resource self.check('provisioningState', 'Succeeded'), # Azure Monitor logs checks self.check('addonProfiles.omsagent.enabled', True), - self.exists('addonProfiles.omsagent.config.logAnalyticsWorkspaceResourceID'), + self.check( + 'addonProfiles.omsagent.config.logAnalyticsWorkspaceResourceID', + self.kwargs['workspace_resource_id'], + case_sensitive=False, + ), self.check('addonProfiles.omsagent.config.useAADAuth', 'true'), # Azure Monitor metrics checks self.check('azureMonitorProfile.metrics.enabled', True), # Azure Monitor app monitoring checks self.check('azureMonitorProfile.appMonitoring.autoInstrumentation.enabled', True), # OpenTelemetry logs checks - self.check('azureMonitorProfile.appMonitoring.openTelemetryLogs.enabled', True), - self.check('azureMonitorProfile.appMonitoring.openTelemetryLogs.port', 8080), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.enabled', True), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.httpPort', 8080), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.grpcPort', 8082), # OpenTelemetry metrics checks self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.enabled', True), - self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.port', 8081), + self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.httpPort', 8081), + self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.grpcPort', 8083), ]) # Phase 2: Update - disable only OpenTelemetry logs (keep everything else) update_cmd = ( 'aks update --resource-group={resource_group} --name={name} --yes --output=json ' - '--disable-opentelemetry-logs' + '--disable-opentelemetry-logs-traces' ) self.cmd(update_cmd, checks=[ self.check('provisioningState', 'Succeeded'), @@ -17158,7 +17323,10 @@ def test_aks_comprehensive_monitoring_integration(self, resource_group, resource # Azure Monitor app monitoring should still be enabled self.check('azureMonitorProfile.appMonitoring.autoInstrumentation.enabled', True), # OpenTelemetry logs should be disabled - self.check('azureMonitorProfile.appMonitoring.openTelemetryLogs.enabled', False), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.enabled', False), + # Disabling OpenTelemetry logs should clear both ports + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.httpPort', None), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.grpcPort', None), # OpenTelemetry metrics should still be enabled self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.enabled', True), ]) @@ -17191,14 +17359,17 @@ def test_aks_comprehensive_monitoring_integration(self, resource_group, resource self.check('azureMonitorProfile.appMonitoring.autoInstrumentation.enabled', True), # OpenTelemetry metrics should be disabled self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.enabled', False), + # Disabling OpenTelemetry metrics should clear both ports + self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.httpPort', None), + self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.grpcPort', None), ]) # Phase 4: Update - re-enable all OpenTelemetry features with different ports update_cmd = ( 'aks update --resource-group={resource_group} --name={name} --yes --output=json ' - '--enable-opentelemetry-logs --opentelemetry-logs-port=9090 ' - '--enable-opentelemetry-metrics --opentelemetry-metrics-port=9091 ' - '--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/AzureMonitorAppMonitoringPreview' + '--enable-opentelemetry-logs-traces --opentelemetry-logs-traces-port-http=9090 --opentelemetry-logs-traces-port-grpc=9092 ' + '--enable-opentelemetry-metrics --opentelemetry-metrics-port-http=9091 --opentelemetry-metrics-port-grpc=9093 ' + '--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/AzureMonitorAppMonitoringPreview ' ) self.cmd(update_cmd, checks=[ self.check('provisioningState', 'Succeeded'), @@ -17220,10 +17391,12 @@ def test_aks_comprehensive_monitoring_integration(self, resource_group, resource self.check('azureMonitorProfile.metrics.enabled', True), self.check('azureMonitorProfile.appMonitoring.autoInstrumentation.enabled', True), # OpenTelemetry features should be re-enabled with new ports - self.check('azureMonitorProfile.appMonitoring.openTelemetryLogs.enabled', True), - self.check('azureMonitorProfile.appMonitoring.openTelemetryLogs.port', 9090), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.enabled', True), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.httpPort', 9090), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.grpcPort', 9092), self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.enabled', True), - self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.port', 9091), + self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.httpPort', 9091), + self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.grpcPort', 9093), ]) # Phase 5: Update - disable Azure Monitor metrics (should also disable OpenTelemetry metrics) @@ -17253,7 +17426,7 @@ def test_aks_comprehensive_monitoring_integration(self, resource_group, resource # Azure Monitor app monitoring should still be enabled self.check('azureMonitorProfile.appMonitoring.autoInstrumentation.enabled', True), # OpenTelemetry logs should still be enabled (independent of metrics) - self.check('azureMonitorProfile.appMonitoring.openTelemetryLogs.enabled', True), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.enabled', True), ]) # Phase 6: Update - disable Azure Monitor logs (should also disable OpenTelemetry logs) @@ -17284,11 +17457,12 @@ def test_aks_comprehensive_monitoring_integration(self, resource_group, resource # Phase 7: Update - re-enable all monitoring features at once update_cmd = ( 'aks update --resource-group={resource_group} --name={name} --yes --output=json ' - '--enable-azure-monitor-logs --enable-azure-monitor-metrics --enable-azure-monitor-app-monitoring ' - '--enable-opentelemetry-logs --opentelemetry-logs-port=7070 ' - '--enable-opentelemetry-metrics --opentelemetry-metrics-port=7071 ' + '--enable-azure-monitor-logs --workspace-resource-id={workspace_resource_id} ' + '--enable-azure-monitor-metrics --azure-monitor-workspace-resource-id={amw_resource_id} --enable-azure-monitor-app-monitoring ' + '--enable-opentelemetry-logs-traces --opentelemetry-logs-traces-port-http=7070 --opentelemetry-logs-traces-port-grpc=7072 ' + '--enable-opentelemetry-metrics --opentelemetry-metrics-port-http=7071 --opentelemetry-metrics-port-grpc=7073 ' '--enable-windows-recording-rules ' - '--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/AzureMonitorAppMonitoringPreview' + '--aks-custom-headers AKSHTTPCustomFeatures=Microsoft.ContainerService/AzureMonitorAppMonitoringPreview ' ) self.cmd(update_cmd, checks=[ self.check('provisioningState', 'Succeeded'), @@ -17306,22 +17480,28 @@ def test_aks_comprehensive_monitoring_integration(self, resource_group, resource self.check('provisioningState', 'Succeeded'), # All Azure Monitor features should be enabled self.check('addonProfiles.omsagent.enabled', True), - self.exists('addonProfiles.omsagent.config.logAnalyticsWorkspaceResourceID'), + self.check( + 'addonProfiles.omsagent.config.logAnalyticsWorkspaceResourceID', + self.kwargs['workspace_resource_id'], + case_sensitive=False, + ), self.check('addonProfiles.omsagent.config.useAADAuth', 'true'), self.check('azureMonitorProfile.metrics.enabled', True), self.check('azureMonitorProfile.appMonitoring.autoInstrumentation.enabled', True), # All OpenTelemetry features should be enabled with new ports - self.check('azureMonitorProfile.appMonitoring.openTelemetryLogs.enabled', True), - self.check('azureMonitorProfile.appMonitoring.openTelemetryLogs.port', 7070), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.enabled', True), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.httpPort', 7070), + self.check('azureMonitorProfile.appMonitoring.openTelemetryLogsAndTraces.grpcPort', 7072), self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.enabled', True), - self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.port', 7071), + self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.httpPort', 7071), + self.check('azureMonitorProfile.appMonitoring.openTelemetryMetrics.grpcPort', 7073), ]) # Phase 8: Final cleanup - disable all monitoring features update_cmd = ( 'aks update --resource-group={resource_group} --name={name} --yes --output=json ' '--disable-azure-monitor-logs --disable-azure-monitor-metrics --disable-azure-monitor-app-monitoring ' - '--disable-opentelemetry-logs --disable-opentelemetry-metrics' + '--disable-opentelemetry-logs-traces --disable-opentelemetry-metrics' ) self.cmd(update_cmd, checks=[ self.check('provisioningState', 'Succeeded'), diff --git a/src/aks-preview/azext_aks_preview/tests/latest/test_managed_cluster_decorator.py b/src/aks-preview/azext_aks_preview/tests/latest/test_managed_cluster_decorator.py index ffce3bf9df1..b481b63b509 100644 --- a/src/aks-preview/azext_aks_preview/tests/latest/test_managed_cluster_decorator.py +++ b/src/aks-preview/azext_aks_preview/tests/latest/test_managed_cluster_decorator.py @@ -8662,6 +8662,30 @@ def test_get_opentelemetry_metrics_port(self): ) self.assertEqual(ctx_2.get_opentelemetry_metrics_port(), 8080) + def test_get_opentelemetry_metrics_port_grpc(self): + # default + ctx_1 = AKSPreviewManagedClusterContext( + self.cmd, + AKSManagedClusterParamDict({"opentelemetry_metrics_port_grpc": None}), + self.models, + decorator_mode=DecoratorMode.CREATE, + ) + self.assertEqual(ctx_1.get_opentelemetry_metrics_port_grpc(), None) + + # custom value + ctx_2 = AKSPreviewManagedClusterContext( + self.cmd, + AKSManagedClusterParamDict( + { + "opentelemetry_metrics_port_grpc": 8082, + "enable_opentelemetry_metrics": True, + } + ), + self.models, + decorator_mode=DecoratorMode.CREATE, + ) + self.assertEqual(ctx_2.get_opentelemetry_metrics_port_grpc(), 8082) + def test_get_enable_opentelemetry_logs(self): # default ctx_1 = AKSPreviewManagedClusterContext( @@ -8721,6 +8745,80 @@ def test_get_opentelemetry_logs_port(self): ) self.assertEqual(ctx_2.get_opentelemetry_logs_port(), 8081) + def test_get_opentelemetry_logs_traces_port_grpc(self): + # default + ctx_1 = AKSPreviewManagedClusterContext( + self.cmd, + AKSManagedClusterParamDict({"opentelemetry_logs_traces_port_grpc": None}), + self.models, + decorator_mode=DecoratorMode.CREATE, + ) + self.assertEqual(ctx_1.get_opentelemetry_logs_traces_port_grpc(), None) + + # custom value + ctx_2 = AKSPreviewManagedClusterContext( + self.cmd, + AKSManagedClusterParamDict( + {"opentelemetry_logs_traces_port_grpc": 8083, "enable_opentelemetry_logs": True} + ), + self.models, + decorator_mode=DecoratorMode.CREATE, + ) + self.assertEqual(ctx_2.get_opentelemetry_logs_traces_port_grpc(), 8083) + + def test_set_up_azure_monitor_profile_with_opentelemetry_grpc_ports(self): + # Test enabling OpenTelemetry metrics with both HTTP and gRPC ports + dec_1 = AKSPreviewManagedClusterCreateDecorator( + self.cmd, + self.client, + { + "enable_azure_monitor_metrics": True, + "enable_opentelemetry_metrics": True, + "opentelemetry_metrics_port": 8080, + "opentelemetry_metrics_port_grpc": 8082, + }, + CUSTOM_MGMT_AKS_PREVIEW, + ) + + mc_1 = self.models.ManagedCluster( + location="test_location", + identity=self.models.ManagedClusterIdentity(type="SystemAssigned"), + ) + dec_1.context.attach_mc(mc_1) + dec_mc_1 = dec_1.set_up_azure_monitor_profile(mc_1) + + otlp_metrics = dec_mc_1.azure_monitor_profile.app_monitoring.open_telemetry_metrics + self.assertTrue(otlp_metrics.enabled) + self.assertEqual(otlp_metrics.http_port, 8080) + self.assertEqual(otlp_metrics.grpc_port, 8082) + + def test_disable_opentelemetry_metrics_clears_grpc_port(self): + # Disabling OpenTelemetry metrics must clear BOTH http and grpc ports + dec_1 = AKSPreviewManagedClusterCreateDecorator( + self.cmd, + self.client, + {"disable_opentelemetry_metrics": True}, + CUSTOM_MGMT_AKS_PREVIEW, + ) + existing_metrics = self.models.ManagedClusterAzureMonitorProfileAppMonitoringOpenTelemetryMetrics( + enabled=True, http_port=8080, grpc_port=8082 + ) + app_monitoring = self.models.ManagedClusterAzureMonitorProfileAppMonitoring( + open_telemetry_metrics=existing_metrics + ) + mc_1 = self.models.ManagedCluster( + location="test_location", + azure_monitor_profile=self.models.ManagedClusterAzureMonitorProfile( + app_monitoring=app_monitoring + ), + ) + dec_1.context.attach_mc(mc_1) + dec_1._disable_opentelemetry_metrics(mc_1) + otlp_metrics = mc_1.azure_monitor_profile.app_monitoring.open_telemetry_metrics + self.assertFalse(otlp_metrics.enabled) + self.assertIsNone(otlp_metrics.http_port) + self.assertIsNone(otlp_metrics.grpc_port) + def test_set_up_azure_monitor_profile_with_opentelemetry(self): # Test enabling Azure Monitor metrics with OpenTelemetry metrics dec_1 = AKSPreviewManagedClusterCreateDecorator( @@ -17585,6 +17683,137 @@ def test_update_azure_monitor_profile_with_opentelemetry_logs(self): 9091, ) + def _otlp_gate_addon_profiles(self): + """Monitoring addon profile used by the OpenTelemetry prerequisites-gate tests.""" + return { + "omsagent": self.models.ManagedClusterAddonProfile( + enabled=True, + config={ + "logAnalyticsWorkspaceResourceID": "/subscriptions/test/resourceGroups/test/providers/Microsoft.OperationalInsights/workspaces/test-workspace" + }, + ) + } + + def _otlp_gate_mc_with_container_insights(self, otlp_logs_enabled=True): + """Cluster with Azure Monitor logs (Container Insights) on and Prometheus metrics off.""" + otel_logs_cls = self.models.ManagedClusterAzureMonitorProfileAppMonitoringOpenTelemetryLogsAndTraces + app_monitoring = self.models.ManagedClusterAzureMonitorProfileAppMonitoring( + open_telemetry_logs_and_traces=otel_logs_cls( + enabled=otlp_logs_enabled, http_port=4318, grpc_port=4317 + ), + ) + return self.models.ManagedCluster( + location="test_location", + addon_profiles=self._otlp_gate_addon_profiles(), + azure_monitor_profile=self.models.ManagedClusterAzureMonitorProfile( + container_insights=self.models.ManagedClusterAzureMonitorProfileContainerInsights( + enabled=True + ), + app_monitoring=app_monitoring, + ), + ) + + def _run_otlp_gate(self, raw_parameters, mc): + """Run update_azure_monitor_profile and return the patched prerequisites mock.""" + dec = AKSPreviewManagedClusterUpdateDecorator( + self.cmd, + self.client, + raw_parameters, + CUSTOM_MGMT_AKS_PREVIEW, + ) + dec.context.attach_mc(mc) + with patch( + "azext_aks_preview.managed_cluster_decorator.ensure_azure_monitor_profile_prerequisites" + ) as mock_prereq, patch.object( + dec.context, "get_subscription_id", return_value="test-subscription" + ), patch.object( + dec.context, "get_resource_group_name", return_value="test-rg" + ), patch.object( + dec.context, "get_name", return_value="test-cluster" + ), patch.object( + dec.context, "get_location", return_value="test-location" + ): + dec.update_azure_monitor_profile(mc) + return mock_prereq + + def test_opentelemetry_logs_traces_ports_do_not_trigger_prometheus_prerequisites(self): + # OpenTelemetry logs and traces ride the Container Insights pipeline. Setting their ports + # must NOT provision the Prometheus artifacts (Azure Monitor Workspace, DCE, DCR, DCRA, + # Grafana link, recording rules) created by ensure_azure_monitor_profile_prerequisites. + mock_prereq = self._run_otlp_gate( + { + "opentelemetry_logs_port": 2331, + "opentelemetry_logs_traces_port_grpc": 2332, + }, + self._otlp_gate_mc_with_container_insights(), + ) + mock_prereq.assert_not_called() + + def test_enable_opentelemetry_logs_traces_does_not_trigger_prometheus_prerequisites(self): + mock_prereq = self._run_otlp_gate( + {"enable_opentelemetry_logs": True}, + self._otlp_gate_mc_with_container_insights(otlp_logs_enabled=False), + ) + mock_prereq.assert_not_called() + + def test_disable_opentelemetry_logs_traces_does_not_trigger_prometheus_prerequisites(self): + mock_prereq = self._run_otlp_gate( + {"disable_opentelemetry_logs": True}, + self._otlp_gate_mc_with_container_insights(), + ) + mock_prereq.assert_not_called() + + def test_opentelemetry_metrics_still_triggers_prometheus_prerequisites(self): + # Guard against over-correcting: OpenTelemetry metrics DOES need the Prometheus artifacts. + mc = self.models.ManagedCluster( + location="test_location", + identity=self.models.ManagedClusterIdentity(type="SystemAssigned"), + azure_monitor_profile=self.models.ManagedClusterAzureMonitorProfile( + metrics=self.models.ManagedClusterAzureMonitorProfileMetrics(enabled=True), + ), + ) + mock_prereq = self._run_otlp_gate( + { + "enable_azure_monitor_metrics": True, + "enable_opentelemetry_metrics": True, + "opentelemetry_metrics_port_grpc": 8082, + }, + mc, + ) + mock_prereq.assert_called_once() + + def test_opentelemetry_mixed_signals_trigger_prometheus_prerequisites_once(self): + # Metrics and logs/traces arguments in the same command: the metrics side still onboards, + # and the logs/traces side neither suppresses nor duplicates that call. + otel_logs_cls = self.models.ManagedClusterAzureMonitorProfileAppMonitoringOpenTelemetryLogsAndTraces + mc = self.models.ManagedCluster( + location="test_location", + identity=self.models.ManagedClusterIdentity(type="SystemAssigned"), + addon_profiles=self._otlp_gate_addon_profiles(), + azure_monitor_profile=self.models.ManagedClusterAzureMonitorProfile( + metrics=self.models.ManagedClusterAzureMonitorProfileMetrics(enabled=True), + container_insights=self.models.ManagedClusterAzureMonitorProfileContainerInsights( + enabled=True + ), + app_monitoring=self.models.ManagedClusterAzureMonitorProfileAppMonitoring( + open_telemetry_logs_and_traces=otel_logs_cls( + enabled=True, http_port=4318, grpc_port=4317 + ), + ), + ), + ) + mock_prereq = self._run_otlp_gate( + { + "enable_azure_monitor_metrics": True, + "enable_opentelemetry_metrics": True, + "opentelemetry_metrics_port_grpc": 8082, + "opentelemetry_logs_port": 2331, + "opentelemetry_logs_traces_port_grpc": 2332, + }, + mc, + ) + mock_prereq.assert_called_once() + def test_disable_azure_monitor_app_monitoring_preserves_opentelemetry(self): # Test that disabling Azure Monitor app monitoring preserves existing OpenTelemetry configuration dec = AKSPreviewManagedClusterUpdateDecorator( diff --git a/src/aks-preview/azext_aks_preview/tests/latest/test_validators.py b/src/aks-preview/azext_aks_preview/tests/latest/test_validators.py index f99641fcdb6..396f18b0acf 100644 --- a/src/aks-preview/azext_aks_preview/tests/latest/test_validators.py +++ b/src/aks-preview/azext_aks_preview/tests/latest/test_validators.py @@ -2037,9 +2037,12 @@ def test_valid_custom_endpoints(self): class OpenTelemetryPortsNamespace: - def __init__(self, opentelemetry_metrics_port=None, opentelemetry_logs_port=None): + def __init__(self, opentelemetry_metrics_port=None, opentelemetry_logs_port=None, + opentelemetry_metrics_port_grpc=None, opentelemetry_logs_traces_port_grpc=None): self.opentelemetry_metrics_port = opentelemetry_metrics_port self.opentelemetry_logs_port = opentelemetry_logs_port + self.opentelemetry_metrics_port_grpc = opentelemetry_metrics_port_grpc + self.opentelemetry_logs_traces_port_grpc = opentelemetry_logs_traces_port_grpc class TestValidateOpenTelemetryPorts(unittest.TestCase): @@ -2063,14 +2066,52 @@ def test_different_ports_specified(self): ) validators.validate_opentelemetry_ports(namespace) - def test_same_ports_throws_error(self): + def test_all_four_distinct_ports_specified(self): + namespace = OpenTelemetryPortsNamespace( + opentelemetry_metrics_port=8080, + opentelemetry_metrics_port_grpc=8081, + opentelemetry_logs_port=8082, + opentelemetry_logs_traces_port_grpc=8083, + ) + validators.validate_opentelemetry_ports(namespace) + + def test_same_http_ports_throws_error(self): namespace = OpenTelemetryPortsNamespace( opentelemetry_metrics_port=8080, opentelemetry_logs_port=8080 ) err = ( - "OpenTelemetry metrics port and logs port cannot be the same. " - "Please specify different ports for --opentelemetry-metrics-port and --opentelemetry-logs-port." + "OpenTelemetry ports must all be different. " + "--opentelemetry-metrics-port-http and --opentelemetry-logs-traces-port-http " + "cannot both be set to 8080." + ) + with self.assertRaises(ArgumentUsageError) as cm: + validators.validate_opentelemetry_ports(namespace) + self.assertEqual(str(cm.exception), err) + + def test_same_metrics_http_and_grpc_throws_error(self): + namespace = OpenTelemetryPortsNamespace( + opentelemetry_metrics_port=8080, + opentelemetry_metrics_port_grpc=8080, + ) + err = ( + "OpenTelemetry ports must all be different. " + "--opentelemetry-metrics-port-http and --opentelemetry-metrics-port-grpc " + "cannot both be set to 8080." + ) + with self.assertRaises(ArgumentUsageError) as cm: + validators.validate_opentelemetry_ports(namespace) + self.assertEqual(str(cm.exception), err) + + def test_same_grpc_ports_throws_error(self): + namespace = OpenTelemetryPortsNamespace( + opentelemetry_metrics_port_grpc=9090, + opentelemetry_logs_traces_port_grpc=9090, + ) + err = ( + "OpenTelemetry ports must all be different. " + "--opentelemetry-metrics-port-grpc and --opentelemetry-logs-traces-port-grpc " + "cannot both be set to 9090." ) with self.assertRaises(ArgumentUsageError) as cm: validators.validate_opentelemetry_ports(namespace) @@ -2078,28 +2119,42 @@ def test_same_ports_throws_error(self): def test_metrics_port_below_range(self): namespace = OpenTelemetryPortsNamespace(opentelemetry_metrics_port=0) - err = "OpenTelemetry metrics port must be between 1 and 65535, got 0." + err = "OpenTelemetry port --opentelemetry-metrics-port-http must be between 1 and 65535, got 0." with self.assertRaises(ArgumentUsageError) as cm: validators.validate_opentelemetry_ports(namespace) self.assertEqual(str(cm.exception), err) def test_metrics_port_above_range(self): namespace = OpenTelemetryPortsNamespace(opentelemetry_metrics_port=65536) - err = "OpenTelemetry metrics port must be between 1 and 65535, got 65536." + err = "OpenTelemetry port --opentelemetry-metrics-port-http must be between 1 and 65535, got 65536." + with self.assertRaises(ArgumentUsageError) as cm: + validators.validate_opentelemetry_ports(namespace) + self.assertEqual(str(cm.exception), err) + + def test_metrics_grpc_port_above_range(self): + namespace = OpenTelemetryPortsNamespace(opentelemetry_metrics_port_grpc=70000) + err = "OpenTelemetry port --opentelemetry-metrics-port-grpc must be between 1 and 65535, got 70000." with self.assertRaises(ArgumentUsageError) as cm: validators.validate_opentelemetry_ports(namespace) self.assertEqual(str(cm.exception), err) def test_logs_port_below_range(self): namespace = OpenTelemetryPortsNamespace(opentelemetry_logs_port=-1) - err = "OpenTelemetry logs port must be between 1 and 65535, got -1." + err = "OpenTelemetry port --opentelemetry-logs-traces-port-http must be between 1 and 65535, got -1." with self.assertRaises(ArgumentUsageError) as cm: validators.validate_opentelemetry_ports(namespace) self.assertEqual(str(cm.exception), err) def test_logs_port_above_range(self): namespace = OpenTelemetryPortsNamespace(opentelemetry_logs_port=100000) - err = "OpenTelemetry logs port must be between 1 and 65535, got 100000." + err = "OpenTelemetry port --opentelemetry-logs-traces-port-http must be between 1 and 65535, got 100000." + with self.assertRaises(ArgumentUsageError) as cm: + validators.validate_opentelemetry_ports(namespace) + self.assertEqual(str(cm.exception), err) + + def test_logs_grpc_port_below_range(self): + namespace = OpenTelemetryPortsNamespace(opentelemetry_logs_traces_port_grpc=0) + err = "OpenTelemetry port --opentelemetry-logs-traces-port-grpc must be between 1 and 65535, got 0." with self.assertRaises(ArgumentUsageError) as cm: validators.validate_opentelemetry_ports(namespace) self.assertEqual(str(cm.exception), err) @@ -2113,6 +2168,7 @@ def test_valid_edge_case_ports(self): validators.validate_opentelemetry_ports(namespace) + class OpenTelemetryMetricsDependenciesNamespace: def __init__(self, enable_opentelemetry_metrics=False, disable_opentelemetry_metrics=False, enable_azure_monitor_metrics=False, enable_azuremonitormetrics=False): @@ -2220,7 +2276,7 @@ def test_mutually_exclusive_flags_throws_error(self): enable_opentelemetry_logs=True, disable_opentelemetry_logs=True ) - err = "Cannot specify both --enable-opentelemetry-logs and --disable-opentelemetry-logs at the same time." + err = "Cannot specify both --enable-opentelemetry-logs-traces and --disable-opentelemetry-logs-traces at the same time." with self.assertRaises(MutuallyExclusiveArgumentError) as cm: validators.validate_opentelemetry_logs_dependencies(namespace) self.assertEqual(str(cm.exception), err) @@ -2258,7 +2314,7 @@ def test_mutually_exclusive_flags_throws_error_for_update(self): enable_opentelemetry_logs=True, disable_opentelemetry_logs=True ) - err = "Cannot specify both --enable-opentelemetry-logs and --disable-opentelemetry-logs at the same time." + err = "Cannot specify both --enable-opentelemetry-logs-traces and --disable-opentelemetry-logs-traces at the same time." with self.assertRaises(MutuallyExclusiveArgumentError) as cm: validators.validate_opentelemetry_logs_dependencies_for_update(namespace) self.assertEqual(str(cm.exception), err) @@ -2269,7 +2325,8 @@ def __init__(self, enable_opentelemetry_metrics=False, disable_opentelemetry_met enable_opentelemetry_logs=False, disable_opentelemetry_logs=False, enable_azure_monitor_metrics=False, enable_azuremonitormetrics=False, enable_azure_monitor_logs=False, - opentelemetry_metrics_port=None, opentelemetry_logs_port=None): + opentelemetry_metrics_port=None, opentelemetry_logs_port=None, + opentelemetry_metrics_port_grpc=None, opentelemetry_logs_traces_port_grpc=None): self.enable_opentelemetry_metrics = enable_opentelemetry_metrics self.disable_opentelemetry_metrics = disable_opentelemetry_metrics self.enable_opentelemetry_logs = enable_opentelemetry_logs @@ -2279,6 +2336,8 @@ def __init__(self, enable_opentelemetry_metrics=False, disable_opentelemetry_met self.enable_azure_monitor_logs = enable_azure_monitor_logs self.opentelemetry_metrics_port = opentelemetry_metrics_port self.opentelemetry_logs_port = opentelemetry_logs_port + self.opentelemetry_metrics_port_grpc = opentelemetry_metrics_port_grpc + self.opentelemetry_logs_traces_port_grpc = opentelemetry_logs_traces_port_grpc class TestValidateAzureMonitorAndOpenTelemetryForCreate(unittest.TestCase): @@ -2302,8 +2361,9 @@ def test_port_conflict_throws_error(self): opentelemetry_logs_port=8080 ) err = ( - "OpenTelemetry metrics port and logs port cannot be the same. " - "Please specify different ports for --opentelemetry-metrics-port and --opentelemetry-logs-port." + "OpenTelemetry ports must all be different. " + "--opentelemetry-metrics-port-http and --opentelemetry-logs-traces-port-http " + "cannot both be set to 8080." ) with self.assertRaises(ArgumentUsageError) as cm: validators.validate_azure_monitor_and_opentelemetry_for_create(namespace) @@ -2352,8 +2412,9 @@ def test_port_conflict_throws_error_for_update(self): opentelemetry_logs_port=8080 ) err = ( - "OpenTelemetry metrics port and logs port cannot be the same. " - "Please specify different ports for --opentelemetry-metrics-port and --opentelemetry-logs-port." + "OpenTelemetry ports must all be different. " + "--opentelemetry-metrics-port-http and --opentelemetry-logs-traces-port-http " + "cannot both be set to 8080." ) with self.assertRaises(ArgumentUsageError) as cm: validators.validate_azure_monitor_and_opentelemetry_for_update(namespace) @@ -2374,7 +2435,7 @@ def test_mutually_exclusive_logs_flags_throws_error(self): enable_opentelemetry_logs=True, disable_opentelemetry_logs=True ) - err = "Cannot specify both --enable-opentelemetry-logs and --disable-opentelemetry-logs at the same time." + err = "Cannot specify both --enable-opentelemetry-logs-traces and --disable-opentelemetry-logs-traces at the same time." with self.assertRaises(MutuallyExclusiveArgumentError) as cm: validators.validate_azure_monitor_and_opentelemetry_for_update(namespace) self.assertEqual(str(cm.exception), err) diff --git a/src/aks-preview/linter_exclusions.yml b/src/aks-preview/linter_exclusions.yml index 685a4f9efc2..480b2dddbeb 100644 --- a/src/aks-preview/linter_exclusions.yml +++ b/src/aks-preview/linter_exclusions.yml @@ -138,15 +138,33 @@ aks create: opentelemetry_metrics_port: rule_exclusions: - option_length_too_long + opentelemetry_metrics_port_deprecated: + rule_exclusions: + - option_length_too_long + opentelemetry_metrics_port_grpc: + rule_exclusions: + - option_length_too_long enable_opentelemetry_logs: rule_exclusions: - option_length_too_long + enable_opentelemetry_logs_deprecated: + rule_exclusions: + - option_length_too_long disable_opentelemetry_logs: rule_exclusions: - option_length_too_long + disable_opentelemetry_logs_deprecated: + rule_exclusions: + - option_length_too_long opentelemetry_logs_port: rule_exclusions: - option_length_too_long + opentelemetry_logs_port_deprecated: + rule_exclusions: + - option_length_too_long + opentelemetry_logs_traces_port_grpc: + rule_exclusions: + - option_length_too_long enable_static_egress_gateway: rule_exclusions: - option_length_too_long @@ -317,15 +335,33 @@ aks update: opentelemetry_metrics_port: rule_exclusions: - option_length_too_long + opentelemetry_metrics_port_deprecated: + rule_exclusions: + - option_length_too_long + opentelemetry_metrics_port_grpc: + rule_exclusions: + - option_length_too_long enable_opentelemetry_logs: rule_exclusions: - option_length_too_long + enable_opentelemetry_logs_deprecated: + rule_exclusions: + - option_length_too_long disable_opentelemetry_logs: rule_exclusions: - option_length_too_long + disable_opentelemetry_logs_deprecated: + rule_exclusions: + - option_length_too_long opentelemetry_logs_port: rule_exclusions: - option_length_too_long + opentelemetry_logs_port_deprecated: + rule_exclusions: + - option_length_too_long + opentelemetry_logs_traces_port_grpc: + rule_exclusions: + - option_length_too_long bootstrap_artifact_source: rule_exclusions: - option_length_too_long diff --git a/src/aks-preview/setup.py b/src/aks-preview/setup.py index 507acbff9e0..e516173f0a3 100644 --- a/src/aks-preview/setup.py +++ b/src/aks-preview/setup.py @@ -9,7 +9,7 @@ from setuptools import find_packages, setup -VERSION = "21.0.0b14" +VERSION = "21.0.0b15" CLASSIFIERS = [ "Development Status :: 4 - Beta",