Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions sdk/storagemover/azure-mgmt-storagemover/assets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "python",
"TagPrefix": "python/storagemover/azure-mgmt-storagemover",
"Tag": "python/storagemover/azure-mgmt-storagemover_ee26229261"
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
-e ../../../eng/tools/azure-sdk-tools
../../identity/azure-identity
aiohttp
# Cross-sub mgmt clients used by scenario-test matrix row #31
# (`test_start_c2c_job_with_private_source`) and row #10's extended public-bucket
# E2E (`test_job_definition_job_run`). Test-only deps; not in the package's
# runtime install_requires.
azure-mgmt-network
azure-mgmt-authorization
azure-mgmt-storage
35 changes: 35 additions & 0 deletions sdk/storagemover/azure-mgmt-storagemover/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
add_general_regex_sanitizer,
add_body_key_sanitizer,
add_header_regex_sanitizer,
remove_batch_sanitizers,
)

load_dotenv()
Expand All @@ -33,3 +34,37 @@ def add_sanitizers(test_proxy):
add_header_regex_sanitizer(key="Set-Cookie", value="[set-cookie;]")
add_header_regex_sanitizer(key="Cookie", value="cookie;")
add_body_key_sanitizer(json_path="$..access_token", value="access_token")

# Cross-subscription shared infra in XDataMove-Synthetics is referenced by
# matrix #31 (`test_start_c2c_job_with_private_source`), the extended
# matrix #10 (`test_job_definition_job_run`), and #32
# (`test_create_get_list_update_delete` on connections). The sub ID is
# well-known shared infrastructure but must be sanitized in recordings.
# **Use the same target value (`00000000-...`) as the default subscription
# sanitizer above** so they cooperate at both record AND playback time —
# at record the default sanitizer wins (env-var match), at playback this
# one rewrites the source-code literal to match the cassette.
add_general_regex_sanitizer(
regex="b6b34ad8-ca89-4f85-beb7-c2ec13702dac",
value="00000000-0000-0000-0000-000000000000",
)
# Sanitize the per-run role-assignment GUID we mint for matrix #31 and #10.
# The GUID is a path segment under `roleAssignments/<guid>` — redact only
# there to avoid clobbering unrelated GUIDs elsewhere in payloads. The
# `variables` mechanism makes the source GUID deterministic across runs,
# so both request and cassette get the same sanitized value.
add_general_regex_sanitizer(
regex=r"(?<=roleAssignments/)[0-9a-fA-F\-]{36}",
value="00000000-0000-0000-0000-000000000000",
)
# Sanitize managed-identity principalId values returned by the RP on
# blob-container endpoints (matrix #10 + #31) so cassettes don't leak
# object IDs we don't control.
add_body_key_sanitizer(json_path="$..principalId", value="00000000-0000-0000-0000-000000000000")

# Remove default sanitizers that clobber non-sensitive fields the storage mover
# tests need to assert on:
# - AZSDK3430: $..id (resource ARM IDs - subscription is already sanitized)
# - AZSDK3493: $..name (resource names like storage mover / endpoint / project names)
# - AZSDK2003: Location header (needed for LRO polling on begin_delete, etc.)
remove_batch_sanitizers(["AZSDK3430", "AZSDK3493", "AZSDK2003"])
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@

# coding=utf-8
# --------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------
import pytest
from azure.core.exceptions import ResourceNotFoundError
from azure.mgmt.storagemover.aio import StorageMoverMgmtClient

from devtools_testutils import AzureMgmtRecordedTestCase
from devtools_testutils.aio import recorded_by_proxy_async

RESOURCE_GROUP_NAME = "teststomover"
STORAGE_MOVER_NAME = "testsm1"
AGENT_NAME = "testagent1"
MISSING_AGENT_NAME = AGENT_NAME + "111"
UPDATED_DESCRIPTION = "This is an updated agent"
UPLOAD_LIMIT_SCHEDULE = {
"weeklyRecurrences": [
{
"startTime": {"hour": 1},
"endTime": {"hour": 2},
"days": ["Monday", "Tuesday"],
"limitInMbps": 100,
}
]
}


def _assert_agent_matches(expected, actual):
assert actual.name == expected.name
assert actual.id == expected.id
assert actual.local_ip_address == expected.local_ip_address


def _assert_upload_limit_schedule(agent):
recurrences = agent.upload_limit_schedule.weekly_recurrences
assert len(recurrences) == 1
recurrence = recurrences[0]
assert recurrence.limit_in_mbps == 100
assert recurrence.days[0] == "Monday"
assert len(recurrence.days) == 2
assert recurrence.start_time.hour == 1
assert recurrence.start_time.minute == 0
assert recurrence.end_time.hour == 2
assert recurrence.end_time.minute == 0


