Skip to content

azure-mgmt-network: 31.0. #48125

Description

@blackboxsw
  • Package Name: azure-mgmt-network
  • Package Version: 31.0.1 released 2026-07-02
  • Operating System: ubuntu
  • Python Version: 3.14.4

Describe the bug
Updates in version 31.0.1 result in inabilty to create network address space using azure python SDK due to a python traceback

! _pytest.outcomes.Exit: HttpResponseError in session setup: (InvalidRequestContent) The request content was invalid and could not be deserialized: 'Could not find member 'address_space' on object of type 'ResourceDefinition'. Path 'address_space', line 1, position 41.'.
Code: InvalidRequestContent
Message: The request content was invalid and could not be deserialized: 'Could not find member 'address_space' on object of type 'ResourceDefinition'. Path 'address_space', line 1, position 41.'. !

This appears to be some incompatibility between azure-mgmt-network ARM API version request of 2025-07-01 versus 2025-05-01 resulting in inability to process the expected address_space parameters which are PUT to the API.

To Reproduce
Steps to reproduce the behavior, make a call to build_virtual_networks_create_or_update_request without specifying an api_version and the default chosen by azure-mgmt-network will be incompatible with Azure APIs resulting in a serialization error from the API.

#!/usr/bin/env python3
"""Reproducer for the Azure integration-test failure

Issue
-----
azure-mgmt-network 31.x defaults to ``api-version=2025-07-01`` for the
``Microsoft.Network/virtualNetworks`` PUT. Azure ARM does not accept that
api-version for the resource type and falls back to validating the request
body against the generic ``ResourceDefinition`` base type, which has no
``address_space`` member. The PUT is therefore rejected:

    HttpResponseError: (InvalidRequestContent) The request content was
    invalid and could not be deserialized: 'Could not find member
    'address_space' on object of type 'ResourceDefinition'. Path
    'address_space', line 1, position 41.'.

Test infrastructure builds a body with a top-level ``address_space`` and
passes it as a plain dict to
``network_client.virtual_networks.begin_create_or_update`` (no explicit
api-version), so the SDK's baked-in default is what gets sent.

This script
------------
1. Prints the installed azure-mgmt-network version and the default
   api-version it bakes into the virtualNetworks PUT request.
2. Prints the JSON body that triggers the failure.
3. Diagnoses whether the installed version is on the broken 31.x line.
4. Optionally fires the real request directly via NetworkManagementClient
   using only ``azure-identity`` for auth. Requires
   service-principal env vars (see LIVE RUN below). All resources created
   during the live run are torn down afterwards.

Usage
-----
    # Offline diagnosis (no Azure creds required):
    python3 repro_azure_vnet_api_version.py

    # Live fire against Azure (reproduces the real CI error):
    export AZURE_SUBSCRIPTION_ID=...
    export AZURE_CLIENT_ID=...
    export AZURE_CLIENT_SECRET=...
    export AZURE_TENANT_ID=...
    export AZURE_LOCATION=eastus            # optional, defaults to eastus
    python3 repro_azure_vnet_api_version.py

    # Keep resources after a live run for inspection:
    KEEP_RESOURCES=1 python3 repro_azure_vnet_api_version.py

Fix
---
Pin ``azure-mgmt-network<31`` (resolves to 30.2.0, api-version 2025-05-01)
until Azure accepts 2025-07-01 or test infrastructure passes an explicit api_version.
"""
import json
import os
import sys
import time
from urllib.parse import parse_qs, urlparse


DEFAULT_LOCATION = "eastus"
DEFAULT_PREFIX = "cirepro"


def section(title: str) -> None:
    print(f"\n=== {title} ===")


def get_sdk_version_and_default_api_version():
    """Return (installed_version, method, url, api_version) for vnet PUT."""
    from azure.mgmt.network import _version
    from azure.mgmt.network.operations._operations import (
        build_virtual_networks_create_or_update_request,
    )

    # Build a request with fake identifiers; the default api-version is
    # baked into the generated builder and surfaces as a query param.
    req = build_virtual_networks_create_or_update_request(
        resource_group_name="rg-fake",
        virtual_network_name="vnet-fake",
        subscription_id="sub-fake",
    )
    qs = parse_qs(urlparse(req.url).query)
    api_version = (qs.get("api-version") or [None])[0]
    return _version.VERSION, req.method, req.url, api_version


def replicate_failing_request_params(location: str, tag: str) -> dict:
    """Replicate the request params."""
    return {
        "location": location,
        "address_space": {"address_prefixes": ["10.0.0.0/16"]},
        "tags": {"name": tag},
    }


def teardown_resource_group(rg_client, rg_name: str) -> None:
    """Delete a resource group; cascades to any resources it contains."""
    print(f"\n--- teardown: deleting resource group {rg_name} ---")
    try:
        poller = rg_client.resource_groups.begin_delete(rg_name)
        poller.result()
        print(f"teardown: resource group {rg_name} deleted.")
    except Exception as e:
        print(f"teardown: FAILED to delete resource group {rg_name}: {e}")
        print("Teardown is best-effort; please delete the resource group")
        print(f"manually if it still exists.")


