Forráskód Böngészése

Minor fixes following Azure SDK update.

Nashwan Azhari 9 éve
szülő
commit
857dd2d7df

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

@@ -1,23 +1,11 @@
 # Copyright 2016 Cloudbase Solutions Srl
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
 # 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. """
 """ This module defines the Azure Resource Manager Importer and Exporter. """
 
 
 import collections
 import collections
 import contextlib
 import contextlib
+import math
 import os
 import os
 import os.path as path
 import os.path as path
 import re
 import re
@@ -46,8 +34,8 @@ from coriolis import utils
 
 
 
 
 OPTIONS = [
 OPTIONS = [
-    cfg.StrOpt("default_auth_url",
     # TODO: AzureStack soon: default for "auth_url", used in client creation.
     # TODO: AzureStack soon: default for "auth_url", used in client creation.
+    cfg.StrOpt("default_auth_url",
                default="https://management.core.windows.net/",
                default="https://management.core.windows.net/",
                help="The API endpoint to authenticate and perform operations"
                help="The API endpoint to authenticate and perform operations"
                     "against."),
                     "against."),
@@ -73,6 +61,8 @@ LOG = logging.getLogger(__name__)
 MIGRATION_RESGROUP_NAME_FORMAT = "coriolis-migration-%s"
 MIGRATION_RESGROUP_NAME_FORMAT = "coriolis-migration-%s"
 MIGRATION_WORKER_NAME_FORMAT = "coriolis-worker-%s"
 MIGRATION_WORKER_NAME_FORMAT = "coriolis-worker-%s"
 MIGRATION_WORKER_DISK_FORMAT = "%s.vhd"
 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"
 MIGRATION_NETWORK_NAME_FORMAT = "coriolis-migrnet-%s"
 
 
 BLOB_PATH_FORMAT = "https://%s.blob.core.windows.net/%s/%s"
 BLOB_PATH_FORMAT = "https://%s.blob.core.windows.net/%s/%s"
@@ -93,7 +83,6 @@ WORKER_IMAGES_MAP = {
         "publisher": "canonical",
         "publisher": "canonical",
         "offer": "UbuntuServer",
         "offer": "UbuntuServer",
         "sku": "14.04.3-LTS"
         "sku": "14.04.3-LTS"
-        # "sku": "15.10"
     },
     },
     "windows": {
     "windows": {
         "version": WORKER_VM_IMAGE_VERSION,
         "version": WORKER_VM_IMAGE_VERSION,
@@ -108,7 +97,7 @@ AZURE_OSTYPES_MAP = {
     "windows": compute.models.OperatingSystemTypes.windows
     "windows": compute.models.OperatingSystemTypes.windows
 }
 }
 
 
-AZURE_VM_VOLUMENOS_MAPPING = collections.OrderedDict([
+AZURE_VMSIZE_VOLUMENOS_MAPPING = collections.OrderedDict([
     # NOTE: this instance size is absolutely useless:
     # NOTE: this instance size is absolutely useless:
     # (1, compute.models.VirtualMachineSizeTypes.standard_a0),
     # (1, compute.models.VirtualMachineSizeTypes.standard_a0),
     (2, compute.models.VirtualMachineSizeTypes.standard_a1),
     (2, compute.models.VirtualMachineSizeTypes.standard_a1),
@@ -177,12 +166,14 @@ class ImportProvider(BaseImportProvider):
             # NOTE: attempt to register to a provider to ensure credentials
             # NOTE: attempt to register to a provider to ensure credentials
             # are indeed actually valid.
             # are indeed actually valid.
             resc = self._get_resource_client(connection_info)
             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,
         except (KeyError, azure_exceptions.CloudError,
                 azexceptions.AzureOperationException) as ex:
                 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
             return False
         else:
         else:
             return True
             return True
@@ -215,38 +206,34 @@ class ImportProvider(BaseImportProvider):
         # validation; but allows for another layer of validation considering
         # validation; but allows for another layer of validation considering
         # a possible discrepancy between the schema and the code...
         # a possible discrepancy between the schema and the code...
         raise azexceptions.AzureOperationException(
         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)
             code=-1)
 
 
     def _get_compute_client(self, connection_info):
     def _get_compute_client(self, connection_info):
         """ Returns an azure.mgmt.compute.ComputeManagementClient.
         """ Returns an azure.mgmt.compute.ComputeManagementClient.
         """
         """
         return 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):
     def _get_network_client(self, connection_info):
         """ Returns an azure.mgmt.network.NetworkResourceProviderClient. """
         """ Returns an azure.mgmt.network.NetworkResourceProviderClient. """
         return network.NetworkManagementClient(
         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):
     def _get_storage_client(self, connection_info):
         """ Returns an azure.mgmt.storage.StorageManagementClient. """
         """ Returns an azure.mgmt.storage.StorageManagementClient. """
         return 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):
     def _get_resource_client(self, connection_info):
         """ Returns an azure.mgmt.resource.ResourceManagementClient. """
         """ Returns an azure.mgmt.resource.ResourceManagementClient. """
         return resources.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):
     def _get_page_blob_client(self, target_environment):
         """ Returns an azure.storage.blob.PageBlobService client. """
         """ Returns an azure.storage.blob.PageBlobService client. """
@@ -264,8 +251,8 @@ class ImportProvider(BaseImportProvider):
 
 
     def _delete_recovery_disk(self, target_environment, vm_name):
     def _delete_recovery_disk(self, target_environment, vm_name):
         """ Removes the recovery disk block blob for the given
         """ 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)
         blobd = self._get_block_blob_client(target_environment)
         cont_name = CONF.azure_migration_provider.migr_container_name
         cont_name = CONF.azure_migration_provider.migr_container_name
 
 
@@ -287,8 +274,9 @@ class ImportProvider(BaseImportProvider):
         """
         """
         self._event_manager.progress_update(
         self._event_manager.progress_update(
             "Uploading disk from '%s' as '%s'." % (disk_path, upload_name))
             "Uploading disk from '%s' as '%s'." % (disk_path, upload_name))
+
         def progressf(curr, total):
         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)
         blobd = self._get_page_blob_client(target_environment)
 
 