class TestStorageMoverMgmtAgentsOperationsAsync(AzureMgmtRecordedTestCase):
def setup_method(self, method):
self.client = self.create_mgmt_client(StorageMoverMgmtClient, is_async=True)

@pytest.mark.skip(reason="Requires a registered agent VM; agents cannot be created via the RP. Live-only test.")
@pytest.mark.asyncio
@recorded_by_proxy_async
async def test_agents_get_list_update(self):
agent = await self.client.agents.get(
resource_group_name=RESOURCE_GROUP_NAME,
storage_mover_name=STORAGE_MOVER_NAME,
agent_name=AGENT_NAME,
)
agent_again = await self.client.agents.get(
resource_group_name=RESOURCE_GROUP_NAME,
storage_mover_name=STORAGE_MOVER_NAME,
agent_name=AGENT_NAME,
)
_assert_agent_matches(agent, agent_again)

agents = [
item
async for item in self.client.agents.list(
resource_group_name=RESOURCE_GROUP_NAME,
storage_mover_name=STORAGE_MOVER_NAME,
)
]
assert len(agents) >= 1

updated_agent = await self.client.agents.update(
resource_group_name=RESOURCE_GROUP_NAME,
storage_mover_name=STORAGE_MOVER_NAME,
agent_name=AGENT_NAME,
agent={
"properties": {
"description": UPDATED_DESCRIPTION,
"uploadLimitSchedule": UPLOAD_LIMIT_SCHEDULE,
}
},
)
assert updated_agent.description == UPDATED_DESCRIPTION
_assert_upload_limit_schedule(updated_agent)

fetched_agent = await self.client.agents.get(
resource_group_name=RESOURCE_GROUP_NAME,
storage_mover_name=STORAGE_MOVER_NAME,
agent_name=AGENT_NAME,
)
assert fetched_agent.description == UPDATED_DESCRIPTION
_assert_upload_limit_schedule(fetched_agent)