def fire_live_request(body, location: str, prefix: str):
    """Fire the real PUT via NetworkManagementClient using azure-identity.

    Requires these env vars:
        AZURE_SUBSCRIPTION_ID
        AZURE_CLIENT_ID
        AZURE_CLIENT_SECRET
        AZURE_TENANT_ID
    Optional:
        AZURE_LOCATION  (default eastus)
        KEEP_RESOURCES  (default unset; set to 1 to skip teardown)

    All resources created (resource group + any partial vnet) are torn down
    in a finally block, unless KEEP_RESOURCES=1 is set.
    """
    required = (
        "AZURE_SUBSCRIPTION_ID",
        "AZURE_CLIENT_ID",
        "AZURE_CLIENT_SECRET",
        "AZURE_TENANT_ID",
    )
    missing = [k for k in required if not os.environ.get(k)]
    if missing:
        print("Skipping live request; missing env vars: " + ", ".join(missing))
        print("To reproduce the live Azure error, export the service-principal")
        print("credentials listed above, then re-run.")
        return None

    try:
        from azure.identity import ClientSecretCredential
        from azure.mgmt.network import NetworkManagementClient
    except ImportError as e:
        print(f"Required SDK not importable ({e}); cannot fire live request.")
        print("Install with: pip install azure-mgmt-network azure-identity")
        return None

    subscription_id = os.environ["AZURE_SUBSCRIPTION_ID"]
    cred = ClientSecretCredential(
        client_id=os.environ["AZURE_CLIENT_ID"],
        client_secret=os.environ["AZURE_CLIENT_SECRET"],
        tenant_id=os.environ["AZURE_TENANT_ID"],
    )
    network_client = NetworkManagementClient(cred, subscription_id)

    rg_name = f"{prefix}-rg"
    vnet_name = f"{prefix}-vnet"
    keep_resources = os.environ.get("KEEP_RESOURCES") == "1"

    # azure-mgmt-resource is needed both to create and to tear down the RG.
    rg_client = None
    rg_created = False
    try:
        from azure.mgmt.resource.resources import ResourceManagementClient
        rg_client = ResourceManagementClient(cred, subscription_id)
    except ImportError:
        print(
            "azure-mgmt-resource not installed; cannot create or tear down a"
            " resource group. Install with: pip install azure-mgmt-resource"
        )
        print("Skipping live request.")
        return None

    result_code = None
    try:
        print(f"Creating resource group {rg_name} in {location} ...")
        rg_client.resource_groups.create_or_update(
            rg_name, {"location": location, "tags": {"name": prefix}}
        )
        rg_created = True

        print(
            f"Firing virtual_networks.begin_create_or_update against\n"
            f"  resource group: {rg_name}\n"
            f"  vnet name:      {vnet_name}\n"
            f"  location:       {location}"
        )
        try:
            poller = network_client.virtual_networks.begin_create_or_update(
                rg_name, vnet_name, body
            )
            poller.result()
        except Exception as e:
            msg = str(e)
            print(f"\nREPRODUCED live: {type(e).__name__}")
            print(msg)
            if "address_space" in msg and "ResourceDefinition" in msg:
                print("\nThis matches the CI failure in cifailure.log:52-54.")
                result_code = 1
            else:
                print("\nNote: error differs from expected CI failure.")
                result_code = 2
        else:
            print("\nUNEXPECTED: request succeeded (issue may be fixed).")
            result_code = 0
    finally:
        if rg_created and not keep_resources:
            teardown_resource_group(rg_client, rg_name)
        elif keep_resources:
            print(f"\nKEEP_RESOURCES=1 set; leaving {rg_name} for inspection.")

    return result_code


def main() -> int:
    section("Installed azure-mgmt-network + default api-version")
    try:
        version, method, url, api_version = get_sdk_version_and_default_api_version()
    except Exception as e:
        print(f"ERROR importing azure-mgmt-network: {e}")
        print("Install with: pip install azure-mgmt-network")
        return 2
    print(f"version:        {version}")
    print(f"HTTP method:    {method}")
    print(f"request URL:    {url}")
    print(f"api-version:    {api_version}")

    location = os.environ.get("AZURE_LOCATION", DEFAULT_LOCATION)
    prefix = f"{DEFAULT_PREFIX}-{int(time.time())}"

    section("Body that triggers the failure")
    body = replicate_failing_request_params(location=location, tag=prefix)
    print(json.dumps(body, indent=2))

    section("Diagnosis")
    broken = "2025-07-01"
    if api_version == broken:
        print(f"api-version {api_version} is the 31.x default that Azure ARM")
        print("treats as unknown for Microsoft.Network/virtualNetworks; ARM")
        print("falls back to validating the body against ResourceDefinition")
        print("(which has no `address_space` member), producing the CI error.")
        print("\nFix: pin azure-mgmt-network<31 (resolves to 30.2.0 /")
        print("api-version 2025-05-01) in integration-requirements.txt.")
    elif api_version and version.startswith("31."):
        print(f"api-version {api_version} on a 31.x install; verify whether")
        print("Azure accepts it. The known-broken default is 2025-07-01.")
    else:
        print(f"api-version {api_version} (version {version}) is NOT the")
        print("known-broken 2025-07-01; the issue likely won't reproduce.")
        print("Install azure-mgmt-network==31.0.1 to see the failure mode.")

    section("Optional live request against Azure")
    rc = fire_live_request(body, location=location, prefix=prefix)
    if rc is not None:
        return rc
    print("\nOffline diagnosis complete. No live request fired.")
    return 0


if __name__ == "__main__":
    sys.exit(main())

Expected behavior
A clear and concise description of what you expected to happen.
Expect the defaults API version to succeed without having to pin azure-mgmt-network <31 or providing an antiquated api_version value of 2025-05-01'

Screenshots
If applicable, add screenshots to help explain your problem.

Additional context
Add any other context about the problem here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    MgmtThis issue is related to a management-plane library.NetworkService AttentionWorkflow: This issue is responsible by Azure service team.customer-reportedIssues that are reported by GitHub users external to the Azure organization.needs-team-attentionWorkflow: This issue needs attention from Azure service team or SDK teamquestionThe issue doesn't require a change to the product in order to be resolved. Most issues start as that

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions