Procházet zdrojové kódy

Minor fixes following Azure SDK update.

Nashwan Azhari před 9 roky
rodič
revize
857dd2d7df

+ 136 - 95
coriolis/providers/azure/__init__.py

@@ -1,23 +1,11 @@
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-#
 
 """ This module defines the Azure Resource Manager Importer and Exporter. """
 
 import collections
 import contextlib
+import math
 import os
 import os.path as path
 import re
@@ -46,8 +34,8 @@ from coriolis import utils
 
 
 OPTIONS = [
-    cfg.StrOpt("default_auth_url",
     # TODO: AzureStack soon: default for "auth_url", used in client creation.
+    cfg.StrOpt("default_auth_url",
                default="https://management.core.windows.net/",
                help="The API endpoint to authenticate and perform operations"
                     "against."),
@@ -73,6 +61,8 @@ LOG = logging.getLogger(__name__)
 MIGRATION_RESGROUP_NAME_FORMAT = "coriolis-migration-%s"
 MIGRATION_WORKER_NAME_FORMAT = "coriolis-worker-%s"
 MIGRATION_WORKER_DISK_FORMAT = "%s.vhd"
+MIGRATION_WORKER_NIC_NAME_FORMAT = "coriolis-worker-nic-%s"
+MIGRATION_WORKER_PIP_NAME_FORMAT = "coriolis-worker-pip-%s"
 MIGRATION_NETWORK_NAME_FORMAT = "coriolis-migrnet-%s"
 
 BLOB_PATH_FORMAT = "https://%s.blob.core.windows.net/%s/%s"
@@ -93,7 +83,6 @@ WORKER_IMAGES_MAP = {
         "publisher": "canonical",
         "offer": "UbuntuServer",
         "sku": "14.04.3-LTS"
-        # "sku": "15.10"
     },
     "windows": {
         "version": WORKER_VM_IMAGE_VERSION,
@@ -108,7 +97,7 @@ AZURE_OSTYPES_MAP = {
     "windows": compute.models.OperatingSystemTypes.windows
 }
 
-AZURE_VM_VOLUMENOS_MAPPING = collections.OrderedDict([
+AZURE_VMSIZE_VOLUMENOS_MAPPING = collections.OrderedDict([
     # NOTE: this instance size is absolutely useless:
     # (1, compute.models.VirtualMachineSizeTypes.standard_a0),
     (2, compute.models.VirtualMachineSizeTypes.standard_a1),
@@ -177,12 +166,14 @@ class ImportProvider(BaseImportProvider):
             # NOTE: attempt to register to a provider to ensure credentials
             # are indeed actually valid.
             resc = self._get_resource_client(connection_info)
-            azutils.checked(resc.providers.register)(PROVIDERS_NAME_MAP["compute"])
+            azutils.checked(resc.providers.register)(
+                PROVIDERS_NAME_MAP["compute"])
         except (KeyError, azure_exceptions.CloudError,
                 azexceptions.AzureOperationException) as ex:
 
-            LOG.info("Invalid or incomplete Azure credentials provided: %s\n%s",
-                     connection_info, ex)
+            LOG.info(
+                "Invalid or incomplete Azure credentials provided: %s\n%s",
+                connection_info, ex)
             return False
         else:
             return True
@@ -215,38 +206,34 @@ class ImportProvider(BaseImportProvider):
         # validation; but allows for another layer of validation considering
         # a possible discrepancy between the schema and the code...
         raise azexceptions.AzureOperationException(
-            msg="Either 'user_credentials' or 'service_principal_credentials' must"
-            " be specified in 'connection_info'.",
+            msg="Either 'user_credentials' or 'service_principal_credentials' "
+            "must be specified in 'connection_info'.",
             code=-1)
 
     def _get_compute_client(self, connection_info):
         """ Returns an azure.mgmt.compute.ComputeManagementClient.
         """
         return compute.ComputeManagementClient(
-            compute.ComputeManagementClientConfiguration(
-                self._get_cloud_credentials(connection_info),
-                connection_info["subscription_id"]))
+            self._get_cloud_credentials(connection_info),
+            connection_info["subscription_id"])
 
     def _get_network_client(self, connection_info):
         """ Returns an azure.mgmt.network.NetworkResourceProviderClient. """
         return network.NetworkManagementClient(
-            network.NetworkManagementClientConfiguration(
-                self._get_cloud_credentials(connection_info),
-                connection_info["subscription_id"]))
+            self._get_cloud_credentials(connection_info),
+            connection_info["subscription_id"])
 
     def _get_storage_client(self, connection_info):
         """ Returns an azure.mgmt.storage.StorageManagementClient. """
         return storage.StorageManagementClient(
-            storage.StorageManagementClientConfiguration(
-                self._get_cloud_credentials(connection_info),
-                connection_info["subscription_id"]))
+            self._get_cloud_credentials(connection_info),
+            connection_info["subscription_id"])
 
     def _get_resource_client(self, connection_info):
         """ Returns an azure.mgmt.resource.ResourceManagementClient. """
         return resources.ResourceManagementClient(
-            resources.ResourceManagementClientConfiguration(
-                self._get_cloud_credentials(connection_info),
-                connection_info["subscription_id"]))
+            self._get_cloud_credentials(connection_info),
+            connection_info["subscription_id"])
 
     def _get_page_blob_client(self, target_environment):
         """ Returns an azure.storage.blob.PageBlobService client. """
@@ -264,8 +251,8 @@ class ImportProvider(BaseImportProvider):
 
     def _delete_recovery_disk(self, target_environment, vm_name):
         """ Removes the recovery disk block blob for the given
-        instance name. If more than one such recovery disk exists,
-        raises an exception so as to prevent damage. """
+        instance name.
+        """
         blobd = self._get_block_blob_client(target_environment)
         cont_name = CONF.azure_migration_provider.migr_container_name
 
@@ -287,8 +274,9 @@ class ImportProvider(BaseImportProvider):
         """
         self._event_manager.progress_update(
             "Uploading disk from '%s' as '%s'." % (disk_path, upload_name))
+
         def progressf(curr, total):
-            LOG.info("Uploading '%s': %d/%d.", upload_name, curr, total)
+            LOG.debug("Uploading '%s': %d/%d.", upload_name, curr, total)
 
         blobd = self._get_page_blob_client(target_environment)
 
@@ -326,12 +314,13 @@ class ImportProvider(BaseImportProvider):
                     "location",
                     CONF.azure_migration_provider.migr_location),
                 address_space=network.models.AddressSpace(
-                    # TODO: move hard-codes to CONF?
+                    # NOTE: can safely be completely arbitrary:
                     address_prefixes=["10.0.0.0/16"]
                 ),
                 subnets=[
                     network.models.Subnet(
                         name=CONF.azure_migration_provider.migr_subnet_name,
+                        # NOTE: can safely be completely arbitrary:
                         address_prefix='10.0.0.0/24'
                     )
                 ]
@@ -376,8 +365,10 @@ class ImportProvider(BaseImportProvider):
             return self._get_windows_worker_osprofile(
                 location, worker_name)
 
-        raise NotImplementedError(
-            "Unsupported migration OS type '%s'." % os_type)
+        raise azexceptions.FatalAzureOperationException(
+            code=-1,
+            msg="Unsupported migration OS type '%s'." % os_type
+        )
 
     def _get_linux_worker_osprofile(self, worker_name):
         """ Returns the AzureWorkerOSProfile afferent to a Linux worker. """
@@ -405,7 +396,7 @@ class ImportProvider(BaseImportProvider):
         return AzureWorkerOSProfile(
             profile=os_profile,
             token=key,
-            port=22, # NOTE: default one used.
+            port=22,  # NOTE: default port used on Azure-provided images.
             extensions=[]
         )
 
@@ -427,9 +418,8 @@ class ImportProvider(BaseImportProvider):
             )
         )
 
-        extension = compute.models.VirtualMachineExtension(
+        winrm_extension = compute.models.VirtualMachineExtension(
             location,
-            name="worker-winrm-setup-extension",
             publisher=PROVIDERS_NAME_MAP["compute"],
             virtual_machine_extension_type="CustomScriptExtension",
             type_handler_version="1.4",
@@ -444,8 +434,8 @@ class ImportProvider(BaseImportProvider):
         return AzureWorkerOSProfile(
             profile=os_profile,
             token=password,
-            port=5986, # NOTE: default used.
-            extensions=[extension]
+            port=5986,  # NOTE: default port opened via the extension
+            extensions=[winrm_extension]
         )
 
     @utils.retry_on_error()
@@ -455,28 +445,25 @@ class ImportProvider(BaseImportProvider):
 
         NOTE: Azure does unfortunately not allow for the setting of MAC
         addresses. A NIC's MAC is determined when the VM the NIC is attached
-        to is booted; and is NOT persistent between boots...
+        to is booted.
         """
 
         awaited = azutils.awaited(timeout=200)
         net_client = self._get_network_client(connection_info)
 
         nic = network.models.NetworkInterface(
-            name=nic_name,
             location=location,
             ip_configurations=ip_configs
         )
 
-        # NOTE: pointless; read docstring...
-        # nic.mac_address = mac_address
-
         awaited(net_client.network_interfaces.create_or_update)(
             resgroup,
             nic_name,
-            parameters=nic
+            nic
         )
 
-        return azutils.checked(net_client.network_interfaces.get)(resgroup, nic_name)
+        return azutils.checked(net_client.network_interfaces.get)(
+            resgroup, nic_name)
 
     @utils.retry_on_error()
     def _create_public_ip(self, connection_info, resgroup, ip_name, location):
@@ -488,8 +475,7 @@ class ImportProvider(BaseImportProvider):
             ip_name,
             network.models.PublicIPAddress(
                 location=location,
-                public_ip_allocation_method=
-                network.models.IPAllocationMethod.dynamic,
+                public_ip_allocation_method=network.models.IPAllocationMethod.dynamic,
             ),
         )
 
@@ -502,20 +488,28 @@ class ImportProvider(BaseImportProvider):
 
         Does NOT check if it already is a VHD.
         """
-        newpath = path.join(
-            tempfile.gettempdir(), azutils.get_unique_id())
-
-        utils.convert_disk_format(
-            disk_path, newpath,
-            constants.DISK_FORMAT_VHD,
-            preallocated=True
+        newpath = "%s.%s" % (
+            path.splitext(disk_path)[0],
+            constants.DISK_FORMAT_VHD
         )
 
+        try:
+            utils.convert_disk_format(
+                disk_path, newpath,
+                constants.DISK_FORMAT_VHD,
+                preallocated=True
+            )
+        except Exception as ex:
+            raise azexceptions.FatalAzureOperationException(
+                code=-1,
+                msg="Unable to convert disk '%s' to fixed VHD." % disk_path
+            ) from ex
+
         return newpath
 
     def _migrate_disk(self, target_environment, lun,
                       disk_path, upload_name):
-        """ Moves the given disk file to Azure as a page blob. """
+        """ Moves the given disk file to Azure storage as a page blob. """
         disk_file_info = utils.get_disk_info(disk_path)
 
         upload_path = disk_path
@@ -533,12 +527,11 @@ class ImportProvider(BaseImportProvider):
             blb = self._upload_disk(
                 target_environment, upload_path, upload_name)
         except Exception as ex:
-            # disk upload too expensive to retry:
+            # NOTE: _upload_disk is set to retry, so it's game over here:
             raise azexceptions.FatalAzureOperationException(
                 code=-1,
-                msg="Failed to upload disk '%s' to Azure: '%s'" % (
-                    upload_name, ex)
-            )
+                msg="Failed to upload disk '%s' to Azure." % upload_name
+            ) from ex
         finally:
             os.remove(upload_path)
 
@@ -547,13 +540,14 @@ class ImportProvider(BaseImportProvider):
             name=blb.name,
             caching=compute.models.CachingTypes.none,
             vhd=compute.models.VirtualHardDisk(uri=blb.uri),
-            # NOTE: here we force Azure to resize the disk itself to ensure the
-            # 1MB internal size alignment without which the VM will not boot...
-            # Sadly we're limited to a max granularity of 1GB here...
-            disk_size_gb=disk_file_info["virtual-size"] / units.Gi + 1,
+            # NOTE: we must force Azure to resize the disk itself to ensure the
+            # 1MB internal size alignment without which the VM will not boot.
+            # Maximum granularity for specifying disk size on Azure is 1GB.
+            disk_size_gb=math.ceil(disk_file_info["virtual-size"] / units.Gi) + 2,
             create_option=compute.models.DiskCreateOptionTypes.attach,
         )
 
+    @utils.retry_on_error()
     def _get_subnet(self, connection_info, resgroup, vn_name, sub_name):
         """ Fetches information for the specified subnet. """
         net_client = self._get_network_client(connection_info)
@@ -561,6 +555,7 @@ class ImportProvider(BaseImportProvider):
         return azutils.checked(net_client.subnets.get)(
             resgroup, vn_name, sub_name)
 
+    @utils.retry_on_error()
     def _get_pub_ip(self, connection_info, resgroup, ip_name):
         """ Fetches information for the given public IP address. """
         net_client = self._get_network_client(connection_info)
@@ -570,8 +565,8 @@ class ImportProvider(BaseImportProvider):
 
     def _get_worker_size(self, worker_name, ndisks):
         """ Returns the required size of the worker needed to handle
-        the migation/transformation of the data disks. """
-        for maxvols, size in AZURE_VM_VOLUMENOS_MAPPING.items():
+        the migration/transformation of the data disks. """
+        for maxvols, size in AZURE_VMSIZE_VOLUMENOS_MAPPING.items():
             if ndisks <= maxvols:
                 self._event_manager.progress_update(
                     "Worker '%s' size chosen as '%s'." %
@@ -585,6 +580,8 @@ class ImportProvider(BaseImportProvider):
                  "as it has too many volumes (%d).", ndisks)
         )
 
+    @utils.retry_on_error(
+        terminal_exceptions=[azexceptions.FatalAzureOperationException])
     def _create_migration_worker(self, connection_info, target_environment,
                                  export_info, migration_id, datadisks):
         """ Creates and returns the connection information of the worker which
@@ -594,7 +591,7 @@ class ImportProvider(BaseImportProvider):
         worker_name = MIGRATION_WORKER_NAME_FORMAT % migration_id
 
         self._event_manager.progress_update(
-            "Begun defining migration worker '%s'." % worker_name)
+            "Defining migration worker '%s'." % worker_name)
 
         awaited = azutils.awaited(timeout=600)
         location = target_environment.get(
@@ -605,14 +602,14 @@ class ImportProvider(BaseImportProvider):
         self._event_manager.progress_update(
             "Creating Public IP for '%s'." % worker_name)
 
-        ip_name = "coriolis-ws-pubip-" + migration_id
+        ip_name = MIGRATION_WORKER_PIP_NAME_FORMAT % migration_id
         pub_ip = self._create_public_ip(connection_info, resgroup,
                                         ip_name, location)
 
         self._event_manager.progress_update(
             "Creating NIC for '%s'." % worker_name)
 
-        nic_name = "coriolis-ws-nic-" + migration_id
+        nic_name = MIGRATION_WORKER_NIC_NAME_FORMAT % migration_id
         nic = self._create_nic(
             connection_info, resgroup, nic_name, location,
             ip_configs=[
@@ -624,11 +621,8 @@ class ImportProvider(BaseImportProvider):
                         MIGRATION_NETWORK_NAME_FORMAT % migration_id,
                         CONF.azure_migration_provider.migr_subnet_name
                     ),
-                    private_ip_allocation_method=
-                    network.models.IPAllocationMethod.dynamic,
-                    public_ip_address=network.models.Resource(
-                        id=pub_ip.id
-                    ),
+                    private_ip_allocation_method=network.models.IPAllocationMethod.dynamic,
+                    public_ip_address=pub_ip,
                 )
             ]
         )