@@ -326,12 +314,13 @@ class ImportProvider(BaseImportProvider):
                     "location",
                     "location",
                     CONF.azure_migration_provider.migr_location),
                     CONF.azure_migration_provider.migr_location),
                 address_space=network.models.AddressSpace(
                 address_space=network.models.AddressSpace(
-                    # TODO: move hard-codes to CONF?
+                    # NOTE: can safely be completely arbitrary:
                     address_prefixes=["10.0.0.0/16"]
                     address_prefixes=["10.0.0.0/16"]
                 ),
                 ),
                 subnets=[
                 subnets=[
                     network.models.Subnet(
                     network.models.Subnet(
                         name=CONF.azure_migration_provider.migr_subnet_name,
                         name=CONF.azure_migration_provider.migr_subnet_name,
+                        # NOTE: can safely be completely arbitrary:
                         address_prefix='10.0.0.0/24'
                         address_prefix='10.0.0.0/24'
                     )
                     )
                 ]
                 ]
@@ -376,8 +365,10 @@ class ImportProvider(BaseImportProvider):
             return self._get_windows_worker_osprofile(
             return self._get_windows_worker_osprofile(
                 location, worker_name)
                 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):
     def _get_linux_worker_osprofile(self, worker_name):
         """ Returns the AzureWorkerOSProfile afferent to a Linux worker. """
         """ Returns the AzureWorkerOSProfile afferent to a Linux worker. """
@@ -405,7 +396,7 @@ class ImportProvider(BaseImportProvider):
         return AzureWorkerOSProfile(
         return AzureWorkerOSProfile(
             profile=os_profile,
             profile=os_profile,
             token=key,
             token=key,
-            port=22, # NOTE: default one used.
+            port=22,  # NOTE: default port used on Azure-provided images.
             extensions=[]
             extensions=[]
         )
         )
 
 
@@ -427,9 +418,8 @@ class ImportProvider(BaseImportProvider):
             )
             )
         )
         )
 
 
