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])