@@ -645,7 +639,7 @@ class ImportProvider(BaseImportProvider):
         awaited(computec.virtual_machines.create_or_update)(
             resgroup,
             worker_name,
-            parameters=compute.models.VirtualMachine(
+            compute.models.VirtualMachine(
                 location=location,
                 os_profile=worker_osprofile.profile,
                 hardware_profile=compute.models.HardwareProfile(
@@ -823,27 +817,74 @@ class ImportProvider(BaseImportProvider):
 
             nics = []
             for i, nic in enumerate(export_info["devices"].get("nics", [])):
-                nic_name = "%s-NIC-%d" % (instance_name, i)
+                nic_name = nic.get("name", i)
+                net_name = nic.get("network_name", None)
+                subnet_name = nic.get("subnet_name", None)
+                if not net_name:
+                    net = target_environment.get("network", {})
+                    net_name = net.get("name", None)
+
+                    if not net or not net_name:
+                        raise azexceptions.FatalAzureOperationException(
+                            code=-1,
+                            msg="A network to which to perform the migration "
+                                "must be specified within the target "
+                                "environment in order to add NIC '%s' to "
+                                "instance '%s'." % (
+                                    nic_name, instance_name)
+                        )
 
+                    LOG.info("'network_name' not provided for NIC '%s'. "
+                             "Attempting to attach to migration network '%s'.",
+                             nic_name, net_name)
+                    net_name = target_environment["network"]["name"]
+                    subnet_name = target_environment["network"]["subnet"]
+                else:
+                    network_map = target_environment.get("network_map", {})
+                    if not network_map:
+                        raise azexceptions.FatalAzureOperationException(
+                            code=-1,
+                            msg="A network map must be specified within the "
+                                "target environment in order to map the "
+                                "network of nic '%s', namely '%s'." % (
+                                    nic_name, net_name
+                                )
+                        )
+
+                    net_name = network_map.get(net_name, None)
+                    if not net_name:
+                        raise azexceptions.FatalAzureOperationException(
+                            code=-1,
+                            msg="Cannot find suitable network name "
+                                "for attaching nic '%s' within the network "
+                                "map under the name '%s'" % (
+                                    nic_name, nic.get("network_name")
+                                )
+                        )
+
+                    subnet_name = "default"
+                    LOG.info(
+                        "Subnet for nic '%s' within '%s' defaulting to "
+                        "'default'.", nic_name, net_name)
+
+                nic_name = "%s-NIC-%d" % (instance_name, i)
                 nics.append(self._create_nic(
                     connection_info,
                     target_environment["resource_group"],
-                    nic_name, location,
+                    nic_name,
+                    location,
                     ip_configs=[
                         network.models.NetworkInterfaceIPConfiguration(
                             name=nic_name + "-ipconf",
                             subnet=self._get_subnet(
                                 connection_info,
                                 target_environment["resource_group"],
-                                target_environment["network"]["name"],
-                                target_environment["network"]["subnet"]
+                                net_name,
+                                subnet_name
                             ),
-                            private_ip_allocation_method=
-                            network.models.IPAllocationMethod.dynamic,
+                            private_ip_allocation_method=network.models.IPAllocationMethod.dynamic,
                             # TOREINST:
-                            public_ip_address=network.models.Resource(
-                                id=pub_ip.id
-                            ),
+                            public_ip_address=pub_ip,
                         )
                     ],
                 ))
@@ -862,9 +903,8 @@ class ImportProvider(BaseImportProvider):
             awaited(vmclient.create_or_update)(
                 target_environment["resource_group"],
                 instance_name,
-                parameters=compute.models.VirtualMachine(
+                compute.models.VirtualMachine(
                     location=location,
-                    name=instance_name,
                     os_profile=vm_profile,
                     hardware_profile=compute.models.HardwareProfile(
                         vm_size=compute.models.VirtualMachineSizeTypes(
@@ -872,9 +912,8 @@ class ImportProvider(BaseImportProvider):
                         )
                     ),
                     network_profile=compute.models.NetworkProfile(
-                        network_interfaces=
-                        [compute.models.NetworkInterfaceReference(x.id)
-                         for x in nics]
+                        network_interfaces=[compute.models.NetworkInterfaceReference(x.id)
+                                            for x in nics]
                     ),
                     storage_profile=compute.models.StorageProfile(
                         os_disk=compute.models.OSDisk(
@@ -933,7 +972,9 @@ class ImportProvider(BaseImportProvider):
         # first; ensure the worker is properly gone:
         computec = self._get_compute_client(connection_info).virtual_machines
         try:
-            awaited(computec.delete)(migration_resgroup, worker_name)
+            worker_vm = computec.get(migration_resgroup, worker_name)
+            if worker_vm:
+                awaited(computec.delete)(migration_resgroup, worker_name)
         except:
             pass
 

+ 1 - 14
coriolis/providers/azure/exceptions.py

@@ -1,18 +1,5 @@
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-#
 
 """
 Defines various exceptions which may arise during migrations to/from Azure.
@@ -25,7 +12,7 @@ from coriolis.i18n import _
 class AzureOperationException(exception.CoriolisException):
     """ Simply wraps a standard CoriolisException. """
     message = _("Azure operation failed with code: %(code)d. "
-                "Error message: %(message)s.")
+                "Error message: %(msg)s.")
 
 
 class FatalAzureOperationException(AzureOperationException):

+ 5 - 3
coriolis/providers/azure/schemas/target_environment_schema.json

@@ -29,10 +29,12 @@
       },
       "required": [
         "account",
-        "key",
-        "container"
+        "key"
       ]
     },
+    "network_map": {
+      "type": "object"
+    },
     "network": {
       "type": "object",
       "properties": {
@@ -54,7 +56,7 @@
       "required": ["secret_ref"]
     },
     {
-      "required": ["size", "location", "resource_group", "storage", "network"]
+      "required": ["size", "location", "resource_group", "storage"]
     }
   ]
 }

+ 4 - 17
coriolis/providers/azure/utils.py

@@ -1,18 +1,5 @@
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-#
 
 """
 General utility functions for performing Azure operations.
@@ -49,7 +36,7 @@ def normalize_location(location):
 
 
 def checked(operation):
-    """ Forces raw status code check on Azure operation. """
+    """ Forces raw status code check on an Azure operation. """
     @functools.wraps(operation)
     def _checked(*args, **kwargs):
         resp = operation(*args, raw=True, **kwargs)
@@ -65,10 +52,10 @@ def awaited(timeout=90):
     def _awaited(operation):
         @functools.wraps(operation)
         def _await(*args, **kwargs):
-            resp = operation(*args, raw=True, **kwargs)
-            res = resp.result(timeout=timeout)
+            resp = operation(*args, raw=True,
+                             long_running_operation_timeout=timeout, **kwargs)
 
-            return res.output
+            return resp.output
         return _await
 
     return _awaited

+ 3 - 0
coriolis/schemas/vm_export_info_schema.json

@@ -132,6 +132,9 @@
               },
               "mac_address": {
                 "$ref": "#/definitions/nullableString"
+              },
+              "subnet_name": {
+                "type": "string"
               }
             },
             "required": [

+ 1 - 2
coriolis/tests/providers/test_arm_import.py

@@ -276,7 +276,6 @@ class ArmImportProviderUnitTestsCase(test_providers.ImportProviderTestCase):
         test_ip_configs = mock.sentinel.test_ip_configs
 
         test_nic = network.models.NetworkInterface(
-            name=test_nic_name,
             location=self._test_location,
             ip_configurations=test_ip_configs
         )
@@ -297,7 +296,7 @@ class ArmImportProviderUnitTestsCase(test_providers.ImportProviderTestCase):
         self._mock_awaited_inner.assert_called_once_with(
             mock_nicc.create_or_update)
         mock_nicc.create_or_update.assert_called_once_with(
-            self._test_resource_group, test_nic_name, parameters=test_nic)
+            self._test_resource_group, test_nic_name, test_nic)
 
         self._mock_checked.assert_called_once_with(mock_nicc.get)
         mock_nicc.get.assert_called_once_with(

+ 6 - 6
requirements.txt

@@ -30,9 +30,9 @@ PyYAML
 requests
 sqlalchemy
 webob
-msrestazure
-azure-mgmt-compute
-azure-mgmt-network
-azure-mgmt-resource
-azure-mgmt-storage
-azure-storage
+msrestazure==0.4.0
+azure-mgmt-compute==0.30.0rc5
+azure-mgmt-network==0.30.0rc5
+azure-mgmt-resource==0.30.0rc5
+azure-mgmt-storage==0.30.0rc5
+azure-storage==0.32.0