with pytest.raises(ResourceNotFoundError):
await self.client.agents.get(
resource_group_name=RESOURCE_GROUP_NAME,
storage_mover_name=STORAGE_MOVER_NAME,
agent_name=MISSING_AGENT_NAME,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# coding=utf-8
# --------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------
import pytest
from azure.core.exceptions import ResourceNotFoundError
from azure.mgmt.storagemover import StorageMoverMgmtClient

from devtools_testutils import AzureMgmtRecordedTestCase, recorded_by_proxy

RESOURCE_GROUP_NAME = "teststomover"
STORAGE_MOVER_NAME = "testsm1"
AGENT_NAME = "testagent1"
MISSING_AGENT_NAME = AGENT_NAME + "111"
UPDATED_DESCRIPTION = "This is an updated agent"
UPLOAD_LIMIT_SCHEDULE = {
"weeklyRecurrences": [
{
"startTime": {"hour": 1},
"endTime": {"hour": 2},
"days": ["Monday", "Tuesday"],
"limitInMbps": 100,
}
]
}


def _assert_agent_matches(expected, actual):
assert actual.name == expected.name
assert actual.id == expected.id
assert actual.local_ip_address == expected.local_ip_address


def _assert_upload_limit_schedule(agent):
recurrences = agent.upload_limit_schedule.weekly_recurrences
assert len(recurrences) == 1
recurrence = recurrences[0]
assert recurrence.limit_in_mbps == 100
assert recurrence.days[0] == "Monday"
assert len(recurrence.days) == 2
assert recurrence.start_time.hour == 1
assert recurrence.start_time.minute == 0
assert recurrence.end_time.hour == 2
assert recurrence.end_time.minute == 0


class TestStorageMoverMgmtAgentsOperations(AzureMgmtRecordedTestCase):
def setup_method(self, method):
self.client = self.create_mgmt_client(StorageMoverMgmtClient)

@pytest.mark.skip(reason="Requires a registered agent VM; agents cannot be created via the RP. Live-only test.")
@recorded_by_proxy
def test_agents_get_list_update(self):
agent = self.client.agents.get(
resource_group_name=RESOURCE_GROUP_NAME,
storage_mover_name=STORAGE_MOVER_NAME,
agent_name=AGENT_NAME,
)
agent_again = self.client.agents.get(
resource_group_name=RESOURCE_GROUP_NAME,
storage_mover_name=STORAGE_MOVER_NAME,
agent_name=AGENT_NAME,
)
_assert_agent_matches(agent, agent_again)

agents = list(
self.client.agents.list(
resource_group_name=RESOURCE_GROUP_NAME,
storage_mover_name=STORAGE_MOVER_NAME,
)
)
assert len(agents) >= 1

updated_agent = self.client.agents.update(
resource_group_name=RESOURCE_GROUP_NAME,
storage_mover_name=STORAGE_MOVER_NAME,
agent_name=AGENT_NAME,
agent={
"properties": {
"description": UPDATED_DESCRIPTION,
"uploadLimitSchedule": UPLOAD_LIMIT_SCHEDULE,
}
},
)
assert updated_agent.description == UPDATED_DESCRIPTION
_assert_upload_limit_schedule(updated_agent)

fetched_agent = self.client.agents.get(
resource_group_name=RESOURCE_GROUP_NAME,
storage_mover_name=STORAGE_MOVER_NAME,
agent_name=AGENT_NAME,
)
assert fetched_agent.description == UPDATED_DESCRIPTION
_assert_upload_limit_schedule(fetched_agent)

with pytest.raises(ResourceNotFoundError):
self.client.agents.get(
resource_group_name=RESOURCE_GROUP_NAME,
storage_mover_name=STORAGE_MOVER_NAME,
agent_name=MISSING_AGENT_NAME,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# coding=utf-8
# --------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------
"""Async scenario test for connections — matrix row #32.

See the sync sibling (test_storage_mover_mgmt_connections_operations_test.py)
for the full rationale.
"""
import pytest
from azure.core.exceptions import ResourceNotFoundError
from azure.mgmt.storagemover.aio import StorageMoverMgmtClient

from devtools_testutils import AzureMgmtRecordedTestCase, RandomNameResourceGroupPreparer
from devtools_testutils.aio import recorded_by_proxy_async

# PrivateLinkService lives in westcentralus, so storage mover must too.
AZURE_LOCATION = "westcentralus"

REAL_PRIVATE_LINK_SERVICE_ID = (
"/subscriptions/b6b34ad8-ca89-4f85-beb7-c2ec13702dac"
"/resourceGroups/E2E-Management-RGsyn"
"/providers/Microsoft.Network/privateLinkServices/test-pls-wcs"
)


class TestStorageMoverMgmtConnectionsOperationsAsync(AzureMgmtRecordedTestCase):
def setup_method(self, method):
self.client = self.create_mgmt_client(StorageMoverMgmtClient, is_async=True)

@RandomNameResourceGroupPreparer(location=AZURE_LOCATION)
@recorded_by_proxy_async
async def test_create_get_list_update_delete(self, resource_group):
rg = resource_group.name
sm_name = "testsm-conn"
connection_name = "testconn1"

await self.client.storage_movers.create_or_update(
resource_group_name=rg, storage_mover_name=sm_name,
storage_mover={"location": AZURE_LOCATION},
)

# Create
created = await self.client.connections.create_or_update(
resource_group_name=rg, storage_mover_name=sm_name, connection_name=connection_name,
connection={"properties": {
"privateLinkServiceId": REAL_PRIVATE_LINK_SERVICE_ID,
"description": "ConnectionDesc",
}},
)
# See sync sibling for why we compare by PLS name suffix instead of full ARM ID.
pls_id_suffix = "/providers/Microsoft.Network/privateLinkServices/test-pls-wcs"
assert created.name == connection_name
assert created.properties.private_link_service_id.endswith(pls_id_suffix)
assert created.properties.description == "ConnectionDesc"

# Get
fetched = await self.client.connections.get(
resource_group_name=rg, storage_mover_name=sm_name, connection_name=connection_name,
)
assert fetched.name == connection_name
assert fetched.id == created.id
assert fetched.properties.private_link_service_id.endswith(pls_id_suffix)
# NOTE: do not assert on `connection_status` — see sync sibling docstring.

# List
items = [c async for c in self.client.connections.list(
resource_group_name=rg, storage_mover_name=sm_name,
)]
assert len(items) >= 1
assert connection_name in [c.name for c in items]

# Update — see sync sibling docstring for why we don't assert on the response.
await self.client.connections.create_or_update(
resource_group_name=rg, storage_mover_name=sm_name, connection_name=connection_name,
connection={"properties": {
"privateLinkServiceId": REAL_PRIVATE_LINK_SERVICE_ID,
"description": "ConnectionDescUpdate",
}},
)

# Delete + 404 verification
poller = await self.client.connections.begin_delete(
resource_group_name=rg, storage_mover_name=sm_name, connection_name=connection_name,
)
await poller.result()
with pytest.raises(ResourceNotFoundError):
await self.client.connections.get(
resource_group_name=rg, storage_mover_name=sm_name, connection_name=connection_name,
)
Loading
Loading