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
5 changes: 4 additions & 1 deletion src/connectedvmware/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Release History
===============
1.2.2
++++++
* Fix `create-from-machines` when machines and the vCenter are in different subscriptions.

1.2.1
++++++
* For pwinput, read input from stdin when tty is not available
Expand Down Expand Up @@ -122,4 +126,3 @@ Issue link: https://github.com/Azure/azure-cli/issues/24781
0.1.0
++++++
* Initial release.

6 changes: 6 additions & 0 deletions src/connectedvmware/azext_connectedvmware/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,12 @@
- name: Create VMware resources from all Arc for Servers machines in the specified subscription belonging to that vCenter
text: |-
az connectedvmware vm create-from-machines \
--subscription 01234567-89ab-cdef-0123-456789abcdef \
--vcenter-id /subscriptions/fedcba98-7654-3210-0123-456789abcdef/resourceGroups/contoso-rg-2/providers/Microsoft.ConnectedVMwareVsphere/vcenters/contoso-vcenter

- name: Create VMware resources using the machine subscription name
text: |-
az connectedvmware vm create-from-machines \
--subscription contoso-sub \
--vcenter-id /subscriptions/fedcba98-7654-3210-0123-456789abcdef/resourceGroups/contoso-rg-2/providers/Microsoft.ConnectedVMwareVsphere/vcenters/contoso-vcenter
"""
Expand Down
12 changes: 11 additions & 1 deletion src/connectedvmware/azext_connectedvmware/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,8 @@ def create_from_machines(
):
vcenter_id = vcenter
machine_id = resource_name
# The global --subscription argument sets the machine subscription.
machine_subscription = get_subscription_id(cmd.cli_ctx)
if resource_name is not None:
if rg_name is None:
raise RequiredArgumentMissingError(
Expand All @@ -826,6 +828,7 @@ def create_from_machines(
assert isinstance(vcenter_id, str)

logger = get_logger(__name__)
logger.info("Creating VM from machines on Subscription %s ...", machine_subscription)
arg_client = cf_resource_graph(cmd.cli_ctx)
machine_client = cf_machine(cmd.cli_ctx)
vcenter_sub = vcenter_id.split("/")[2]
Expand All @@ -835,6 +838,7 @@ def create_from_machines(

query = f"""
Resources
{machine_subscription and "| where subscriptionId =~ '{}'".format(machine_subscription) or ""}
{rg_name and "| where resourceGroup =~ '{}'".format(rg_name) or ""}
{machine_id and "| where id =~ '{}'".format(machine_id) or ""}
| where type =~ 'Microsoft.HybridCompute/machines'
Expand Down Expand Up @@ -871,8 +875,13 @@ def create_from_machines(
vm_list = []
while True:
query_options = QueryRequestOptions(skip_token=skip_token)
# Include the vCenter subscription so ARG can join its inventory items.
query_subscriptions = [machine_subscription]
if vcenter_sub.lower() != machine_subscription.lower():
query_subscriptions.append(vcenter_sub)
logger.debug("Querying subscriptions: %s", query_subscriptions)
query_request = QueryRequest(
subscriptions=[get_subscription_id(cmd.cli_ctx)],
subscriptions=query_subscriptions,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to test that the subscription provided in the input parameter to the CLI command is considered in the query here

query=query,
options=query_options,
)
Expand All @@ -896,6 +905,7 @@ def create_from_machines(
inventoryId = vm["inventoryId"]
managedResourceId = vm["managedResourceId"]
biosId = vm["biosId"]
logger.info("Processing machine %s in resource group %s | machineId: %s", machineName, machineRG, machineId)
if len(biosId2VM[biosId]) > 1:
logger.warning(
"%s Skipping machine %s with biosId %s "
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,111 @@


class ConnectedvmwareScenarioTest(ScenarioTest):
def test_create_from_machines_cross_subscription(self):
self.kwargs.update(
{
'machine_subscription': 'ARC-Testing',
'machine_rg': 'azcli-machine-integration-test',
'machine_name': 'test-vm-azcli',
'vcenter_id': (
'/subscriptions/ef8e2098-7ed6-4399-9fb6-556da62b3cf7/'
'resourceGroups/azcli-integration-test/providers/'
'Microsoft.ConnectedVMwareVsphere/vcenters/azcli-vcenter-scenario'
),
}
)

machine_subscription_id = self.cmd(
'az account show --subscription {machine_subscription} --query id -o tsv'
).output.strip()
self.assertRegex(
machine_subscription_id,
r'^[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$',
)
vcenter_subscription_id = self.kwargs['vcenter_id'].split('/')[2]
self.assertNotEqual(
machine_subscription_id.lower(),
vcenter_subscription_id.lower(),
'The machine and vCenter must be in different subscriptions.',
)
machine_id = (
f'/subscriptions/{machine_subscription_id}/resourceGroups/{self.kwargs["machine_rg"]}/'
f'providers/Microsoft.HybridCompute/machines/{self.kwargs["machine_name"]}'
)
self.kwargs.update({
'machine_subscription_id': machine_subscription_id,
'machine_id': machine_id,
'vm_instance_id': (
f'{machine_id}/providers/'
'Microsoft.ConnectedVMwareVsphere/virtualMachineInstances/default'
),
})

with self.assertLogs('azext_connectedvmware.custom', level='DEBUG') as logs:
self.cmd(
'az connectedvmware vm create-from-machines '
'--subscription {machine_subscription} '
'--resource-group {machine_rg} '
'--name {machine_name} '
'--vcenter-id {vcenter_id}'
)
messages = [record.getMessage() for record in logs.records]
self.assertIn(
f'Creating VM from machines on Subscription {machine_subscription_id} ...',
messages,
)
self.assertIn(
f'Querying subscriptions: {[machine_subscription_id, vcenter_subscription_id]}',
messages,
)
self.assertIn(
(
f'Processing machine {self.kwargs["machine_name"]} '
f'in resource group {self.kwargs["machine_rg"]} | machineId: {machine_id}'
).lower(),
[message.lower() for message in messages],
)
vcenter_name = self.kwargs['vcenter_id'].rsplit('/', 1)[1]
# The command catches per-machine failures, so exit code zero is not enough.
self.assertIn(
f'[1/1] machines were successfully linked to the vCenter {vcenter_name} .',
messages,
)
self.assertIn(
f'[0/1] machines failed to be linked to the vCenter {vcenter_name} .',
messages,
)
self.assertIn('[0/1] machines were skipped.', messages)

self.cmd(
'az connectedvmware vm show '
'--subscription {machine_subscription} '
'--resource-group {machine_rg} '
'--name {machine_name}',
checks=[
self.check('id', '{vm_instance_id}', case_sensitive=False),
self.check('infrastructureProfile.vCenterId', '{vcenter_id}', case_sensitive=False),
self.check('provisioningState', 'Succeeded'),
],
)

self.cmd(
'az connectedvmware vm delete '
'--subscription {machine_subscription} '
'--resource-group {machine_rg} '
'--name {machine_name} '
'--retain-machine --yes'
)

self.cmd(
'az resource show --ids {machine_id}',
checks=[
self.check('id', '{machine_id}', case_sensitive=False),
self.check('name', '{machine_name}'),
self.check('type', 'Microsoft.HybridCompute/machines', case_sensitive=False),
],
)

def test_connectedvmware(self):
self.kwargs.update(
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import unittest
from types import SimpleNamespace
from unittest.mock import MagicMock, patch

from azext_connectedvmware.custom import create_from_machines


class CreateFromMachinesTest(unittest.TestCase):
@patch('azext_connectedvmware.custom.get_logger')
@patch('azext_connectedvmware.custom.get_resources_client')
@patch('azext_connectedvmware.custom.cf_machine')
@patch('azext_connectedvmware.custom.cf_resource_graph')
def test_cross_subscription_machines(
self,
mock_cf_resource_graph,
mock_cf_machine,
mock_get_resources_client,
_,
):
machine_sub = "11111111-1111-1111-1111-111111111111"
vcenter_sub = "22222222-2222-2222-2222-222222222222"
machine_id = (
f"/subscriptions/{machine_sub}/resourceGroups/machine-rg/"
"providers/Microsoft.HybridCompute/machines/machine-1"
)
vcenter_id = (
f"/subscriptions/{vcenter_sub}/resourceGroups/vcenter-rg/"
"providers/Microsoft.ConnectedVMwareVsphere/vcenters/vcenter-1"
)

cmd = SimpleNamespace(cli_ctx=MagicMock())
cmd.cli_ctx.data = {"subscription_id": machine_sub}
vcenter = SimpleNamespace(
id=vcenter_id,
name="vcenter-1",
kind="VMware",
location="eastus",
extended_location=SimpleNamespace(name="custom-location"),
)
mock_get_resources_client.return_value.get_by_id.return_value = vcenter

query_response = SimpleNamespace(
data=[{
"machineId": machine_id,
"name": "machine-1",
"resourceGroup": "machine-rg",
"kind": "",
"inventoryId": f"{vcenter_id}/InventoryItems/vm-1",
"managedResourceId": "",
"biosId": "bios-1",
}],
skip_token=None,
)
arg_client = mock_cf_resource_graph.return_value
arg_client.resources.return_value = query_response

machine_client = mock_cf_machine.return_value
vm_client = MagicMock()

create_from_machines(
cmd,
vm_client,
vcenter_id,
rg_name="machine-rg",
resource_name="machine-1",
)

query_request = arg_client.resources.call_args.args[0]
self.assertEqual(
query_request.subscriptions,
[machine_sub, vcenter_sub],
)
self.assertIn(
f"subscriptionId =~ '{machine_sub}'",
query_request.query,
)
self.assertIn(machine_id, query_request.query)
mock_get_resources_client.assert_called_once_with(
cmd.cli_ctx, vcenter_sub
)
mock_cf_machine.assert_called_once_with(cmd.cli_ctx)
machine_client.update.assert_called_once()
vm_client.begin_create_or_update.assert_called_once()


if __name__ == '__main__':
unittest.main()
2 changes: 1 addition & 1 deletion src/connectedvmware/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
# TODO: Confirm this is the right version number you want and it matches your
# HISTORY.rst entry.

VERSION = '1.2.1'
VERSION = '1.2.2'

# The full list of classifiers is available at
# https://pypi.python.org/pypi?%3Aaction=list_classifiers
Expand Down