-        extension = compute.models.VirtualMachineExtension(
+        winrm_extension = compute.models.VirtualMachineExtension(
             location,
             location,
-            name="worker-winrm-setup-extension",
             publisher=PROVIDERS_NAME_MAP["compute"],
             publisher=PROVIDERS_NAME_MAP["compute"],
             virtual_machine_extension_type="CustomScriptExtension",
             virtual_machine_extension_type="CustomScriptExtension",
             type_handler_version="1.4",
             type_handler_version="1.4",
@@ -444,8 +434,8 @@ class ImportProvider(BaseImportProvider):
         return AzureWorkerOSProfile(
         return AzureWorkerOSProfile(
             profile=os_profile,
             profile=os_profile,
             token=password,
             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()
     @utils.retry_on_error()
@@ -455,28 +445,25 @@ class ImportProvider(BaseImportProvider):
 
 
         NOTE: Azure does unfortunately not allow for the setting of MAC
         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
         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)
         awaited = azutils.awaited(timeout=200)
         net_client = self._get_network_client(connection_info)
         net_client = self._get_network_client(connection_info)
 
 
         nic = network.models.NetworkInterface(
         nic = network.models.NetworkInterface(
-            name=nic_name,
             location=location,
             location=location,
             ip_configurations=ip_configs
             ip_configurations=ip_configs
         )
         )
 
 
-        # NOTE: pointless; read docstring...
-        # nic.mac_address = mac_address
-
         awaited(net_client.network_interfaces.create_or_update)(
         awaited(net_client.network_interfaces.create_or_update)(
             resgroup,
             resgroup,
             nic_name,
             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()
     @utils.retry_on_error()
     def _create_public_ip(self, connection_info, resgroup, ip_name, location):
     def _create_public_ip(self, connection_info, resgroup, ip_name, location):
@@ -488,8 +475,7 @@ class ImportProvider(BaseImportProvider):
             ip_name,
             ip_name,
             network.models.PublicIPAddress(
             network.models.PublicIPAddress(
                 location=location,
                 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.
         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
         return newpath
 
 
     def _migrate_disk(self, target_environment, lun,
     def _migrate_disk(self, target_environment, lun,
                       disk_path, upload_name):
                       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)
         disk_file_info = utils.get_disk_info(disk_path)
 
 
         upload_path = disk_path
         upload_path = disk_path
@@ -533,12 +527,11 @@ class ImportProvider(BaseImportProvider):
             blb = self._upload_disk(
             blb = self._upload_disk(
                 target_environment, upload_path, upload_name)
                 target_environment, upload_path, upload_name)
         except Exception as ex:
         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(
             raise azexceptions.FatalAzureOperationException(
                 code=-1,
                 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:
         finally:
             os.remove(upload_path)
             os.remove(upload_path)
 
 
@@ -547,13 +540,14 @@ class ImportProvider(BaseImportProvider):
             name=blb.name,
             name=blb.name,
             caching=compute.models.CachingTypes.none,
             caching=compute.models.CachingTypes.none,
             vhd=compute.models.VirtualHardDisk(uri=blb.uri),
             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,
             create_option=compute.models.DiskCreateOptionTypes.attach,
         )
         )
 
 
+    @utils.retry_on_error()
     def _get_subnet(self, connection_info, resgroup, vn_name, sub_name):
     def _get_subnet(self, connection_info, resgroup, vn_name, sub_name):
         """ Fetches information for the specified subnet. """
         """ Fetches information for the specified subnet. """
         net_client = self._get_network_client(connection_info)
         net_client = self._get_network_client(connection_info)
@@ -561,6 +555,7 @@ class ImportProvider(BaseImportProvider):
         return azutils.checked(net_client.subnets.get)(
         return azutils.checked(net_client.subnets.get)(
             resgroup, vn_name, sub_name)
             resgroup, vn_name, sub_name)
 
 
+    @utils.retry_on_error()
     def _get_pub_ip(self, connection_info, resgroup, ip_name):
     def _get_pub_ip(self, connection_info, resgroup, ip_name):
         """ Fetches information for the given public IP address. """
         """ Fetches information for the given public IP address. """
         net_client = self._get_network_client(connection_info)
         net_client = self._get_network_client(connection_info)
@@ -570,8 +565,8 @@ class ImportProvider(BaseImportProvider):
 
 
     def _get_worker_size(self, worker_name, ndisks):
     def _get_worker_size(self, worker_name, ndisks):
         """ Returns the required size of the worker needed to handle
         """ 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:
             if ndisks <= maxvols:
                 self._event_manager.progress_update(
                 self._event_manager.progress_update(
                     "Worker '%s' size chosen as '%s'." %
                     "Worker '%s' size chosen as '%s'." %
@@ -585,6 +580,8 @@ class ImportProvider(BaseImportProvider):
                  "as it has too many volumes (%d).", ndisks)
                  "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,
     def _create_migration_worker(self, connection_info, target_environment,
                                  export_info, migration_id, datadisks):
                                  export_info, migration_id, datadisks):
         """ Creates and returns the connection information of the worker which
         """ 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
         worker_name = MIGRATION_WORKER_NAME_FORMAT % migration_id
 
 
         self._event_manager.progress_update(
         self._event_manager.progress_update(
-            "Begun defining migration worker '%s'." % worker_name)
+            "Defining migration worker '%s'." % worker_name)
 
 
         awaited = azutils.awaited(timeout=600)
         awaited = azutils.awaited(timeout=600)
         location = target_environment.get(
         location = target_environment.get(
@@ -605,14 +602,14 @@ class ImportProvider(BaseImportProvider):
         self._event_manager.progress_update(
         self._event_manager.progress_update(
             "Creating Public IP for '%s'." % worker_name)
             "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,
         pub_ip = self._create_public_ip(connection_info, resgroup,
                                         ip_name, location)
                                         ip_name, location)
 
 
         self._event_manager.progress_update(
         self._event_manager.progress_update(
             "Creating NIC for '%s'." % worker_name)
             "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(
         nic = self._create_nic(
             connection_info, resgroup, nic_name, location,
             connection_info, resgroup, nic_name, location,
             ip_configs=[
             ip_configs=[
@@ -624,11 +621,8 @@ class ImportProvider(BaseImportProvider):
                         MIGRATION_NETWORK_NAME_FORMAT % migration_id,
                         MIGRATION_NETWORK_NAME_FORMAT % migration_id,
                         CONF.azure_migration_provider.migr_subnet_name
                         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)(
         awaited(computec.virtual_machines.create_or_update)(
             resgroup,
             resgroup,
             worker_name,
             worker_name,
-            parameters=compute.models.VirtualMachine(
+            compute.models.VirtualMachine(
                 location=location,
                 location=location,
                 os_profile=worker_osprofile.profile,
                 os_profile=worker_osprofile.profile,
                 hardware_profile=compute.models.HardwareProfile(
                 hardware_profile=compute.models.HardwareProfile(
@@ -823,27 +817,74 @@ class ImportProvider(BaseImportProvider):
 
 
             nics = []
             nics = []
             for i, nic in enumerate(export_info["devices"].get("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(
                 nics.append(self._create_nic(
                     connection_info,
                     connection_info,
                     target_environment["resource_group"],
                     target_environment["resource_group"],
-                    nic_name, location,
+                    nic_name,
+                    location,
                     ip_configs=[
                     ip_configs=[
                         network.models.NetworkInterfaceIPConfiguration(
                         network.models.NetworkInterfaceIPConfiguration(
                             name=nic_name + "-ipconf",
                             name=nic_name + "-ipconf",
                             subnet=self._get_subnet(
                             subnet=self._get_subnet(
                                 connection_info,
                                 connection_info,
                                 target_environment["resource_group"],
                                 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:
                             # 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)(
             awaited(vmclient.create_or_update)(
                 target_environment["resource_group"],
                 target_environment["resource_group"],
                 instance_name,
                 instance_name,
-                parameters=compute.models.VirtualMachine(
+                compute.models.VirtualMachine(
                     location=location,
                     location=location,
-                    name=instance_name,
                     os_profile=vm_profile,
                     os_profile=vm_profile,
                     hardware_profile=compute.models.HardwareProfile(
                     hardware_profile=compute.models.HardwareProfile(
                         vm_size=compute.models.VirtualMachineSizeTypes(
                         vm_size=compute.models.VirtualMachineSizeTypes(
@@ -872,9 +912,8 @@ class ImportProvider(BaseImportProvider):
                         )
                         )
                     ),
                     ),
                     network_profile=compute.models.NetworkProfile(
                     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(
                     storage_profile=compute.models.StorageProfile(
                         os_disk=compute.models.OSDisk(
                         os_disk=compute.models.OSDisk(
@@ -933,7 +972,9 @@ class ImportProvider(BaseImportProvider):
         # first; ensure the worker is properly gone:
         # first; ensure the worker is properly gone:
         computec = self._get_compute_client(connection_info).virtual_machines
         computec = self._get_compute_client(connection_info).virtual_machines
         try:
         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:
         except:
             pass
             pass
 
 

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

@@ -1,18 +1,5 @@
 # Copyright 2016 Cloudbase Solutions Srl
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
 # 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.
 Defines various exceptions which may arise during migrations to/from Azure.
@@ -25,7 +12,7 @@ from coriolis.i18n import _
 class AzureOperationException(exception.CoriolisException):
 class AzureOperationException(exception.CoriolisException):
     """ Simply wraps a standard CoriolisException. """
     """ Simply wraps a standard CoriolisException. """
     message = _("Azure operation failed with code: %(code)d. "
     message = _("Azure operation failed with code: %(code)d. "
-                "Error message: %(message)s.")
+                "Error message: %(msg)s.")
 
 
 
 
 class FatalAzureOperationException(AzureOperationException):
 class FatalAzureOperationException(AzureOperationException):

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

@@ -29,10 +29,12 @@
       },
       },
       "required": [
       "required": [
         "account",
         "account",
-        "key",
-        "container"
+        "key"
       ]
       ]
     },
     },
+    "network_map": {
+      "type": "object"
+    },
     "network": {
     "network": {
       "type": "object",
       "type": "object",
       "properties": {
       "properties": {
@@ -54,7 +56,7 @@
       "required": ["secret_ref"]
       "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
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
 # 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.
 General utility functions for performing Azure operations.
@@ -49,7 +36,7 @@ def normalize_location(location):
 
 
 
 
 def checked(operation):
 def checked(operation):
-    """ Forces raw status code check on Azure operation. """
+    """ Forces raw status code check on an Azure operation. """
     @functools.wraps(operation)
     @functools.wraps(operation)
     def _checked(*args, **kwargs):
     def _checked(*args, **kwargs):
         resp = operation(*args, raw=True, **kwargs)
         resp = operation(*args, raw=True, **kwargs)
@@ -65,10 +52,10 @@ def awaited(timeout=90):
     def _awaited(operation):
     def _awaited(operation):
         @functools.wraps(operation)
         @functools.wraps(operation)
         def _await(*args, **kwargs):
         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 _await
 
 
     return _awaited
     return _awaited

+ 3 - 0
coriolis/schemas/vm_export_info_schema.json

@@ -132,6 +132,9 @@
               },
               },
               "mac_address": {
               "mac_address": {
                 "$ref": "#/definitions/nullableString"
                 "$ref": "#/definitions/nullableString"
+              },
+              "subnet_name": {
+                "type": "string"
               }
               }
             },
             },
             "required": [
             "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_ip_configs = mock.sentinel.test_ip_configs
 
 
         test_nic = network.models.NetworkInterface(
         test_nic = network.models.NetworkInterface(
-            name=test_nic_name,
             location=self._test_location,
             location=self._test_location,
             ip_configurations=test_ip_configs
             ip_configurations=test_ip_configs
         )
         )
@@ -297,7 +296,7 @@ class ArmImportProviderUnitTestsCase(test_providers.ImportProviderTestCase):
         self._mock_awaited_inner.assert_called_once_with(
         self._mock_awaited_inner.assert_called_once_with(
             mock_nicc.create_or_update)
             mock_nicc.create_or_update)
         mock_nicc.create_or_update.assert_called_once_with(
         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)
         self._mock_checked.assert_called_once_with(mock_nicc.get)
         mock_nicc.get.assert_called_once_with(
         mock_nicc.get.assert_called_once_with(

+ 6 - 6
requirements.txt

@@ -30,9 +30,9 @@ PyYAML
 requests
 requests
 sqlalchemy
 sqlalchemy
 webob
 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