Skip to content

Commit 156a08a

Browse files
authored
Support AAD auth for live metrics (#37258)
1 parent bdfda87 commit 156a08a

File tree

8 files changed

+57
-34
lines changed

8 files changed

+57
-34
lines changed

sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
- Allow passing in of custom `TracerProvider` for `AzureMonitorTraceExporter`
88
([#36363](https://github.com/Azure/azure-sdk-for-python/pull/36363))
9+
- Support AAD Auth for live metrics
10+
([#37258](https://github.com/Azure/azure-sdk-for-python/pull/37258))
911

1012
### Breaking Changes
1113

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,4 +200,8 @@
200200

201201
_SAMPLE_RATE_KEY = "_MS.sampleRate"
202202

203+
# AAD Auth
204+
205+
_APPLICATION_INSIGHTS_RESOURCE_SCOPE = "https://monitor.azure.com//.default"
206+
203207
# cSpell:disable

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_quickpulse/_exporter.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@
4646
_metric_to_quick_pulse_data_points,
4747
)
4848
from azure.monitor.opentelemetry.exporter._connection_string_parser import ConnectionStringParser
49-
from azure.monitor.opentelemetry.exporter._utils import _ticks_since_dot_net_epoch, PeriodicTask
49+
from azure.monitor.opentelemetry.exporter._utils import (
50+
_get_auth_policy,
51+
_ticks_since_dot_net_epoch,
52+
PeriodicTask,
53+
)
5054

5155

5256
_logger = logging.getLogger(__name__)
@@ -75,19 +79,20 @@ class _UnsuccessfulQuickPulsePostError(Exception):
7579

7680
class _QuickpulseExporter(MetricExporter):
7781

78-
def __init__(self, connection_string: Optional[str]) -> None:
82+
def __init__(self, **kwargs: Any) -> None:
7983
"""Metric exporter for Quickpulse.
8084
8185
:param str connection_string: The connection string used for your Application Insights resource.
86+
:keyword TokenCredential credential: Token credential, such as ManagedIdentityCredential or
87+
ClientSecretCredential, used for Azure Active Directory (AAD) authentication. Defaults to None.
8288
:rtype: None
8389
"""
84-
parsed_connection_string = ConnectionStringParser(connection_string)
90+
parsed_connection_string = ConnectionStringParser(kwargs.get('connection_string'))
8591

8692
self._live_endpoint = parsed_connection_string.live_endpoint
8793
self._instrumentation_key = parsed_connection_string.instrumentation_key
88-
# TODO: Support AADaudience (scope)/credentials
89-
# Pass `None` for now until swagger definition is fixed
90-
config = QuickpulseClientConfiguration(credential=None) # type: ignore
94+
self._credential = kwargs.get('credential')
95+
config = QuickpulseClientConfiguration(credential=self._credential) # type: ignore
9196
qp_redirect_policy = _QuickpulseRedirectPolicy(permit_redirects=False)
9297
policies = [
9398
# Custom redirect policy for QP
@@ -96,13 +101,13 @@ def __init__(self, connection_string: Optional[str]) -> None:
96101
ContentDecodePolicy(),
97102
# Logging for client calls
98103
config.http_logging_policy,
99-
# TODO: Support AADaudience (scope)/credentials
104+
_get_auth_policy(self._credential, config.authentication_policy),
100105
config.authentication_policy,
101106
# Explicitly disabling to avoid tracing live metrics calls
102107
# DistributedTracingPolicy(),
103108
]
104109
self._client = QuickpulseClient(
105-
credential=None, # type: ignore
110+
credential=self._credential, # type: ignore
106111
endpoint=self._live_endpoint,
107112
policies=policies
108113
)

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_quickpulse/_live_metrics.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Licensed under the MIT License.
33
# cSpell:disable
44
from datetime import datetime
5-
from typing import Any, Iterable, Optional
5+
from typing import Any, Iterable
66

77
import platform
88
import psutil
@@ -70,21 +70,25 @@ def enable_live_metrics(**kwargs: Any) -> None: # pylint: disable=C4758
7070
7171
:keyword str connection_string: The connection string used for your Application Insights resource.
7272
:keyword Resource resource: The OpenTelemetry Resource used for this Python application.
73+
:keyword TokenCredential credential: Token credential, such as ManagedIdentityCredential or
74+
ClientSecretCredential, used for Azure Active Directory (AAD) authentication. Defaults to None.
7375
:rtype: None
7476
"""
75-
_QuickpulseManager(kwargs.get('connection_string'), kwargs.get('resource'))
77+
_QuickpulseManager(**kwargs)
7678
set_statsbeat_live_metrics_feature_set()
7779

7880

7981
# pylint: disable=protected-access,too-many-instance-attributes
8082
class _QuickpulseManager(metaclass=Singleton):
8183

82-
def __init__(self, connection_string: Optional[str], resource: Optional[Resource]) -> None:
84+
def __init__(self, **kwargs: Any) -> None:
8385
_set_global_quickpulse_state(_QuickpulseState.PING_SHORT)
84-
self._exporter = _QuickpulseExporter(connection_string)
86+
self._exporter = _QuickpulseExporter(**kwargs)
8587
part_a_fields = {}
86-
if resource:
87-
part_a_fields = _populate_part_a_fields(resource)
88+
resource = kwargs.get('resource')
89+
if not resource:
90+
resource = Resource.create({})
91+
part_a_fields = _populate_part_a_fields(resource)
8892
id_generator = RandomIdGenerator()
8993
self._base_monitoring_data_point = MonitoringDataPoint(
9094
version=_get_sdk_version(),
@@ -97,7 +101,10 @@ def __init__(self, connection_string: Optional[str], resource: Optional[Resource
97101
performance_collection_supported=True,
98102
)
99103
self._reader = _QuickpulseMetricReader(self._exporter, self._base_monitoring_data_point)
100-
self._meter_provider = MeterProvider([self._reader])
104+
self._meter_provider = MeterProvider(
105+
metric_readers=[self._reader],
106+
resource=resource,
107+
)
101108
self._meter = self._meter_provider.get_meter("azure_monitor_live_metrics")
102109

103110
self._request_duration = self._meter.create_histogram(

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_utils.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
from opentelemetry.sdk.util import ns_to_iso_str
1818
from opentelemetry.util.types import Attributes
1919

20+
from azure.core.pipeline.policies import BearerTokenCredentialPolicy
2021
from azure.monitor.opentelemetry.exporter._generated.models import ContextTagKeys, TelemetryItem
2122
from azure.monitor.opentelemetry.exporter._version import VERSION as ext_version
2223
from azure.monitor.opentelemetry.exporter._constants import (
24+
_AKS_ARM_NAMESPACE_ID,
25+
_APPLICATION_INSIGHTS_RESOURCE_SCOPE,
2326
_INSTRUMENTATIONS_BIT_MAP,
24-
_WEBSITE_SITE_NAME,
2527
_FUNCTIONS_WORKER_RUNTIME,
26-
_AKS_ARM_NAMESPACE_ID,
28+
_WEBSITE_SITE_NAME,
2729
)
2830

2931

@@ -260,6 +262,19 @@ def _filter_custom_properties(properties: Attributes, filter=None) -> Dict[str,
260262
return truncated_properties
261263

262264

265+
def _get_auth_policy(credential, default_auth_policy):
266+
if credential:
267+
if hasattr(credential, 'get_token'):
268+
return BearerTokenCredentialPolicy(
269+
credential,
270+
_APPLICATION_INSIGHTS_RESOURCE_SCOPE,
271+
)
272+
raise ValueError(
273+
'Must pass in valid TokenCredential.'
274+
)
275+
return default_auth_policy
276+
277+
263278
class Singleton(type):
264279
_instance = None
265280
def __call__(cls, *args, **kwargs):

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/_base.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
from azure.core.exceptions import HttpResponseError, ServiceRequestError
1212
from azure.core.pipeline.policies import (
13-
BearerTokenCredentialPolicy,
1413
ContentDecodePolicy,
1514
HttpLoggingPolicy,
1615
RedirectPolicy,
@@ -44,6 +43,7 @@
4443
)
4544
from azure.monitor.opentelemetry.exporter._connection_string_parser import ConnectionStringParser
4645
from azure.monitor.opentelemetry.exporter._storage import LocalFileStorage
46+
from azure.monitor.opentelemetry.exporter._utils import _get_auth_policy
4747
from azure.monitor.opentelemetry.exporter.statsbeat._state import (
4848
get_statsbeat_initial_success,
4949
get_statsbeat_shutdown,
@@ -58,7 +58,6 @@
5858
_AZURE_TEMPDIR_PREFIX = "Microsoft/AzureMonitor"
5959
_TEMPDIR_PREFIX = "opentelemetry-python-"
6060
_SERVICE_API_LATEST = "2020-09-15_Preview"
61-
_APPLICATION_INSIGHTS_RESOURCE_SCOPE = "https://monitor.azure.com//.default"
6261

6362
class ExportResult(Enum):
6463
SUCCESS = 0
@@ -346,19 +345,6 @@ def _is_stats_exporter(self):
346345
return self.__class__.__name__ == "_StatsBeatExporter"
347346

348347

349-
def _get_auth_policy(credential, default_auth_policy):
350-
if credential:
351-
if hasattr(credential, 'get_token'):
352-
return BearerTokenCredentialPolicy(
353-
credential,
354-
_APPLICATION_INSIGHTS_RESOURCE_SCOPE,
355-
)
356-
raise ValueError(
357-
'Must pass in valid TokenCredential.'
358-
)
359-
return default_auth_policy
360-
361-
362348
def _is_invalid_code(response_code: Optional[int]) -> bool:
363349
"""Determine if response is a invalid response.
364350

sdk/monitor/azure-monitor-opentelemetry-exporter/tests/quickpulse/test_exporter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def setUpClass(cls):
9191
performance_collection_supported=True,
9292
)
9393
cls._exporter = _QuickpulseExporter(
94-
"InstrumentationKey=4321abcd-5678-4efa-8abc-1234567890ac;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/"
94+
connection_string="InstrumentationKey=4321abcd-5678-4efa-8abc-1234567890ac;LiveEndpoint=https://eastus.livediagnostics.monitor.azure.com/"
9595
)
9696
cls._reader = _QuickpulseMetricReader(
9797
cls._exporter,

sdk/monitor/azure-monitor-opentelemetry-exporter/tests/quickpulse/test_live_metrics.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ def test_enable_live_metrics(self, manager_mock):
6464
connection_string="test_cs",
6565
resource=mock_resource,
6666
)
67-
manager_mock.assert_called_with("test_cs", mock_resource)
67+
manager_mock.assert_called_with(
68+
connection_string="test_cs",
69+
resource=mock_resource
70+
)
6871

6972

7073
class TestQuickpulseManager(unittest.TestCase):
@@ -118,6 +121,7 @@ def test_init(self, generator_mock):
118121
self.assertEqual(qpm._reader._base_monitoring_data_point, qpm._base_monitoring_data_point)
119122
self.assertTrue(isinstance(qpm._meter_provider, MeterProvider))
120123
self.assertEqual(qpm._meter_provider._sdk_config.metric_readers, [qpm._reader])
124+
self.assertEqual(qpm._meter_provider._sdk_config.resource, resource)
121125
self.assertTrue(isinstance(qpm._meter, Meter))
122126
self.assertEqual(qpm._meter.name, "azure_monitor_live_metrics")
123127
self.assertTrue(isinstance(qpm._request_duration, Histogram))

0 commit comments

Comments
 (0)