diff --git a/CHANGELOG.md b/CHANGELOG.md
index 909eea507d..f8a82eec8f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
+### Fixed
+
+- `opentelemetry-instrumentation-asgi` Fix UnboundLocalError local variable 'start' referenced before assignment
+ ([#1889](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1889))
+
+### Added
+
+- Added Azure VM Resource Detector
+ ([#1904](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1904))
+
## Version 1.19.0/0.40b0 (2023-07-13)
- `opentelemetry-instrumentation-asgi` Add `http.server.request.size` metric
([#1867](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1867))
diff --git a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py
index a1fa1f8e31..c0dcd39fd2 100644
--- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py
+++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py
@@ -538,6 +538,7 @@ async def __call__(self, scope, receive, send):
receive: An awaitable callable yielding dictionaries
send: An awaitable callable taking a single dictionary as argument.
"""
+ start = default_timer()
if scope["type"] not in ("http", "websocket"):
return await self.app(scope, receive, send)
@@ -591,7 +592,6 @@ async def __call__(self, scope, receive, send):
send,
duration_attrs,
)
- start = default_timer()
await self.app(scope, otel_receive, otel_send)
finally:
diff --git a/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py b/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py
index 8ca82d0226..cb22174482 100644
--- a/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py
+++ b/instrumentation/opentelemetry-instrumentation-asgi/tests/test_asgi_middleware.py
@@ -14,6 +14,7 @@
# pylint: disable=too-many-lines
+import asyncio
import sys
import unittest
from timeit import default_timer
@@ -796,5 +797,38 @@ async def wrapped_app(scope, receive, send):
)
+class TestAsgiApplicationRaisingError(AsgiTestBase):
+ def tearDown(self):
+ pass
+
+ @mock.patch(
+ "opentelemetry.instrumentation.asgi.collect_custom_request_headers_attributes",
+ side_effect=ValueError("whatever"),
+ )
+ def test_asgi_issue_1883(
+ self, mock_collect_custom_request_headers_attributes
+ ):
+ """
+ Test that exception UnboundLocalError local variable 'start' referenced before assignment is not raised
+ See https://github.com/open-telemetry/opentelemetry-python-contrib/issues/1883
+ """
+ app = otel_asgi.OpenTelemetryMiddleware(simple_asgi)
+ self.seed_app(app)
+ self.send_default_request()
+ try:
+ asyncio.get_event_loop().run_until_complete(
+ self.communicator.stop()
+ )
+ except ValueError as exc_info:
+ self.assertEqual(exc_info.args[0], "whatever")
+ except Exception as exc_info: # pylint: disable=W0703
+ self.fail(
+ "expecting ValueError('whatever'), received instead: "
+ + str(exc_info)
+ )
+ else:
+ self.fail("expecting ValueError('whatever')")
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/resource/opentelemetry-resource-detector-azure-vm/LICENSE b/resource/opentelemetry-resource-detector-azure-vm/LICENSE
new file mode 100644
index 0000000000..63447fd8bb
--- /dev/null
+++ b/resource/opentelemetry-resource-detector-azure-vm/LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) Microsoft Corporation.
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/resource/opentelemetry-resource-detector-azure-vm/MANIFEST.rst b/resource/opentelemetry-resource-detector-azure-vm/MANIFEST.rst
new file mode 100644
index 0000000000..2906eeef0f
--- /dev/null
+++ b/resource/opentelemetry-resource-detector-azure-vm/MANIFEST.rst
@@ -0,0 +1,9 @@
+graft src
+graft tests
+global-exclude *.pyc
+global-exclude *.pyo
+global-exclude __pycache__/*
+include CHANGELOG.md
+include MANIFEST.in
+include README.rst
+include LICENSE
\ No newline at end of file
diff --git a/resource/opentelemetry-resource-detector-azure-vm/README.rst b/resource/opentelemetry-resource-detector-azure-vm/README.rst
new file mode 100644
index 0000000000..28ca64efac
--- /dev/null
+++ b/resource/opentelemetry-resource-detector-azure-vm/README.rst
@@ -0,0 +1,66 @@
+OpenTelemetry Resource detectors for Azure Virtual Machines
+==========================================================
+
+|pypi|
+
+.. |pypi| image:: https://badge.fury.io/py/opentelemetry-resource-detector-azure-vm.svg
+ :target: https://pypi.org/project/opentelemetry-resource-detector-azure-vm/
+
+
+This library provides custom resource detector for Azure VMs. OpenTelemetry Python has an experimental feature whereby Resource Detectors can be injected to Resource Attributes. This package includes a resource detector for Azure VM. This detector fills out the following Resource Attributes:
+ * `azure.vm.scaleset.name`
+ * `azure.vm.sku`
+ * `cloud.platform`
+ * `cloud.provider`
+ * `cloud.region`
+ * `cloud.resource_id`
+ * `host.id`
+ * `host.name`
+ * `host.type`
+ * `os.type`
+ * `os.version`
+ * `service.instance.id`
+
+ For more information, see the Semantic Conventions for Cloud Resource Attributes.
+
+Installation
+------------
+
+::
+
+ pip install opentelemetry-resource-detector-azure-vm
+
+---------------------------
+
+Usage example for `opentelemetry-resource-detector-azure-vm`
+
+.. code-block:: python
+
+ from opentelemetry import trace
+ from opentelemetry.sdk.trace import TracerProvider
+ from opentelemetry.resource.detector.azure.vm import (
+ AzureVMResourceDetector,
+ )
+ from opentelemetry.sdk.resources import get_aggregated_resources
+
+
+ trace.set_tracer_provider(
+ TracerProvider(
+ resource=get_aggregated_resources(
+ [
+ AzureVMResourceDetector(),
+ ]
+ ),
+ )
+ )
+
+You can also enable the Azure VM Resource Detector by adding `azure_vm` to the `OTEL_EXPERIMENTAL_RESOURCE_DETECTORS` environment variable:
+
+`export OTEL_EXPERIMENTAL_RESOURCE_DETECTORS=azure_vm`
+
+References
+----------
+
+* `OpenTelemetry Project `_
+* `Resource Detector Docs `
+* `Cloud Semantic Conventions `_
diff --git a/resource/opentelemetry-resource-detector-azure-vm/pyproject.toml b/resource/opentelemetry-resource-detector-azure-vm/pyproject.toml
new file mode 100644
index 0000000000..c1c7b42209
--- /dev/null
+++ b/resource/opentelemetry-resource-detector-azure-vm/pyproject.toml
@@ -0,0 +1,50 @@
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[project]
+name = "opentelemetry-resource-detector-container"
+dynamic = ["version"]
+description = "Container Resource Detector for OpenTelemetry"
+readme = "README.rst"
+license = "Apache-2.0"
+requires-python = ">=3.7"
+authors = [
+ { name = "OpenTelemetry Authors", email = "cncf-opentelemetry-contributors@lists.cncf.io" },
+]
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: Apache Software License",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+]
+dependencies = [
+ "opentelemetry-sdk ~= 1.19",
+]
+
+[project.optional-dependencies]
+test = []
+
+[project.entry-points.opentelemetry_resource_detector]
+azure_vm = "opentelemetry.resource.detector.azure.vm:AzureVMResourceDetector"
+
+[project.urls]
+Homepage = "https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/resource/opentelemetry-resource-detector-azure-vm"
+
+[tool.hatch.version]
+path = "src/opentelemetry/resource/detector/azure/vm/version.py"
+
+[tool.hatch.build.targets.sdist]
+include = [
+ "/src",
+ "/tests",
+]
+
+[tool.hatch.build.targets.wheel]
+packages = ["src/opentelemetry"]
diff --git a/resource/opentelemetry-resource-detector-azure-vm/src/opentelemetry/resource/detector/azure/vm/__init__.py b/resource/opentelemetry-resource-detector-azure-vm/src/opentelemetry/resource/detector/azure/vm/__init__.py
new file mode 100644
index 0000000000..2f686dfa7a
--- /dev/null
+++ b/resource/opentelemetry-resource-detector-azure-vm/src/opentelemetry/resource/detector/azure/vm/__init__.py
@@ -0,0 +1,85 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+from json import loads
+from logging import getLogger
+from os import environ
+from urllib.request import Request, urlopen
+from urllib.error import URLError
+
+from opentelemetry.sdk.resources import ResourceDetector, Resource
+from opentelemetry.semconv.resource import ResourceAttributes, CloudPlatformValues, CloudProviderValues
+
+
+# TODO: Remove when cloud resource id is no longer missing in Resource Attributes
+_CLOUD_RESOURCE_ID_RESOURCE_ATTRIBUTE = "cloud.resource_id"
+_AZURE_VM_METADATA_ENDPOINT = "http://169.254.169.254/metadata/instance/compute?api-version=2021-12-13&format=json"
+_AZURE_VM_SCALE_SET_NAME_ATTRIBUTE = "azure.vm.scaleset.name"
+_AZURE_VM_SKU_ATTRIBUTE = "azure.vm.sku"
+_logger = getLogger(__name__)
+
+EXPECTED_AZURE_AMS_ATTRIBUTES = [
+ _AZURE_VM_SCALE_SET_NAME_ATTRIBUTE,
+ _AZURE_VM_SKU_ATTRIBUTE,
+ ResourceAttributes.CLOUD_PLATFORM,
+ ResourceAttributes.CLOUD_PROVIDER,
+ ResourceAttributes.CLOUD_REGION,
+ _CLOUD_RESOURCE_ID_RESOURCE_ATTRIBUTE,
+ ResourceAttributes.HOST_ID,
+ ResourceAttributes.HOST_NAME,
+ ResourceAttributes.HOST_TYPE,
+ ResourceAttributes.OS_TYPE,
+ ResourceAttributes.OS_VERSION,
+ ResourceAttributes.SERVICE_INSTANCE_ID,
+]
+
+class AzureVMResourceDetector(ResourceDetector):
+ # pylint: disable=no-self-use
+ def detect(self) -> "Resource":
+ attributes = {}
+ metadata_json = _AzureVMMetadataServiceRequestor().get_azure_vm_metadata()
+ if not metadata_json:
+ return Resource(attributes)
+ for attribute_key in EXPECTED_AZURE_AMS_ATTRIBUTES:
+ attributes[attribute_key] = _AzureVMMetadataServiceRequestor().get_attribute_from_metadata(metadata_json, attribute_key)
+ return Resource(attributes)
+
+class _AzureVMMetadataServiceRequestor:
+ def get_azure_vm_metadata(self):
+ request = Request(_AZURE_VM_METADATA_ENDPOINT)
+ request.add_header("Metadata", "True")
+ try:
+ response = urlopen(request).read()
+ return loads(response)["compute"]
+ except URLError:
+ # Not on Azure VM
+ return None
+ except Exception as e:
+ _logger.exception("Failed to receive Azure VM metadata: %s", e)
+ return None
+
+ def get_attribute_from_metadata(self, metadata_json, attribute_key):
+ ams_value = ""
+ if attribute_key == _AZURE_VM_SCALE_SET_NAME_ATTRIBUTE:
+ ams_value = metadata_json["vmScaleSetName"]
+ elif attribute_key == _AZURE_VM_SKU_ATTRIBUTE:
+ ams_value = metadata_json["sku"]
+ elif attribute_key == ResourceAttributes.CLOUD_PLATFORM:
+ ams_value = CloudPlatformValues.AZURE_VM.value
+ elif attribute_key == ResourceAttributes.CLOUD_PROVIDER:
+ ams_value = CloudProviderValues.AZURE.value
+ elif attribute_key == ResourceAttributes.CLOUD_REGION:
+ ams_value = metadata_json["location"]
+ elif attribute_key == _CLOUD_RESOURCE_ID_RESOURCE_ATTRIBUTE:
+ ams_value = metadata_json["resourceId"]
+ elif attribute_key == ResourceAttributes.HOST_ID or \
+ attribute_key == ResourceAttributes.SERVICE_INSTANCE_ID:
+ ams_value = metadata_json["vmId"]
+ elif attribute_key == ResourceAttributes.HOST_NAME:
+ ams_value = metadata_json["name"]
+ elif attribute_key == ResourceAttributes.HOST_TYPE:
+ ams_value = metadata_json["vmSize"]
+ elif attribute_key == ResourceAttributes.OS_TYPE:
+ ams_value = metadata_json["osType"]
+ elif attribute_key == ResourceAttributes.OS_VERSION:
+ ams_value = metadata_json["version"]
+ return ams_value
\ No newline at end of file
diff --git a/resource/opentelemetry-resource-detector-azure-vm/src/opentelemetry/resource/detector/azure/vm/version.py b/resource/opentelemetry-resource-detector-azure-vm/src/opentelemetry/resource/detector/azure/vm/version.py
new file mode 100644
index 0000000000..a0e1bb43d9
--- /dev/null
+++ b/resource/opentelemetry-resource-detector-azure-vm/src/opentelemetry/resource/detector/azure/vm/version.py
@@ -0,0 +1,4 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+__version__ = "0.41b0.dev"
diff --git a/resource/opentelemetry-resource-detector-azure-vm/tests/__init__.py b/resource/opentelemetry-resource-detector-azure-vm/tests/__init__.py
new file mode 100644
index 0000000000..5b7f7a925c
--- /dev/null
+++ b/resource/opentelemetry-resource-detector-azure-vm/tests/__init__.py
@@ -0,0 +1,2 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
diff --git a/resource/opentelemetry-resource-detector-azure-vm/tests/test_vm_resource_detector.py b/resource/opentelemetry-resource-detector-azure-vm/tests/test_vm_resource_detector.py
new file mode 100644
index 0000000000..739df55030
--- /dev/null
+++ b/resource/opentelemetry-resource-detector-azure-vm/tests/test_vm_resource_detector.py
@@ -0,0 +1,374 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+import unittest
+from unittest.mock import patch, Mock
+
+from opentelemetry.semconv.resource import ResourceAttributes
+from opentelemetry.resource.detector.azure.vm import (
+ AzureVMResourceDetector,
+)
+
+LINUX_JSON = """
+{
+ "compute": {
+ "azEnvironment": "AZUREPUBLICCLOUD",
+ "additionalCapabilities": {
+ "hibernationEnabled": "true"
+ },
+ "hostGroup": {
+ "id": "testHostGroupId"
+ },
+ "extendedLocation": {
+ "type": "edgeZone",
+ "name": "microsoftlosangeles"
+ },
+ "evictionPolicy": "",
+ "isHostCompatibilityLayerVm": "true",
+ "licenseType": "",
+ "location": "westus",
+ "name": "examplevmname",
+ "offer": "UbuntuServer",
+ "osProfile": {
+ "adminUsername": "admin",
+ "computerName": "examplevmname",
+ "disablePasswordAuthentication": "true"
+ },
+ "osType": "Linux",
+ "placementGroupId": "f67c14ab-e92c-408c-ae2d-da15866ec79a",
+ "plan": {
+ "name": "planName",
+ "product": "planProduct",
+ "publisher": "planPublisher"
+ },
+ "platformFaultDomain": "36",
+ "platformSubFaultDomain": "",
+ "platformUpdateDomain": "42",
+ "priority": "Regular",
+ "publicKeys": [{
+ "keyData": "ssh-rsa 0",
+ "path": "/home/user/.ssh/authorized_keys0"
+ },
+ {
+ "keyData": "ssh-rsa 1",
+ "path": "/home/user/.ssh/authorized_keys1"
+ }
+ ],
+ "publisher": "Canonical",
+ "resourceGroupName": "macikgo-test-may-23",
+ "resourceId": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/macikgo-test-may-23/providers/Microsoft.Compute/virtualMachines/examplevmname",
+ "securityProfile": {
+ "secureBootEnabled": "true",
+ "virtualTpmEnabled": "false",
+ "encryptionAtHost": "true",
+ "securityType": "TrustedLaunch"
+ },
+ "sku": "18.04-LTS",
+ "storageProfile": {
+ "dataDisks": [{
+ "bytesPerSecondThrottle": "979202048",
+ "caching": "None",
+ "createOption": "Empty",
+ "diskCapacityBytes": "274877906944",
+ "diskSizeGB": "1024",
+ "image": {
+ "uri": ""
+ },
+ "isSharedDisk": "false",
+ "isUltraDisk": "true",
+ "lun": "0",
+ "managedDisk": {
+ "id": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/macikgo-test-may-23/providers/Microsoft.Compute/disks/exampledatadiskname",
+ "storageAccountType": "StandardSSD_LRS"
+ },
+ "name": "exampledatadiskname",
+ "opsPerSecondThrottle": "65280",
+ "vhd": {
+ "uri": ""
+ },
+ "writeAcceleratorEnabled": "false"
+ }],
+ "imageReference": {
+ "id": "",
+ "offer": "UbuntuServer",
+ "publisher": "Canonical",
+ "sku": "16.04.0-LTS",
+ "version": "latest"
+ },
+ "osDisk": {
+ "caching": "ReadWrite",
+ "createOption": "FromImage",
+ "diskSizeGB": "30",
+ "diffDiskSettings": {
+ "option": "Local"
+ },
+ "encryptionSettings": {
+ "enabled": "false",
+ "diskEncryptionKey": {
+ "sourceVault": {
+ "id": "/subscriptions/test-source-guid/resourceGroups/testrg/providers/Microsoft.KeyVault/vaults/test-kv"
+ },
+ "secretUrl": "https://test-disk.vault.azure.net/secrets/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx"
+ },
+ "keyEncryptionKey": {
+ "sourceVault": {
+ "id": "/subscriptions/test-key-guid/resourceGroups/testrg/providers/Microsoft.KeyVault/vaults/test-kv"
+ },
+ "keyUrl": "https://test-key.vault.azure.net/secrets/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx"
+ }
+ },
+ "image": {
+ "uri": ""
+ },
+ "managedDisk": {
+ "id": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/macikgo-test-may-23/providers/Microsoft.Compute/disks/exampleosdiskname",
+ "storageAccountType": "StandardSSD_LRS"
+ },
+ "name": "exampleosdiskname",
+ "osType": "Linux",
+ "vhd": {
+ "uri": ""
+ },
+ "writeAcceleratorEnabled": "false"
+ },
+ "resourceDisk": {
+ "size": "4096"
+ }
+ },
+ "subscriptionId": "xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",
+ "tags": "baz:bash;foo:bar",
+ "version": "15.05.22",
+ "virtualMachineScaleSet": {
+ "id": "/subscriptions/xxxxxxxx-xxxxx-xxx-xxx-xxxx/resourceGroups/resource-group-name/providers/Microsoft.Compute/virtualMachineScaleSets/virtual-machine-scale-set-name"
+ },
+ "vmId": "02aab8a4-74ef-476e-8182-f6d2ba4166a6",
+ "vmScaleSetName": "crpteste9vflji9",
+ "vmSize": "Standard_A3",
+ "zone": ""
+ },
+ "network": {
+ "interface": [{
+ "ipv4": {
+ "ipAddress": [{
+ "privateIpAddress": "10.144.133.132",
+ "publicIpAddress": ""
+ }],
+ "subnet": [{
+ "address": "10.144.133.128",
+ "prefix": "26"
+ }]
+ },
+ "ipv6": {
+ "ipAddress": [
+ ]
+ },
+ "macAddress": "0011AAFFBB22"
+ }]
+ }
+}
+"""
+WINDOWS_JSON ="""{
+ "compute": {
+ "azEnvironment": "AZUREPUBLICCLOUD",
+ "additionalCapabilities": {
+ "hibernationEnabled": "true"
+ },
+ "hostGroup": {
+ "id": "testHostGroupId"
+ },
+ "extendedLocation": {
+ "type": "edgeZone",
+ "name": "microsoftlosangeles"
+ },
+ "evictionPolicy": "",
+ "isHostCompatibilityLayerVm": "true",
+ "licenseType": "Windows_Client",
+ "location": "westus",
+ "name": "examplevmname",
+ "offer": "WindowsServer",
+ "osProfile": {
+ "adminUsername": "admin",
+ "computerName": "examplevmname",
+ "disablePasswordAuthentication": "true"
+ },
+ "osType": "Windows",
+ "placementGroupId": "f67c14ab-e92c-408c-ae2d-da15866ec79a",
+ "plan": {
+ "name": "planName",
+ "product": "planProduct",
+ "publisher": "planPublisher"
+ },
+ "platformFaultDomain": "36",
+ "platformSubFaultDomain": "",
+ "platformUpdateDomain": "42",
+ "priority": "Regular",
+ "publicKeys": [{
+ "keyData": "ssh-rsa 0",
+ "path": "/home/user/.ssh/authorized_keys0"
+ },
+ {
+ "keyData": "ssh-rsa 1",
+ "path": "/home/user/.ssh/authorized_keys1"
+ }
+ ],
+ "publisher": "RDFE-Test-Microsoft-Windows-Server-Group",
+ "resourceGroupName": "macikgo-test-may-23",
+ "resourceId": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/macikgo-test-may-23/providers/Microsoft.Compute/virtualMachines/examplevmname",
+ "securityProfile": {
+ "secureBootEnabled": "true",
+ "virtualTpmEnabled": "false",
+ "encryptionAtHost": "true",
+ "securityType": "TrustedLaunch"
+ },
+ "sku": "2019-Datacenter",
+ "storageProfile": {
+ "dataDisks": [{
+ "bytesPerSecondThrottle": "979202048",
+ "caching": "None",
+ "createOption": "Empty",
+ "diskCapacityBytes": "274877906944",
+ "diskSizeGB": "1024",
+ "image": {
+ "uri": ""
+ },
+ "isSharedDisk": "false",
+ "isUltraDisk": "true",
+ "lun": "0",
+ "managedDisk": {
+ "id": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/macikgo-test-may-23/providers/Microsoft.Compute/disks/exampledatadiskname",
+ "storageAccountType": "StandardSSD_LRS"
+ },
+ "name": "exampledatadiskname",
+ "opsPerSecondThrottle": "65280",
+ "vhd": {
+ "uri": ""
+ },
+ "writeAcceleratorEnabled": "false"
+ }],
+ "imageReference": {
+ "id": "",
+ "offer": "WindowsServer",
+ "publisher": "MicrosoftWindowsServer",
+ "sku": "2019-Datacenter",
+ "version": "latest"
+ },
+ "osDisk": {
+ "caching": "ReadWrite",
+ "createOption": "FromImage",
+ "diskSizeGB": "30",
+ "diffDiskSettings": {
+ "option": "Local"
+ },
+ "encryptionSettings": {
+ "enabled": "false",
+ "diskEncryptionKey": {
+ "sourceVault": {
+ "id": "/subscriptions/test-source-guid/resourceGroups/testrg/providers/Microsoft.KeyVault/vaults/test-kv"
+ },
+ "secretUrl": "https://test-disk.vault.azure.net/secrets/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx"
+ },
+ "keyEncryptionKey": {
+ "sourceVault": {
+ "id": "/subscriptions/test-key-guid/resourceGroups/testrg/providers/Microsoft.KeyVault/vaults/test-kv"
+ },
+ "keyUrl": "https://test-key.vault.azure.net/secrets/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx"
+ }
+ },
+ "image": {
+ "uri": ""
+ },
+ "managedDisk": {
+ "id": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/macikgo-test-may-23/providers/Microsoft.Compute/disks/exampleosdiskname",
+ "storageAccountType": "StandardSSD_LRS"
+ },
+ "name": "exampleosdiskname",
+ "osType": "Windows",
+ "vhd": {
+ "uri": ""
+ },
+ "writeAcceleratorEnabled": "false"
+ },
+ "resourceDisk": {
+ "size": "4096"
+ }
+ },
+ "subscriptionId": "xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",
+ "tags": "baz:bash;foo:bar",
+ "userData": "Zm9vYmFy",
+ "version": "15.05.22",
+ "virtualMachineScaleSet": {
+ "id": "/subscriptions/xxxxxxxx-xxxxx-xxx-xxx-xxxx/resourceGroups/resource-group-name/providers/Microsoft.Compute/virtualMachineScaleSets/virtual-machine-scale-set-name"
+ },
+ "vmId": "02aab8a4-74ef-476e-8182-f6d2ba4166a6",
+ "vmScaleSetName": "crpteste9vflji9",
+ "vmSize": "Standard_A3",
+ "zone": ""
+ },
+ "network": {
+ "interface": [{
+ "ipv4": {
+ "ipAddress": [{
+ "privateIpAddress": "10.144.133.132",
+ "publicIpAddress": ""
+ }],
+ "subnet": [{
+ "address": "10.144.133.128",
+ "prefix": "26"
+ }]
+ },
+ "ipv6": {
+ "ipAddress": [
+ ]
+ },
+ "macAddress": "0011AAFFBB22"
+ }]
+ }
+}
+"""
+LINUX_ATTRIBUTES = {
+ "azure.vm.scaleset.name": "crpteste9vflji9",
+ "azure.vm.sku": "18.04-LTS",
+ "cloud.platform": "azure_vm",
+ "cloud.provider": "azure",
+ "cloud.region": "westus",
+ "cloud.resource_id": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/macikgo-test-may-23/providers/Microsoft.Compute/virtualMachines/examplevmname",
+ "host.id": "02aab8a4-74ef-476e-8182-f6d2ba4166a6",
+ "host.name": "examplevmname",
+ "host.type": "Standard_A3",
+ "os.type": "Linux",
+ "os.version": "15.05.22",
+ "service.instance.id": "02aab8a4-74ef-476e-8182-f6d2ba4166a6",
+}
+WINDOWS_ATTRIBUTES = {
+ "azure.vm.scaleset.name": "crpteste9vflji9",
+ "azure.vm.sku": "2019-Datacenter",
+ "cloud.platform": "azure_vm",
+ "cloud.provider": "azure",
+ "cloud.region": "westus",
+ "cloud.resource_id": "/subscriptions/xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/macikgo-test-may-23/providers/Microsoft.Compute/virtualMachines/examplevmname",
+ "host.id": "02aab8a4-74ef-476e-8182-f6d2ba4166a6",
+ "host.name": "examplevmname",
+ "host.type": "Standard_A3",
+ "os.type": "Windows",
+ "os.version": "15.05.22",
+ "service.instance.id": "02aab8a4-74ef-476e-8182-f6d2ba4166a6",
+}
+
+
+class TestAzureVMResourceDetector(unittest.TestCase):
+ @patch("opentelemetry.resource.detector.azure.vm.urlopen")
+ def test_linux(self, mock_urlopen):
+ mock_open = Mock()
+ mock_urlopen.return_value = mock_open
+ mock_open.read.return_value = LINUX_JSON
+ attributes = AzureVMResourceDetector().detect().attributes
+ for attribute_key in LINUX_ATTRIBUTES:
+ self.assertEqual(attributes[attribute_key], LINUX_ATTRIBUTES[attribute_key])
+
+ @patch("opentelemetry.resource.detector.azure.vm.urlopen")
+ def test_windows(self, mock_urlopen):
+ mock_open = Mock()
+ mock_urlopen.return_value = mock_open
+ mock_open.read.return_value = WINDOWS_JSON
+ attributes = AzureVMResourceDetector().detect().attributes
+ for attribute_key in WINDOWS_ATTRIBUTES:
+ self.assertEqual(attributes[attribute_key], WINDOWS_ATTRIBUTES[attribute_key])