Browse Source

Merged in alexpilotti/coriolis (pull request #39)

OSMorphing refactoring
Alessandro Pilotti 9 years ago
parent
commit
f365d9cdbb
52 changed files with 455 additions and 5455 deletions
  1. 0 1
      coriolis/api/v1/views/migration_view.py
  2. 12 1
      coriolis/api/v1/views/replica_tasks_execution_view.py
  3. 0 4
      coriolis/api/wsgi.py
  4. 4 4
      coriolis/cmd/api.py
  5. 5 5
      coriolis/cmd/conductor.py
  6. 5 5
      coriolis/cmd/worker.py
  7. 40 8
      coriolis/conductor/rpc/server.py
  8. 8 0
      coriolis/constants.py
  9. 2 1
      coriolis/db/sqlalchemy/migrate_repo/manage.py
  10. 4 0
      coriolis/exception.py
  11. 22 13
      coriolis/osmorphing/base.py
  12. 26 0
      coriolis/osmorphing/coreos.py
  13. 3 9
      coriolis/osmorphing/debian.py
  14. 0 45
      coriolis/osmorphing/factory.py
  15. 60 22
      coriolis/osmorphing/manager.py
  16. 27 0
      coriolis/osmorphing/openwrt.py
  17. 1 44
      coriolis/osmorphing/oracle.py
  18. 0 3
      coriolis/osmorphing/osmount/windows.py
  19. 9 63
      coriolis/osmorphing/redhat.py
  20. 2 45
      coriolis/osmorphing/suse.py
  21. 1 11
      coriolis/osmorphing/ubuntu.py
  22. 1 210
      coriolis/osmorphing/windows.py
  23. 0 1013
      coriolis/providers/azure/__init__.py
  24. 0 20
      coriolis/providers/azure/exceptions.py
  25. 0 57
      coriolis/providers/azure/schemas/connection_info_schema.json
  26. 0 98
      coriolis/providers/azure/schemas/target_environment_schema.json
  27. 0 101
      coriolis/providers/azure/utils.py
  28. 1 1
      coriolis/providers/backup_writers.py
  29. 48 0
      coriolis/providers/base.py
  30. 10 7
      coriolis/providers/factory.py
  31. 0 5
      coriolis/providers/openstack/__init__.py
  32. 0 329
      coriolis/providers/openstack/common.py
  33. 0 479
      coriolis/providers/openstack/exp.py
  34. 0 1033
      coriolis/providers/openstack/imp.py
  35. 0 64
      coriolis/providers/openstack/schemas/connection_info_schema.json
  36. 0 57
      coriolis/providers/openstack/schemas/target_environment_schema.json
  37. 0 647
      coriolis/providers/vmware_vsphere/__init__.py
  38. 0 250
      coriolis/providers/vmware_vsphere/guestid.py
  39. 0 42
      coriolis/providers/vmware_vsphere/schemas/connection_info_schema.json
  40. 0 244
      coriolis/providers/vmware_vsphere/vixdisklib.py
  41. 1 2
      coriolis/schemas_exceptions.py
  42. 0 37
      coriolis/service.py
  43. 28 0
      coriolis/tasks/base.py
  44. 11 0
      coriolis/tasks/factory.py
  45. 39 1
      coriolis/tasks/migration_tasks.py
  46. 33 0
      coriolis/tasks/osmorphing_tasks.py
  47. 48 34
      coriolis/tasks/replica_tasks.py
  48. 0 0
      coriolis/tests/providers/azure/__init__.py
  49. 0 427
      coriolis/tests/providers/azure/test_azure_import.py
  50. 3 1
      coriolis/tests/providers/base.py
  51. 1 0
      coriolis/tests/testutils.py
  52. 0 12
      requirements.txt

+ 0 - 1
coriolis/api/v1/views/migration_view.py

@@ -15,7 +15,6 @@ def _format_migration(req, migration, keys=None):
     migration_dict = dict(itertools.chain.from_iterable(
         transform(k, v) for k, v in migration.items()))
 
-    # Migrations have a single tasks execution
     execution = replica_tasks_execution_view.format_replica_tasks_execution(
         req, migration_dict["executions"][0])
 

+ 12 - 1
coriolis/api/v1/views/replica_tasks_execution_view.py

@@ -9,7 +9,18 @@ from coriolis import utils
 
 def _sort_tasks(tasks):
     non_error_only_tasks = [t for t in tasks if
-                            t["depends_on"] or not t["on_error"]]
+                            not t["depends_on"] and not t["on_error"]]
+
+    def _add_non_error_tasks(task_id):
+        for t in tasks:
+            if (t["depends_on"] and task_id in t["depends_on"] and
+                    t not in non_error_only_tasks):
+                non_error_only_tasks.append(t)
+                _add_non_error_tasks(t["id"])
+
+    for t in non_error_only_tasks:
+        _add_non_error_tasks(t["id"])
+
     # Include error only tasks only if executed
     error_only_tasks = [t for t in tasks if t["status"] !=
                         constants.TASK_STATUS_ON_ERROR_ONLY and

+ 0 - 4
coriolis/api/wsgi.py

@@ -1224,9 +1224,6 @@ class Fault(webob.exc.HTTPException):
             if retry:
                 fault_data[fault_name]['retryAfter'] = retry
 
-        # 'code' is an attribute on the fault tag itself
-        metadata = {'attributes': {fault_name: 'code'}}
-
         content_type = req.best_match_content_type()
         serializer = {
             'application/json': JSONDictSerializer(),
@@ -1299,7 +1296,6 @@ class OverLimitFault(webob.exc.HTTPException):
     def __call__(self, request):
         """Serializes the wrapped exception conforming to our error format."""
         content_type = request.best_match_content_type()
-        metadata = {"attributes": {"overLimitFault": "code"}}
 
         def translate(msg):
             locale = request.best_match_language()

+ 4 - 4
coriolis/cmd/api.py

@@ -4,12 +4,12 @@
 import eventlet
 eventlet.monkey_patch()
 
-import sys
+import sys # noqa
 
-from coriolis import service
-from coriolis import utils
+from coriolis import service # noqa
+from coriolis import utils # noqa
 
-from oslo_config import cfg
+from oslo_config import cfg # noqa
 
 CONF = cfg.CONF
 

+ 5 - 5
coriolis/cmd/conductor.py

@@ -4,13 +4,13 @@
 import eventlet
 eventlet.monkey_patch()
 
-import sys
+import sys # noqa
 
-from oslo_config import cfg
+from oslo_config import cfg # noqa
 
-from coriolis.conductor.rpc import server as rpc_server
-from coriolis import service
-from coriolis import utils
+from coriolis.conductor.rpc import server as rpc_server # noqa
+from coriolis import service # noqa
+from coriolis import utils # noqa
 
 CONF = cfg.CONF
 

+ 5 - 5
coriolis/cmd/worker.py

@@ -4,13 +4,13 @@
 import eventlet
 eventlet.monkey_patch()
 
-import sys
+import sys # noqa
 
-from oslo_config import cfg
+from oslo_config import cfg # noqa
 
-from coriolis.worker.rpc import server as rpc_server
-from coriolis import service
-from coriolis import utils
+from coriolis.worker.rpc import server as rpc_server # noqa
+from coriolis import service # noqa
+from coriolis import utils # noqa
 
 CONF = cfg.CONF
 

+ 40 - 8
coriolis/conductor/rpc/server.py

@@ -75,11 +75,16 @@ class ConductorServerEndpoint(object):
         task.depends_on = depends_on
         task.on_error = on_error
 
-        if not depends_on and on_error:
-            task.status = constants.TASK_STATUS_ON_ERROR_ONLY
-        else:
+        if not on_error:
             task.status = constants.TASK_STATUS_PENDING
-
+        else:
+            task.status = constants.TASK_STATUS_ON_ERROR_ONLY
+            if depends_on:
+                for task_id in depends_on:
+                    if [t for t in task.execution.tasks if t.id == task_id and
+                            t.status != constants.TASK_STATUS_ON_ERROR_ONLY]:
+                        task.status = constants.TASK_STATUS_PENDING
+                        break
         return task
 
     def _begin_tasks(self, ctxt, execution, task_info={}):
@@ -349,16 +354,31 @@ class ConductorServerEndpoint(object):
                 instance, constants.TASK_TYPE_DEPLOY_REPLICA_INSTANCE,
                 execution, [create_snapshot_task.id])
 
+            osmorphing_task = self._create_task(
+                instance, constants.TASK_TYPE_OS_MORPHING,
+                execution, depends_on=[deploy_replica_task.id])
+
+            finalize_deployment_task = self._create_task(
+                instance,
+                constants.TASK_TYPE_FINALIZE_REPLICA_INSTANCE_DEPLOYMENT,
+                execution, depends_on=[osmorphing_task.id])
+
             self._create_task(
                 instance, constants.TASK_TYPE_DELETE_REPLICA_DISK_SNAPSHOTS,
-                execution, [deploy_replica_task.id],
+                execution, depends_on=[finalize_deployment_task.id],
                 on_error=clone_disks)
 
+            cleanup_deployment_task = self._create_task(
+                instance,
+                constants.TASK_TYPE_CLEANUP_FAILED_REPLICA_INSTANCE_DEPLOYMENT,
+                execution, on_error=True)
+
             if not clone_disks:
                 self._create_task(
                     instance,
                     constants.TASK_TYPE_RESTORE_REPLICA_DISK_SNAPSHOTS,
-                    execution, on_error=True)
+                    execution, depends_on=[cleanup_deployment_task.id],
+                    on_error=True)
 
         db_api.add_migration(ctxt, migration)
         LOG.info("Migration created: %s", migration.id)
@@ -382,14 +402,26 @@ class ConductorServerEndpoint(object):
         migration.info = {}
 
         for instance in instances:
-
             task_export = self._create_task(
                 instance, constants.TASK_TYPE_EXPORT_INSTANCE, execution)
 
-            self._create_task(
+            task_import = self._create_task(
                 instance, constants.TASK_TYPE_IMPORT_INSTANCE,
                 execution, depends_on=[task_export.id])
 
+            task_osmorphing = self._create_task(
+                instance, constants.TASK_TYPE_OS_MORPHING,
+                execution, depends_on=[task_import.id])
+
+            self._create_task(
+                instance, constants.TASK_TYPE_FINALIZE_IMPORT_INSTANCE,
+                execution, depends_on=[task_osmorphing.id])
+
+            self._create_task(
+                instance,
+                constants.TASK_TYPE_CLEANUP_FAILED_IMPORT_INSTANCE,
+                execution, on_error=True)
+
         db_api.add_migration(ctxt, migration)
         LOG.info("Migration created: %s", migration.id)
 

+ 8 - 0
coriolis/constants.py

@@ -14,6 +14,10 @@ TASK_STATUS_ON_ERROR_ONLY = "EXECUTE_ON_ERROR_ONLY"
 
 TASK_TYPE_EXPORT_INSTANCE = "EXPORT_INSTANCE"
 TASK_TYPE_IMPORT_INSTANCE = "IMPORT_INSTANCE"
+TASK_TYPE_FINALIZE_IMPORT_INSTANCE = "FINALIZE_IMPORT_INSTANCE"
+TASK_TYPE_CLEANUP_FAILED_IMPORT_INSTANCE = "CLEANUP_FAILED_IMPORT_INSTANCE"
+
+TASK_TYPE_OS_MORPHING = "OS_MORPHING"
 
 TASK_TYPE_GET_INSTANCE_INFO = "GET_INSTANCE_INFO"
 TASK_TYPE_DEPLOY_REPLICA_DISKS = "DEPLOY_REPLICA_DISKS"
@@ -25,6 +29,10 @@ TASK_TYPE_DEPLOY_REPLICA_TARGET_RESOURCES = "DEPLOY_REPLICA_TARGET_RESOURCES"
 TASK_TYPE_DELETE_REPLICA_TARGET_RESOURCES = "DELETE_REPLICA_TARGET_RESOURCES"
 TASK_TYPE_SHUTDOWN_INSTANCE = "SHUTDOWN_INSTANCE"
 TASK_TYPE_DEPLOY_REPLICA_INSTANCE = "DEPLOY_REPLICA_INSTANCE"
+TASK_TYPE_FINALIZE_REPLICA_INSTANCE_DEPLOYMENT = (
+    "FINALIZE_REPLICA_INSTANCE_DEPLOYMENT")
+TASK_TYPE_CLEANUP_FAILED_REPLICA_INSTANCE_DEPLOYMENT = (
+    "CLEANUP_FAILED_REPLICA_INSTANCE_DEPLOYMENT")
 TASK_TYPE_CREATE_REPLICA_DISK_SNAPSHOTS = "CREATE_REPLICA_DISK_SNAPSHOTS"
 TASK_TYPE_DELETE_REPLICA_DISK_SNAPSHOTS = "DELETE_REPLICA_DISK_SNAPSHOTS"
 TASK_TYPE_RESTORE_REPLICA_DISK_SNAPSHOTS = "RESTORE_REPLICA_DISK_SNAPSHOTS"

+ 2 - 1
coriolis/db/sqlalchemy/migrate_repo/manage.py

@@ -1,7 +1,8 @@
+#!/usr/bin/env python
+
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
 
-#!/usr/bin/env python
 from migrate.versioning.shell import main
 
 if __name__ == '__main__':

+ 4 - 0
coriolis/exception.py

@@ -205,6 +205,10 @@ class NotFound(CoriolisException):
     safe = True
 
 
+class OSMorphingToolsNotFound(NotFound):
+    message = _("Couldn't find any morphing tools for this OS.")
+
+
 class FileNotFound(NotFound):
     message = _("File %(file_path)s could not be found.")
 

+ 22 - 13
coriolis/osmorphing/base.py

@@ -14,15 +14,14 @@ class BaseOSMorphingTools(object):
     __metaclass__ = abc.ABCMeta
 
     def __init__(
-            self, conn, os_root_dir, os_root_device,
-            hypervisor, platform, event_manager):
+            self, conn, os_root_dir, os_root_device, hypervisor,
+            event_manager):
         self._conn = conn
         self._os_root_dir = os_root_dir
         self._os_root_device = os_root_device
-        self._hypervisor = hypervisor
-        self._platform = platform
         self._distro = None
         self._version = None
+        self._hypervisor = hypervisor
         self._event_manager = event_manager
 
     def check_os(self):
@@ -50,10 +49,16 @@ class BaseOSMorphingTools(object):
     def install_packages(self, package_names):
         pass
 
+    def post_packages_install(self, package_names):
+        pass
+
+    def pre_packages_uninstall(self, package_names):
+        pass
+
     def uninstall_packages(self, package_names):
         pass
 
-    def post_packages_install(self, package_names):
+    def post_packages_uninstall(self, package_names):
         pass
 
 
@@ -62,17 +67,15 @@ class BaseLinuxOSMorphingTools(BaseOSMorphingTools):
 
     _packages = {}
 
-    def __init__(self, conn, os_root_dir, os_root_dev,
-                 hypervisor, platform, event_manager):
+    def __init__(self, conn, os_root_dir, os_root_dev, hypervisor,
+                 event_manager):
         super(BaseLinuxOSMorphingTools, self).__init__(
-            conn, os_root_dir, os_root_dev,
-            hypervisor, platform, event_manager)
+            conn, os_root_dir, os_root_dev, hypervisor, event_manager)
         self._ssh = conn
 
     def get_packages(self):
-        k_add = [(h, p) for (h, p) in self._packages.keys() if
-                 (h is None or h == self._hypervisor) and
-                 (p is None or p == self._platform)]
+        k_add = [h for h in self._packages.keys() if
+                 h is None or h == self._hypervisor]
 
         add = [p[0] for p in itertools.chain.from_iterable(
                [l for k, l in self._packages.items() if k in k_add])]
@@ -90,6 +93,12 @@ class BaseLinuxOSMorphingTools(BaseOSMorphingTools):
     def post_packages_install(self, package_names):
         self._restore_resolv_conf()
 
+    def pre_packages_uninstall(self, package_names):
+        self._copy_resolv_conf()
+
+    def post_packages_uninstall(self, package_names):
+        self._restore_resolv_conf()
+
     def _test_path(self, chroot_path):
         path = os.path.join(self._os_root_dir, chroot_path)
         return utils.test_ssh_path(self._ssh, path)
@@ -142,7 +151,7 @@ class BaseLinuxOSMorphingTools(BaseOSMorphingTools):
     def _get_config(self, config_content):
         config = {}
         for config_line in config_content.split('\n'):
-            m = re.match('(.*)="?([^"]*)"?', config_line)
+            m = re.match('(.*)=(?:"|\')?([^"\']*)(?:"|\')?', config_line)
             if m:
                 name, value = m.groups()
                 config[name] = value

+ 26 - 0
coriolis/osmorphing/coreos.py

@@ -0,0 +1,26 @@
+# Copyright 2017 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+from coriolis.osmorphing import base
+
+
+class BaseCoreOSMorphingTools(base.BaseLinuxOSMorphingTools):
+    def _check_os(self):
+        os_release = self._get_os_release()
+        id = os_release.get("ID")
+        if id == "coreos":
+            name = os_release.get("NAME")
+            version = os_release.get("VERSION_ID")
+            return (name, version)
+
+    def pre_packages_install(self, package_names):
+        pass
+
+    def post_packages_install(self, package_names):
+        pass
+
+    def pre_packages_uninstall(self, package_names):
+        pass
+
+    def post_packages_uninstall(self, package_names):
+        pass

+ 3 - 9
coriolis/osmorphing/debian.py

@@ -3,17 +3,10 @@
 
 import os
 
-from coriolis import constants
 from coriolis.osmorphing import base
 
 
-class DebianMorphingTools(base.BaseLinuxOSMorphingTools):
-    _packages = {
-        (constants.HYPERVISOR_VMWARE, None): [("open-vm-tools", True)],
-        # TODO: add cloud-initramfs-growroot
-        (None, constants.PLATFORM_OPENSTACK): [("cloud-init", True)],
-    }
-
+class BaseDebianMorphingTools(base.BaseLinuxOSMorphingTools):
     def _check_os(self):
         lsb_release_path = "etc/lsb-release"
         debian_version_path = "etc/debian_version"
@@ -37,7 +30,8 @@ class DebianMorphingTools(base.BaseLinuxOSMorphingTools):
                            interfaces_path)
 
     def pre_packages_install(self, package_names):
-        super(DebianMorphingTools, self).pre_packages_install(package_names)
+        super(BaseDebianMorphingTools, self).pre_packages_install(
+            package_names)
 
         if package_names:
             self._event_manager.progress_update("Updating packages list")

+ 0 - 45
coriolis/osmorphing/factory.py

@@ -1,45 +0,0 @@
-# Copyright 2016 Cloudbase Solutions Srl
-# All Rights Reserved.
-
-import itertools
-
-from oslo_log import log as logging
-
-from coriolis import constants
-from coriolis import exception
-from coriolis.osmorphing import debian
-from coriolis.osmorphing import oracle
-from coriolis.osmorphing import redhat
-from coriolis.osmorphing import suse
-from coriolis.osmorphing import ubuntu
-from coriolis.osmorphing import windows
-
-LOG = logging.getLogger(__name__)
-
-
-def get_os_morphing_tools(
-        conn, os_type, os_root_dir, os_root_dev,
-        target_hypervisor, target_platform, event_manager):
-    os_morphing_tools_clss = {
-        constants.OS_TYPE_LINUX: [debian.DebianMorphingTools,
-                                  ubuntu.UbuntuMorphingTools,
-                                  oracle.OracleMorphingTools,
-                                  redhat.RedHatMorphingTools,
-                                  suse.SUSEMorphingTools],
-        constants.OS_TYPE_WINDOWS: [windows.WindowsMorphingTools],
-    }
-
-    if os_type and os_type not in os_morphing_tools_clss:
-        raise exception.CoriolisException("Unsupported OS type: %s" % os_type)
-
-    for cls in os_morphing_tools_clss.get(
-            os_type, itertools.chain(*os_morphing_tools_clss.values())):
-        tools = cls(
-            conn, os_root_dir, os_root_dev, target_hypervisor,
-            target_platform, event_manager)
-        LOG.debug("Testing OS morphing tools: %s", cls.__name__)
-        os_info = tools.check_os()
-        if os_info:
-            return (tools, os_info)
-    raise exception.CoriolisException(
-        "Cannot find the morphing tools for this OS image")

+ 60 - 22
coriolis/osmorphing/manager.py

@@ -3,46 +3,84 @@
 
 from oslo_log import log as logging
 
-from coriolis.osmorphing import factory as osmorphing_factory
+from coriolis import exception
+from coriolis import events
 from coriolis.osmorphing.osmount import factory as osmount_factory
 
 LOG = logging.getLogger(__name__)
 
 
-def morph_image(connection_info, os_type, target_hypervisor, target_platform,
-                nics_info, event_manager, ignore_devices=[]):
+def morph_image(origin_provider, destination_provider, connection_info,
+                osmorphing_info, event_handler):
+    event_manager = events.EventManager(event_handler)
+
+    event_manager.progress_update("Preparing instance for target platform")
+
+    os_type = osmorphing_info.get('os_type')
+    ignore_devices = osmorphing_info.get('ignore_devices', [])
+
     os_mount_tools = osmount_factory.get_os_mount_tools(
         os_type, connection_info, event_manager, ignore_devices)
 
     event_manager.progress_update("Discovering and mounting OS partitions")
     os_root_dir, other_mounted_dirs, os_root_dev = os_mount_tools.mount_os()
 
+    osmorphing_info['os_root_dir'] = os_root_dir
+    osmorphing_info['os_root_dev'] = os_root_dev
     conn = os_mount_tools.get_connection()
-    os_morphing_tools, os_info = osmorphing_factory.get_os_morphing_tools(
-        conn, os_type, os_root_dir, os_root_dev, target_hypervisor,
-        target_platform, event_manager)
 
-    event_manager.progress_update('OS being migrated: %s' % str(os_info))
+    try:
+        (export_os_morphing_tools, _) = origin_provider.get_os_morphing_tools(
+            conn, osmorphing_info)
+    except exception.OSMorphingToolsNotFound:
+        export_os_morphing_tools = None
 
-    os_morphing_tools.set_net_config(nics_info, dhcp=True)
-    LOG.info("Pre packages")
-    (packages_add,
-     packages_remove) = os_morphing_tools.get_packages()
+    try:
+        (import_os_morphing_tools,
+         os_info) = destination_provider.get_os_morphing_tools(
+            conn, osmorphing_info)
+    except exception.OSMorphingToolsNotFound:
+        import_os_morphing_tools = None
+        os_info = None
 
-    os_morphing_tools.pre_packages_install(packages_add)
-
-    if packages_remove:
+    if not import_os_morphing_tools:
         event_manager.progress_update(
-            "Removing packages: %s" % str(packages_remove))
-        os_morphing_tools.uninstall_packages(packages_remove)
+            'No OS morphing tools found for this instance')
+    else:
+        event_manager.progress_update('OS being migrated: %s' % str(os_info))
 
-    if packages_add:
-        event_manager.progress_update(
-            "Adding packages: %s" % str(packages_add))
-        os_morphing_tools.install_packages(packages_add)
+        (packages_add, _) = import_os_morphing_tools.get_packages()
+
+        if export_os_morphing_tools:
+            (_, packages_remove) = export_os_morphing_tools.get_packages()
+            # Don't remove packages that need to be installed
+            packages_remove = list(set(packages_remove) - set(packages_add))
+
+            LOG.info("Pre packages uninstall")
+            export_os_morphing_tools.pre_packages_uninstall(packages_remove)
+
+            if packages_remove:
+                event_manager.progress_update(
+                    "Removing packages: %s" % str(packages_remove))
+                export_os_morphing_tools.uninstall_packages(packages_remove)
+
+            LOG.info("Post packages uninstall")
+            export_os_morphing_tools.post_packages_uninstall(packages_remove)
+
+        LOG.info("Pre packages install")
+        import_os_morphing_tools.pre_packages_install(packages_add)
+
+        nics_info = osmorphing_info.get('nics_info')
+        import_os_morphing_tools.set_net_config(nics_info, dhcp=True)
+        LOG.info("Pre packages")
+
+        if packages_add:
+            event_manager.progress_update(
+                "Adding packages: %s" % str(packages_add))
+            import_os_morphing_tools.install_packages(packages_add)
 
-    LOG.info("Post packages")
-    os_morphing_tools.post_packages_install(packages_add)
+        LOG.info("Post packages install")
+        import_os_morphing_tools.post_packages_install(packages_add)
 
     event_manager.progress_update("Dismounting OS partitions")
     os_mount_tools.dismount_os(other_mounted_dirs + [os_root_dir])

+ 27 - 0
coriolis/osmorphing/openwrt.py

@@ -0,0 +1,27 @@
+# Copyright 2017 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+from coriolis.osmorphing import base
+
+
+class BaseOpenWRTMorphingTools(base.BaseLinuxOSMorphingTools):
+    def _check_os(self):
+        openwrt_release = self._read_config_file(
+            "etc/openwrt_release", check_exists=True)
+        distrib_id = openwrt_release.get("DISTRIB_ID")
+        if distrib_id == "OpenWrt":
+            name = openwrt_release.get("DISTRIB_DESCRIPTION", distrib_id)
+            version = openwrt_release.get("DISTRIB_RELEASE")
+            return (name, version)
+
+    def pre_packages_install(self, package_names):
+        pass
+
+    def post_packages_install(self, package_names):
+        pass
+
+    def pre_packages_uninstall(self, package_names):
+        pass
+
+    def post_packages_uninstall(self, package_names):
+        pass

+ 1 - 44
coriolis/osmorphing/oracle.py

@@ -3,12 +3,10 @@
 
 import re
 
-from coriolis import constants
-from coriolis import exception
 from coriolis.osmorphing import redhat
 
 
-class OracleMorphingTools(redhat.RedHatMorphingTools):
+class BaseOracleMorphingTools(redhat.BaseRedHatMorphingTools):
     def _check_os(self):
         oracle_release_path = "etc/oracle-release"
         if self._test_path(oracle_release_path):
@@ -19,47 +17,6 @@ class OracleMorphingTools(redhat.RedHatMorphingTools):
                 distro, version = m.groups()
                 return (distro, version)
 
-    def install_packages(self, package_names):
-        self._yum_install(package_names, self._enable_repos)
-
-    def pre_packages_install(self, package_names):
-        self._enable_repos = []
-        super(OracleMorphingTools, self).pre_packages_install(package_names)
-
-        if self._platform == constants.PLATFORM_OPENSTACK:
-            self._enable_cloud_init_repos()
-
-    def _enable_cloud_init_repos(self):
-        self._yum_install(['yum-utils'])
-
-        distro, version = self.check_os()
-        major_version = version.split(".")[0]
-
-        # TODO: for ULN users, use the corresponding repos
-        # e.g.: ol7_x86_64_addons
-        self._exec_cmd_chroot(
-            "yum-config-manager --add-repo "
-            "http://public-yum.oracle.com/public-yum-ol%s.repo" %
-            major_version)
-
-        self._enable_repos = ["ol%s_software_collections" % major_version,
-                              "ol%s_addons" % major_version]
-
-        if major_version == "7":
-            try:
-                self._yum_install(["python-cheetah"], self._enable_repos)
-            except exception.CoriolisException:
-                # The python-pygments RPM required by python-cheetah is called
-                # python27-python-pygments. Force the installation.
-                self._yum_install(
-                    ["python-markdown", "python27-python-pygments"],
-                    self._enable_repos)
-
-                self._exec_cmd_chroot(
-                    "rpm -Uvh %s --nodeps" %
-                    "http://public-yum.oracle.com/repo/OracleLinux/OL7/addons/"
-                    "x86_64/getPackage/python-cheetah-2.4.1-1.el7.x86_64.rpm")
-
     def _run_dracut(self):
         self._run_dracut_base('kernel')
         self._run_dracut_base('kernel-uek')

+ 0 - 3
coriolis/osmorphing/osmount/windows.py

@@ -1,9 +1,6 @@
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
 
-import os
-import re
-
 from oslo_log import log as logging
 
 from coriolis import exception

+ 9 - 63
coriolis/osmorphing/redhat.py

@@ -6,10 +6,7 @@ import re
 import uuid
 
 from oslo_log import log as logging
-import yaml
 
-from coriolis import constants
-from coriolis import exception
 from coriolis.osmorphing import base
 from coriolis import utils
 
@@ -22,20 +19,16 @@ RELEASE_FEDORA = "Fedora"
 DEFAULT_CLOUD_USER = "cloud-user"
 
 
-class RedHatMorphingTools(base.BaseLinuxOSMorphingTools):
-    _packages = {
-        (None, None): [("dracut-config-generic", False)],
-        (constants.HYPERVISOR_VMWARE, None): [("open-vm-tools", True)],
-        (constants.HYPERVISOR_HYPERV, None): [("hyperv-daemons", True)],
-        (None, constants.PLATFORM_OPENSTACK): [
-            ("cloud-init", True),
-            ("cloud-utils", False),
-            ("parted", False),
-            ("git", False),
-            ("cloud-utils-growpart", False)],
-    }
+class BaseRedHatMorphingTools(base.BaseLinuxOSMorphingTools):
     _NETWORK_SCRIPTS_PATH = "etc/sysconfig/network-scripts"
 
+    def __init__(self, conn, os_root_dir, os_root_dev,
+                 hypervisor, event_manager):
+        super(BaseRedHatMorphingTools, self).__init__(
+            conn, os_root_dir, os_root_dev,
+            hypervisor, event_manager)
+        self._enable_repos = []
+
     def _check_os(self):
         redhat_release_path = "etc/redhat-release"
         if self._test_path(redhat_release_path):
@@ -137,7 +130,7 @@ class RedHatMorphingTools(base.BaseLinuxOSMorphingTools):
             self._exec_cmd_chroot(yum_cmd)
 
     def install_packages(self, package_names):
-        self._yum_install(package_names)
+        self._yum_install(package_names, self._enable_repos)
 
     def uninstall_packages(self, package_names):
         self._yum_uninstall(package_names)
@@ -158,16 +151,6 @@ class RedHatMorphingTools(base.BaseLinuxOSMorphingTools):
                     "dracut -f /boot/initramfs-%(version)s.img %(version)s" %
                     {"version": kernel_version})
 
-    def _get_default_cloud_user(self):
-        cloud_cfg_path = os.path.join(self._os_root_dir, 'etc/cloud/cloud.cfg')
-        if not self._test_path(cloud_cfg_path):
-            raise exception.CoriolisException(
-                "cloud-init config file not found: %s" % cloud_cfg_path)
-        cloud_cfg_content = self._read_file(cloud_cfg_path)
-        cloud_cfg = yaml.load(cloud_cfg_content)
-        return cloud_cfg.get('system_info', {}).get('default_user', {}).get(
-            'name', DEFAULT_CLOUD_USER)
-
     def _set_network_nozeroconf_config(self):
         network_cfg_file = "etc/sysconfig/network"
         network_cfg = self._read_config_file(network_cfg_file,
@@ -175,23 +158,6 @@ class RedHatMorphingTools(base.BaseLinuxOSMorphingTools):
         network_cfg["NOZEROCONF"] = "yes"
         self._write_config_file(network_cfg_file, network_cfg)
 
-    def _configure_cloud_init(self):
-        if "cloud-init" in self.get_packages()[0]:
-            cloud_user = self._get_default_cloud_user()
-            if not self._check_user_exists(cloud_user):
-                self._exec_cmd_chroot("useradd %s" % cloud_user)
-            self._set_network_nozeroconf_config()
-            if self._has_systemd():
-                self._enable_systemd_service("cloud-init")
-
-    def _add_hyperv_ballooning_udev_rules(self):
-        udev_file = "etc/udev/rules.d/100-balloon.rules"
-        content = 'SUBSYSTEM=="memory", ACTION=="add", ATTR{state}="online"\n'
-
-        if (self._hypervisor == constants.HYPERVISOR_HYPERV and
-                not self._test_path(udev_file)):
-            self._write_file_sudo(udev_file, content)
-
     def _write_config_file(self, chroot_path, config_data):
         content = self._get_config_file_content(config_data)
         self._write_file_sudo(chroot_path, content)
@@ -207,23 +173,3 @@ class RedHatMorphingTools(base.BaseLinuxOSMorphingTools):
 
     def _set_selinux_autorelabel(self):
         self._exec_cmd_chroot("touch /.autorelabel")
-
-    def pre_packages_install(self, package_names):
-        super(RedHatMorphingTools, self).pre_packages_install(package_names)
-
-        distro, version = self.check_os()
-        if distro == RELEASE_RHEL and "cloud-init" in self.get_packages()[0]:
-            major_version = version.split(".")[0]
-            repo_name = "rhel-%s-server-rh-common-rpms" % major_version
-            # This is necessary for cloud-init
-            self._event_manager.progress_update(
-                "Enabling repository: %s" % repo_name)
-            self._exec_cmd_chroot(
-                "subscription-manager repos --enable %s" % repo_name)
-
-    def post_packages_install(self, package_names):
-        self._add_hyperv_ballooning_udev_rules()
-        self._run_dracut()
-        self._configure_cloud_init()
-        self._set_selinux_autorelabel()
-        super(RedHatMorphingTools, self).post_packages_install(package_names)

+ 2 - 45
coriolis/osmorphing/suse.py

@@ -1,23 +1,16 @@
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
 
-import os
 import re
 
-from coriolis import constants
 from coriolis.osmorphing import base
 
 
-class SUSEMorphingTools(base.BaseLinuxOSMorphingTools):
-    _packages = {
-        (constants.HYPERVISOR_VMWARE, None): [("open-vm-tools", True)],
-        (None, constants.PLATFORM_OPENSTACK): [("cloud-init", True)],
-    }
-
+class BaseSUSEMorphingTools(base.BaseLinuxOSMorphingTools):
     def _check_os(self):
         os_release = self._get_os_release()
         name = os_release.get("NAME")
-        if name == "SLES" or name.startswith("openSUSE"):
+        if name and (name == "SLES" or name.startswith("openSUSE")):
             pretty_name = os_release.get("PRETTY_NAME")
             if name == "openSUSE Tumbleweed":
                 self._version_id = None
@@ -40,32 +33,6 @@ class SUSEMorphingTools(base.BaseLinuxOSMorphingTools):
         # TODO: add networking support
         pass
 
-    def pre_packages_install(self, package_names):
-        super(SUSEMorphingTools, self).pre_packages_install(package_names)
-
-        if self._platform == constants.PLATFORM_OPENSTACK:
-            # TODO: use OS version to choose the right repo
-            repo_version_map = {("SLES", "11.4"): "SLE_11_SP4",
-                                ("SLES", "11.3"): "SLE_11_SP3",
-                                ("SLES", "11.2"): "SLE_11_SP2",
-                                ("SLES", "12.1"): "SLE_12_SP1",
-                                ("SLES", "12"): "SLE_12",
-                                ("openSUSE", "13.2"): "openSUSE_13.2",
-                                ("openSUSE Leap", "42.1"):
-                                "openSUSE_Leap_42.1",
-                                ("openSUSE Tumbleweed", None):
-                                "openSUSE_Tumbleweed"}
-
-            repo_version = repo_version_map.get(
-                (self._distro, self._version_id), "SLE_12_SP1")
-
-            repo = "obs://Cloud:Tools/%s" % repo_version
-            self._event_manager.progress_update("Adding repository: %s" % repo)
-            self._exec_cmd_chroot("zypper --non-interactive addrepo -f %s "
-                                  "Cloud-Tools" % repo)
-        self._exec_cmd_chroot(
-            "zypper --non-interactive --no-gpg-checks refresh")
-
     def _run_dracut(self):
         package_names = self._exec_cmd_chroot(
             'rpm -q kernel-default').decode().split('\n')[:-1]
@@ -86,11 +53,6 @@ class SUSEMorphingTools(base.BaseLinuxOSMorphingTools):
         except:
             return False
 
-    def _configure_cloud_init(self):
-        if "cloud-init" in self.get_packages()[0]:
-            if self._has_systemd():
-                self._enable_systemd_service("cloud-init")
-
     def install_packages(self, package_names):
         self._exec_cmd_chroot(
             'zypper --non-interactive install %s' % " ".join(package_names))
@@ -98,8 +60,3 @@ class SUSEMorphingTools(base.BaseLinuxOSMorphingTools):
     def uninstall_packages(self, package_names):
         self._exec_cmd_chroot(
             'zypper --non-interactive remove %s' % " ".join(package_names))
-
-    def post_packages_install(self, packages_names):
-        self._run_dracut()
-        self._configure_cloud_init()
-        super(SUSEMorphingTools, self).post_packages_install(package_names)

+ 1 - 11
coriolis/osmorphing/ubuntu.py

@@ -1,20 +1,10 @@
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
 
-from coriolis import constants
 from coriolis.osmorphing import debian
 
 
-class UbuntuMorphingTools(debian.DebianMorphingTools):
-    _packages = {
-        (constants.HYPERVISOR_VMWARE, None): [("open-vm-tools", True)],
-        # TODO: sudo agt-get install linux-tool-<kernel release>
-        # linux-cloud-tools-<kernel release> -y
-        (constants.HYPERVISOR_HYPERV, None): [("hv-kvp-daemon-init", True)],
-        # TODO: add cloud-initramfs-growroot
-        (None, constants.PLATFORM_OPENSTACK): [("cloud-init", True)],
-    }
-
+class BaseUbuntuMorphingTools(debian.BaseDebianMorphingTools):
     def _check_os(self):
         config = self._read_config_file("etc/lsb-release", check_exists=True)
         dist_id = config.get('DISTRIB_ID')

+ 1 - 210
coriolis/osmorphing/windows.py

@@ -6,31 +6,11 @@ import re
 import uuid
 
 from distutils import version
-from oslo_config import cfg
 from oslo_log import log as logging
 
-from coriolis import constants
 from coriolis import exception
 from coriolis.osmorphing import base
 
-opts = [
-    cfg.StrOpt('virtio_iso_url',
-               default='https://fedorapeople.org/groups/virt/virtio-win/'
-               'direct-downloads/stable-virtio/virtio-win.iso',
-               help="Location of the virtio-win ISO"),
-    cfg.StrOpt('cloudbaseinit_x64_url',
-               default="https://www.cloudbase.it/downloads/"
-               "CloudbaseInitSetup_x64.zip",
-               help="Location of the Cloudbase-Init ZIP for amd64 systems"),
-    cfg.StrOpt('cloudbaseinit_x86_url',
-               default="https://www.cloudbase.it/downloads/"
-               "CloudbaseInitSetup_x86.zip",
-               help="Location of the Cloudbase-Init ZIP for amd64 systems"),
-]
-
-CONF = cfg.CONF
-CONF.register_opts(opts, 'windows_images')
-
 LOG = logging.getLogger(__name__)
 
 SERVICE_START_AUTO = 2
@@ -38,10 +18,9 @@ SERVICE_START_MANUAL = 3
 SERVICE_START_DISABLED = 4
 
 SERVICE_PATH_FORMAT = "HKLM:\\%s\\ControlSet001\\Services\\%s"
-CLOUDBASEINIT_SERVICE_NAME = "cloudbase-init"
 
 
-class WindowsMorphingTools(base.BaseOSMorphingTools):
+class BaseWindowsMorphingTools(base.BaseOSMorphingTools):
     def _check_os(self):
         try:
             (self._version_number,
@@ -52,16 +31,6 @@ class WindowsMorphingTools(base.BaseOSMorphingTools):
         except exception.CoriolisException as ex:
             LOG.debug("Exception during OS detection: %s", ex)
 
-    def pre_packages_install(self, packages_add):
-        if (not self._hypervisor or
-                self._hypervisor == constants.HYPERVISOR_KVM):
-            self._add_virtio_drivers()
-
-        if self._platform == constants.PLATFORM_OPENSTACK:
-            self._add_cloudbase_init()
-        else:
-            self._disable_cloudbase_init()
-
     def set_net_config(self, nics_info, dhcp):
         # TODO: implement
         pass
@@ -157,73 +126,6 @@ class WindowsMorphingTools(base.BaseOSMorphingTools):
         self._conn.exec_ps_command("Dismount-DiskImage '%s'" % path,
                                    ignore_stdout=True)
 
-    def _add_virtio_drivers(self):
-        # TODO: add support for x86
-        arch = "amd64"
-
-        CLIENT = 1
-        SERVER = 2
-
-        # Ordered by version number
-        virtio_dirs = [
-            ("xp", version.LooseVersion("5.1"), CLIENT),
-            ("2k3", version.LooseVersion("5.2"), SERVER),
-            ("2k8", version.LooseVersion("6.0"), SERVER | CLIENT),
-            ("w7", version.LooseVersion("6.1"), CLIENT),
-            ("2k8R2", version.LooseVersion("6.1"), SERVER),
-            ("w8", version.LooseVersion("6.2"), CLIENT),
-            ("2k12", version.LooseVersion("6.2"), SERVER),
-            ("w8.1", version.LooseVersion("6.3"), CLIENT),
-            ("2k12R2", version.LooseVersion("6.3"), SERVER),
-            ("w10", version.LooseVersion("10.0"), SERVER | CLIENT),
-            ]
-
-        # The list of all possible editions is huge, this is a semplification
-        if "Server" in self._edition_id:
-            edition_type = SERVER
-        else:
-            edition_type = CLIENT
-
-        drivers = ["Balloon", "NetKVM", "qxl", "qxldod", "pvpanic", "viorng",
-                   "vioscsi", "vioserial", "viostor"]
-
-        self._event_manager.progress_update("Downloading virtio-win drivers")
-
-        virtio_iso_path = "c:\\virtio-win.iso"
-        self._conn.download_file(
-            CONF.windows_images.virtio_iso_url, virtio_iso_path)
-
-        self._event_manager.progress_update("Adding virtio-win drivers")
-
-        virtio_drive = self._mount_disk_image(virtio_iso_path)
-        try:
-            for virtio_dir, dir_version, dir_edition_type in reversed(
-                    virtio_dirs):
-                if self._version_number >= dir_version and (
-                        edition_type & dir_edition_type):
-                    path = "%s:\\Balloon\\%s\\%s" % (
-                        virtio_drive, virtio_dir, arch)
-                    if self._conn.test_path(path):
-                        break
-
-            driver_paths = ["%s:\\%s\\%s\\%s" % (
-                            virtio_drive, d, virtio_dir, arch)
-                            for d in drivers]
-
-            sid = self._get_sid()
-            # Fails on Nano Server without explicitly granting permissions
-            file_repo_path = ("%sWindows\System32\DriverStore\FileRepository" %
-                              self._os_root_dir)
-            self._grant_permissions(file_repo_path, "*%s" % sid)
-            try:
-                for driver_path in driver_paths:
-                    if self._conn.test_path(driver_path):
-                        self._add_dism_driver(driver_path)
-            finally:
-                self._revoke_permissions(file_repo_path, "*%s" % sid)
-        finally:
-            self._dismount_disk_image(virtio_iso_path)
-
     def _expand_archive(self, path, destination):
         LOG.info("Expanding archive \"%(path)s\" in \"%(destination)s\"",
                  {"path": path, "destination": destination})
@@ -286,114 +188,3 @@ class WindowsMorphingTools(base.BaseOSMorphingTools):
              "service_account": service_account,
              "start_mode": start_mode},
             ignore_stdout=True)
-
-    def _write_cloudbase_init_conf(self, cloudbaseinit_base_dir,
-                                   local_base_dir, com_port="COM1"):
-        LOG.info("Writing Cloudbase-Init configuration files")
-        conf_dir = "%s\\conf" % cloudbaseinit_base_dir
-        self._conn.exec_ps_command("mkdir '%s' -Force" % conf_dir,
-                                   ignore_stdout=True)
-
-        conf_file_path = "%s\\cloudbase-init.conf" % conf_dir
-
-        conf_content = (
-            "[DEFAULT]\n"
-            "username = Admin\n"
-            "groups = Administrators\n"
-            "inject_user_password = true\n"
-            "config_drive_raw_hhd = true\n"
-            "config_drive_cdrom = true\n"
-            "config_drive_vfat = true\n"
-            "bsdtar_path = %(bin_path)s\\bsdtar.exe\n"
-            "mtools_path = %(bin_path)s\n"
-            "logdir = %(log_path)s\n"
-            "logfile = cloudbase-init.log\n"
-            "default_log_levels = "
-            "comtypes=INFO,suds=INFO,iso8601=WARN,requests=WARN\n"
-            "mtu_use_dhcp_config = true\n"
-            "ntp_use_dhcp_config = true\n"
-            "allow_reboot = true\n"
-            "debug = true\n"
-            "logging_serial_port_settings = %(com_port)s,115200,N,8\n" %
-            {"bin_path": "%s\\Bin" % local_base_dir,
-             "log_path": "%s\\Log" % local_base_dir,
-             "com_port": com_port})
-
-        self._conn.write_file(conf_file_path, conf_content.encode())
-
-    def _check_cloudbase_init_exists(self, key_name):
-        reg_service_path = (SERVICE_PATH_FORMAT %
-                            (key_name, CLOUDBASEINIT_SERVICE_NAME))
-        return self._conn.exec_ps_command(
-            "Test-Path %s" % reg_service_path) == "True"
-
-    def _disable_cloudbase_init(self):
-        key_name = str(uuid.uuid4())
-        self._load_registry_hive(
-            "HKLM\%s" % key_name,
-            "%sWindows\\System32\\config\\SYSTEM" % self._os_root_dir)
-        try:
-            if self._check_cloudbase_init_exists(key_name):
-                self._event_manager.progress_update(
-                    "Disabling cloudbase-init")
-                self._set_service_start_mode(
-                    key_name, CLOUDBASEINIT_SERVICE_NAME,
-                    SERVICE_START_DISABLED)
-        finally:
-            self._unload_registry_hive("HKLM\%s" % key_name)
-
-    def _add_cloudbase_init(self):
-        # TODO: add support for x86
-        arch = "amd64"
-        arch_url_map = {"amd64": CONF.windows_images.cloudbaseinit_x64_url,
-                        "x86": CONF.windows_images.cloudbaseinit_x86_url}
-
-        self._event_manager.progress_update("Adding cloudbase-init")
-
-        key_name = str(uuid.uuid4())
-        self._load_registry_hive(
-            "HKLM\%s" % key_name,
-            "%sWindows\\System32\\config\\SYSTEM" % self._os_root_dir)
-        try:
-            if self._check_cloudbase_init_exists(key_name):
-                self._event_manager.progress_update(
-                    "Enabling cloudbase-init")
-                self._set_service_start_mode(
-                    key_name, CLOUDBASEINIT_SERVICE_NAME, SERVICE_START_AUTO)
-            else:
-                cloudbaseinit_zip_path = "c:\\cloudbaseinit.zip"
-                cloudbaseinit_base_dir = "%sCloudbase-Init" % self._os_root_dir
-
-                self._event_manager.progress_update(
-                    "Downloading cloudbase-init")
-                self._conn.download_file(arch_url_map[arch],
-                                         cloudbaseinit_zip_path)
-
-                self._event_manager.progress_update(
-                    "Installing cloudbase-init")
-                self._expand_archive(cloudbaseinit_zip_path,
-                                     cloudbaseinit_base_dir)
-
-                log_dir = "%s\\Log" % cloudbaseinit_base_dir
-                self._conn.exec_ps_command("mkdir '%s' -Force" % log_dir,
-                                           ignore_stdout=True)
-
-                local_base_dir = "C%s" % cloudbaseinit_base_dir[1:]
-                self._write_cloudbase_init_conf(
-                    cloudbaseinit_base_dir, local_base_dir)
-
-                image_path = (
-                    '""""%(path)s\\Bin\\OpenStackService.exe"""" '
-                    'cloudbase-init """"%(path)s\\Python\\Python.exe"""" -c '
-                    '""""from cloudbaseinit import shell;shell.main()"""" '
-                    '--config-file """"%(path)s\\conf\\cloudbase-init.conf""""'
-                    % {'path': local_base_dir})
-
-                self._create_service(
-                    key_name=key_name,
-                    service_name=CLOUDBASEINIT_SERVICE_NAME,
-                    image_path=image_path,
-                    display_name="Cloud Initialization Service",
-                    description="Service wrapper for cloudbase-init")
-        finally:
-            self._unload_registry_hive("HKLM\%s" % key_name)

+ 0 - 1013
coriolis/providers/azure/__init__.py

@@ -1,1013 +0,0 @@
-# Copyright 2016 Cloudbase Solutions Srl
-# All Rights Reserved.
-
-""" 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
-import tempfile
-import time
-import traceback
-
-import paramiko
-import requests
-from azure.mgmt import compute, network, storage
-from azure.mgmt.resource import resources
-from azure.storage import blob
-from msrestazure import azure_active_directory, azure_exceptions
-from oslo_config import cfg
-from oslo_log import log as logging
-from oslo_utils import units
-
-from coriolis import constants
-from coriolis import events
-from coriolis import exception
-from coriolis.osmorphing import manager as osmorpher
-from coriolis.providers import base
-from coriolis.providers.azure import utils as azutils
-from coriolis.providers.azure import exceptions as azexceptions
-from coriolis import schemas
-from coriolis import utils
-
-
-OPTIONS = [
-    # 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."),
-    cfg.StrOpt("migr_location",
-               default="westus",
-               help="The default Azure location to migrate to "
-                    "when no location is specified through the "
-                    "target_environment."),
-    cfg.StrOpt("migr_subnet_name",
-               default="coriolis-worknet",
-               help="Name of the subnet to be used by worker instances."),
-    cfg.StrOpt("migr_container_name",
-               default="coriolis",
-               help="Name of the azure storage container which"
-                    "will support the migration."),
-    cfg.StrOpt("default_migration_hostname",
-               default="migratedinst",
-               help="Default hostname to be used in case the hostname of the "
-                    "instance being migrated does not conform with Azure's "
-                    "standards (no special characters of maximum length 15)."),
-    cfg.DictOpt("worker_volume_count_to_size_map",
-                default=collections.OrderedDict([
-                    (2, compute.models.VirtualMachineSizeTypes.standard_d1),
-                    (4, compute.models.VirtualMachineSizeTypes.standard_d2),
-                    (8, compute.models.VirtualMachineSizeTypes.standard_d3),
-                    (16, compute.models.VirtualMachineSizeTypes.standard_d4),
-                    (32, compute.models.VirtualMachineSizeTypes.standard_d5_v2)
-                ]),
-                help="Mapping between the number of volumes supported by "
-                     "an Azure instance size and its name (with correct "
-                     "capitalization and underscores) as show here: "
-                     "https://azure.microsoft.com/en-us/documentation/"
-                     "articles/virtual-machines-windows-sizes/"),
-    cfg.DictOpt("migr_image_sku_map",
-                default={
-                    constants.OS_TYPE_LINUX: "14.04.3-LTS",
-                    constants.OS_TYPE_WINDOWS: "2012-R2-Datacenter"
-                },
-                help="Mapping of image SKUs to be used for migration workers "
-                     "with respect to the type of OS being migrated.")
-]
-
-CONF = cfg.CONF
-CONF.register_opts(OPTIONS, "azure_migration_provider")
-
-LOG = logging.getLogger(__name__)
-
-MIGRATION_RESGROUP_NAME_FORMAT = "coriolis-migration-%s"
-MIGRATION_WORKER_NAME_FORMAT = "coriolis-worker-%s"
-AZURE_DISK_NAME_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"
-
-SSH_PUBKEY_FORMAT = "ssh-rsa %s tmp@migration"
-SSH_PUBKEY_FILEPATH_FORMAT = "/home/%s/.ssh/authorized_keys"
-
-PROVIDERS_NAME_MAP = {
-    x: "Microsoft." + x.capitalize()
-    for x in ["compute", "network"]
-}
-
-WORKER_VM_IMAGE_VERSION = "latest"
-
-WORKER_IMAGES_MAP = {
-    constants.OS_TYPE_LINUX: {
-        "version": WORKER_VM_IMAGE_VERSION,
-        "publisher": "canonical",
-        "offer": "UbuntuServer"
-    },
-    constants.OS_TYPE_WINDOWS: {
-        "version": WORKER_VM_IMAGE_VERSION,
-        "publisher": "MicrosoftWindowsServer",
-        "offer": "WindowsServer"
-    },
-}
-
-AZURE_OSTYPES_MAP = {
-    "linux":   compute.models.OperatingSystemTypes.linux,
-    "windows": compute.models.OperatingSystemTypes.windows
-}
-
-WINRM_EXTENSION_FILES_BASE_URI = (
-    # TODO: not fetch off github
-    "https://raw.githubusercontent.com/cloudbase/coriolis-resources/master/azure/")
-
-WINRM_EXTENSION_FILE_URIS = [
-    WINRM_EXTENSION_FILES_BASE_URI + f
-    for f in ["ConfigureWinRM.ps1", "makecert.exe", "winrmconf.cmd"]
-]
-
-WORKER_USERNAME = "coriolis"
-
-# NOTE: connection_info
-CREDENTIALS_CATEGORIES = ["user_credentials", "service_principal_credentials"]
-USER_CREDENTIALS_FIELDS = ["username", "password"]
-SP_CREDENTIALS_FIELDS = ["client_id", "client_secret", "tenant_id"]
-
-# An AzureStorageBlob is identified by its unique name and URI on Azure.
-AzureStorageBlob = collections.namedtuple(
-    "AzureStorageBlob", "name uri")
-
-# An AzureWorkerOSProfile is identified by the compute.models.OSProfile, the
-# authentication token to the machine (ssh key or cert thubprint), the port
-# awaiting the respective connection, as well as a list of
-# compute.models.VirtualMachineExtension's required by the worker.
-AzureWorkerOSProfile = collections.namedtuple(
-    "AzureWorkerOSProfile", "profile token port extensions")
-
-# An AzureWorkerInstance is identified by its unique name, ip address, port,
-# username, password, authentication token and its list of datadisks.
-AzureWorkerInstance = collections.namedtuple(
-    "AzureWorkerInstance", "name ip port username password pkey datadisks")
-
-
-class ImportProvider(base.BaseImportProvider):
-    """ Provides import capabilities. """
-    platform = constants.PLATFORM_AZURE_RM
-
-    connection_info_schema = schemas.get_schema(
-        __name__, schemas.PROVIDER_CONNECTION_INFO_SCHEMA_NAME)
-
-    target_environment_schema = schemas.get_schema(
-        __name__, schemas.PROVIDER_TARGET_ENVIRONMENT_SCHEMA_NAME)
-
-    def __init__(self, event_handler):
-        self._event_manager = events.EventManager(event_handler)
-
-    def validate_connection_info(self, connection_info):
-        """ Validates the provided connection information. """
-        LOG.info("Validating connection info: %s", connection_info)
-
-        super(ImportProvider, self).validate_connection_info(connection_info)
-
-        # NOTE: considering we cannot check the validity of the credentials per
-        # se if a secret href is provided here, we simply return on the spot:
-        if "secret_ref" in connection_info:
-            return
-
-        try:
-            # NOTE: attempt to register to a provider to ensure credentials
-            # are indeed actually valid.
-            resc = self._get_resource_client(connection_info)
-            utils.retry_on_error()(azutils.checked(resc.providers.register))(
-                PROVIDERS_NAME_MAP["compute"])
-        except (KeyError, azure_exceptions.CloudError,
-                azexceptions.AzureOperationException) as ex:
-
-            raise exception.InvalidInput(
-                "Invalid or incomplete Azure credentials provided")
-
-    def _get_cloud_credentials(self, connection_info):
-        """ returns the msrestazure.azure_active_directory.Credentials
-        implementation for the given connection_info. Should both user/pass and
-        service principal details be provided, the user/pass auth flow is
-        preffered.
-        """
-        user_creds, sp_creds = [connection_info.get(x, {}) for x in
-                                CREDENTIALS_CATEGORIES]
-
-        if user_creds and all([x in user_creds for x in
-                               USER_CREDENTIALS_FIELDS]):
-            return azure_active_directory.UserPassCredentials(
-                user_creds["username"],
-                user_creds["password"]
-            )
-
-        if sp_creds and all([x in sp_creds for x in
-                             SP_CREDENTIALS_FIELDS]):
-            return azure_active_directory.ServicePrincipalCredentials(
-                client_id=sp_creds["client_id"],
-                secret=sp_creds["client_secred"],
-                tenent=sp_creds["tenant_id"]
-            )
-
-        # NOTE: this ending raise is redundant considering the schema-based
-        # 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'.",
-            code=-1)
-
-    def _get_compute_client(self, connection_info):
-        """ Returns an azure.mgmt.compute.ComputeManagementClient.
-        """
-        return compute.ComputeManagementClient(
-            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(
-            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(
-            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(
-            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. """
-        return blob.PageBlobService(
-            account_name=target_environment["storage"]["account"],
-            account_key=target_environment["storage"]["key"]
-        )
-
-    def _get_block_blob_client(self, target_environment):
-        """ Returns an azure.storage.blob.PageBlobService client. """
-        return blob.BlockBlobService(
-            account_name=target_environment["storage"]["account"],
-            account_key=target_environment["storage"]["key"]
-        )
-
-    def _delete_recovery_disk(self, target_environment, vm_name):
-        """ Removes the recovery disk block blob for the given
-        instance name.
-        """
-        blobd = self._get_block_blob_client(target_environment)
-        cont_name = CONF.azure_migration_provider.migr_container_name
-
-        blob_names = [b.name
-                      for b in blobd.list_blobs(cont_name)
-                      if re.match(r"%s\..*\.status" % vm_name, b.name)]
-        if len(blob_names) > 1:
-            self._event_manager.progress_update(
-                "VM \"%s\" has more than one recovery disk. "
-                "Skipped deleting any to avoid adverse loss")
-
-        if len(blob_names) == 1:
-            blobd.delete_blob(cont_name, blob_names[0])
-
-    @utils.retry_on_error()
-    def _upload_disk(self, target_environment, disk_path, upload_name):
-        """ Uploads the disk from the provided path to an Azure blob
-        and returns the corresponding AzureStorageBlob.
-        """
-        self._event_manager.progress_update(
-            "Uploading disk from \"%s\" as \"%s\"" % (disk_path, upload_name))
-
-        def progressf(curr, total):
-            LOG.debug("Uploading '%s': %d/%d.", upload_name, curr, total)
-
-        blobd = self._get_page_blob_client(target_environment)
-
-        stor_name = target_environment["storage"]["account"]
-        cont_name = CONF.azure_migration_provider.migr_container_name
-        disk_uri = BLOB_PATH_FORMAT % (stor_name, cont_name, upload_name)
-
-        blobd.create_blob_from_path(
-            cont_name, upload_name, disk_path, progress_callback=progressf)
-
-        return AzureStorageBlob(
-            name=upload_name,
-            uri=disk_uri
-        )
-
-    @utils.retry_on_error()
-    def _create_migration_network(self, connection_info, target_environment,
-                                  migration_id):
-        """ Creates the virtual network to be used for the migration and
-        returns the response from its creation operation.
-        """
-        self._event_manager.progress_update(
-            "Creating migration network")
-
-        awaited = azutils.awaited(timeout=150)
-        netc = self._get_network_client(connection_info)
-        resgroup = MIGRATION_RESGROUP_NAME_FORMAT % migration_id
-        vn_name = MIGRATION_NETWORK_NAME_FORMAT % migration_id
-
-        awaited(netc.virtual_networks.create_or_update)(
-            resgroup,
-            vn_name,
-            network.models.VirtualNetwork(
-                location=target_environment.get(
-                    "location",
-                    CONF.azure_migration_provider.migr_location),
-                address_space=network.models.AddressSpace(
-                    # 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'
-                    )
-                ]
-            ),
-        )
-
-        return azutils.checked(netc.virtual_networks.get)(resgroup, vn_name)
-
-    @utils.retry_on_error(
-        terminal_exceptions=[azexceptions.FatalAzureOperationException])
-    def _wait_for_vm(self, connection_info, resgroup, vm_name, period=30):
-        """ Blocks until the given VM has been started. """
-        vmclient = self._get_compute_client(connection_info).virtual_machines
-        state = azutils.checked(vmclient.get)(resgroup, vm_name)
-
-        while state.provisioning_state != "Succeeded":
-            if state.provisioning_state not in ("Creating", "Updating"):
-                raise azexceptions.FatalAzureOperationException(
-                    code=-1,
-                    msg="Awaited VM '%s' has reached an invalid state: %s" %
-                    (vm_name, state.provisioning_state)
-                )
-
-            time.sleep(period)
-            state = azutils.checked(vmclient.get)(resgroup, vm_name)
-
-    def _get_worker_osprofile(self, os_type, location, worker_name):
-        """ Returns the AzureWorkerOSProfile associated to the worker instance
-        on Azure. For Linux workers, the returned authentication token is a
-        paramiko.RSAKey the machine has been configured with.
-        For windows workers, the configured password is returned.
-        """
-        if os_type == constants.OS_TYPE_LINUX:
-            return self._get_linux_worker_osprofile(worker_name)
-        elif os_type == constants.OS_TYPE_WINDOWS:
-            return self._get_windows_worker_osprofile(
-                location, worker_name)
-
-        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. """
-        LOG.info("Linux chosen as worker '%s' OS.", worker_name)
-
-        key = paramiko.RSAKey.generate(2048)
-
-        os_profile = compute.models.OSProfile(
-            admin_username=WORKER_USERNAME,
-            # NOTE: intentional lack of 'admin_password' provided so
-            # as to enable passwordless sudo on the target machine.
-            computer_name=worker_name,
-            linux_configuration=compute.models.LinuxConfiguration(
-                disable_password_authentication=True,
-                ssh=compute.models.SshConfiguration(
-                    public_keys=[compute.models.SshPublicKey(
-                        key_data=SSH_PUBKEY_FORMAT % key.get_base64(),
-                        path=SSH_PUBKEY_FILEPATH_FORMAT % WORKER_USERNAME
-                    )],
-                ),
-            )
-        )
-
-        return AzureWorkerOSProfile(
-            profile=os_profile,
-            token=key,
-            port=22,  # NOTE: default port used on Azure-provided images.
-            extensions=[]
-        )
-
-    def _get_windows_worker_osprofile(self, location, worker_name):
-        """ Returns the AzureWorkerOSProfile afferent to a Windows worker. """
-        LOG.info("Windows was chosen as worker '%s' OS.", worker_name)
-
-        password = azutils.get_random_password()
-        os_profile = compute.models.OSProfile(
-            computer_name=azutils.normalize_hostname(worker_name),
-            admin_username=WORKER_USERNAME,
-            admin_password=password,
-            windows_configuration=compute.models.WindowsConfiguration(
-                provision_vm_agent=True,
-                enable_automatic_updates=False,
-            )
-        )
-
-        winrm_extension = compute.models.VirtualMachineExtension(
-            location,
-            publisher=PROVIDERS_NAME_MAP["compute"],
-            virtual_machine_extension_type="CustomScriptExtension",
-            type_handler_version="1.4",
-            settings=dict(
-                fileUris=WINRM_EXTENSION_FILE_URIS,
-                commandToExecute="powershell -ExecutionPolicy Unrestricted "
-                                 "-file ConfigureWinRM.ps1 "
-                                 "*.%s.cloudapp.azure.com" % location
-            ),
-        )
-
-        return AzureWorkerOSProfile(
-            profile=os_profile,
-            token=password,
-            port=5986,  # NOTE: default port opened via the extension
-            extensions=[winrm_extension]
-        )
-
-    @utils.retry_on_error()
-    def _create_nic(self, connection_info, resgroup, nic_name,
-                    location, ip_configs):
-        """ Creates a network interface with the specified params.
-
-        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.
-        """
-
-        awaited = azutils.awaited(timeout=200)
-        net_client = self._get_network_client(connection_info)
-
-        nic = network.models.NetworkInterface(
-            location=location,
-            ip_configurations=ip_configs
-        )
-
-        awaited(net_client.network_interfaces.create_or_update)(
-            resgroup,
-            nic_name,
-            nic
-        )
-
-        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):
-        awaited = azutils.awaited(timeout=90)
-        net_client = self._get_network_client(connection_info)
-
-        awaited(net_client.public_ip_addresses.create_or_update)(
-            resgroup,
-            ip_name,
-            network.models.PublicIPAddress(
-                location=location,
-                public_ip_allocation_method=network.models.IPAllocationMethod.dynamic,
-            ),
-        )
-
-        return azutils.checked(net_client.public_ip_addresses.get)(
-            resgroup, ip_name)
-
-    def _convert_to_vhd(self, disk_path):
-        """ Converts the disk file given by its path to a fixed VHD and returns
-        the path of the resulting disk.
-
-        Does NOT check if it already is a VHD.
-        """
-        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 storage as a page blob. """
-        disk_file_info = utils.get_disk_info(disk_path)
-
-        upload_path = disk_path
-
-        # convert disk if necessary:
-        if disk_file_info["format"] != constants.DISK_FORMAT_VHD:
-            self._event_manager.progress_update(
-                "Converting disk \"%s\" from \"%s\" to \"%s\"" %
-                (upload_name, disk_file_info["format"],
-                 constants.DISK_FORMAT_VHD))
-
-            upload_path = self._convert_to_vhd(disk_path)
-            os.remove(disk_path)
-
-        try:
-            blb = self._upload_disk(
-                target_environment, upload_path, upload_name)
-        except Exception as ex:
-            # 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." % upload_name
-            ) from ex
-        finally:
-            os.remove(upload_path)
-
-        return compute.models.DataDisk(
-            lun=lun,
-            name=blb.name,
-            caching=compute.models.CachingTypes.none,
-            vhd=compute.models.VirtualHardDisk(uri=blb.uri),
-            # 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)
-
-        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)
-
-        return azutils.checked(net_client.public_ip_addresses.get)(
-            resgroup, ip_name)
-
-    def _get_worker_size(self, worker_name, ndisks):
-        """ Returns the required size of the worker needed to handle
-        the migration/transformation of the data disks. """
-        mapping = CONF.azure_migration_provider.worker_volume_count_to_size_map
-        volsmap = collections.OrderedDict(
-            [(n, mapping[n]) for n in sorted(mapping.keys())])
-
-        for maxvols, size in volsmap.items():
-            if ndisks <= maxvols:
-                self._event_manager.progress_update(
-                    "Migration worker size chosen as \"%s\"" %
-                    str(size).split(".")[-1])
-
-                return size
-
-        raise azexceptions.FatalAzureOperationException(
-            code=-1,
-            msg=("No Azure size suitable for migrating the instance "
-                 "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
-        will perform the final operations on the migrating VM.
-        """
-        resgroup = MIGRATION_RESGROUP_NAME_FORMAT % migration_id
-        worker_name = MIGRATION_WORKER_NAME_FORMAT % migration_id
-
-        self._event_manager.progress_update("Creating migration worker")
-
-        awaited = azutils.awaited(timeout=600)
-        location = target_environment.get(
-            "location", CONF.azure_migration_provider.migr_location)
-        location = azutils.normalize_location(location)
-
-        # create the NIC and public IP:
-        self._event_manager.progress_update(
-            "Creating migration worker Public IP")
-
-        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 migration worker NIC")
-
-        nic_name = MIGRATION_WORKER_NIC_NAME_FORMAT % migration_id
-        nic = self._create_nic(
-            connection_info, resgroup, nic_name, location,
-            ip_configs=[
-                network.models.NetworkInterfaceIPConfiguration(
-                    name=nic_name + "-ipconf",
-                    subnet=self._get_subnet(
-                        connection_info,
-                        resgroup,
-                        MIGRATION_NETWORK_NAME_FORMAT % migration_id,
-                        CONF.azure_migration_provider.migr_subnet_name
-                    ),
-                    private_ip_allocation_method=network.models.IPAllocationMethod.dynamic,
-                    public_ip_address=pub_ip,
-                )
-            ]
-        )
-
-        # create the worker:
-        worker_disk_name = AZURE_DISK_NAME_FORMAT % worker_name
-        computec = self._get_compute_client(connection_info)
-        worker_osprofile = self._get_worker_osprofile(
-            export_info["os_type"], location, worker_name)
-        worker_img = WORKER_IMAGES_MAP[export_info["os_type"]]
-        target_env_sku_map = target_environment.get("migr_image_sku_map", {})
-        conf_sku = CONF.azure_migration_provider.migr_image_sku_map.get(
-            export_info["os_type"])
-        worker_img_sku = target_environment.get(
-            "migr_image_sku",
-            target_env_sku_map.get(export_info["os_type"], conf_sku))
-
-
-        awaited(computec.virtual_machines.create_or_update)(
-            resgroup,
-            worker_name,
-            compute.models.VirtualMachine(
-                location=location,
-                os_profile=worker_osprofile.profile,
-                hardware_profile=compute.models.HardwareProfile(
-                    self._get_worker_size(worker_name, len(datadisks))
-                ),
-                network_profile=compute.models.NetworkProfile(
-                    network_interfaces=[
-                        compute.models.NetworkInterfaceReference(
-                            id=nic.id,
-                        ),
-                    ]
-                ),
-                storage_profile=compute.models.StorageProfile(
-                    os_disk=compute.models.OSDisk(
-                        caching=compute.models.CachingTypes.none,
-                        create_option=compute.models.DiskCreateOptionTypes.from_image,
-                        name=worker_disk_name,
-                        vhd=compute.models.VirtualHardDisk(
-                            BLOB_PATH_FORMAT % (
-                                target_environment["storage"]["account"],
-                                CONF.azure_migration_provider.migr_container_name,
-                                worker_disk_name
-                            )
-                        ),
-                    ),
-                    data_disks=datadisks,
-                    image_reference=compute.models.ImageReference(
-                        publisher=worker_img["publisher"],
-                        offer=worker_img["offer"],
-                        sku=worker_img_sku,
-                        version=worker_img["version"]
-                    )
-                )
-            )
-        )
-
-        self._event_manager.progress_update(
-            "Waiting for migration worker to start")
-        self._wait_for_vm(connection_info, resgroup, worker_name)
-        self._event_manager.progress_update(
-            "Migration worker has started succesfully")
-
-        # add any neccessary extensions to the Worker:
-        for ext in worker_osprofile.extensions:
-            self._event_manager.progress_update(
-                "Creating Worker VM extensions \"%s\"" % ext.name)
-
-            awaited(computec.virtual_machine_extensions.create_or_update)(
-                resgroup,
-                worker_name,
-                ext.name,
-                ext
-            )
-
-        # fetch the instance's allocated ip:
-        pub_ip = self._get_pub_ip(connection_info, resgroup, ip_name)
-
-        return AzureWorkerInstance(
-            name=worker_name,
-            ip=pub_ip.ip_address,
-            port=worker_osprofile.port,
-            datadisks=datadisks,
-            username=worker_osprofile.profile.admin_username,
-            password=worker_osprofile.profile.admin_password,
-            pkey=worker_osprofile.token
-        )
-
-    def _get_migration_os_profile(self, os_type, instance_name):
-        computer_name = instance_name
-        if os_type == constants.OS_TYPE_WINDOWS:
-            computer_name = azutils.normalize_hostname(instance_name)
-
-        profile = compute.models.OSProfile(
-            computer_name=computer_name,
-            # NOTE: here we are forced to provide a username and password to
-            # the API. These values will be ignored by the Azure agent as
-            # provisioning is turned off in the agent's configuration in
-            # the osmorphing stage.
-            admin_username="coriolis",
-            admin_password=azutils.get_random_password()
-        )
-
-        if os_type == constants.OS_TYPE_WINDOWS:
-            profile.windows_configuration = (
-                compute.models.WindowsConfiguration(
-                    provision_vm_agent=True,
-                    enable_automatic_updates=False,
-                )
-            )
-
-        return profile
-
-    def import_instance(self, ctxt, connection_info, target_environment,
-                        instance_name, export_info):
-        """ Runs the process of importing the instance to Azure. """
-        self._event_manager.progress_update(
-            "Importing instance \"%s\"" % instance_name)
-
-        awaited = azutils.awaited(300)
-        location = target_environment.get(
-            "location", CONF.azure_migration_provider.migr_location)
-        location = azutils.normalize_location(location)
-
-        migration_id = azutils.get_unique_id()[:10]
-        resgroup = MIGRATION_RESGROUP_NAME_FORMAT % migration_id
-
-        worker_info = None
-
-        try:
-            # setup storage container:
-            cont_name = CONF.azure_migration_provider.migr_container_name
-            self._event_manager.progress_update(
-                "Creating migration storage container \"%s\"" % cont_name)
-
-            blobd = self._get_page_blob_client(target_environment)
-            utils.retry_on_error()(blobd.create_container)(
-                cont_name, public_access=blob.PublicAccess.Container)
-
-            # migrate the instance's attached volumes:
-            datadisks = []
-            for lun, disk in enumerate(export_info["devices"]["disks"]):
-                LOG.info("Processing instance disk number %d", (lun + 1))
-
-                disk_name = AZURE_DISK_NAME_FORMAT % (
-                    instance_name + "_" + str(lun))
-                try:
-                    datadisk = self._migrate_disk(
-                        target_environment, lun, disk["path"], disk_name)
-                except:
-                    raise
-                else:
-                    datadisks.append(datadisk)
-
-            # setup migration resource group:
-            self._event_manager.progress_update(
-                "Creating migration resource group \"%s\"" % resgroup)
-
-            resc = self._get_resource_client(connection_info)
-            utils.retry_on_error()(
-                azutils.checked(resc.resource_groups.create_or_update))(
-                    resgroup,
-                    resources.models.ResourceGroup(location))
-
-            # create the migration network:
-            self._create_migration_network(
-                connection_info, target_environment, migration_id)
-
-            # create the worker:
-            worker_info = self._create_migration_worker(
-                connection_info, target_environment, export_info,
-                migration_id, datadisks)
-
-            # morph the images:
-            self._event_manager.progress_update(
-                "Preparing instance for new environment")
-
-            osmorpher.morph_image(
-                worker_info._asdict(),
-                export_info["os_type"],
-                constants.HYPERVISOR_HYPERV,
-                constants.PLATFORM_AZURE_RM,
-                None,
-                self._event_manager,
-                ignore_devices=["/dev/sdb"]
-            )
-
-            # delete the worker:
-            self._event_manager.progress_update("Deleting migration worker")
-            vmclient = self._get_compute_client(
-                connection_info).virtual_machines
-            utils.retry_on_error()(awaited(vmclient.delete))(
-                resgroup, worker_info.name)
-
-            # setup vm nics:
-            self._event_manager.progress_update(
-                "Setting up instance NICs")
-
-            nics = []
-            for i, nic in enumerate(export_info["devices"].get("nics", [])):
-                nic_name = nic.get("name", "%s-NIC-%d" % (instance_name, i + 1))
-                net_name = nic.get("network_name", None)
-                subnet_name = nic.get("subnet_name", None)
-                add_public_ip = False
-
-                if not net_name:
-                    net = target_environment.get("destination_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["destination_network"]["name"]
-                    subnet_name = target_environment[
-                        "destination_network"]["subnet"]
-                    add_public_ip = target_environment[
-                        "destination_network"]["is_public_network"]
-
-                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 = None
-                    for netm in network_map:
-                        if netm["source_network"] == net_name:
-                            net = netm
-                    if not net:
-                        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")
-                                )
-                        )
-
-                    net_name = net["destination_network"]
-                    subnet_name = net["destination_subnet"]
-                    add_public_ip = net["is_public_network"]
-
-                nic_name = azutils.normalize_resource_name(nic_name)
-
-                nic_ip_config = network.models.NetworkInterfaceIPConfiguration(
-                    name=nic_name + "-ipconf",
-                    subnet=self._get_subnet(
-                        connection_info,
-                        target_environment["resource_group"],
-                        net_name,
-                        subnet_name
-                    ),
-                    private_ip_allocation_method=network.models.IPAllocationMethod.dynamic,
-                )
-
-                if add_public_ip:
-                    ip_name = "%s-pip" % nic_name
-                    pub_ip = self._create_public_ip(
-                        connection_info,
-                        target_environment["resource_group"],
-                        ip_name, location
-                    )
-
-                    nic_ip_config.public_ip_address = pub_ip
-
-                nics.append(self._create_nic(
-                    connection_info,
-                    target_environment["resource_group"],
-                    nic_name,
-                    location,
-                    ip_configs=[nic_ip_config],
-                ))
-
-            # create the VM:
-            self._event_manager.progress_update(
-                "Starting migrated instance as \"%s\"" %
-                    azutils.normalize_resource_name(instance_name))
-
-            disk_name = AZURE_DISK_NAME_FORMAT % (
-                "".join(instance_name) + "_os_disk")
-            vm_profile = self._get_migration_os_profile(
-                export_info["os_type"], instance_name)
-
-            vmclient = self._get_compute_client(
-                connection_info).virtual_machines
-            utils.retry_on_error()(awaited(vmclient.create_or_update))(
-                target_environment["resource_group"],
-                azutils.normalize_resource_name(instance_name),
-                compute.models.VirtualMachine(
-                    location=location,
-                    os_profile=vm_profile,
-                    hardware_profile=compute.models.HardwareProfile(
-                        vm_size=compute.models.VirtualMachineSizeTypes(
-                            target_environment["size"]
-                        )
-                    ),
-                    network_profile=compute.models.NetworkProfile(
-                        network_interfaces=[compute.models.NetworkInterfaceReference(x.id)
-                                            for x in nics]
-                    ),
-                    storage_profile=compute.models.StorageProfile(
-                        os_disk=compute.models.OSDisk(
-                            name=disk_name,
-                            os_type=AZURE_OSTYPES_MAP[export_info["os_type"]],
-                            caching=compute.models.CachingTypes.none,
-                            create_option=compute.models.DiskCreateOptionTypes.from_image,
-                            # NOTE: we are guaranteed that the first disk is
-                            # the one with the OS inside it:
-                            image=worker_info.datadisks[0].vhd,
-                            vhd=compute.models.VirtualHardDisk(
-                                BLOB_PATH_FORMAT % (
-                                    target_environment["storage"]["account"],
-                                    CONF.azure_migration_provider.migr_container_name,
-                                    disk_name
-                                )
-                            ),
-                        ),
-                        # NOTE: we are guaranteed that the first disk in the
-                        # export_info is the one with the OS inside of it,
-                        # thus the rest are the datadisks:
-                        data_disks=worker_info.datadisks[1:],
-                    )
-                ),
-            )
-        except:
-            self._cleanup(connection_info, target_environment,
-                          resgroup, worker_info)
-            LOG.error(
-                "Exception occurred during import:\n" + traceback.format_exc())
-            raise
-        finally:
-            self._cleanup(connection_info, target_environment,
-                          resgroup, worker_info)
-
-    @utils.retry_on_error()
-    def _cleanup(self, connection_info, target_environment,
-                 migration_resgroup, worker_info):
-        """ Cleans up all resources for the migration. Is idempotent. """
-        awaited = azutils.awaited()
-
-        self._event_manager.progress_update("Cleaning up migration resource group")
-        resc = self._get_resource_client(connection_info)
-
-        # check if the migration resource group still exists:
-        if resc.resource_groups.check_existence(migration_resgroup):
-            # then, delete it:
-            awaited(resc.resource_groups.delete)(migration_resgroup)
-
-        if not worker_info:
-            # it means that the worker never got defined, and we may return:
-            return
-
-        worker_name = worker_info.name
-
-        # lastly, delete the worker's disks:
-        # delete worker disks:
-        blobd = self._get_page_blob_client(target_environment)
-        cont_name = CONF.azure_migration_provider.migr_container_name
-        blob_name = AZURE_DISK_NAME_FORMAT % worker_name
-
-        if blobd.exists(cont_name, blob_name):
-            blobd.delete_blob(cont_name, blob_name)
-
-        self._delete_recovery_disk(target_environment, worker_name)

+ 0 - 20
coriolis/providers/azure/exceptions.py

@@ -1,20 +0,0 @@
-# Copyright 2016 Cloudbase Solutions Srl
-# All Rights Reserved.
-
-"""
-Defines various exceptions which may arise during migrations to/from Azure.
-"""
-
-from coriolis import exception
-from coriolis.i18n import _
-
-
-class AzureOperationException(exception.CoriolisException):
-    """ Simply wraps a standard CoriolisException. """
-    message = _("Azure operation failed with code: %(code)d. "
-                "Error message: %(msg)s.")
-
-
-class FatalAzureOperationException(AzureOperationException):
-    """ Extends AzureOperationExceptions to indicate fatal errors. """
-    pass

+ 0 - 57
coriolis/providers/azure/schemas/connection_info_schema.json

@@ -1,57 +0,0 @@
-{
-  "$schema": "http://cloudbase.it/coriolis/schemas/azure_connection#",
-  "type": "object",
-  "properties": {
-    "secret_ref": {
-      "type": "string"
-    },
-    "subscription_id": {
-      "type": "string"
-    },
-    "user_credentials": {
-      "type": "object",
-      "properties": {
-        "username": {
-          "type": "string"
-        },
-        "password": {
-          "type": "string"
-        }
-      },
-      "required": [
-        "username",
-        "password"
-      ]
-    },
-    "service_principal_credentials": {
-      "type": "object",
-      "properties": {
-        "client_id": {
-          "type": "string"
-        },
-        "client_secret": {
-          "type": "string"
-        },
-        "tenant_id": {
-          "type": "string"
-        }
-      },
-      "required": [
-        "client_id",
-        "client_secret",
-        "tenant_id"
-      ]
-    }
-  },
-  "oneOf": [
-    {
-      "required": ["secret_ref"]
-    },
-    {
-      "required": ["subscription_id", "user_credentials"]
-    },
-    {
-      "required": ["subscription_id", "service_principal_credentials"]
-    }
-  ]
-}

+ 0 - 98
coriolis/providers/azure/schemas/target_environment_schema.json

@@ -1,98 +0,0 @@
-{
-  "$schema": "http://cloudbase.it/coriolis/schemas/azure_target_environment#",
-  "type": "object",
-  "properties": {
-    "secret_ref": {
-      "type": "string"
-    },
-    "size": {
-      "type": "string"
-    },
-    "location": {
-      "type": "string"
-    },
-    "resource_group": {
-      "type": "string"
-    },
-    "storage": {
-      "type": "object",
-      "properties": {
-        "account": {
-          "type": "string"
-        },
-        "key": {
-          "type": "string"
-        },
-        "container": {
-          "type": "string"
-        }
-      },
-      "required": [
-        "account",
-        "key"
-      ]
-    },
-    "migr_image_sku": {
-        "type": "string"
-    },
-    "migr_image_sku_map": {
-        "type": "object"
-    },
-    "network_map": {
-      "type": "array",
-      "items": {
-        "type": "object",
-        "properties": {
-          "source_network": {
-            "type": "string"
-          },
-          "destination_network": {
-            "type": "string"
-          },
-          "destination_subnet_name": {
-            "type": "string"
-          },
-          "is_public_network": {
-            "type": "boolean"
-          }
-        },
-        "required": [
-          "source_network",
-          "destination_network",
-          "destination_subnet",
-          "is_public_network"
-        ]
-      }
-    },
-    "destination_network": {
-      "type": "object",
-      "properties": {
-        "name": {
-          "type": "string"
-        },
-        "subnet": {
-          "type": "string"
-        },
-        "is_public_network": {
-          "type": "boolean"
-        }
-      },
-      "required": [
-        "name",
-        "subnet",
-        "is_public_network"
-      ]
-    }
-  },
-  "oneOf": [
-    {
-      "required": ["secret_ref"]
-    },
-    {
-      "required": ["size", "location", "resource_group", "storage", "destination_network"]
-    },
-    {
-      "required": ["size", "location", "resource_group", "storage", "network_map"]
-    }
-  ]
-}

+ 0 - 101
coriolis/providers/azure/utils.py

@@ -1,101 +0,0 @@
-# Copyright 2016 Cloudbase Solutions Srl
-# All Rights Reserved.
-
-"""
-General utility functions for performing Azure operations.
-"""
-
-import functools
-import random
-import string
-import uuid
-
-from oslo_config import cfg
-from oslo_log import log as logging
-
-
-CONF = cfg.CONF
-LOG = logging.getLogger(__name__)
-
-
-def get_random_password():
-    """ Returns a random password compatible with the minimal requirements of
-    Azure to be used for worker instances (namely, to contain 8+ characters
-    with any 3 of: a character (lower or uppoercase), digit or special symbol).
-    """
-    upper = random.choice(string.ascii_uppercase)
-    lower = random.choice(string.ascii_lowercase)
-    digit = random.choice(string.digits)
-
-    return "%s%s%s%s" % (
-        upper, digit, lower, get_unique_id()
-    )
-
-
-def get_unique_id():
-    """ Returns a generically Azure-friendly ID. """
-    return "".join([x for x in str(uuid.uuid4()) if x.isalnum()])
-
-
-def normalize_resource_name(name):
-    """ Normalizes a resource name.
-
-    Constraints are:
-        - alphanumeric characters or '.', '-', '_'
-        - maximum length is 80
-    """
-    return "".join([x for x in name if x.isalnum() or x in ['.', '-', '_']])
-
-
-def normalize_hostname(hostname):
-    """ Normalizes the provided hostname for Azure.
-
-    Constraints are:
-        - no special characters
-        - maximum length of 15
-    """
-    new = "".join([x for x in hostname if x.isalnum])
-    if not new:
-        new = CONF.azure_migration_provider.default_migration_hostname
-        LOG.info("Cannot normalize instance hostname '%s' for Azure. "
-                 "Defaulting to using '%s'.", hostname, new)
-
-    if len(new) > 15:
-        new = new[:15]
-
-    if new != hostname:
-        LOG.info("Normalized instance hostname '%s' to '%s' "
-                 "for booting instance on Azure.", new, hostname)
-
-    return new
-
-
-def normalize_location(location):
-    """ Normalizes a location for azure. """
-    return "".join([x.lower() for x in location if x.isalnum()])
-
-
-def checked(operation):
-    """ Forces raw status code check on an Azure operation. """
-    @functools.wraps(operation)
-    def _checked(*args, **kwargs):
-        resp = operation(*args, raw=True, **kwargs)
-
-        return resp.output
-
-    return _checked
-
-
-def awaited(timeout=90):
-    """ Awaits for the result of the given operation. """
-
-    def _awaited(operation):
-        @functools.wraps(operation)
-        def _await(*args, **kwargs):
-            resp = operation(
-                *args, long_running_operation_timeout=timeout, **kwargs)
-
-            return resp.result()
-        return _await
-
-    return _awaited

+ 1 - 1
coriolis/providers/backup_writers.py

@@ -141,7 +141,7 @@ class SSHBackupWriter(BaseBackupWriter):
         self._msg_id += 1
         self._stdin.write(data)
         self._stdin.flush()
-        out_msg_id = self._stdout.read(4)
+        self._stdout.read(4)
 
     def _open(self):
         self._connect_ssh()

+ 48 - 0
coriolis/providers/base.py

@@ -2,9 +2,15 @@
 # All Rights Reserved.
 
 import abc
+import itertools
 
+from oslo_log import log as logging
+
+from coriolis import exception
 from coriolis import schemas
 
+LOG = logging.getLogger(__name__)
+
 
 class BaseProvider(object):
     __metaclass__ = abc.ABCMeta
@@ -30,6 +36,9 @@ class BaseProvider(object):
                 "Error validating provider '%s' connection "
                 "info: %s" % str(ex)) from ex
 
+    def get_os_morphing_tools(self, conn, osmorphing_info):
+        raise exception.OSMorphingToolsNotFound()
+
 
 class BaseImportProvider(BaseProvider):
     __metaclass__ = abc.ABCMeta
@@ -60,6 +69,16 @@ class BaseImportProvider(BaseProvider):
         """
         pass
 
+    @abc.abstractmethod
+    def finalize_import_instance(self, ctxt, connection_info,
+                                 instance_deployment_info):
+        pass
+
+    @abc.abstractmethod
+    def cleanup_failed_import_instance(self, ctxt, connection_info,
+                                       instance_deployment_info):
+        pass
+
 
 class BaseReplicaImportProvider(BaseProvider):
     __metaclass__ = abc.ABCMeta
@@ -70,6 +89,16 @@ class BaseReplicaImportProvider(BaseProvider):
                                 volumes_info, clone_disks):
         pass
 
+    @abc.abstractmethod
+    def finalize_replica_instance_deployment(self, ctxt, connection_info,
+                                             instance_deployment_info):
+        pass
+
+    @abc.abstractmethod
+    def cleanup_failed_replica_instance_deployment(self, ctxt, connection_info,
+                                                   instance_deployment_info):
+        pass
+
     @abc.abstractmethod
     def deploy_replica_disks(self, ctxt, connection_info, target_environment,
                              instance_name, export_info, volumes_info):
@@ -142,3 +171,22 @@ class BaseReplicaExportProvider(BaseProvider):
     @abc.abstractmethod
     def shutdown_instance(self, ctxt, connection_info, instance_name):
         pass
+
+
+def get_os_morphing_tools_helper(conn, os_morphing_tools_clss,
+                                 hypervisor_type, os_type, os_root_dir,
+                                 os_root_dev, event_manager):
+    if os_type and os_type not in os_morphing_tools_clss:
+        raise exception.OSMorphingToolsNotFound(
+            "Unsupported OS type: %s" % os_type)
+
+    for cls in os_morphing_tools_clss.get(
+            os_type, itertools.chain(*os_morphing_tools_clss.values())):
+        LOG.debug("Loading osmorphing instance: %s", cls)
+        tools = cls(
+            conn, os_root_dir, os_root_dev, hypervisor_type, event_manager)
+        LOG.debug("Testing OS morphing tools: %s", cls.__name__)
+        os_info = tools.check_os()
+        if os_info:
+            return (tools, os_info)
+    raise exception.OSMorphingToolsNotFound()

+ 10 - 7
coriolis/providers/factory.py

@@ -1,18 +1,21 @@
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
 
+from oslo_config import cfg
+
 from coriolis import constants
 from coriolis import exception
 from coriolis.providers import base
 from coriolis import utils
 
-PROVIDERS = {
-    "coriolis.providers.azure.ImportProvider",
-    "coriolis.providers.openstack.ExportProvider",
-    "coriolis.providers.openstack.ImportProvider",
-    "coriolis.providers.vmware_vsphere.ExportProvider",
-}
+serialization_opts = [
+    cfg.ListOpt('providers',
+                default=[],
+                help='List of provider class paths'),
+]
 
+CONF = cfg.CONF
+CONF.register_opts(serialization_opts)
 
 PROVIDER_TYPE_MAP = {
     constants.PROVIDER_TYPE_EXPORT: base.BaseExportProvider,
@@ -23,7 +26,7 @@ PROVIDER_TYPE_MAP = {
 
 
 def get_provider(platform_name, provider_type, event_handler):
-    for provider in PROVIDERS:
+    for provider in CONF.providers:
         cls = utils.load_class(provider)
         if (cls.platform == platform_name and
                 PROVIDER_TYPE_MAP[provider_type] in cls.__bases__):

+ 0 - 5
coriolis/providers/openstack/__init__.py

@@ -1,5 +0,0 @@
-from coriolis.providers.openstack import exp
-from coriolis.providers.openstack import imp
-
-ExportProvider = exp.ExportProvider
-ImportProvider = imp.ImportProvider

+ 0 - 329
coriolis/providers/openstack/common.py

@@ -1,329 +0,0 @@
-# Copyright 2016 Cloudbase Solutions Srl
-# All Rights Reserved.
-
-import collections
-import math
-import time
-import uuid
-
-from oslo_log import log as logging
-from oslo_utils import units
-
-from coriolis import exception
-from coriolis import utils
-
-MIGRATION_TMP_FORMAT = "migration_tmp_%s"
-
-NOVA_API_VERSION = 2
-GLANCE_API_VERSION = 1
-NEUTRON_API_VERSION = '2.0'
-CINDER_API_VERSION = 2
-
-LOG = logging.getLogger(__name__)
-
-
-GlanceImage = collections.namedtuple(
-    "GlanceImage", "id format size path os_type")
-
-
-def get_unique_name():
-    return MIGRATION_TMP_FORMAT % str(uuid.uuid4())
-
-
-def create_image(glance, name, disk_path, disk_format, container_format,
-                 hypervisor_type):
-    properties = {}
-    if hypervisor_type:
-        properties["hypervisor_type"] = hypervisor_type
-
-    if glance.version == 1:
-        return _create_image_v1(glance, name, disk_path, disk_format,
-                                container_format, properties)
-    elif glance.version == 2:
-        return _create_image_v2(glance, name, disk_path, disk_format,
-                                container_format, properties)
-    else:
-        raise NotImplementedError("Unsupported Glance version")
-
-
-@utils.retry_on_error()
-def _create_image_v2(glance, name, disk_path, disk_format, container_format,
-                     properties):
-    image = glance.images.create(
-        name=name,
-        disk_format=disk_format,
-        container_format=container_format,
-        **properties)
-    try:
-        with open(disk_path, 'rb') as f:
-            glance.images.upload(image.id, f)
-        return image
-    except:
-        glance.images.delete(image.id)
-        raise
-
-
-@utils.retry_on_error()
-def _create_image_v1(glance, name, disk_path, disk_format, container_format,
-                     properties):
-    with open(disk_path, 'rb') as f:
-        return glance.images.create(
-            name=name,
-            disk_format=disk_format,
-            container_format=container_format,
-            properties=properties,
-            data=f)
-
-
-@utils.retry_on_error()
-def wait_for_image(nova, image_id, expected_status='ACTIVE'):
-    image = nova.images.get(image_id)
-    while image.status not in [expected_status, 'ERROR']:
-        LOG.debug('Image "%(id)s" in status: "%(status)s". '
-                  'Waiting for status: "%(expected_status)s".',
-                  {'id': image_id, 'status': image.status,
-                   'expected_status': expected_status})
-        time.sleep(2)
-        image = nova.images.get(image.id)
-    if image.status != expected_status:
-        raise exception.CoriolisException(
-            "Image is in status: %s" % image.status)
-
-
-@utils.retry_on_error()
-def wait_for_instance(nova, instance_id, expected_status='ACTIVE'):
-    instance = nova.servers.get(instance_id)
-    while instance.status not in [expected_status, 'ERROR']:
-        LOG.debug('Instance %(id)s status: %(status)s. '
-                  'Waiting for status: "%(expected_status)s".',
-                  {'id': instance_id, 'status': instance.status,
-                   'expected_status': expected_status})
-        time.sleep(2)
-        instance = nova.servers.get(instance.id)
-    if instance.status != expected_status:
-        raise exception.CoriolisException(
-            "VM is in status: %s" % instance.status)
-
-
-@utils.retry_on_error(terminal_exceptions=[exception.CoriolisException])
-def wait_for_instance_deletion(
-        nova, instance_id, max_retries=150, retry_period=2):
-    instances = nova.servers.findall(id=instance_id)
-    i = 0
-    while i < max_retries and instances:
-        i = i + 1
-        instance = utils.get_single_result(instances)
-        if instance.status == 'error':
-            raise exception.CoriolisException(
-                "Instance \"%s\" has reached invalid state \"%s\" while "
-                "deleting." % (instance_id, instance.status))
-
-        LOG.debug('Instance %(id)s status: %(status)s. '
-                  'Waiting %(period)s seconds for its deletion.',
-                  {'id': instance_id, 'status': instance.status,
-                   'period': retry_period})
-        time.sleep(retry_period)
-        instances = nova.servers.findall(id=instance_id)
-
-    if instances:
-        instance = utils.get_single_result(instances)
-        raise exception.CoriolisException(
-            "Max attempts of %(attempts)s reached while waiting for VM "
-            "\"%(instance_id)s\" deletion. Last known status: \"%(status)s\"" %
-            {'attempts': max_retries, 'status': instance.status,
-             'instance_id': instance_id})
-
-
-@utils.retry_on_error()
-def find_volume(cinder, volume_id):
-    volumes = cinder.volumes.findall(id=volume_id)
-    if volumes:
-        return utils.get_single_result(volumes)
-
-
-@utils.retry_on_error()
-def extend_volume(cinder, volume_id, new_size):
-    volume_size_gb = math.ceil(new_size / units.Gi)
-    cinder.volumes.extend(volume_id, volume_size_gb)
-
-
-@utils.retry_on_error()
-def get_volume_from_snapshot(cinder, snapshot_id):
-    snapshot = cinder.volume_snapshots.get(snapshot_id)
-    return cinder.volumes.get(snapshot.volume_id)
-
-
-@utils.retry_on_error()
-def create_volume(cinder, size, name, image_ref=None,
-                  snapshot_id=None, volume_type=None):
-    if snapshot_id:
-        volume_size_gb = None
-    else:
-        volume_size_gb = math.ceil(size / units.Gi)
-    return cinder.volumes.create(
-        size=volume_size_gb,
-        name=name,
-        volume_type=volume_type,
-        imageRef=image_ref,
-        snapshot_id=snapshot_id)
-
-
-@utils.retry_on_error(terminal_exceptions=[exception.NotFound])
-def get_flavor(nova, flavor_name):
-    flavors = nova.flavors.findall(name=flavor_name)
-    if not flavors:
-        raise exception.FlavorNotFound(flavor_name=flavor_name)
-    return flavors[0]
-
-
-@utils.retry_on_error(terminal_exceptions=[exception.NotFound])
-def get_image(glance, image_name):
-    images = glance.images.findall(name=image_name)
-    if not images:
-        raise exception.ImageNotFound(image_name=image_name)
-    return images[0]
-
-
-@utils.retry_on_error(terminal_exceptions=[exception.NotFound])
-def check_floating_ip_pool(nova, pool_name):
-    if not nova.floating_ip_pools.findall(name=pool_name):
-        raise exception.FloatingIPPoolNotFound(pool_name=pool_name)
-
-
-@utils.retry_on_error(terminal_exceptions=[exception.NotFound])
-def wait_for_volume(cinder, volume_id, expected_status='available'):
-    volumes = cinder.volumes.findall(id=volume_id)
-    if not volumes:
-        raise exception.VolumeNotFound(volume_id=volume_id)
-    volume = utils.get_single_result(volumes)
-
-    terminal_statuses = [expected_status, 'error']
-    if expected_status == 'in-use':
-        # if we're waiting for a volume to become attached, we are guaranteed
-        # that its status would no longer be 'available' the moment the
-        # attachment request is accepted.
-        terminal_statuses.append('available')
-
-    while volume.status not in terminal_statuses:
-        LOG.debug('Volume %(id)s status: %(status)s. '
-                  'Waiting for status: "%(expected_status)s".',
-                  {'id': volume_id, 'status': volume.status,
-                   'expected_status': expected_status})
-        time.sleep(2)
-        volume = cinder.volumes.get(volume.id)
-    if volume.status != expected_status:
-        raise exception.CoriolisException(
-            "Volume is in status: %s" % volume.status)
-
-
-@utils.retry_on_error()
-def delete_volume(cinder, volume_id):
-    volumes = cinder.volumes.findall(id=volume_id)
-    for volume in volumes:
-        volume.delete()
-
-
-@utils.retry_on_error()
-def create_volume_snapshot(cinder, volume_id, name, force=False):
-    return cinder.volume_snapshots.create(volume_id, name=name, force=force)
-
-
-@utils.retry_on_error(terminal_exceptions=[exception.NotFound])
-def wait_for_volume_snapshot(cinder, snapshot_id,
-                             expected_status='available'):
-    snapshots = cinder.volume_snapshots.findall(id=snapshot_id)
-
-    if not snapshots:
-        if expected_status == 'deleted':
-            return
-        raise exception.VolumeSnapshotNotFound(snapshot_id=snapshot_id)
-    snapshot = utils.get_single_result(snapshots)
-
-    while snapshot.status not in [expected_status, 'error']:
-        if expected_status == 'deleted' and snapshot.status == 'available':
-            LOG.debug("Cinder volume snapshot '%s' became 'available' while "
-                      "waiting for its deletion. This may be the behavior "
-                      "of the Cinder driver being used, so no further "
-                      "deletion attempts will be made.",
-                      snapshot_id)
-            return
-
-        LOG.debug('Volume snapshot %(id)s status: %(status)s. '
-                  'Waiting for status: "%(expected_status)s".',
-                  {'id': snapshot_id, 'status': snapshot.status,
-                   'expected_status': expected_status})
-        time.sleep(2)
-        if expected_status == 'deleted':
-            snapshots = cinder.volume_snapshots.findall(id=snapshot_id)
-            if not snapshots:
-                return
-            snapshot = utils.get_single_result(snapshots)
-        else:
-            snapshot = cinder.volume_snapshots.get(snapshot.id)
-    if snapshot.status != expected_status:
-        raise exception.CoriolisException(
-            "Volume snapshot is in status: %s" % snapshot.status)
-
-
-@utils.retry_on_error()
-def delete_volume_snapshot(cinder, snapshot_id):
-    snapshots = cinder.volume_snapshots.findall(id=snapshot_id)
-    for snapshot in snapshots:
-        return cinder.volume_snapshots.delete(snapshot.id)
-
-
-@utils.retry_on_error()
-def create_volume_backup(cinder, volume_id, snapshot_id, name, container,
-                         incremental, force=False):
-    return cinder.backups.create(
-        volume_id=volume_id,
-        snapshot_id=snapshot_id,
-        container=container,
-        name=name,
-        incremental=incremental,
-        force=force)
-
-
-@utils.retry_on_error(terminal_exceptions=[exception.NotFound])
-def wait_for_volume_backup(cinder, backup_id, expected_status='available'):
-    backups = cinder.backups.findall(id=backup_id)
-
-    if not backups:
-        if expected_status == 'deleted':
-            return
-        raise exception.VolumeBackupNotFound(backup_id=backup_id)
-    backup = utils.get_single_result(backups)
-
-    while backup.status not in [expected_status, 'error']:
-        LOG.debug('Volume backup %(id)s status: %(status)s. '
-                  'Waiting for status: "%(expected_status)s".',
-                  {'id': backup_id, 'status': backup.status,
-                   'expected_status': expected_status})
-        time.sleep(2)
-        if expected_status == 'deleted':
-            backups = cinder.backups.findall(id=backup_id)
-            if not backups:
-                return
-            backup = utils.get_single_result(backups)
-        else:
-            backup = cinder.backups.get(backup.id)
-    if backup.status != expected_status:
-        raise exception.CoriolisException(
-            "Volume backup is in status: %s" % backup.status)
-
-
-@utils.retry_on_error()
-def delete_volume_backup(cinder, backup_id):
-    cinder.backups.delete(backup_id)
-
-
-@utils.retry_on_error()
-def find_volume_backups(cinder, volume_id=None, container=None):
-    return cinder.backups.list(search_opts={
-        "volume_id": volume_id,
-        "container": container})
-
-
-@utils.retry_on_error()
-def get_instance_volumes(nova, instance_id):
-    return nova.volumes.get_server_volumes(instance_id)

+ 0 - 479
coriolis/providers/openstack/exp.py

@@ -1,479 +0,0 @@
-# Copyright 2016 Cloudbase Solutions Srl
-# All Rights Reserved.
-import gc
-import json
-import os
-import re
-import zlib
-
-from cinderclient import client as cinder_client
-from glanceclient import client as glance_client
-from novaclient import client as nova_client
-from oslo_config import cfg
-from oslo_log import log as logging
-from oslo_utils import units
-from swiftclient import client as swift_client
-
-from coriolis import constants
-from coriolis import events
-from coriolis import exception
-from coriolis import keystone
-from coriolis.providers import backup_writers
-from coriolis.providers import base
-from coriolis.providers.openstack import common
-from coriolis import schemas
-from coriolis import utils
-
-opts = [
-    cfg.StrOpt('volume_backups_container',
-               default="coriolis",
-               help='Cinder volume backups container name.'),
-]
-
-CONF = cfg.CONF
-CONF.register_opts(opts, 'openstack_migration_provider')
-
-LOG = logging.getLogger(__name__)
-
-VOLUME_BACKUP_VERSION = '1.0.0'
-
-
-class ExportProvider(base.BaseExportProvider, base.BaseReplicaExportProvider):
-
-    platform = constants.PLATFORM_OPENSTACK
-
-    _OS_DISTRO_MAP = {
-        'windows': constants.OS_TYPE_WINDOWS,
-        'freebsd': constants.OS_TYPE_BSD,
-        'netbsd': constants.OS_TYPE_BSD,
-        'openbsd': constants.OS_TYPE_BSD,
-        'opensolaris': constants.OS_TYPE_SOLARIS,
-        'arch': constants.OS_TYPE_LINUX,
-        'centos': constants.OS_TYPE_LINUX,
-        'debian': constants.OS_TYPE_LINUX,
-        'fedora': constants.OS_TYPE_LINUX,
-        'gentoo': constants.OS_TYPE_LINUX,
-        'mandrake': constants.OS_TYPE_LINUX,
-        'mandriva': constants.OS_TYPE_LINUX,
-        'mes': constants.OS_TYPE_LINUX,
-        'opensuse': constants.OS_TYPE_LINUX,
-        'rhel': constants.OS_TYPE_LINUX,
-        'sled': constants.OS_TYPE_LINUX,
-        'ubuntu': constants.OS_TYPE_LINUX,
-    }
-
-    connection_info_schema = schemas.get_schema(
-        __name__, schemas.PROVIDER_CONNECTION_INFO_SCHEMA_NAME)
-
-    def __init__(self, event_handler):
-        self._event_manager = events.EventManager(event_handler)
-
-    @utils.retry_on_error(terminal_exceptions=[exception.CoriolisException])
-    def _get_instance(self, nova, instance_name):
-        instances = nova.servers.list(
-            search_opts={'name': "^%s$" % instance_name})
-        if len(instances) > 1:
-            raise exception.CoriolisException(
-                'More than one instance exists with name: %s' % instance_name)
-        elif not instances:
-            raise exception.InstanceNotFound(instance_name=instance_name)
-        return instances[0]
-
-    def _get_os_type(self, image):
-        os_type = constants.DEFAULT_OS_TYPE
-        os_distro = image.properties.get('os_distro')
-        if not os_distro:
-            if 'os_type' in image.properties:
-                os_type = image.properties['os_type']
-            else:
-                self._event_manager.progress_update(
-                    "Image os_distro not set, defaulting to \"%s\"" % os_type)
-        elif os_distro not in self._OS_DISTRO_MAP:
-            self._event_manager.progress_update(
-                "Image os_distro \"%s\" not found, defaulting to \"%s\"" %
-                (os_distro, os_type))
-        else:
-            os_type = self._OS_DISTRO_MAP[os_distro]
-        return os_type
-
-    @utils.retry_on_error()
-    def _export_image(self, glance, export_path, image_id):
-        path = os.path.join(export_path, image_id)
-        LOG.debug('Saving snapshot to path: %s', export_path)
-        with open(path, 'wb') as f:
-            for chunk in glance.images.data(image_id):
-                f.write(chunk)
-
-        disk_info = utils.get_disk_info(path)
-        new_path = path + "." + disk_info['format']
-        os.rename(path, new_path)
-        LOG.debug('Renamed snapshot path: %s', new_path)
-        return new_path, disk_info['format']
-
-    @utils.retry_on_error()
-    def _create_snapshot(self, nova, glance, instance, export_path):
-        image_id = instance.create_image(common.get_unique_name())
-        try:
-            self._event_manager.progress_update(
-                "Waiting for instance snapshot to complete")
-            common.wait_for_image(nova, image_id)
-
-            image = glance.images.get(image_id)
-            image_size = image.size
-
-            if image.container_format != 'bare':
-                raise exception.CoriolisException(
-                    "Unsupported container format: %s" %
-                    image.container_format)
-
-            self._event_manager.progress_update(
-                "Exporting instance snapshot")
-
-            image_path, image_format = self._export_image(
-                glance, export_path, image_id)
-        finally:
-            self._event_manager.progress_update("Removing instance snapshot")
-
-            @utils.ignore_exceptions
-            @utils.retry_on_error()
-            def _del_image():
-                glance.images.delete(image_id)
-            _del_image()
-
-        os_type = self._get_os_type(image)
-        return common.GlanceImage(
-            id=image_id,
-            path=image_path,
-            format=image_format,
-            os_type=os_type,
-            size=image_size
-        )
-
-    def _get_instance_info(self, nova, cinder, instance_name):
-        self._event_manager.progress_update("Retrieving OpenStack instance")
-        instance = self._get_instance(nova, instance_name)
-
-        @utils.retry_on_error()
-        def _get_flavor_by_id():
-            return nova.flavors.get(instance.flavor["id"])
-        flavor = _get_flavor_by_id()
-
-        nics = []
-        for iface in instance.interface_list():
-            ips = set([ip['ip_address'] for ip in iface.fixed_ips])
-            net_name = [
-                n for n, v in instance.networks.items() if set(v) & ips][0]
-
-            nics.append({'name': iface.port_id,
-                         'id': iface.port_id,
-                         'mac_address': iface.mac_addr,
-                         'ip_addresses': list(ips),
-                         'network_id': iface.net_id,
-                         'network_name': net_name})
-
-        disks = []
-        vol_attachments = common.get_instance_volumes(nova, instance.id)
-        for vol_attachment in vol_attachments:
-            volume = cinder.volumes.get(vol_attachment.volumeId)
-            disks.append({
-                'format': constants.DISK_FORMAT_RAW,
-                'guest_device': vol_attachment.device,
-                'size_bytes': volume.size * units.Gi,
-                'storage_backend_identifier': volume.volume_type,
-                'path': '',
-                'id': volume.id
-            })
-
-        vm_info = {
-            'num_cpu': flavor.vcpus,
-            'num_cores_per_socket': 1,
-            'memory_mb': flavor.ram,
-            'nested_virtualization': False,
-            'name': instance_name,
-            'id': instance.id,
-            'flavor_name': flavor.name,
-            'devices': {
-                "nics": nics,
-                "disks": disks,
-                "cdroms": [],
-                "serial_ports": [],
-                "floppies": [],
-                "controllers": []
-            }
-        }
-
-        return instance, vm_info
-
-    def _check_shutdown_instance(self, nova, instance):
-        if instance.status != 'SHUTOFF':
-            self._event_manager.progress_update(
-                "Shutting down instance")
-
-            @utils.retry_on_error()
-            def _stop_instance():
-                instance.stop()
-
-            _stop_instance()
-            common.wait_for_instance(nova, instance.id, 'SHUTOFF')
-
-    def export_instance(self, ctxt, connection_info, instance_name,
-                        export_path):
-        session = keystone.create_keystone_session(ctxt, connection_info)
-        glance_api_version = connection_info.get("image_api_version",
-                                                 common.GLANCE_API_VERSION)
-        nova = nova_client.Client(common.NOVA_API_VERSION, session=session)
-        glance = glance_client.Client(glance_api_version, session=session)
-        cinder = cinder_client.Client(common.CINDER_API_VERSION,
-                                      session=session)
-
-        instance, vm_info = self._get_instance_info(
-            nova, cinder, instance_name)
-        self._check_shutdown_instance(nova, instance)
-
-        self._event_manager.progress_update("Creating instance snapshot")
-        image = self._create_snapshot(
-            nova, glance, instance, export_path)
-
-        disks = []
-        disks.append({
-            'format': image.format,
-            'path': image.path,
-            'size_bytes': image.size,
-            'id': str(image.id)
-        })
-
-        vm_info['os_type'] = image.os_type
-        vm_info['devices']['disks'] = disks
-
-        LOG.info("vm info: %s" % str(vm_info))
-        return vm_info
-
-    def get_replica_instance_info(self, ctxt, connection_info, instance_name):
-        session = keystone.create_keystone_session(ctxt, connection_info)
-        nova = nova_client.Client(common.NOVA_API_VERSION, session=session)
-        cinder = cinder_client.Client(common.CINDER_API_VERSION,
-                                      session=session)
-
-        instance, vm_info = self._get_instance_info(
-            nova, cinder, instance_name)
-
-        vm_info["os_type"] = constants.DEFAULT_OS_TYPE
-
-        return vm_info
-
-    def deploy_replica_source_resources(self, ctxt, connection_info):
-        return {"migr_resources": None, "connection_info": None}
-
-    def delete_replica_source_resources(self, ctxt, connection_info,
-                                        migr_resources_dict):
-        pass
-
-    @utils.retry_on_error()
-    def _get_backup_metadata(self, swift, volume_id, backup_id, container):
-        objects = swift.get_container(container)[1]
-
-        base_re = (r'volume_%(volume_id)s/\d+/az_nova_backup_%(backup_id)s' %
-                   {'volume_id': volume_id, 'backup_id': backup_id})
-
-        metadata_object_name = None
-        for obj in objects:
-            if re.match("%s_metadata" % base_re, obj["name"]):
-                metadata_object_name = obj["name"]
-                break
-
-        if not metadata_object_name:
-            raise exception.NotFound('Cinder backup metadata not found')
-
-        metadata_json = swift.get_object(container, metadata_object_name)[1]
-        metadata = json.loads(metadata_json.decode())
-
-        if metadata['version'] != VOLUME_BACKUP_VERSION:
-            raise exception.CoriolisException(
-                "Unsupported Cinder backup metadata version: %s" %
-                metadata['version'])
-
-        if metadata['backup_id'] != backup_id:
-            raise exception.CoriolisException(
-                "Metadata backup id does not match")
-
-        return metadata
-
-    @utils.retry_on_error()
-    def _read_backup_metadata_object(self, swift, metadata, container):
-        name, info = list(metadata.items())[0]
-        offset = info.get('offset', 0)
-        compression = info.get('compression')
-        md5 = info.get('md5')
-
-        data = swift.get_object(container, name)[1]
-
-        if compression == 'none':
-            pass
-        elif compression == 'zlib':
-            data = zlib.decompress(data)
-        else:
-            raise exception.CoriolisException(
-                "Unsupported compression format: %s" % compression)
-
-        if md5:
-            utils.check_md5(data, md5)
-
-        return data, offset
-
-    def replicate_disks(self, ctxt, connection_info, instance_name,
-                        source_conn_info, target_conn_info, volumes_info,
-                        incremental):
-        session = keystone.create_keystone_session(ctxt, connection_info)
-        cinder = cinder_client.Client(common.CINDER_API_VERSION,
-                                      session=session)
-        swift = swift_client.Connection(session=session)
-
-        ip = target_conn_info["ip"]
-        port = target_conn_info.get("port", 22)
-        username = target_conn_info["username"]
-        pkey = target_conn_info.get("pkey")
-        password = target_conn_info.get("password")
-
-        container = CONF.openstack_migration_provider.volume_backups_container
-
-        volumes_info, backups = self._create_volume_backups(
-            cinder, volumes_info, container)
-
-        LOG.info("Waiting for connectivity on host: %(ip)s:%(port)s",
-                 {"ip": ip, "port": port})
-        utils.wait_for_port_connectivity(ip, port)
-
-        for volume_info in volumes_info:
-            self._event_manager.progress_update(
-                "Retrieving backup metadata")
-
-            last_backup_id = volume_info.get("last_backup_id")
-            backup_metadata_list = []
-
-            volume_id = volume_info["disk_id"]
-            backup_id = backups[volume_id].id
-
-            # Other backups might have occurred since the last replica
-            # execution, make sure to replicate all data
-            while True:
-                backup_metadata = self._get_backup_metadata(
-                    swift, volume_id, backup_id, container)
-                backup_metadata_list.insert(0, backup_metadata)
-                previous_backup_id = backup_metadata.get("parent_id")
-                if (not previous_backup_id or
-                        previous_backup_id == last_backup_id):
-                    break
-                backup_id = previous_backup_id
-
-            backup_writer = backup_writers.SSHBackupWriter(
-                ip, port, username, pkey, password, volumes_info)
-
-            for backup_metadata in backup_metadata_list:
-                objects_metadata = backup_metadata["objects"]
-                backup_id = backup_metadata["backup_id"]
-
-                total_written_bytes = 0
-                total_bytes = sum([list(o.values())[0]['length']
-                                  for o in objects_metadata])
-
-                perc_step = self._event_manager.add_percentage_step(
-                    total_bytes,
-                    message_format="Volume backup %s replica progress: "
-                    "{:.0f}%%" % backup_id)
-
-                max_chunk_size = 10 * units.Mi
-
-                with backup_writer.open("", volume_id) as f:
-                    for object_metadata in objects_metadata:
-                        data, offset = self._read_backup_metadata_object(
-                            swift, object_metadata, container)
-
-                        f.seek(offset)
-
-                        i = 0
-                        while i < len(data):
-                            f.write(data[i:i + max_chunk_size])
-                            i += max_chunk_size
-
-                        total_written_bytes += len(data)
-                        self._event_manager.set_percentage_step(
-                            perc_step, total_written_bytes)
-
-                        data = None
-                        gc.collect()
-
-            volume_info["last_backup_id"] = backups[volume_id].id
-
-        return volumes_info
-
-    def _create_volume_backups(self, cinder, volumes_info, container):
-        snapshots = {}
-        backups = {}
-        try:
-            self._event_manager.progress_update("Creating volume snapshots")
-            for volume_info in volumes_info:
-                volume_id = volume_info["disk_id"]
-                snapshot = common.create_volume_snapshot(
-                    cinder, volume_id, common.get_unique_name(), force=True)
-                snapshots[volume_id] = snapshot
-
-            self._event_manager.progress_update(
-                "Waiting for volume snapshots to complete")
-            for snapshot in snapshots.values():
-                common.wait_for_volume_snapshot(cinder, snapshot.id)
-
-            self._event_manager.progress_update("Creating volume backups")
-            for volume_info in volumes_info:
-                volume_id = volume_info["disk_id"]
-
-                volume_backups = common.find_volume_backups(
-                    cinder, volume_id, container)
-                incremental = len(volume_backups) > 0
-
-                snapshot = snapshots[volume_id]
-                # TODO: add Cinder v1 check ('incremental' and 'force' are v2+)
-                volume_backup = common.create_volume_backup(
-                    cinder,
-                    volume_id=volume_id,
-                    snapshot_id=snapshot.id,
-                    container=container,
-                    name=common.get_unique_name(),
-                    incremental=incremental,
-                    # NOTE: 'force' is required by standard API definition for
-                    # any incremental backup while the VM is on, but some
-                    # Cinder backends will allow its absence.
-                    force=True
-                )
-                backups[volume_id] = volume_backup
-
-            self._event_manager.progress_update(
-                "Waiting for volume backups to complete")
-            for backup in backups.values():
-                common.wait_for_volume_backup(cinder, backup.id)
-
-            return volumes_info, backups
-        except Exception as ex:
-            LOG.exception(ex)
-            self._event_manager.progress_update(
-                "Deleting volume backups")
-            for backup in backups.values():
-                common.delete_volume_backup(cinder, backup.id)
-            self._event_manager.progress_update(
-                "Waiting for volume backups to be deleted")
-            for backup in backups.values():
-                common.wait_for_volume_backup(cinder, backup.id, 'deleted')
-            raise
-        finally:
-            self._event_manager.progress_update("Deleting volume snapshots")
-            for snapshot in snapshots.values():
-                common.delete_volume_snapshot(cinder, snapshot.id)
-            self._event_manager.progress_update(
-                "Waiting for volume snapshots to be deleted")
-            for snapshot in snapshots.values():
-                common.wait_for_volume_snapshot(cinder, snapshot.id, 'deleted')
-
-    def shutdown_instance(self, ctxt, connection_info, instance_name):
-        session = keystone.create_keystone_session(ctxt, connection_info)
-        nova = nova_client.Client(common.NOVA_API_VERSION, session=session)
-        instance = self._get_instance(nova, instance_name)
-        self._check_shutdown_instance(nova, instance)

+ 0 - 1033
coriolis/providers/openstack/imp.py

@@ -1,1033 +0,0 @@
-# Copyright 2016 Cloudbase Solutions Srl
-# All Rights Reserved.
-
-import collections
-import math
-import os
-import tempfile
-
-from cinderclient import client as cinder_client
-from glanceclient import client as glance_client
-from neutronclient.neutron import client as neutron_client
-from novaclient import client as nova_client
-from oslo_config import cfg
-from oslo_log import log as logging
-from oslo_utils import units
-import paramiko
-
-from coriolis import constants
-from coriolis import events
-from coriolis import exception
-from coriolis import keystone
-from coriolis.osmorphing import manager as osmorphing_manager
-from coriolis.providers import base
-from coriolis.providers.openstack import common
-from coriolis import schemas
-from coriolis import utils
-
-opts = [
-    cfg.StrOpt('disk_format',
-               default=constants.DISK_FORMAT_QCOW2,
-               help='Default image disk format.'),
-    cfg.StrOpt('container_format',
-               default='bare',
-               help='Default image container format.'),
-    cfg.StrOpt('hypervisor_type',
-               default=None,
-               help='Default hypervisor type.'),
-    cfg.StrOpt('boot_from_volume',
-               default=True,
-               help='Set to "True" to boot from volume by default instead of '
-               'using local storage.'),
-    cfg.StrOpt('delete_disks_on_vm_termination',
-               default=False,
-               help='Configure Cinder volumes to be deleted on an eventual '
-                    'termination of the migrated/replicated instance.'),
-    cfg.StrOpt('glance_upload',
-               default=True,
-               help='Set to "True" to use Glance to upload images.'),
-    cfg.StrOpt('default_cinder_volume_type',
-               default='',
-               help='Name of the Cinder volume type to be used for '
-                    'volumes with unspecified storage backing options.'),
-    cfg.DictOpt('migr_image_name_map',
-                default={},
-                help='Default image names used for worker instances during '
-                'migrations.'),
-    cfg.StrOpt('migr_flavor_name',
-               default='m1.small',
-               help='Default flavor name used for worker instances '
-               'during migrations.'),
-    cfg.StrOpt('migr_network_name',
-               default='private',
-               help='Default network name used for worker instances '
-               'during migrations.'),
-    cfg.StrOpt('fip_pool_name',
-               default='public',
-               help='Default floating ip pool name.'),
-]
-
-CONF = cfg.CONF
-CONF.register_opts(opts, 'openstack_migration_provider')
-
-DISK_HEADER_SIZE = 10 * units.Mi
-
-SSH_PORT = 22
-WINRM_HTTPS_PORT = 5986
-
-MIGR_USER_DATA = (
-    "#cloud-config\n"
-    "users:\n"
-    "  - name: %s\n"
-    "    ssh-authorized-keys:\n"
-    "      - %s\n"
-    "    sudo: ['ALL=(ALL) NOPASSWD:ALL']\n"
-    "    groups: sudo\n"
-    "    shell: /bin/bash\n"
-)
-MIGR_GUEST_USERNAME = 'cloudbase'
-MIGR_GUEST_USERNAME_WINDOWS = "admin"
-
-VOLUME_NAME_FORMAT = "%(instance_name)s %(num)s"
-REPLICA_VOLUME_NAME_FORMAT = "Coriolis Replica - %(instance_name)s %(num)s"
-
-LOG = logging.getLogger(__name__)
-
-
-class _MigrationResources(object):
-    def __init__(self, nova, neutron, keypair, instance, port,
-                 floating_ip, guest_port, sec_group, username, password, k):
-        self._nova = nova
-        self._neutron = neutron
-        self._instance = instance
-        self._port = port
-        self._floating_ip = floating_ip
-        self._guest_port = guest_port
-        self._sec_group = sec_group
-        self._keypair = keypair
-        self._k = k
-        self._username = username
-        self._password = password
-
-    def get_resources_dict(self):
-        return {
-            "instance_id": self._instance.id,
-            "keypair_name": self._keypair.name,
-            "port_id": self._port["id"],
-            "floating_ip_id": self._floating_ip.id,
-            "secgroup_id": self._sec_group.id,
-        }
-
-    @classmethod
-    @utils.retry_on_error()
-    def from_resources_dict(cls, nova, neutron, resources_dict):
-        instance_id = resources_dict["instance_id"]
-        keypair_name = resources_dict["keypair_name"]
-        floating_ip_id = resources_dict["floating_ip_id"]
-        secgroup_id = resources_dict["secgroup_id"]
-        port_id = resources_dict["port_id"]
-
-        instance = None
-        instances = nova.servers.findall(id=instance_id)
-        if instances:
-            instance = instances[0]
-
-        keypair = None
-        keypairs = nova.keypairs.findall(name=keypair_name)
-        if keypairs:
-            keypair = keypairs[0]
-
-        floating_ip = None
-        floating_ips = nova.floating_ips.findall(id=floating_ip_id)
-        if floating_ips:
-            floating_ip = floating_ips[0]
-
-        sec_group = None
-        sec_groups = nova.security_groups.findall(id=secgroup_id)
-        if sec_groups:
-            sec_group = sec_groups[0]
-
-        port = None
-        ports = neutron.list_ports(id=port_id)["ports"]
-        if ports:
-            port = ports[0]
-
-        return cls(
-            nova, neutron, keypair, instance, port, floating_ip, None,
-            sec_group, None, None, None)
-
-    def get_guest_connection_info(self):
-        return {
-            "ip": self._floating_ip.ip,
-            "port": self._guest_port,
-            "username": self._username,
-            "password": self._password,
-            "pkey": self._k,
-        }
-
-    def get_instance(self):
-        return self._instance
-
-    @utils.retry_on_error()
-    def delete(self):
-        if self._instance:
-            self._nova.servers.delete(self._instance)
-            common.wait_for_instance_deletion(self._nova, self._instance.id)
-            self._instance = None
-        if self._floating_ip:
-            self._nova.floating_ips.delete(self._floating_ip)
-            self._floating_ip = None
-        if self._port:
-            self._neutron.delete_port(self._port['id'])
-            self._port = None
-        if self._sec_group:
-            self._nova.security_groups.delete(self._sec_group.id)
-            self._sec_group = None
-        if self._keypair:
-            self._nova.keypairs.delete(self._keypair.name)
-            self._keypair = None
-
-
-class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
-
-    platform = constants.PLATFORM_OPENSTACK
-
-    connection_info_schema = schemas.get_schema(
-        __name__, schemas.PROVIDER_CONNECTION_INFO_SCHEMA_NAME)
-
-    target_environment_schema = schemas.get_schema(
-        __name__, schemas.PROVIDER_TARGET_ENVIRONMENT_SCHEMA_NAME)
-
-    def __init__(self, event_handler):
-        self._event_manager = events.EventManager(event_handler)
-
-    @utils.retry_on_error(terminal_exceptions=[exception.NotFound])
-    def _create_neutron_port(self, neutron, network_name, mac_address=None):
-        networks = neutron.list_networks(name=network_name)
-        if not networks['networks']:
-            raise exception.NetworkNotFound(network_name=network_name)
-        network_id = networks['networks'][0]['id']
-
-        # make sure that the port is not already existing from a previous
-        # migration attempt
-        if mac_address:
-            ports = neutron.list_ports(
-                mac_address=mac_address).get('ports', [])
-            if ports:
-                neutron.delete_port(ports[0]['id'])
-
-        body = {"port": {
-                "network_id": network_id,
-                }}
-        if mac_address:
-            body["port"]["mac_address"] = mac_address
-
-        return neutron.create_port(body=body)['port']
-
-    @utils.retry_on_error()
-    def _create_keypair(self, nova, name, public_key):
-        if nova.keypairs.findall(name=name):
-            nova.keypairs.delete(name)
-        return nova.keypairs.create(name=name, public_key=public_key)
-
-    @utils.retry_on_error(max_attempts=10, sleep_seconds=10)
-    def _get_instance_password(self, instance, k):
-        self._event_manager.progress_update("Getting instance password")
-        fd, key_path = tempfile.mkstemp()
-        try:
-            k.write_private_key_file(key_path)
-            return instance.get_password(private_key=key_path).decode()
-        finally:
-            os.close(fd)
-            os.remove(key_path)
-
-    @utils.retry_on_error(max_attempts=10, sleep_seconds=30,
-                          terminal_exceptions=[exception.NotFound])
-    def _deploy_migration_resources(self, nova, glance, neutron,
-                                    os_type, migr_image_name, migr_flavor_name,
-                                    migr_network_name, migr_fip_pool_name):
-        LOG.debug("Migration image name: %s", migr_image_name)
-        image = common.get_image(glance, migr_image_name)
-
-        LOG.debug("Migration flavor name: %s", migr_flavor_name)
-        flavor = common.get_flavor(nova, migr_flavor_name)
-
-        LOG.debug("Migration FIP pool name: %s", migr_fip_pool_name)
-        common.check_floating_ip_pool(nova, migr_fip_pool_name)
-
-        keypair = None
-        instance = None
-        floating_ip = None
-        sec_group = None
-        port = None
-
-        try:
-            migr_keypair_name = common.get_unique_name()
-
-            self._event_manager.progress_update(
-                "Creating migration worker instance keypair")
-
-            k = paramiko.RSAKey.generate(2048)
-            public_key = "ssh-rsa %s tmp@migration" % k.get_base64()
-            keypair = self._create_keypair(nova, migr_keypair_name, public_key)
-
-            self._event_manager.progress_update(
-                "Creating migration worker instance Neutron port")
-
-            port = self._create_neutron_port(neutron, migr_network_name)
-
-            # TODO(alexpilotti): use a single username
-            if os_type == constants.OS_TYPE_WINDOWS:
-                username = MIGR_GUEST_USERNAME_WINDOWS
-            else:
-                username = MIGR_GUEST_USERNAME
-
-            userdata = MIGR_USER_DATA % (username, public_key)
-            instance = nova.servers.create(
-                name=common.get_unique_name(),
-                image=image.id,
-                flavor=flavor,
-                key_name=migr_keypair_name,
-                userdata=userdata,
-                nics=[{'port-id': port['id']}])
-
-            self._event_manager.progress_update(
-                "Adding migration worker instance floating IP")
-
-            floating_ip = nova.floating_ips.create(pool=migr_fip_pool_name)
-            common.wait_for_instance(nova, instance.id, 'ACTIVE')
-
-            LOG.info("Floating IP: %s", floating_ip.ip)
-            instance.add_floating_ip(floating_ip)
-
-            self._event_manager.progress_update(
-                "Adding migration worker instance security group")
-
-            if os_type == constants.OS_TYPE_WINDOWS:
-                guest_port = WINRM_HTTPS_PORT
-            else:
-                guest_port = SSH_PORT
-
-            migr_sec_group_name = common.get_unique_name()
-            sec_group = nova.security_groups.create(
-                name=migr_sec_group_name, description=migr_sec_group_name)
-            nova.security_group_rules.create(
-                sec_group.id,
-                ip_protocol="tcp",
-                from_port=guest_port,
-                to_port=guest_port)
-            instance.add_security_group(sec_group.id)
-
-            self._event_manager.progress_update(
-                "Waiting for connectivity on host: %(ip)s:%(port)s" %
-                {"ip": floating_ip.ip, "port": guest_port})
-
-            utils.wait_for_port_connectivity(floating_ip.ip, guest_port)
-
-            if os_type == constants.OS_TYPE_WINDOWS:
-                password = self._get_instance_password(instance, k)
-            else:
-                password = None
-
-            return _MigrationResources(nova, neutron, keypair, instance, port,
-                                       floating_ip, guest_port, sec_group,
-                                       username, password, k)
-        except Exception as ex:
-            self._event_manager.progress_update(
-                "An error occurred, cleaning up worker resources: %s" %
-                str(ex))
-
-            if instance:
-                nova.servers.delete(instance)
-            if floating_ip:
-                nova.floating_ips.delete(floating_ip)
-            if port:
-                neutron.delete_port(port['id'])
-            if sec_group:
-                nova.security_groups.delete(sec_group.id)
-            if keypair:
-                nova.keypairs.delete(keypair.name)
-            raise
-
-    @utils.retry_on_error()
-    def _attach_volume(self, nova, cinder, instance, volume_id,
-                       volume_dev=None):
-        # volume can be either a Volume object or an id
-        volume = nova.volumes.create_server_volume(
-            instance.id, volume_id, volume_dev)
-        common.wait_for_volume(cinder, volume.id, 'in-use')
-        return volume
-
-    def _get_import_config(self, target_environment, os_type):
-        config = collections.namedtuple(
-            "ImportConfig",
-            ["glance_upload",
-             "target_disk_format",
-             "container_format",
-             "hypervisor_type",
-             "delete_disks_on_vm_termination",
-             "fip_pool_name",
-             "network_map",
-             "storage_map",
-             "keypair_name",
-             "migr_image_name",
-             "migr_flavor_name",
-             "migr_fip_pool_name",
-             "migr_network_name",
-             "flavor_name"])
-
-        config.glance_upload = target_environment.get(
-            "glance_upload", CONF.openstack_migration_provider.glance_upload)
-        config.target_disk_format = target_environment.get(
-            "disk_format", CONF.openstack_migration_provider.disk_format)
-        config.container_format = target_environment.get(
-            "container_format",
-            CONF.openstack_migration_provider.container_format)
-        config.hypervisor_type = target_environment.get(
-            "hypervisor_type",
-            CONF.openstack_migration_provider.hypervisor_type)
-        config.fip_pool_name = target_environment.get(
-            "fip_pool_name", CONF.openstack_migration_provider.fip_pool_name)
-        config.delete_disks_on_vm_termination = target_environment.get(
-            "delete_disks_on_vm_termination",
-            CONF.openstack_migration_provider.delete_disks_on_vm_termination)
-        config.network_map = target_environment.get("network_map", {})
-        config.storage_map = target_environment.get("storage_map", {})
-        config.keypair_name = target_environment.get("keypair_name")
-
-        config.migr_image_name = target_environment.get(
-            "migr_image_name",
-            target_environment.get("migr_image_name_map", {}).get(
-                os_type,
-                CONF.openstack_migration_provider.migr_image_name_map.get(
-                    os_type)))
-        config.migr_flavor_name = target_environment.get(
-            "migr_flavor_name",
-            CONF.openstack_migration_provider.migr_flavor_name)
-
-        config.migr_fip_pool_name = target_environment.get(
-            "migr_fip_pool_name",
-            config.fip_pool_name or
-            CONF.openstack_migration_provider.fip_pool_name)
-        config.migr_network_name = target_environment.get(
-            "migr_network_name",
-            CONF.openstack_migration_provider.migr_network_name)
-
-        config.flavor_name = target_environment.get(
-            "flavor_name", config.migr_flavor_name)
-
-        if not config.migr_image_name:
-            raise exception.CoriolisException(
-                "No matching migration image type found")
-
-        if not config.migr_network_name:
-            if len(config.network_map) != 1:
-                raise exception.CoriolisException(
-                    'If "migr_network_name" is not provided, "network_map" '
-                    'must contain exactly one entry')
-            config.migr_network_name = list(config.network_map.values())[0]
-
-        return config
-
-    @utils.retry_on_error()
-    def _convert_disk_format(
-            self, source_path, destination_path, destination_format):
-        """ Converts the  disk to the provided destination_format and
-        returns the new path to the converted disk. """
-        try:
-            LOG.info("Converting disk '%s' to %s" % (
-                     source_path,
-                     destination_format))
-            utils.convert_disk_format(
-                source_path, destination_path, destination_format)
-        except Exception as ex:
-            utils.ignore_exceptions(os.remove)(destination_path)
-            raise
-
-    def _create_images_and_volumes(self, glance, nova, cinder, config,
-                                   disks_info):
-        if not config.glance_upload:
-            raise exception.CoriolisException(
-                "Glance upload is currently required for migrations")
-
-        images = []
-        volumes = []
-
-        for disk_info in disks_info:
-            disk_path = disk_info["path"]
-            disk_file_info = utils.get_disk_info(disk_path)
-
-            target_disk_path = disk_path
-            if config.target_disk_format != disk_file_info["format"]:
-                target_disk_path = ("%s.%s" % (
-                    os.path.splitext(disk_path)[0], config.target_disk_format))
-                self._convert_disk_format(
-                    disk_path, target_disk_path, config.target_disk_format)
-                disk_file_info = utils.get_disk_info(target_disk_path)
-
-                LOG.info(
-                    "Succesfully converted '%s' to %s as '%s'. "
-                    "Removing original path." % (
-                        disk_path, config.target_disk_format,
-                        target_disk_path))
-                os.remove(disk_path)
-
-            self._event_manager.progress_update(
-                "Uploading Glance image")
-
-            disk_format = disk_file_info["format"]
-            if disk_format == "vhdx":
-                disk_format = "vhd"
-
-            image = common.create_image(
-                glance, common.get_unique_name(),
-                target_disk_path, disk_format,
-                config.container_format,
-                config.hypervisor_type)
-            images.append(image)
-
-            self._event_manager.progress_update(
-                "Waiting for Glance image to become active")
-            common.wait_for_image(nova, image.id)
-
-            virtual_disk_size = disk_file_info["virtual-size"]
-            if disk_format != constants.DISK_FORMAT_RAW:
-                virtual_disk_size += DISK_HEADER_SIZE
-
-            self._event_manager.progress_update(
-                "Creating Cinder volume")
-
-            volume_type = self._get_volume_type_for_disk(
-                cinder, disk_info, config.storage_map)
-
-            volume = common.create_volume(
-                cinder, virtual_disk_size, common.get_unique_name(),
-                image.id, volume_type=volume_type)
-            volumes.append(volume)
-
-        return images, volumes
-
-    def _get_volume_type_for_disk(self, cinder, disk_info, storage_map):
-        default = CONF.openstack_migration_provider.default_cinder_volume_type
-        if not default:
-            # NOTE: needed so as to explicitly use None instead of an
-            # empty string in case a default is not configured.
-            default = None
-
-        dest_stor = None
-        source_stor = None
-        if 'storage_backend_identifier' in disk_info:
-            # if 'storage_backend_identifier' was provided, fetch its
-            # correspondent from the 'storage_map' or use the default.
-            source_stor = disk_info['storage_backend_identifier']
-            dest_stor = storage_map.get(source_stor, None)
-            if not dest_stor:
-                LOG.debug(
-                    'Unable to find mapping for storage system "%s" in the '
-                    'storage_map for volume "%s". Setting volume type to the '
-                    'configured default of \"%s\"',
-                    source_stor, disk_info['path'], default)
-                dest_stor = default
-        else:
-            # else if unspecified, just use the default volume type:
-            LOG.debug("No 'storage_backend_identifier' provided for disk %s. "
-                     "Trying to use default volume type of '%s'", default)
-            dest_stor = default
-
-        # ensure the volume type exists:
-        if dest_stor and not utils.retry_on_error()(
-                cinder.volume_types.findall)(name=dest_stor):
-            raise exception.CoriolisException(
-                'Unable to find volume type "%s" (mapped from "%s" on the '
-                'source). Please ensure the storage_map is correct' % (
-                    dest_stor, source_stor))
-
-        LOG.info("Chosen volume_type for disk '%s' is '%s'",
-                 disk_info['path'], dest_stor)
-        return dest_stor
-
-    def _create_neutron_ports(self, neutron, config, nics_info):
-        ports = []
-
-        for nic_info in nics_info:
-            origin_network_name = nic_info.get("network_name")
-            if not origin_network_name:
-                self._event_manager.progress_update(
-                    "Origin network name not provided for nic: %s, skipping" % 
-                    nic_info.get("name"))
-                continue
-
-            network_name = config.network_map.get(origin_network_name)
-            if not network_name:
-                raise exception.CoriolisException(
-                    "Network not mapped in network_map: %s" %
-                    origin_network_name)
-
-            ports.append(self._create_neutron_port(
-                neutron, network_name, nic_info.get("mac_address")))
-
-        return ports
-
-    @utils.retry_on_error()
-    def _get_replica_volumes(self, cinder, volumes_info):
-        volumes = []
-        for volume_id in [v["volume_id"] for v in volumes_info]:
-            volumes.append(cinder.volumes.get(volume_id))
-        return volumes
-
-    @utils.retry_on_error()
-    def _rename_volumes(self, cinder, volumes, instance_name):
-        for i, volume in enumerate(volumes):
-            new_volume_name = VOLUME_NAME_FORMAT % {
-                "instance_name": instance_name, "num": i + 1}
-            cinder.volumes.update(volume.id, name=new_volume_name)
-
-    @utils.retry_on_error()
-    def _set_bootable_volumes(self, cinder, volumes):
-        # TODO: check if just setting the first volume as bootable is enough
-        for volume in volumes:
-            if not volume.bootable or volume.bootable == 'false':
-                cinder.volumes.set_bootable(volume, True)
-
-    def _create_volumes_from_replica_snapshots(self, cinder, volumes_info):
-        volumes = []
-        for volume_info in volumes_info:
-            snapshot_id = volume_info["volume_snapshot_id"]
-            volume_name = common.get_unique_name()
-
-            self._event_manager.progress_update(
-                "Creating Cinder volume from snapshot")
-
-            volume = common.create_volume(
-                cinder, None, volume_name, snapshot_id=snapshot_id)
-            volumes.append(volume)
-        return volumes
-
-    def _deploy_instance(self, ctxt, connection_info, target_environment,
-                         instance_name, export_info, volumes_info=None,
-                         clone_disks=False):
-        session = keystone.create_keystone_session(ctxt, connection_info)
-
-        glance_api_version = connection_info.get("image_api_version",
-                                                 common.GLANCE_API_VERSION)
-
-        nova = nova_client.Client(common.NOVA_API_VERSION, session=session)
-        glance = glance_client.Client(glance_api_version, session=session)
-        neutron = neutron_client.Client(common.NEUTRON_API_VERSION,
-                                        session=session)
-        cinder = cinder_client.Client(common.CINDER_API_VERSION,
-                                      session=session)
-
-        os_type = export_info.get('os_type')
-        LOG.info("os_type: %s", os_type)
-
-        config = self._get_import_config(target_environment, os_type)
-
-        images = []
-        volumes = []
-        ports = []
-
-        try:
-            if not volumes_info:
-                # Migration
-                disks_info = export_info["devices"]["disks"]
-                images, volumes = self._create_images_and_volumes(
-                    glance, nova, cinder, config, disks_info)
-            else:
-                # Migration from replica
-                if not clone_disks:
-                    volumes = self._get_replica_volumes(cinder, volumes_info)
-                else:
-                    volumes = self._create_volumes_from_replica_snapshots(
-                        cinder, volumes_info)
-
-            migr_resources = self._deploy_migration_resources(
-                nova, glance, neutron, os_type, config.migr_image_name,
-                config.migr_flavor_name, config.migr_network_name,
-                config.migr_fip_pool_name)
-
-            nics_info = export_info["devices"].get("nics", [])
-
-            try:
-                for i, volume in enumerate(volumes):
-                    common.wait_for_volume(cinder, volume.id)
-
-                    self._event_manager.progress_update(
-                        "Attaching volume to worker instance")
-
-                    self._attach_volume(
-                        nova, cinder, migr_resources.get_instance(), volume.id)
-
-                    conn_info = migr_resources.get_guest_connection_info()
-
-                osmorphing_hv_type = self._get_osmorphing_hypervisor_type(
-                    config.hypervisor_type)
-
-                self._event_manager.progress_update(
-                    "Preparing instance for target platform")
-                osmorphing_manager.morph_image(conn_info,
-                                               os_type,
-                                               osmorphing_hv_type,
-                                               constants.PLATFORM_OPENSTACK,
-                                               nics_info,
-                                               self._event_manager)
-            finally:
-                self._event_manager.progress_update(
-                    "Removing worker instance resources")
-                migr_resources.delete()
-
-            self._event_manager.progress_update("Renaming volumes")
-            self._rename_volumes(cinder, volumes, instance_name)
-
-            self._event_manager.progress_update(
-                "Ensuring volumes are bootable")
-            self._set_bootable_volumes(cinder, volumes)
-
-            self._event_manager.progress_update(
-                "Creating Neutron ports for migrated instance")
-            ports = self._create_neutron_ports(neutron, config, nics_info)
-
-            self._event_manager.progress_update(
-                "Creating migrated instance")
-            self._create_target_instance(
-                nova, config.flavor_name, instance_name,
-                config.keypair_name, ports, volumes,
-                ephemeral_volumes=config.delete_disks_on_vm_termination)
-        except:
-            if not volumes_info or clone_disks:
-                # Don't remove replica volumes
-                self._event_manager.progress_update("Deleting volumes")
-                for volume in volumes:
-                    @utils.ignore_exceptions
-                    @utils.retry_on_error()
-                    def _del_volume():
-                        volume.delete()
-                    _del_volume()
-            self._event_manager.progress_update("Deleting Neutron ports")
-            for port in ports:
-                @utils.ignore_exceptions
-                @utils.retry_on_error()
-                def _del_port():
-                    neutron.delete_port(port["id"])
-                _del_port()
-            raise
-        finally:
-            self._event_manager.progress_update("Deleting Glance images")
-            for image in images:
-                @utils.ignore_exceptions
-                @utils.retry_on_error()
-                def _del_image():
-                    image.delete()
-                _del_image()
-
-    def import_instance(self, ctxt, connection_info, target_environment,
-                        instance_name, export_info):
-        self._deploy_instance(ctxt, connection_info, target_environment,
-                              instance_name, export_info)
-
-    def _get_osmorphing_hypervisor_type(self, hypervisor_type):
-        if (hypervisor_type and
-                hypervisor_type.lower() == constants.HYPERVISOR_QEMU):
-            return constants.HYPERVISOR_KVM
-        elif hypervisor_type:
-            return hypervisor_type.lower()
-
-    @utils.retry_on_error(max_attempts=10, sleep_seconds=30,
-                          terminal_exceptions=[exception.NotFound])
-    def _create_target_instance(self, nova, flavor_name, instance_name,
-                                keypair_name, ports, volumes,
-                                ephemeral_volumes=False):
-        flavor = common.get_flavor(nova, flavor_name)
-
-        block_device_mapping = {}
-        for i, volume in enumerate(volumes):
-            # Delete volume on termination
-            block_device_mapping[
-                'vd%s' % chr(ord('a') + i)] = "%s:volume::%s" % (
-                    volume.id, ephemeral_volumes)
-
-        nics = [{'port-id': p['id']} for p in ports]
-
-        # Note: Nova requires an image even when booting from volume
-        LOG.info('Creating target instance...')
-        instance = nova.servers.create(
-            name=instance_name,
-            image='',
-            flavor=flavor,
-            key_name=keypair_name,
-            block_device_mapping=block_device_mapping,
-            nics=nics)
-
-        try:
-            common.wait_for_instance(nova, instance.id, 'ACTIVE')
-            return instance
-        except:
-            if instance:
-                nova.servers.delete(instance)
-            raise
-
-    def deploy_replica_instance(self, ctxt, connection_info,
-                                target_environment, instance_name, export_info,
-                                volumes_info, clone_disks):
-        self._deploy_instance(ctxt, connection_info, target_environment,
-                              instance_name, export_info, volumes_info,
-                              clone_disks)
-
-    def _update_existing_disk_volumes(self, cinder, disks_info, volumes_info):
-        for disk_info in disks_info:
-            disk_id = disk_info["id"]
-
-            vi = [v for v in volumes_info
-                  if v["disk_id"] == disk_id and v.get("volume_id")]
-            if vi:
-                volume_info = vi[0]
-                volume_id = volume_info["volume_id"]
-
-                volume = common.find_volume(cinder, volume_id)
-                if volume:
-                    virtual_disk_size_gb = math.ceil(
-                        disk_info["size_bytes"] / units.Gi)
-
-                    if virtual_disk_size_gb > volume.size:
-                        LOG.info(
-                            "Extending volume %(volume_id)s. "
-                            "Current size: %(curr_size)s GB, "
-                            "Requested size: %(requested_size)s GB",
-                            {"volume_id": volume_id,
-                             "curr_size": virtual_disk_size_gb,
-                             "requested_size": volume.size})
-                        self._event_manager.progress_update("Extending volume")
-                        common.extend_volume(
-                            cinder, volume_id, virtual_disk_size_gb * units.Gi)
-                    elif virtual_disk_size_gb < volume.size:
-                        LOG.warning(
-                            "Cannot shrink volume %(volume_id)s. "
-                            "Current size: %(curr_size)s GB, "
-                            "Requested size: %(requested_size)s GB",
-                            {"volume_id": volume_id,
-                             "curr_size": volume.size,
-                             "requested_size": virtual_disk_size_gb})
-                else:
-                    volumes_info.remove(volume_info)
-
-        return volumes_info
-
-    def _delete_removed_disk_volumes(self, cinder, disks_info, volumes_info):
-        for volume_info in volumes_info:
-            if volume_info["disk_id"] not in [
-                    d["id"] for d in disks_info if d["id"]]:
-
-                volume_id = volume_info["volume_id"]
-                volume = common.find_volume(cinder, volume_id)
-                if volume:
-                    self._event_manager.progress_update("Deleting volume")
-                    common.delete_volume(cinder, volume_id)
-                volumes_info.remove(volume_info)
-        return volumes_info
-
-    def _create_new_disk_volumes(
-            self, cinder, target_environment, disks_info,
-            volumes_info, instance_name):
-        try:
-            new_volumes = []
-            for i, disk_info in enumerate(disks_info):
-                disk_id = disk_info["id"]
-                virtual_disk_size = disk_info["size_bytes"]
-
-                if not [v for v in volumes_info if v["disk_id"] == disk_id]:
-                    self._event_manager.progress_update(
-                        "Creating volume")
-
-                    volume_name = REPLICA_VOLUME_NAME_FORMAT % {
-                        "instance_name": instance_name, "num": i + 1}
-
-                    storage_map = target_environment.get(
-                        'storage_map', {})
-                    volume_type = self._get_volume_type_for_disk(
-                        cinder, disk_info, storage_map)
-                    volume = common.create_volume(
-                        cinder, virtual_disk_size, volume_name,
-                        volume_type=volume_type)
-
-                    new_volumes.append(volume)
-                    volumes_info.append({
-                        "volume_id": volume.id,
-                        "disk_id": disk_id})
-                else:
-                    self._event_manager.progress_update(
-                        "Using previously deployed volume")
-
-            for volume in new_volumes:
-                common.wait_for_volume(cinder, volume.id)
-
-            return volumes_info
-        except:
-            for volume in new_volumes:
-                common.delete_volume(cinder, volume.id)
-            raise
-
-    def deploy_replica_disks(self, ctxt, connection_info, target_environment,
-                             instance_name, export_info, volumes_info):
-        session = keystone.create_keystone_session(ctxt, connection_info)
-
-        cinder = cinder_client.Client(common.CINDER_API_VERSION,
-                                      session=session)
-
-        disks_info = export_info["devices"]["disks"]
-
-        volumes_info = self._update_existing_disk_volumes(
-            cinder, disks_info, volumes_info)
-
-        volumes_info = self._delete_removed_disk_volumes(
-            cinder, disks_info, volumes_info)
-
-        volumes_info = self._create_new_disk_volumes(
-            cinder, target_environment, disks_info,
-            volumes_info, instance_name)
-
-        return volumes_info
-
-    def deploy_replica_target_resources(self, ctxt, connection_info,
-                                        target_environment, volumes_info):
-        session = keystone.create_keystone_session(ctxt, connection_info)
-
-        glance_api_version = connection_info.get("image_api_version",
-                                                 common.GLANCE_API_VERSION)
-        nova = nova_client.Client(common.NOVA_API_VERSION, session=session)
-        glance = glance_client.Client(glance_api_version, session=session)
-        neutron = neutron_client.Client(common.NEUTRON_API_VERSION,
-                                        session=session)
-        cinder = cinder_client.Client(common.CINDER_API_VERSION,
-                                      session=session)
-
-        # Data migration uses a Linux guest binary
-        os_type = constants.OS_TYPE_LINUX
-
-        config = self._get_import_config(target_environment, os_type)
-
-        migr_resources = self._deploy_migration_resources(
-            nova, glance, neutron, os_type, config.migr_image_name,
-            config.migr_flavor_name, config.migr_network_name,
-            config.migr_fip_pool_name)
-
-        try:
-            for i, volume_info in enumerate(volumes_info):
-                self._event_manager.progress_update(
-                    "Attaching volume to worker instance")
-
-                volume_id = volume_info["volume_id"]
-                ret_volume = self._attach_volume(
-                    nova, cinder, migr_resources.get_instance(), volume_id)
-                volume_info["volume_dev"] = ret_volume.device
-
-            return {
-                "migr_resources": migr_resources.get_resources_dict(),
-                "volumes_info": volumes_info,
-                "connection_info": migr_resources.get_guest_connection_info(),
-            }
-        except:
-            self._event_manager.progress_update(
-                "Removing worker instance resources")
-            migr_resources.delete()
-            raise
-
-    def delete_replica_target_resources(self, ctxt, connection_info,
-                                        migr_resources_dict):
-        session = keystone.create_keystone_session(ctxt, connection_info)
-
-        nova = nova_client.Client(common.NOVA_API_VERSION, session=session)
-        neutron = neutron_client.Client(common.NEUTRON_API_VERSION,
-                                        session=session)
-
-        migr_resources = _MigrationResources.from_resources_dict(
-            nova, neutron, migr_resources_dict)
-        self._event_manager.progress_update(
-            "Removing worker instance resources")
-        migr_resources.delete()
-
-    def delete_replica_disks(self, ctxt, connection_info, volumes_info):
-        session = keystone.create_keystone_session(ctxt, connection_info)
-
-        cinder = cinder_client.Client(common.CINDER_API_VERSION,
-                                      session=session)
-
-        self._event_manager.progress_update(
-            "Removing replica disk volumes")
-        for volume_info in volumes_info:
-            common.delete_volume(cinder, volume_info["volume_id"])
-
-    def create_replica_disk_snapshots(self, ctxt, connection_info,
-                                      volumes_info):
-        session = keystone.create_keystone_session(ctxt, connection_info)
-
-        cinder = cinder_client.Client(common.CINDER_API_VERSION,
-                                      session=session)
-
-        snapshots = []
-        self._event_manager.progress_update(
-            "Creating replica disk snapshots")
-        for volume_info in volumes_info:
-            snapshot = common.create_volume_snapshot(
-                cinder, volume_info["volume_id"], common.get_unique_name())
-            snapshots.append(snapshot)
-            volume_info["volume_snapshot_id"] = snapshot.id
-
-        for snapshot in snapshots:
-            common.wait_for_volume_snapshot(cinder, snapshot.id)
-
-        return volumes_info
-
-    def delete_replica_disk_snapshots(self, ctxt, connection_info,
-                                      volumes_info):
-        session = keystone.create_keystone_session(ctxt, connection_info)
-
-        cinder = cinder_client.Client(common.CINDER_API_VERSION,
-                                      session=session)
-
-        self._event_manager.progress_update(
-            "Removing replica disk snapshots")
-        for volume_info in volumes_info:
-            snapshot_id = volume_info.get("volume_snapshot_id")
-            if snapshot_id:
-                common.delete_volume_snapshot(cinder, snapshot_id)
-                common.wait_for_volume_snapshot(cinder, snapshot_id, 'deleted')
-                volume_info["volume_snapshot_id"] = None
-
-    def restore_replica_disk_snapshots(self, ctxt, connection_info,
-                                       volumes_info):
-        session = keystone.create_keystone_session(ctxt, connection_info)
-
-        cinder = cinder_client.Client(common.CINDER_API_VERSION,
-                                      session=session)
-
-        self._event_manager.progress_update(
-            "Restoring replica disk snapshots")
-
-        new_volumes = []
-        try:
-            for volume_info in volumes_info:
-                snapshot_id = volume_info.get("volume_snapshot_id")
-                if snapshot_id:
-                    original_volume = common.get_volume_from_snapshot(
-                        cinder, snapshot_id)
-
-                    volume_name = original_volume.name
-                    volume = common.create_volume(
-                        cinder, None, volume_name, snapshot_id=snapshot_id)
-                    new_volumes.append((volume_info, snapshot_id, volume))
-
-            for volume_info, snapshot_id, volume in new_volumes:
-                old_volume_id = volume_info["volume_id"]
-                common.wait_for_volume(cinder, volume.id)
-                common.delete_volume_snapshot(cinder, snapshot_id)
-                common.wait_for_volume_snapshot(cinder, snapshot_id, 'deleted')
-                common.delete_volume(cinder, old_volume_id)
-
-                volume_info["volume_id"] = volume.id
-                volume_info["volume_snapshot_id"] = None
-        except:
-            for _, _, volume in new_volumes:
-                common.delete_volume(cinder, volume.id)
-            raise
-
-        return volumes_info

+ 0 - 64
coriolis/providers/openstack/schemas/connection_info_schema.json

@@ -1,64 +0,0 @@
-{
-  "$schema": "http://cloudbase.it/coriolis/schemas/openstack_connection#",
-  "oneOf": [
-    {
-      "type": "object",
-      "properties": {
-        "identity_api_version": {
-          "type": "integer",
-          "minimum": 2,
-          "maximum": 3
-        },
-        "username": {
-          "type": "string"
-        },
-        "password": {
-          "type": "string"
-        },
-        "project_name": {
-          "type": "string"
-        },
-        "user_domain_name": {
-          "type": "string"
-        },
-        "project_domain_name": {
-          "type": "string"
-        },
-        "auth_url": {
-          "type": "string"
-        },
-        "allow_untrusted": {
-          "type": "boolean",
-          "default": false
-        }
-      },
-      "required": [
-        "username",
-        "password",
-        "project_name",
-        "auth_url"
-      ],
-      "additionalProperties": false
-    },
-    {
-      "type": "object",
-      "properties": {
-        "secret_ref": {
-          "type": "string",
-          "format": "uri"
-        }
-      },
-      "required": ["secret_ref"],
-      "additionalProperties": false
-    },
-    {
-      "type": "object",
-      "properties": {
-      },
-      "additionalProperties": false
-    },
-    {
-      "type": "null"
-    }
-  ]
-}

+ 0 - 57
coriolis/providers/openstack/schemas/target_environment_schema.json

@@ -1,57 +0,0 @@
-{
-  "$schema": "http://cloudbase.it/coriolis/schemas/openstack_target_environment#",
-  "oneOf": [
-    {
-      "type": "object",
-      "properties": {
-        "network_map": {
-          "type": "object"
-        },
-        "storage_map": {
-          "type": "object"
-        },
-        "glance_upload": {
-          "type": "boolean"
-        },
-        "disk_format": {
-          "type": "string"
-        },
-        "delete_disks_on_vm_termination": {
-            "type": "boolean"
-        },
-        "container_format": {
-          "type": "string"
-        },
-        "hypervisor_type": {
-          "type": "string"
-        },
-        "migr_image_name": {
-          "type": "string"
-        },
-        "migr_image_name_map": {
-          "type": "object"
-        },
-        "migr_flavor_name": {
-          "type": "string"
-        },
-        "flavor_name": {
-          "type": "string"
-        },
-        "fip_pool_name": {
-          "type": "string"
-        },
-        "migr_fip_pool_name": {
-          "type": "string"
-        },
-        "keypair_name": {
-          "type": "string"
-        }
-      },
-      "required": [
-        "network_map",
-        "flavor_name"
-      ],
-      "additionalProperties": false
-    }
-  ]
-}

+ 0 - 647
coriolis/providers/vmware_vsphere/__init__.py

@@ -1,647 +0,0 @@
-# Copyright 2016 Cloudbase Solutions Srl
-# All Rights Reserved.
-
-import contextlib
-import gc
-import os
-import re
-import sys
-import threading
-import time
-from urllib import request
-import uuid
-
-import eventlet
-from oslo_config import cfg
-from oslo_log import log as logging
-from oslo_utils import units
-from pyVim import connect
-from pyVmomi import vim
-
-from coriolis import constants
-from coriolis import events
-from coriolis import exception
-from coriolis.providers import backup_writers
-from coriolis.providers import base
-from coriolis.providers.vmware_vsphere import guestid
-from coriolis import schemas
-from coriolis.providers.vmware_vsphere import vixdisklib
-from coriolis import utils
-
-vmware_vsphere_opts = [
-    cfg.StrOpt('vdiskmanager_path',
-               default='vmware-vdiskmanager',
-               help='The vmware-vdiskmanager path.'),
-]
-
-CONF = cfg.CONF
-CONF.register_opts(vmware_vsphere_opts, 'vmware_vsphere')
-
-LOG = logging.getLogger(__name__)
-
-vixdisklib.init()
-
-
-class ExportProvider(base.BaseExportProvider, base.BaseReplicaExportProvider):
-
-    platform = constants.PLATFORM_VMWARE_VSPHERE
-
-    connection_info_schema = schemas.get_schema(
-        __name__, schemas.PROVIDER_CONNECTION_INFO_SCHEMA_NAME)
-
-    def __init__(self, event_handler):
-        self._event_manager = events.EventManager(event_handler)
-
-    @utils.retry_on_error()
-    def _convert_disk_type(self, disk_path, target_disk_path, target_type=0):
-        utils.exec_process([CONF.vmware_vsphere.vdiskmanager_path, "-r",
-                            disk_path, "-t", str(target_type),
-                            target_disk_path])
-
-    def _wait_for_task(self, task):
-        while task.info.state not in [vim.TaskInfo.State.success,
-                                      vim.TaskInfo.State.error]:
-            time.sleep(.1)
-        if task.info.state == vim.TaskInfo.State.error:
-            raise exception.CoriolisException(task.info.error.msg)
-
-    @staticmethod
-    def _keep_alive_vmware_conn(si, exit_event):
-        try:
-            while True:
-                LOG.debug("VMware connection keep alive")
-                si.CurrentTime()
-                if exit_event.wait(60):
-                    return
-        finally:
-            LOG.debug("Exiting VMware connection keep alive thread")
-
-    @utils.retry_on_error()
-    @contextlib.contextmanager
-    def _connect(self, connection_info):
-        host = connection_info["host"]
-        port = connection_info.get("port", 443)
-        username = connection_info["username"]
-        password = connection_info["password"]
-        allow_untrusted = connection_info.get("allow_untrusted", False)
-
-        # pyVmomi locks otherwise
-        sys.modules['socket'] = eventlet.patcher.original('socket')
-        ssl = eventlet.patcher.original('ssl')
-
-        context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
-        if allow_untrusted:
-            context.verify_mode = ssl.CERT_NONE
-
-        LOG.info("Connecting to: %s:%s" % (host, port))
-        si = connect.SmartConnect(
-            host=host,
-            user=username,
-            pwd=password,
-            port=port,
-            sslContext=context)
-
-        thread = None
-        try:
-            thread_exit_event = threading.Event()
-            thread = threading.Thread(
-                target=self._keep_alive_vmware_conn,
-                args=(si, thread_exit_event))
-            thread.start()
-
-            yield context, si
-        finally:
-            connect.Disconnect(si)
-            if thread:
-                thread_exit_event.set()
-                thread.join()
-
-    def _wait_for_vm_status(self, vm, status, max_wait=120):
-        i = 0
-        while i < max_wait and vm.runtime.powerState != status:
-            time.sleep(1)
-            i += 1
-        return i < max_wait
-
-    def _get_vm(self, si, instance_path):
-        vm = None
-        container = si.content.rootFolder
-        path_items = [p.replace('\\/', '/') for p in
-                      re.split(r'(?<!\\)/', instance_path)]
-        if len(path_items) == 1:
-            if len(container.childEntity) > 1:
-                raise exception.InvalidInput(
-                    "There's more than one container in the VMWare root "
-                    "folder, please specify the full path for the VM, e.g. "
-                    "\"Datacenter1/VM1\"")
-            else:
-                container = container.childEntity[0].vmFolder
-
-        LOG.debug("VM path items: %s", path_items)
-        for i, path_item in enumerate(path_items):
-            l = [o for o in container.childEntity if o.name == path_item]
-            if not l:
-                raise exception.InstanceNotFound(instance_name=instance_path)
-            item = l[0]
-            if (i + 1 == len(path_items) and
-                    isinstance(item, vim.VirtualMachine)):
-                vm = item
-            elif isinstance(item, vim.Datacenter):
-                container = item.vmFolder
-            else:
-                container = item
-
-        if vm is None:
-            raise exception.InstanceNotFound(instance_name=instance_path)
-
-        return vm
-
-    @utils.retry_on_error()
-    def _shutdown_vm(self, vm):
-        if vm.runtime.powerState != vim.VirtualMachinePowerState.poweredOff:
-            power_off = True
-            if (vm.guest.toolsRunningStatus !=
-                    vim.vm.GuestInfo.ToolsRunningStatus.guestToolsNotRunning):
-                self._event_manager.progress_update("Shutting down guest OS")
-                vm.ShutdownGuest()
-                if self._wait_for_vm_status(
-                        vm, vim.VirtualMachinePowerState.poweredOff):
-                    power_off = False
-
-            if power_off:
-                self._event_manager.progress_update(
-                    "Powering off the virtual machine")
-                task = vm.PowerOff()
-                self._wait_for_task(task)
-
-    @utils.retry_on_error()
-    def _get_vm_info(self, si, instance_path):
-
-        LOG.info("Retrieving data for VM: %s" % instance_path)
-        vm = self._get_vm(si, instance_path)
-
-        firmware_type_map = {
-            vim.vm.GuestOsDescriptor.FirmwareType.bios:
-                constants.FIRMWARE_TYPE_BIOS,
-            vim.vm.GuestOsDescriptor.FirmwareType.efi:
-                constants.FIRMWARE_TYPE_EFI
-        }
-
-        vm_info = {
-            'num_cpu': vm.config.hardware.numCPU,
-            'num_cores_per_socket': vm.config.hardware.numCoresPerSocket,
-            'memory_mb': vm.config.hardware.memoryMB,
-            'firmware_type': firmware_type_map[vm.config.firmware],
-            'nested_virtualization': vm.config.nestedHVEnabled,
-            'dynamic_memory_enabled':
-                not vm.config.memoryReservationLockedToMax,
-            'name': vm.config.name,
-            'guest_id': vm.config.guestId,
-            'os_type': guestid.GUEST_ID_OS_TYPE_MAP.get(vm.config.guestId),
-            'id': vm._moId,
-        }
-
-        LOG.info("vm info: %s" % str(vm_info))
-
-        disk_ctrls = []
-        devices = [d for d in vm.config.hardware.device if
-                   isinstance(d, vim.vm.device.VirtualController)]
-        for device in devices:
-            ctrl_type = None
-            if isinstance(device, vim.vm.device.VirtualPCIController):
-                ctrl_type = "PCI"
-            elif isinstance(device, vim.vm.device.VirtualSIOController):
-                ctrl_type = "SIO"
-            elif isinstance(device, vim.vm.device.VirtualIDEController):
-                ctrl_type = "IDE"
-            elif isinstance(device, vim.vm.device.VirtualSATAController):
-                ctrl_type = "SATA"
-            elif isinstance(device, vim.vm.device.VirtualSCSIController):
-                ctrl_type = "SCSI"
-            else:
-                continue
-            disk_ctrls.append({'id': device.key, 'type': ctrl_type,
-                               'bus_number': device.busNumber})
-
-        disks = []
-        devices = [d for d in vm.config.hardware.device if
-                   isinstance(d, vim.vm.device.VirtualDisk)]
-        for device in devices:
-            size = None
-            if hasattr(device, 'capacityInBytes'):
-                # vSphere >= 5.5
-                size = device.capacityInBytes
-            elif hasattr(device, 'capacityInKB'):
-                # vSphere <= 5.5, though left for backwards compatibility
-                size = device.capacityInKB * units.Ki
-            else:
-                raise exception.CoriolisException(
-                    "Cannot determine size of disk "
-                    "%s" % device.backing.fileName)
-
-            disks.append({'size_bytes': size,
-                          'unit_number': device.unitNumber,
-                          'id': device.key,
-                          'controller_id': device.controllerKey,
-                          'storage_backend_identifier':
-                              device.backing.datastore.name,
-                          'path': device.backing.fileName,
-                          'format': constants.DISK_FORMAT_VMDK})
-
-        cdroms = []
-        devices = [d for d in vm.config.hardware.device if
-                   isinstance(d, vim.vm.device.VirtualCdrom)]
-        for device in devices:
-            cdroms.append({'unit_number': device.unitNumber, 'id': device.key,
-                           'controller_id': device.controllerKey})
-
-        floppies = []
-        devices = [d for d in vm.config.hardware.device if
-                   isinstance(d, vim.vm.device.VirtualFloppy)]
-        for device in devices:
-            floppies.append({'unit_number': device.unitNumber,
-                             'id': device.key,
-                             'controller_id': device.controllerKey})
-
-        nics = []
-        devices = [d for d in vm.config.hardware.device if
-                   isinstance(d, vim.vm.device.VirtualEthernetCard)]
-        for device in devices:
-            nics.append({'mac_address': device.macAddress, 'id': device.key,
-                         'name': device.deviceInfo.label,
-                         'network_name': device.backing.network.name})
-
-        serial_ports = []
-        devices = [d for d in vm.config.hardware.device if
-                   isinstance(d, vim.vm.device.VirtualSerialPort)]
-        for device in devices:
-            serial_ports.append({'id': device.key})
-
-        boot_order = []
-        for boot_device in vm.config.bootOptions.bootOrder:
-            if isinstance(boot_device, vim.vm.BootOptions.BootableDiskDevice):
-                boot_order.append({"type": "disk",
-                                   "id": boot_device.deviceKey})
-            elif isinstance(boot_device,
-                            vim.vm.BootOptions.BootableCdromDevice):
-                boot_order.append({"type": "cdrom", "id": None})
-            elif isinstance(boot_device,
-                            vim.vm.BootOptions.BootableEthernetDevice):
-                boot_order.append({"type": "ethernet",
-                                   "id": boot_device.deviceKey})
-            elif isinstance(boot_device,
-                            vim.vm.BootOptions.BootableFloppyDevice):
-                boot_order.append({"type": "floppy", "id": None})
-
-        vm_info["devices"] = {
-            "nics": nics,
-            "controllers": disk_ctrls,
-            "disks": disks,
-            "cdroms": cdroms,
-            "floppies": floppies,
-            "serial_ports": serial_ports
-        }
-        vm_info["boot_order"] = boot_order
-
-        return vm_info, vm
-
-    @utils.retry_on_error()
-    def _export_disks(self, vm, export_path, context):
-        disk_paths = []
-
-        self._shutdown_vm(vm)
-        lease = vm.ExportVm()
-        while True:
-            if lease.state == vim.HttpNfcLease.State.ready:
-                try:
-                    tot_downloaded_bytes = 0
-                    for du in [du for du in lease.info.deviceUrl if du.disk]:
-                        # Key format: '/vm-70/VirtualLsiLogicController0:0'
-                        ctrl_str, address = du.key[
-                            du.key.rindex('/') + 1:].split(':')
-
-                        def _get_class_name(obj):
-                            return obj.__class__.__name__.split('.')[-1]
-
-                        for i, ctrl in enumerate(
-                            [d for d in vm.config.hardware.device if
-                             isinstance(
-                                d, vim.vm.device.VirtualController) and
-                                ctrl_str.startswith(_get_class_name(d))]):
-                            if int(ctrl_str[len(_get_class_name(ctrl)):]) == i:
-                                disk_key = [
-                                    d for d in vm.config.hardware.device if
-                                    isinstance(
-                                        d, vim.vm.device.VirtualDisk) and
-                                    d.controllerKey == ctrl.key and
-                                    d.unitNumber == int(address)][0].key
-                                break
-
-                        response = request.urlopen(du.url, context=context)
-                        path = os.path.join(export_path, du.targetId)
-                        disk_paths.append({
-                            'path': path,
-                            'id': disk_key,
-                            'format': constants.DISK_FORMAT_VMDK})
-
-                        LOG.info("Downloading: %s" % path)
-                        with open(path, 'wb') as f:
-                            while True:
-                                chunk = response.read(1024 * 1024)
-                                if not chunk:
-                                    break
-                                tot_downloaded_bytes += len(chunk)
-                                f.write(chunk)
-                                lease.HttpNfcLeaseProgress(
-                                    int(tot_downloaded_bytes * 100 /
-                                        (lease.info.totalDiskCapacityInKB *
-                                         1024)))
-
-                    lease.HttpNfcLeaseComplete()
-                    return disk_paths
-                except:
-                    lease.HttpNfcLeaseAbort()
-                    raise
-            elif lease.state == vim.HttpNfcLease.State.error:
-                raise exception.CoriolisException(lease.error.msg)
-            else:
-                time.sleep(.1)
-
-    def _connect_vixdisklib(self, connection_info, context,
-                            vmx_spec, snapshot_ref):
-        host = connection_info["host"]
-        port = connection_info.get("port", 443)
-        username = connection_info["username"]
-        password = connection_info["password"]
-
-        thumbprint = utils.get_ssl_cert_thumbprint(context, host, port)
-
-        return vixdisklib.connect(
-            server_name=host,
-            port=port,
-            thumbprint=thumbprint,
-            username=username,
-            password=password,
-            vmx_spec=vmx_spec,
-            snapshot_ref=snapshot_ref)
-
-    def _take_vm_snapshot(self, vm, snapshot_name, memory=False, quiesce=True):
-        task = vm.CreateSnapshot_Task(
-            name=snapshot_name, memory=False, quiesce=True)
-        self._wait_for_task(task)
-        return task.info.result
-
-    def _remove_vm_snapshot(self, snapshot, remove_children=False):
-        self._wait_for_task(snapshot.RemoveSnapshot_Task(remove_children))
-
-    @contextlib.contextmanager
-    def _take_temp_vm_snapshot(self, vm, snapshot_name, memory=False,
-                               quiesce=True):
-        self._event_manager.progress_update("Creating snapshot")
-        snapshot = self._take_vm_snapshot(vm, snapshot_name, memory, quiesce)
-        try:
-            yield snapshot
-        finally:
-            self._event_manager.progress_update("Removing snapshot")
-            self._remove_vm_snapshot(snapshot)
-
-    @utils.retry_on_error()
-    def _backup_snapshot_disks(self, snapshot, export_path, connection_info,
-                               context, disk_paths, backup_writer,
-                               incremental):
-        vm = snapshot.vm
-        vmx_spec = "moref=%s" % vm._GetMoId()
-        snapshot_ref = snapshot._GetMoId()
-        pos = 0
-        sector_size = vixdisklib.VIXDISKLIB_SECTOR_SIZE
-        max_sectors_per_read = 10 * 2048  # 10 MB
-
-        with self._connect_vixdisklib(connection_info, context,
-                                      vmx_spec, snapshot_ref) as conn:
-            for disk in [d for d in snapshot.config.hardware.device
-                         if isinstance(d, vim.vm.device.VirtualDisk)]:
-                change_id = '*'
-
-                l = [d for d in disk_paths if d['id'] == disk.key]
-                if l:
-                    disk_path = l[0]
-                    if incremental:
-                        change_id = disk_path.get("change_id", "*")
-                    path = disk_path["path"]
-                else:
-                    path = os.path.join(export_path, "disk-%s.raw" % disk.key)
-                    disk_path = {
-                        'path': path,
-                        'id': disk.key,
-                        'format': constants.DISK_FORMAT_RAW}
-                    disk_paths.append(disk_path)
-
-                LOG.info("CBT change id: %s", change_id)
-                changed_disk_areas = vm.QueryChangedDiskAreas(
-                    snapshot, disk.key, pos, change_id)
-
-                backup_disk_path = disk.backing.fileName
-
-                disk_size = changed_disk_areas.length
-                changed_area_size = sum(
-                    [x.length for x in changed_disk_areas.changedArea])
-
-                if not changed_area_size:
-                    self._event_manager.progress_update(
-                        "No blocks to replicate for disk: %s" %
-                        backup_disk_path)
-                    continue
-
-                if change_id == '*':
-                    self._event_manager.progress_update(
-                        "Performing full CBT replica for disk: {path}. "
-                        "Disk size: {disk_size:,}. Written blocks size: "
-                        "{changed_area_size:,}".format(
-                            path=backup_disk_path,
-                            disk_size=disk_size,
-                            changed_area_size=changed_area_size))
-                else:
-                    self._event_manager.progress_update(
-                        "Performing incremental CBT replica for disk: {path}. "
-                        "Disk size: {disk_size:,}. Changed blocks size: "
-                        "{changed_area_size:,}".format(
-                            path=backup_disk_path,
-                            disk_size=disk_size,
-                            changed_area_size=changed_area_size))
-
-                with vixdisklib.open(
-                        conn, backup_disk_path) as disk_handle:
-
-                    with backup_writer.open(path, disk.key) as f:
-                        # Create a sparse file
-                        f.truncate(disk.capacityInBytes)
-
-                        total_written_bytes = 0
-                        perc_step = self._event_manager.add_percentage_step(
-                            changed_area_size,
-                            message_format="Disk %s replica progress: "
-                            "{:.0f}%%" % backup_disk_path)
-
-                        for area in changed_disk_areas.changedArea:
-                            start_sector = area.start // sector_size
-                            num_sectors = area.length // sector_size
-
-                            f.seek(area.start)
-
-                            i = 0
-                            while i < num_sectors:
-                                curr_num_sectors = min(
-                                    num_sectors - i, max_sectors_per_read)
-
-                                buf_size = curr_num_sectors * sector_size
-                                buf = vixdisklib.get_buffer(buf_size)
-
-                                LOG.debug(
-                                    "Read start sector: %s, num sectors: %s" %
-                                    (start_sector + i, curr_num_sectors))
-
-                                vixdisklib.read(
-                                    disk_handle, start_sector + i,
-                                    curr_num_sectors, buf)
-                                i += curr_num_sectors
-
-                                f.write(buf.raw)
-
-                                buf = None
-                                gc.collect()
-
-                                total_written_bytes += buf_size
-                                self._event_manager.set_percentage_step(
-                                    perc_step, total_written_bytes)
-
-                disk_path['change_id'] = disk.backing.changeId
-
-    def _backup_disks(self, vm, export_path, connection_info, context):
-        if not vm.config.changeTrackingEnabled:
-            raise exception.CoriolisException("Change Tracking not enabled")
-
-        disk_paths = []
-
-        LOG.info("First backup pass")
-        snapshot_name = str(uuid.uuid4())
-        with self._take_temp_vm_snapshot(vm, snapshot_name) as snapshot:
-            self._backup_snapshot_disks(
-                snapshot, export_path, connection_info, context,
-                disk_paths, backup_writers.FileBackupWriter(),
-                incremental=False)
-
-        self._shutdown_vm(vm)
-
-        LOG.info("Second backup pass")
-        snapshot_name = str(uuid.uuid4())
-        with self._take_temp_vm_snapshot(vm, snapshot_name) as snapshot:
-            self._backup_snapshot_disks(
-                snapshot, export_path, connection_info, context,
-                disk_paths, backup_writers.FileBackupWriter(),
-                incremental=True)
-
-        return disk_paths
-
-    def export_instance(self, ctxt, connection_info, instance_name,
-                        export_path):
-        self._event_manager.progress_update("Connecting to vSphere host")
-        with self._connect(connection_info) as (context, si):
-            self._event_manager.progress_update(
-                "Retrieving virtual machine data")
-            vm_info, vm = self._get_vm_info(si, instance_name)
-
-            # Take advantage of CBT if available
-            backup_disks = vm.config.changeTrackingEnabled
-
-            self._event_manager.progress_update("Exporting disks")
-            if backup_disks:
-                disk_paths = self._backup_disks(
-                    vm, export_path, connection_info, context)
-            else:
-                disk_paths = self._export_disks(vm, export_path, context)
-
-        if not backup_disks:
-            self._event_manager.progress_update(
-                "Converting virtual disks format")
-            for disk_path in disk_paths:
-                path = disk_path["path"]
-                LOG.info("Converting VMDK type: %s" % path)
-                tmp_path = "%s.tmp" % path
-                self._convert_disk_type(path, tmp_path)
-                os.remove(path)
-                os.rename(tmp_path, path)
-
-        disks = vm_info["devices"]["disks"]
-
-        for disk_path in disk_paths:
-            disk_info = [d for d in disks if d["id"] == disk_path["id"]][0]
-            disk_info["format"] = disk_path["format"]
-            disk_info["path"] = os.path.abspath(disk_path["path"])
-
-        return vm_info
-
-    def get_replica_instance_info(self, ctxt, connection_info, instance_name):
-        self._event_manager.progress_update("Connecting to vSphere host")
-        with self._connect(connection_info) as (context, si):
-            self._event_manager.progress_update(
-                "Retrieving virtual machine data")
-            vm_info, vm = self._get_vm_info(si, instance_name)
-
-            if not vm.config.changeTrackingEnabled:
-                raise exception.CoriolisException(
-                    "Changed Block Tracking must be enabled in order to "
-                    "replicate a VM")
-
-        return vm_info
-
-    def shutdown_instance(self, ctxt, connection_info, instance_name):
-        self._event_manager.progress_update("Connecting to vSphere host")
-        with self._connect(connection_info) as (context, si):
-            vm = self._get_vm(si, instance_name)
-            self._shutdown_vm(vm)
-
-    def replicate_disks(self, ctxt, connection_info, instance_name,
-                        source_conn_info, target_conn_info, volumes_info,
-                        incremental):
-        ip = target_conn_info["ip"]
-        port = target_conn_info.get("port", 22)
-        username = target_conn_info["username"]
-        pkey = target_conn_info.get("pkey")
-        password = target_conn_info.get("password")
-
-        LOG.info("Waiting for connectivity on host: %(ip)s:%(port)s",
-                 {"ip": ip, "port": port})
-        utils.wait_for_port_connectivity(ip, port)
-
-        with self._connect(connection_info) as (context, si):
-            vm = self._get_vm(si, instance_name)
-
-            backup_writer = backup_writers.SSHBackupWriter(
-                ip, port, username, pkey, password, volumes_info)
-
-            snapshot_name = str(uuid.uuid4())
-            disk_paths = []
-            for volume_info in volumes_info:
-                disk_paths.append(
-                    {"id": volume_info["disk_id"],
-                     "change_id": volume_info.get("change_id", "*"),
-                     "path": ""})
-
-            with self._take_temp_vm_snapshot(vm, snapshot_name) as snapshot:
-                self._backup_snapshot_disks(
-                    snapshot, "", connection_info, context,
-                    disk_paths, backup_writer, incremental)
-
-            for volume_info in volumes_info:
-                change_id = [d["change_id"] for d in disk_paths
-                             if d["id"] == volume_info["disk_id"]][0]
-                volume_info["change_id"] = change_id
-
-        return volumes_info
-
-    def deploy_replica_source_resources(self, ctxt, connection_info):
-        return {"migr_resources": None, "connection_info": None}
-
-    def delete_replica_source_resources(self, ctxt, connection_info,
-                                        migr_resources_dict):
-        pass

+ 0 - 250
coriolis/providers/vmware_vsphere/guestid.py

@@ -1,250 +0,0 @@
-# Copyright 2016 Cloudbase Solutions Srl
-# All Rights Reserved.
-
-from pyVmomi import vim
-
-from coriolis import constants
-
-
-GUEST_ID_OS_TYPE_MAP = {
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.winNetStandard64Guest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.win95Guest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.centosGuest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.other24xLinux64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.fedoraGuest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.asianux3Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.slesGuest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.darwin11Guest:
-    constants.OS_TYPE_OS_X,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.darwin13_64Guest:
-    constants.OS_TYPE_OS_X,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.win31Guest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.rhel3Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.openServer6Guest:
-    None,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.winVista64Guest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.windows7Server64Guest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.solaris6Guest:
-    constants.OS_TYPE_SOLARIS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.rhel7Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.solaris7Guest:
-    constants.OS_TYPE_SOLARIS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.debian6_64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.debian7_64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.winLonghorn64Guest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.darwin11_64Guest:
-    constants.OS_TYPE_OS_X,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.mandrakeGuest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.winVistaGuest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.windows7Guest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.sles10_64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.windows7_64Guest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.solaris10_64Guest:
-    constants.OS_TYPE_SOLARIS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.winNTGuest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.sles12_64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.winNetDatacenter64Guest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.win98Guest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.winNetWebGuest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.genericLinuxGuest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.darwin10_64Guest:
-    constants.OS_TYPE_OS_X,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.netware6Guest:
-    None,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.debian7Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.freebsd64Guest:
-    constants.OS_TYPE_BSD,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.solaris10Guest:
-    constants.OS_TYPE_SOLARIS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.other24xLinuxGuest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.windows8Server64Guest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.fedora64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.debian6Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.win2000AdvServGuest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.nld9Guest:
-    None,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.asianux4_64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.rhel7_64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.darwin12_64Guest:
-    constants.OS_TYPE_OS_X,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.sles10Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.darwinGuest:
-    constants.OS_TYPE_OS_X,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.solaris8Guest:
-    constants.OS_TYPE_SOLARIS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.win2000ProGuest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.winMeGuest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.eComStation2Guest:
-    None,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.winNetStandardGuest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.mandrivaGuest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.opensuse64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.suseGuest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.windows8_64Guest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.windowsHyperVGuest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.rhel2Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.rhel5Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.other3xLinuxGuest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.netware4Guest:
-    None,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.other26xLinuxGuest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.opensuseGuest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.winXPHomeGuest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.openServer5Guest:
-    None,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.winLonghornGuest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.sles11_64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.rhel4Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.rhel6_64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.debian5_64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.sles12Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.solaris11_64Guest:
-    constants.OS_TYPE_SOLARIS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.oesGuest:
-    None,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.turboLinux64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.centos64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.oracleLinuxGuest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.oracleLinux64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.os2Guest:
-    None,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.debian4Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.otherGuest:
-    None,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.sles11Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.windows8Guest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.netware5Guest:
-    None,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.other3xLinux64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.mandriva64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.other26xLinux64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.ubuntuGuest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.winNetDatacenterGuest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.otherGuest64:
-    None,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.suse64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.redhatGuest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.vmkernelGuest:
-    None,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.winXPPro64Guest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.unixWare7Guest:
-    None,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.otherLinux64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.turboLinuxGuest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.dosGuest:
-    None,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.otherLinuxGuest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.darwin10Guest:
-    constants.OS_TYPE_OS_X,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.debian5Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.win2000ServGuest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.rhel4_64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.rhel5_64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.debian4_64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.ubuntu64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.asianux4Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.winNetEnterprise64Guest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.darwin64Guest:
-    constants.OS_TYPE_OS_X,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.sjdsGuest:
-    None,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.rhel6Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.freebsdGuest:
-    constants.OS_TYPE_BSD,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.eComStationGuest:
-    None,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.sles64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.asianux3_64Guest:
-    constants.OS_TYPE_LINUX,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.winNetEnterpriseGuest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.winXPProGuest:
-    constants.OS_TYPE_WINDOWS,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.vmkernel5Guest:
-    None,
-    vim.vm.GuestOsDescriptor.GuestOsIdentifier.solaris9Guest:
-    constants.OS_TYPE_SOLARIS,
-}

+ 0 - 42
coriolis/providers/vmware_vsphere/schemas/connection_info_schema.json

@@ -1,42 +0,0 @@
-{
-  "$schema": "http://cloudbase.it/coriolis/schemas/vmware_vsphere_connection#",
-  "oneOf": [
-    {
-      "type": "object",
-      "properties": {
-        "host": {
-          "type": "string"
-        },
-        "port": {
-          "type": "integer"
-        },
-        "username": {
-          "type": "string"
-        },
-        "password": {
-          "type": "string"
-        },
-        "allow_untrusted": {
-          "type": "boolean"
-        }
-      },
-      "required": [
-        "host",
-        "port",
-        "username",
-        "password"
-      ],
-      "additionalProperties": false
-    },
-    {
-      "type": "object",
-      "properties": {
-        "secret_ref": {
-          "type": "string"
-        }
-      },
-      "required": ["secret_ref"],
-      "additionalProperties": false
-    }
-  ]
-}

+ 0 - 244
coriolis/providers/vmware_vsphere/vixdisklib.py

@@ -1,244 +0,0 @@
-# Copyright 2016 Cloudbase Solutions Srl
-# All Rights Reserved.
-
-import contextlib
-import ctypes
-import os
-
-from oslo_log import log as logging
-
-LOG = logging.getLogger(__name__)
-
-if os.name == 'nt':
-    vixDiskLibName = 'vixDiskLib.dll'
-else:
-    vixDiskLibName = 'libvixDiskLib.so'
-
-vixDiskLib = ctypes.cdll.LoadLibrary(vixDiskLibName)
-
-
-class VixDiskLibUidPasswdCreds(ctypes.Structure):
-    _fields_ = [
-        ("userName", ctypes.c_char_p),
-        ("password", ctypes.c_char_p),
-    ]
-
-
-class VixDiskLibSessionIdCreds(ctypes.Structure):
-    _fields_ = [
-        ("cookie", ctypes.c_char_p),
-        ("userName", ctypes.c_char_p),
-        ("key", ctypes.c_char_p),
-    ]
-
-
-class VixDiskLibCreds(ctypes.Union):
-    _fields_ = [
-        ("uid", VixDiskLibUidPasswdCreds),
-        ("sessionId", VixDiskLibSessionIdCreds),
-    ]
-
-
-class VixDiskLibConnectParams(ctypes.Structure):
-    _fields_ = [
-        ("vmxSpec", ctypes.c_char_p),
-        ("serverName", ctypes.c_char_p),
-        ("thumbPrint", ctypes.c_char_p),
-        # Note: this is 32bit on Windows
-        ("privateUse", ctypes.c_longlong),
-        ("credType", ctypes.c_uint32),
-        ("creds", VixDiskLibCreds),
-        ("port", ctypes.c_uint32),
-        ("nfcHostPort", ctypes.c_uint32),
-    ]
-
-
-class VixDiskLibConnection(ctypes.Structure):
-    _fields_ = []
-
-
-vixDiskLib.VixDiskLib_InitEx.argtypes = [
-    ctypes.c_uint32, ctypes.c_uint32, ctypes.c_void_p, ctypes.c_void_p,
-    ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p]
-vixDiskLib.VixDiskLib_InitEx.restype = ctypes.c_uint64
-
-
-vixDiskLib.VixDiskLib_GetErrorText.argtypes = [
-    ctypes.c_uint64, ctypes.c_char_p]
-vixDiskLib.VixDiskLib_GetErrorText.restype = ctypes.c_void_p
-
-vixDiskLib.VixDiskLib_FreeErrorText.arg_types = [ctypes.c_char_p]
-vixDiskLib.VixDiskLib_FreeErrorText.restype = None
-
-vixDiskLib.VixDiskLib_ListTransportModes.argtypes = []
-vixDiskLib.VixDiskLib_ListTransportModes.restype = ctypes.c_char_p
-
-vixDiskLib.VixDiskLib_ConnectEx.argtypes = [
-    ctypes.POINTER(VixDiskLibConnectParams), ctypes.c_char, ctypes.c_char_p,
-    ctypes.c_char_p, ctypes.POINTER(ctypes.c_void_p)]
-vixDiskLib.VixDiskLib_ConnectEx.restype = ctypes.c_uint64
-
-vixDiskLib.VixDiskLib_Open.argtypes = [
-    ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint32,
-    ctypes.POINTER(ctypes.c_void_p)]
-vixDiskLib.VixDiskLib_Open.restype = ctypes.c_uint64
-
-vixDiskLib.VixDiskLib_Read.argtypes = [
-    ctypes.c_void_p, ctypes.c_uint64, ctypes.c_uint64, ctypes.c_char_p]
-vixDiskLib.VixDiskLib_Read.restype = ctypes.c_uint64
-
-vixDiskLib.VixDiskLib_GetMetadataKeys.argtypes = [
-    ctypes.c_void_p, ctypes.c_char_p, ctypes.c_uint64,
-    ctypes.POINTER(ctypes.c_uint64)]
-vixDiskLib.VixDiskLib_GetMetadataKeys.restype = ctypes.c_uint64
-
-vixDiskLib.VixDiskLib_ReadMetadata.argtypes = [
-    ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_uint64,
-    ctypes.POINTER(ctypes.c_uint64)]
-vixDiskLib.VixDiskLib_ReadMetadata.restype = ctypes.c_uint64
-
-vixDiskLib.VixDiskLib_Close.argtypes = [ctypes.c_void_p]
-vixDiskLib.VixDiskLib_Close.restype = ctypes.c_uint64
-
-vixDiskLib.VixDiskLib_Disconnect.argtypes = [ctypes.c_void_p]
-vixDiskLib.VixDiskLib_Disconnect.restype = ctypes.c_uint64
-
-vixDiskLib.VixDiskLib_Exit.argtypes = []
-vixDiskLib.VixDiskLib_Exit.restype = None
-
-
-VIXDISKLIB_VERSION_MAJOR = 6
-VIXDISKLIB_VERSION_MINOR = 0
-
-VIXDISKLIB_SECTOR_SIZE = 512
-
-VIXDISKLIB_CRED_UID = 1
-
-VIXDISKLIB_FLAG_OPEN_UNBUFFERED = 1
-VIXDISKLIB_FLAG_OPEN_SINGLE_LINK = 2
-VIXDISKLIB_FLAG_OPEN_READ_ONLY = 4
-
-VIX_OK = 0
-VIX_E_BUFFER_TOOSMALL = 24
-
-
-def _check_err(err, allowed_values=[VIX_OK]):
-    if err not in allowed_values:
-        err_msg = vixDiskLib.VixDiskLib_GetErrorText(err, None)
-        err_msg_copy = str(ctypes.cast(
-            err_msg, ctypes.c_char_p).value.decode())
-        vixDiskLib.VixDiskLib_FreeErrorText(err_msg)
-        raise Exception(err_msg_copy)
-
-
-def init(config_path=None, major_ver=VIXDISKLIB_VERSION_MAJOR,
-         minor_ver=VIXDISKLIB_VERSION_MINOR):
-    if config_path:
-        config_path = config_path.encode()
-
-    _check_err(vixDiskLib.VixDiskLib_InitEx(
-        major_ver, minor_ver, None, None, None, None, config_path))
-
-
-def get_transport_modes():
-    transport_modes = vixDiskLib.VixDiskLib_ListTransportModes()
-    return transport_modes.decode().split(':')
-
-
-@contextlib.contextmanager
-def connect(server_name, thumbprint, username, password, vmx_spec=None,
-            snapshot_ref=None, read_only=True, transport_modes=None, port=443):
-    LOG.debug("Connecting VixDiskLib: %s", server_name)
-
-    connectParams = VixDiskLibConnectParams()
-
-    connectParams.serverName = server_name.encode()
-    if vmx_spec:
-        connectParams.vmxSpec = vmx_spec.encode()
-    if thumbprint:
-        connectParams.thumbPrint = thumbprint.encode()
-
-    connectParams.credType = VIXDISKLIB_CRED_UID
-    connectParams.creds.uid.userName = username.encode()
-    connectParams.creds.uid.password = password.encode()
-    connectParams.port = port
-
-    if transport_modes:
-        transport_modes = transport_modes.encode()
-
-    if snapshot_ref:
-        snapshot_ref = snapshot_ref.encode()
-
-    conn = ctypes.c_void_p()
-    _check_err(vixDiskLib.VixDiskLib_ConnectEx(
-        connectParams, read_only, snapshot_ref, transport_modes,
-        ctypes.byref(conn)))
-    try:
-        yield conn
-    finally:
-        disconnect(conn)
-
-
-@contextlib.contextmanager
-def open(conn, disk_path, flags=VIXDISKLIB_FLAG_OPEN_READ_ONLY):
-    LOG.debug("Openning VixDiskLib disk: %s", disk_path)
-
-    disk_handle = ctypes.c_void_p()
-    _check_err(vixDiskLib.VixDiskLib_Open(
-        conn, disk_path.encode(), flags, ctypes.byref(disk_handle)))
-    try:
-        yield disk_handle
-    finally:
-        close(disk_handle)
-
-
-def get_metadata_keys(disk_handle):
-    buf_len = ctypes.c_uint64()
-
-    _check_err(vixDiskLib.VixDiskLib_GetMetadataKeys(
-        disk_handle, None, 0, ctypes.byref(buf_len)),
-        [VIX_OK, VIX_E_BUFFER_TOOSMALL])
-
-    buf = ctypes.create_string_buffer(buf_len.value)
-    _check_err(vixDiskLib.VixDiskLib_GetMetadataKeys(
-        disk_handle, buf, buf_len, None))
-
-    return [k.decode() for k in buf.raw.split(b'\0') if len(k)]
-
-
-def read_metadata(disk_handle, key):
-    key = key.encode()
-    buf_len = ctypes.c_uint64()
-
-    _check_err(vixDiskLib.VixDiskLib_ReadMetadata(
-        disk_handle, key, None, 0, ctypes.byref(buf_len)),
-        [VIX_OK, VIX_E_BUFFER_TOOSMALL])
-
-    buf = ctypes.create_string_buffer(buf_len.value)
-    _check_err(vixDiskLib.VixDiskLib_ReadMetadata(
-        disk_handle, key, buf, buf_len, None))
-
-    return buf.value.decode()
-
-
-def get_buffer(size):
-    return ctypes.create_string_buffer(size)
-
-
-def read(disk_handle, start_sector, num_sectors, buf):
-    _check_err(vixDiskLib.VixDiskLib_Read(
-        disk_handle, start_sector, num_sectors, buf))
-
-
-def close(disk_handle):
-    LOG.debug("Closing VixDiskLib disk handle: %s", disk_handle)
-    _check_err(vixDiskLib.VixDiskLib_Close(disk_handle))
-
-
-def disconnect(conn):
-    LOG.debug("Disconnecting VixDiskLib")
-    _check_err(vixDiskLib.VixDiskLib_Disconnect(conn))
-
-
-def exit():
-    vixDiskLib.VixDiskLib_Exit()

+ 1 - 2
coriolis/schemas_exceptions.py

@@ -3,8 +3,6 @@
 
 """ Defines a set of exceptions possible during schema loading/validation. """
 
-import json
-
 import jinja2
 import jsonschema
 
@@ -15,6 +13,7 @@ class CoriolisSchemaException(exception.CoriolisException):
     """ Base class for all coriolis schema handling exceptions. """
     message = "Exception occured during schema validation: %(msg)s."
 
+
 class CoriolisSchemaValidationError(
         CoriolisSchemaException, jsonschema.ValidationError):
     """ Raised when a schema validation has failed. """

+ 0 - 37
coriolis/service.py

@@ -1,7 +1,6 @@
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
 
-import os
 import platform
 
 from oslo_concurrency import processutils
@@ -95,39 +94,3 @@ class MessagingService(service.ServiceBase):
 
     def reset(self):
         self._server.reset()
-
-
-'''
-_launcher = None
-
-def serve(server, workers=None):
-    global _launcher
-    if _launcher:
-        raise RuntimeError(_('serve() can only be called once'))
-
-    _launcher = service.launch(CONF, server, workers=workers)
-
-
-def wait():
-    try:
-        _launcher.wait()
-    except KeyboardInterrupt:
-        _launcher.stop()
-
-
-class Launcher(object):
-    def __init__(self):
-        self.launch_service = serve
-        self.wait = wait
-
-
-def get_process_launcher():
-    # Note(lpetrut): ProcessLauncher uses green pipes which fail on Windows
-    # due to missing support of non-blocking I/O pipes. For this reason, the
-    # service must be spawned differently on Windows, using the ServiceLauncher
-    # class instead.
-    if os.name == 'nt':
-        return Launcher()
-    else:
-        return service.ProcessLauncher(CONF)
-'''

+ 28 - 0
coriolis/tasks/base.py

@@ -4,9 +4,19 @@
 import abc
 
 from coriolis import secrets
+from coriolis import utils
 
+from oslo_config import cfg
 from oslo_log import log as logging
 
+serialization_opts = [
+    cfg.StrOpt('temp_keypair_password',
+               default=None,
+               help='Password to be used when serializing temporary keys'),
+]
+
+CONF = cfg.CONF
+CONF.register_opts(serialization_opts, 'serialization')
 LOG = logging.getLogger(__name__)
 
 
@@ -24,3 +34,21 @@ def get_connection_info(ctxt, data):
         LOG.info("Retrieving connection info from secret: %s", secret_ref)
         connection_info = secrets.get_secret(ctxt, secret_ref)
     return connection_info
+
+
+def marshal_migr_conn_info(migr_connection_info):
+    if migr_connection_info and "pkey" in migr_connection_info:
+        migr_connection_info = migr_connection_info.copy()
+        migr_connection_info["pkey"] = utils.serialize_key(
+            migr_connection_info["pkey"],
+            CONF.serialization.temp_keypair_password)
+    return migr_connection_info
+
+
+def unmarshal_migr_conn_info(migr_connection_info):
+    if migr_connection_info and "pkey" in migr_connection_info:
+        migr_connection_info = migr_connection_info.copy()
+        pkey_str = migr_connection_info["pkey"]
+        migr_connection_info["pkey"] = utils.deserialize_key(
+            pkey_str, CONF.serialization.temp_keypair_password)
+    return migr_connection_info

+ 11 - 0
coriolis/tasks/factory.py

@@ -4,6 +4,7 @@
 from coriolis import constants
 from coriolis import exception
 from coriolis.tasks import migration_tasks
+from coriolis.tasks import osmorphing_tasks
 from coriolis.tasks import replica_tasks
 
 _TASKS_MAP = {
@@ -11,6 +12,12 @@ _TASKS_MAP = {
         migration_tasks.ExportInstanceTask,
     constants.TASK_TYPE_IMPORT_INSTANCE:
         migration_tasks.ImportInstanceTask,
+    constants.TASK_TYPE_FINALIZE_IMPORT_INSTANCE:
+        migration_tasks.FinalizeImportInstanceTask,
+    constants.TASK_TYPE_CLEANUP_FAILED_IMPORT_INSTANCE:
+        migration_tasks.CleanupFailedImportInstanceTask,
+    constants.TASK_TYPE_OS_MORPHING:
+        osmorphing_tasks.OSMorphingTask,
     constants.TASK_TYPE_GET_INSTANCE_INFO:
         replica_tasks.GetInstanceInfoTask,
     constants.TASK_TYPE_REPLICATE_DISKS:
@@ -31,6 +38,10 @@ _TASKS_MAP = {
         replica_tasks.DeleteReplicaSourceResourcesTask,
     constants.TASK_TYPE_DEPLOY_REPLICA_INSTANCE:
         replica_tasks.DeployReplicaInstanceTask,
+    constants.TASK_TYPE_FINALIZE_REPLICA_INSTANCE_DEPLOYMENT:
+        replica_tasks.FinalizeReplicaInstanceDeploymentTask,
+    constants.TASK_TYPE_CLEANUP_FAILED_REPLICA_INSTANCE_DEPLOYMENT:
+        replica_tasks.CleanupFailedReplicaInstanceDeploymentTask,
     constants.TASK_TYPE_CREATE_REPLICA_DISK_SNAPSHOTS:
         replica_tasks.CreateReplicaDiskSnapshotsTask,
     constants.TASK_TYPE_DELETE_REPLICA_DISK_SNAPSHOTS:

+ 39 - 1
coriolis/tasks/migration_tasks.py

@@ -41,7 +41,45 @@ class ImportInstanceTask(base.TaskRunner):
             destination["type"], constants.PROVIDER_TYPE_IMPORT, event_handler)
         connection_info = base.get_connection_info(ctxt, destination)
 
-        provider.import_instance(
+        import_info = provider.import_instance(
             ctxt, connection_info, target_environment, instance, export_info)
 
+        task_info["instance_deployment_info"] = import_info[
+            "instance_deployment_info"]
+        task_info["osmorphing_info"] = import_info.get("osmorphing_info", {})
+        task_info["osmorphing_connection_info"] = base.marshal_migr_conn_info(
+            import_info["osmorphing_connection_info"])
+
+        task_info["origin_provider_type"] = constants.PROVIDER_TYPE_EXPORT
+        task_info["destination_provider_type"] = constants.PROVIDER_TYPE_IMPORT
+
+        return task_info
+
+
+class FinalizeImportInstanceTask(base.TaskRunner):
+    def run(self, ctxt, instance, origin, destination, task_info,
+            event_handler):
+        provider = providers_factory.get_provider(
+            destination["type"], constants.PROVIDER_TYPE_IMPORT, event_handler)
+        connection_info = base.get_connection_info(ctxt, destination)
+        instance_deployment_info = task_info["instance_deployment_info"]
+
+        provider.finalize_import_instance(
+            ctxt, connection_info, instance_deployment_info)
+
+        return task_info
+
+
+class CleanupFailedImportInstanceTask(base.TaskRunner):
+    def run(self, ctxt, instance, origin, destination, task_info,
+            event_handler):
+        provider = providers_factory.get_provider(
+            destination["type"], constants.PROVIDER_TYPE_IMPORT, event_handler)
+        connection_info = base.get_connection_info(ctxt, destination)
+        instance_deployment_info = task_info.get(
+            "instance_deployment_info", {})
+
+        provider.cleanup_failed_import_instance(
+            ctxt, connection_info, instance_deployment_info)
+
         return task_info

+ 33 - 0
coriolis/tasks/osmorphing_tasks.py

@@ -0,0 +1,33 @@
+# Copyright 2016 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+from coriolis.osmorphing import manager as osmorphing_manager
+from coriolis.providers import factory as providers_factory
+from coriolis.tasks import base
+
+
+class OSMorphingTask(base.TaskRunner):
+    def run(self, ctxt, instance, origin, destination, task_info,
+            event_handler):
+
+        origin_provider_type = task_info["origin_provider_type"]
+        destination_provider_type = task_info["destination_provider_type"]
+
+        origin_provider = providers_factory.get_provider(
+            origin["type"], origin_provider_type, event_handler)
+
+        destination_provider = providers_factory.get_provider(
+            destination["type"], destination_provider_type, event_handler)
+
+        osmorphing_connection_info = base.unmarshal_migr_conn_info(
+            task_info['osmorphing_connection_info'])
+        osmorphing_info = task_info.get('osmorphing_info', {})
+
+        osmorphing_manager.morph_image(
+            origin_provider,
+            destination_provider,
+            osmorphing_connection_info,
+            osmorphing_info,
+            event_handler)
+
+        return task_info

+ 48 - 34
coriolis/tasks/replica_tasks.py

@@ -6,41 +6,12 @@ from coriolis import exception
 from coriolis.providers import factory as providers_factory
 from coriolis import schemas
 from coriolis.tasks import base
-from coriolis import utils
 
-from oslo_config import cfg
 from oslo_log import log as logging
 
-serialization_opts = [
-    cfg.StrOpt('temp_keypair_password',
-               default=None,
-               help='Password to be used when serializing temporary keys'),
-]
-
-CONF = cfg.CONF
-CONF.register_opts(serialization_opts, 'serialization')
-
 LOG = logging.getLogger(__name__)
 
 
-def _marshal_migr_conn_info(migr_connection_info):
-    if migr_connection_info and "pkey" in migr_connection_info:
-        migr_connection_info = migr_connection_info.copy()
-        migr_connection_info["pkey"] = utils.serialize_key(
-            migr_connection_info["pkey"],
-            CONF.serialization.temp_keypair_password)
-    return migr_connection_info
-
-
-def _unmarshal_migr_conn_info(migr_connection_info):
-    if migr_connection_info and "pkey" in migr_connection_info:
-        migr_connection_info = migr_connection_info.copy()
-        pkey_str = migr_connection_info["pkey"]
-        migr_connection_info["pkey"] = utils.deserialize_key(
-            pkey_str, CONF.serialization.temp_keypair_password)
-    return migr_connection_info
-
-
 def _get_volumes_info(task_info):
     volumes_info = task_info.get("volumes_info")
     if not volumes_info:
@@ -91,10 +62,10 @@ class ReplicateDisksTask(base.TaskRunner):
 
         volumes_info = _get_volumes_info(task_info)
 
-        migr_source_conn_info = _unmarshal_migr_conn_info(
+        migr_source_conn_info = base.unmarshal_migr_conn_info(
             task_info["migr_source_connection_info"])
 
-        migr_target_conn_info = _unmarshal_migr_conn_info(
+        migr_target_conn_info = base.unmarshal_migr_conn_info(
             task_info["migr_target_connection_info"])
 
         incremental = task_info.get("incremental", True)
@@ -161,7 +132,7 @@ class DeployReplicaSourceResourcesTask(base.TaskRunner):
 
         task_info["migr_source_resources"] = replica_resources_info[
             "migr_resources"]
-        migr_connection_info = _marshal_migr_conn_info(
+        migr_connection_info = base.marshal_migr_conn_info(
             replica_resources_info["connection_info"])
         task_info["migr_source_connection_info"] = migr_connection_info
 
@@ -207,7 +178,7 @@ class DeployReplicaTargetResourcesTask(base.TaskRunner):
         task_info["migr_target_resources"] = replica_resources_info[
             "migr_resources"]
 
-        migr_connection_info = _marshal_migr_conn_info(
+        migr_connection_info = base.marshal_migr_conn_info(
             replica_resources_info["connection_info"])
         task_info["migr_target_connection_info"] = migr_connection_info
 
@@ -249,10 +220,53 @@ class DeployReplicaInstanceTask(base.TaskRunner):
         clone_disks = task_info.get("clone_disks", True)
         LOG.debug("Clone disks: %s", clone_disks)
 
-        provider.deploy_replica_instance(
+        import_info = provider.deploy_replica_instance(
             ctxt, connection_info, target_environment, instance,
             export_info, volumes_info, clone_disks)
 
+        task_info["instance_deployment_info"] = import_info[
+            "instance_deployment_info"]
+        task_info["osmorphing_info"] = import_info.get("osmorphing_info", {})
+        task_info["osmorphing_connection_info"] = base.marshal_migr_conn_info(
+            import_info["osmorphing_connection_info"])
+
+        task_info[
+            "origin_provider_type"] = constants.PROVIDER_TYPE_REPLICA_EXPORT
+        task_info[
+            "destination_provider_type"
+        ] = constants.PROVIDER_TYPE_REPLICA_IMPORT
+
+        return task_info
+
+
+class FinalizeReplicaInstanceDeploymentTask(base.TaskRunner):
+    def run(self, ctxt, instance, origin, destination, task_info,
+            event_handler):
+        provider = providers_factory.get_provider(
+            destination["type"], constants.PROVIDER_TYPE_REPLICA_IMPORT,
+            event_handler)
+        connection_info = base.get_connection_info(ctxt, destination)
+        instance_deployment_info = task_info["instance_deployment_info"]
+
+        provider.finalize_replica_instance_deployment(
+            ctxt, connection_info, instance_deployment_info)
+
+        return task_info
+
+
+class CleanupFailedReplicaInstanceDeploymentTask(base.TaskRunner):
+    def run(self, ctxt, instance, origin, destination, task_info,
+            event_handler):
+        provider = providers_factory.get_provider(
+            destination["type"], constants.PROVIDER_TYPE_REPLICA_IMPORT,
+            event_handler)
+        connection_info = base.get_connection_info(ctxt, destination)
+        instance_deployment_info = task_info.get(
+            "instance_deployment_info", {})
+
+        provider.cleanup_failed_replica_instance_deployment(
+            ctxt, connection_info, instance_deployment_info)
+
         return task_info
 
 

+ 0 - 0
coriolis/tests/providers/azure/__init__.py


+ 0 - 427
coriolis/tests/providers/azure/test_azure_import.py

@@ -1,427 +0,0 @@
-import os
-import tempfile
-
-import mock
-from oslo_config import cfg
-from oslo_utils import units
-from azure.mgmt import compute, network
-
-from coriolis import constants
-from coriolis.providers import azure
-from coriolis.providers.azure import exceptions
-from coriolis.tests import testutils
-from coriolis.tests.providers import base
-
-
-class AzureImportProviderUnitTestsCase(base.ImportProviderTestCase):
-
-    _platform = constants.PLATFORM_AZURE_RM
-    _hypervisor = constants.HYPERVISOR_HYPERV
-
-    def setUp(self):
-        super(AzureImportProviderUnitTestsCase, self).setUp()
-
-        self._patch_utils_retry()
-
-        self._provider = azure.ImportProvider(self._mock_event_manager)
-
-        event_manager_patcher = mock.patch.object(
-            self._provider, '_event_manager',
-            new=self._mock_event_manager)
-        event_manager_patcher.start()
-
-        self._test_location = mock.sentinel.test_location
-        self._test_migration_id = mock.sentinel.test_migration_id
-        self._test_container_name = mock.sentinel.test_container
-        self._test_storage_name = mock.sentinel.test_storage_account
-        self._test_resource_group = mock.sentinel.test_resource_group
-        self._test_subnet_name = mock.sentinel.test_subnet_name
-
-        # various mocks for various helpers in azure.utils.
-        # to patch them '_patch_azure_utils' should be called.
-        self._test_random_password = mock.sentinel.test_azutils_random_password
-        self._mock_azutils_randpass = mock.MagicMock(
-            return_value=self._test_random_password)
-
-        self._test_unique_id = mock.sentinel.test_azutils_unique_id
-        self._mock_azutils_uniqueid = mock.MagicMock(
-            return_value=self._test_unique_id)
-
-        self._test_normalized_location = mock.sentinel.test_normalized_location
-        self._mock_azutils_normalize_location = mock.MagicMock(
-            return_value=self._test_normalized_location)
-
-        self._patch_azure_decorators()
-        self._setup_config_mock()
-
-    def _setup_config_mock(self):
-        azure_provider_conf = mock.MagicMock(
-            migr_container_name=self._test_container_name,
-            migr_subnet_name=self._test_subnet_name
-        )
-
-        conf_patcher = mock.patch.object(
-            cfg.CONF, 'azure_migration_provider', new=azure_provider_conf)
-        conf_patcher.start()
-
-    def _patch_azure_utils(self):
-        azutils_patcher = mock.patch.multiple(
-            'coriolis.providers.azure.azutils',
-            get_random_password=self._mock_azutils_randpass,
-            get_unique_id=self._mock_azutils_uniqueid,
-            normalize_location=self._mock_azutils_normalize_location
-        )
-        azutils_patcher.start()
-
-    def _patch_azure_decorators(self):
-        # NOTE: we patch the standard decorators found in azutils, whose core
-        # functionality lies in injecting the 'raw=True' kwarg to all ARM API
-        # calls which forces more checkable output/waitable long running
-        # operations (checked and awaited, respectively).
-        # Considering it's ideally a background thing; no tests focus on
-        # checking for that kwarg (except the tests for the decorators
-        # themselves...).
-        self._mock_checked = testutils.make_identity_decorator_mock()
-
-        self._mock_awaited_inner = testutils.make_identity_decorator_mock()
-        def awaited_dec(*args, **kwargs):
-            return self._mock_awaited_inner
-        self._mock_awaited = mock.MagicMock(side_effect=awaited_dec)
-
-        decs_patcher = mock.patch.multiple(
-            'coriolis.providers.azure.azutils',
-            checked=self._mock_checked,
-            awaited=self._mock_awaited
-        )
-        decs_patcher.start()
-
-    @mock.patch.object(azure.ImportProvider, '_get_block_blob_client')
-    def test_delete_recovery_disk(self, mock_get_blobc):
-        interesting_blob_name = ("%s.veryinteresting.status" %
-                                 self._test_instance_name)
-        blob_names = [
-            "uninteresting", interesting_blob_name, "boring"
-        ]
-        # NOTE: small workaround mocks already having a 'name' attribute:
-        mock_blobs = []
-        for name in blob_names:
-            m = mock.MagicMock()
-            m.name = name
-            mock_blobs.append(m)
-
-        mock_blob_client = mock.MagicMock()
-        mock_blob_client.list_blobs.return_value = mock_blobs
-
-        mock_get_blobc.return_value = mock_blob_client
-
-        self._provider._delete_recovery_disk(
-            self._test_target_env, self._test_instance_name)
-
-        mock_get_blobc.assert_called_once_with(self._test_target_env)
-
-        mock_blob_client.list_blobs.assert_called_once_with(
-            self._test_container_name)
-        mock_blob_client.delete_blob.assert_called_once_with(
-            self._test_container_name, interesting_blob_name)
-
-    @mock.patch.object(azure.ImportProvider, '_get_page_blob_client')
-    def test_upload_disk(self, mock_get_pagec):
-        test_env = {"storage": {"account": self._test_storage_name}}
-        test_disk_path = mock.sentinel.test_disk_path
-        test_upload_name = mock.sentinel.test_upload_name
-
-        mock_page_client = mock.MagicMock()
-        mock_get_pagec.return_value = mock_page_client
-
-        res = self._provider._upload_disk(
-            test_env, test_disk_path, test_upload_name)
-
-        disk_uri = azure.BLOB_PATH_FORMAT % (
-            self._test_storage_name,
-            self._test_container_name,
-            test_upload_name
-        )
-
-        mock_get_pagec.assert_called_once_with(test_env)
-        mock_page_client.create_blob_from_path.assert_called_once_with(
-            self._test_container_name, test_upload_name,
-            test_disk_path, progress_callback=mock.ANY)
-
-        self.assertEqual(res.name, test_upload_name)
-        self.assertEqual(res.uri, disk_uri)
-
-    @mock.patch.object(azure.ImportProvider, '_get_network_client')
-    def test_create_migration_network(self, mock_get_netc):
-        target_env = {"location": self._test_location}
-
-        # NOTE: this definition should remain rigid throughout as it has no
-        # real reason of being migration-specific.
-        test_vnet = network.models.VirtualNetwork(
-            location=self._test_location,
-            address_space=network.models.AddressSpace(
-                address_prefixes=["10.0.0.0/16"]
-            ),
-            subnets=[
-                network.models.Subnet(
-                    name=self._test_subnet_name,
-                    address_prefix='10.0.0.0/24'
-                )
-            ]
-        )
-
-        resgroup_name = azure.MIGRATION_RESGROUP_NAME_FORMAT % (
-            self._test_migration_id)
-        vn_name = azure.MIGRATION_NETWORK_NAME_FORMAT % (
-            self._test_migration_id)
-
-        mock_vnetc = mock.MagicMock()
-        mock_vnetc.create_or_update.return_value = test_vnet
-        mock_vnetc.get.return_value = test_vnet
-
-        mock_netc = mock.MagicMock()
-        mock_netc.virtual_networks = mock_vnetc
-
-        mock_get_netc.return_value = mock_netc
-
-        res = self._provider._create_migration_network(
-            self._test_conn_info, target_env, self._test_migration_id)
-
-        self.assertEqual(res, test_vnet)
-
-        mock_get_netc.assert_called_once_with(self._test_conn_info)
-
-        mock_vnetc.create_or_update.assert_called_once_with(
-            resgroup_name, vn_name, test_vnet)
-
-        mock_vnetc.get.assert_called_once_with(resgroup_name, vn_name)
-        self._mock_awaited_inner.assert_called_once_with(
-            mock_vnetc.create_or_update)
-        self._mock_checked.assert_called_once_with(mock_vnetc.get)
-
-    @mock.patch.object(azure.ImportProvider, '_get_compute_client')
-    def test_wait_for_vm_success(self, mock_get_computec):
-        vm_states = [
-            mock.Mock(provisioning_state=s) for s in
-            ["Creating", "Creating", "Updating", "Creating", "Succeeded"]
-        ]
-
-        mock_vmclient = mock.MagicMock()
-        mock_vmclient.get.side_effect = vm_states
-
-        mock_get_computec.return_value = mock.MagicMock(
-            virtual_machines=mock_vmclient)
-
-        self._provider._wait_for_vm(
-            self._test_conn_info,
-            self._test_resource_group,
-            self._test_instance_name,
-            period=0
-        )
-
-        mock_get_computec.assert_called_once_with(self._test_conn_info)
-
-        mock_vmclient.get.assert_has_calls([
-            mock.call(self._test_resource_group, self._test_instance_name)
-            for _ in vm_states
-        ])
-        self._mock_checked.assert_has_calls([
-            mock.call(mock_vmclient.get) for _ in vm_states
-        ])
-
-    @mock.patch.object(azure.ImportProvider, '_get_linux_worker_osprofile')
-    @mock.patch.object(azure.ImportProvider, '_get_windows_worker_osprofile')
-    def test_get_worker_osprofile(self, mock_get_windows_osprofile,
-                                  mock_get_linux_osprofile):
-        windows = constants.OS_TYPE_WINDOWS
-        linux = constants.OS_TYPE_LINUX
-        random_export_info = {"os_type": "some random OS"}
-
-        worker_name = mock.sentinel.worker_name
-        windows_profile = mock.sentinel.windows_osprofile
-        linux_profile = mock.sentinel.linux_osprofile
-
-        mock_get_windows_osprofile.return_value = windows_profile
-        mock_get_linux_osprofile.return_value = linux_profile
-
-        self._provider._get_worker_osprofile(
-            windows, self._test_location, worker_name)
-
-        mock_get_windows_osprofile.assert_called_once_with(
-            self._test_location, worker_name)
-        mock_get_linux_osprofile.assert_not_called()
-
-        mock_get_windows_osprofile.reset_mock()
-        mock_get_linux_osprofile.reset_mock()
-
-        self._provider._get_worker_osprofile(
-            linux, self._test_location, worker_name)
-
-        mock_get_linux_osprofile.assert_called_once_with(worker_name)
-        mock_get_windows_osprofile.assert_not_called()
-
-        mock_get_windows_osprofile.reset_mock()
-        mock_get_linux_osprofile.reset_mock()
-
-        self.assertRaises(
-            exceptions.FatalAzureOperationException,
-            self._provider._get_worker_osprofile,
-            random_export_info, self._test_location, worker_name)
-
-        mock_get_linux_osprofile.assert_not_called()
-        mock_get_windows_osprofile.assert_not_called()
-
-    @mock.patch.object(azure.ImportProvider, '_get_network_client')
-    def test_create_nic(self, mock_get_netc):
-
-        test_nic_name = mock.sentinel.test_nic_name
-        test_ip_configs = mock.sentinel.test_ip_configs
-
-        test_nic = network.models.NetworkInterface(
-            location=self._test_location,
-            ip_configurations=test_ip_configs
-        )
-
-        mock_nicc = mock.MagicMock()
-        mock_nicc.create_or_update.return_value = test_nic
-        mock_nicc.get.return_value = test_nic
-
-        mock_get_netc.return_value = mock.MagicMock()
-        mock_get_netc.return_value.network_interfaces = mock_nicc
-
-        res = self._provider._create_nic(
-            self._test_conn_info, self._test_resource_group, test_nic_name,
-            self._test_location, test_ip_configs)
-
-        mock_get_netc.assert_called_once_with(self._test_conn_info)
-
-        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, test_nic)
-
-        self._mock_checked.assert_called_once_with(mock_nicc.get)
-        mock_nicc.get.assert_called_once_with(
-            self._test_resource_group, test_nic_name)
-
-        self.assertEqual(res, test_nic)
-
-    @mock.patch.object(azure.ImportProvider, '_get_network_client')
-    def test_create_public_ip(self, mock_get_netc):
-        test_ip_name = mock.sentinel.test_pulic_ip_name
-
-        test_pip = network.models.PublicIPAddress(
-            location=self._test_location,
-            public_ip_allocation_method=
-            network.models.IPAllocationMethod.dynamic
-        )
-
-        mock_pipc = mock.MagicMock()
-        mock_pipc.create_or_update.return_value = test_pip
-        mock_pipc.get.return_value = test_pip
-
-        mock_get_netc.return_value = mock.MagicMock()
-        mock_get_netc.return_value.public_ip_addresses = mock_pipc
-
-        res = self._provider._create_public_ip(
-            self._test_conn_info, self._test_resource_group,
-            test_ip_name, self._test_location)
-
-        mock_get_netc.assert_called_once_with(self._test_conn_info)
-
-        self._mock_awaited_inner.assert_called_once_with(
-            mock_pipc.create_or_update)
-        mock_pipc.create_or_update.assert_called_once_with(
-            self._test_resource_group, test_ip_name, test_pip)
-
-        self._mock_checked.assert_called_once_with(mock_pipc.get)
-        mock_pipc.get.assert_called_once_with(
-            self._test_resource_group, test_ip_name)
-
-        self.assertEqual(res, test_pip)
-
-    @mock.patch.object(os, 'remove')
-    @mock.patch.object(os.path, 'splitext')
-    def test_convert_to_vhd(self, mock_splitext, mock_osremove):
-        self._patch_utils_disk_functions()
-        self._patch_azure_utils()
-
-        test_disk_path = mock.sentinel.test_disk_path
-
-        test_newpath = mock.sentinel.test_new_diskpath
-        mock_splitext.return_value = ["testdiskname"]
-
-        test_newpath = "%s.%s" % (
-            mock_splitext.return_value[0],
-            constants.DISK_FORMAT_VHD
-        )
-
-        res = self._provider._convert_to_vhd(test_disk_path)
-
-        self._mock_utils_convert_disk.assert_called_once_with(
-            test_disk_path, test_newpath, constants.DISK_FORMAT_VHD,
-            preallocated=True)
-
-        self.assertEqual(res, test_newpath)
-
-    @mock.patch.object(os, 'remove')
-    @mock.patch.object(azure.ImportProvider, '_upload_disk')
-    @mock.patch.object(azure.ImportProvider, '_convert_to_vhd')
-    def _test_migrate_disk(self, mock_convert, mock_upload_disk, mock_osremove,
-            test_disk_format=None):
-        self._patch_utils_disk_functions()
-
-        test_disk_info = {
-            "virtual-size": 1 * units.Gi,
-            "format": test_disk_format
-        }
-        self._mock_utils_get_disk_info.return_value = test_disk_info
-
-        test_lun = mock.sentinel.test_lun
-        test_upload_name = mock.sentinel.test_upload_name
-        test_disk_path = mock.sentinel.test_disk_path
-
-        osremove_calls = [mock.call(test_disk_path)] # should get deleted anyhow...
-
-        test_blob_uri = mock.sentinel.test_blob_uri
-        test_blob_name = mock.sentinel.test_blob_name
-        test_blob = azure.AzureStorageBlob(
-            name=test_blob_name,
-            uri=test_blob_uri
-        )
-        mock_upload_disk.return_value = test_blob
-
-        expected_upload_path = test_disk_path
-        expected_data_disk = compute.models.DataDisk(
-            lun=test_lun,
-            disk_size_gb=2,
-            name=test_blob_name,
-            caching=compute.models.CachingTypes.none,
-            vhd=compute.models.VirtualHardDisk(uri=test_blob_uri),
-            create_option=compute.models.DiskCreateOptionTypes.attach
-        )
-
-        test_upload_path = mock.sentinel.test_upload_path
-        mock_convert.return_value = test_upload_path
-
-        res = self._provider._migrate_disk(
-            self._test_target_env, test_lun, test_disk_path, test_upload_name)
-
-        self._mock_utils_get_disk_info.assert_called_once_with(test_disk_path)
-
-        if test_disk_format != constants.DISK_FORMAT_VHD:
-            mock_convert.assert_called_once_with(test_disk_path)
-            osremove_calls.append(mock.call(test_upload_path))
-            expected_upload_path = test_upload_path
-
-        mock_upload_disk.assert_called_once_with(
-            self._test_target_env, expected_upload_path, test_upload_name)
-
-        mock_osremove.assert_has_calls(osremove_calls)
-
-        self.assertEqual(res, expected_data_disk)
-
-    def test_migrate_disk(self):
-        self._test_migrate_disk(test_disk_format=constants.DISK_FORMAT_VHD)
-
-    def test_migrate_disk_with_conversion(self):
-        self._test_migrate_disk(test_disk_format="somerandomformat")

+ 3 - 1
coriolis/tests/providers/base.py

@@ -24,6 +24,7 @@ class ProvidersBaseTestCase(test_base.CoriolisBaseTestCase):
         # NOTE: declare utils.retry_on_error mock; '_patch_utils_retry' should
         # be called to enable them.
         self._mock_utils_retry_inner = testutils.make_identity_decorator_mock()
+
         def retry_dec(*args, **kwargs):
             return self._mock_utils_retry_inner
         self._mock_utils_retry = mock.MagicMock(side_effect=retry_dec)
@@ -64,7 +65,8 @@ class ImportProviderTestCase(ProvidersBaseTestCase):
         self._test_target_env = mock.sentinel.target_environment
         self._test_export_info = mock.sentinel.export_info
 
-    def _test_morphing_called(self, os_type="", nics_info=None, ignore_devs=[]):
+    def _test_morphing_called(self, os_type="", nics_info=None,
+                              ignore_devs=[]):
         self._mock_morph.morph_image.assert_called_once_with(
             self._mock_conn_info, os_type,
             self._hypervisor, self._platform,

+ 1 - 0
coriolis/tests/testutils.py

@@ -7,6 +7,7 @@ def identity_dec(item, *args, **kwargs):
     """ A decorator which adds nothing to the decorated item. """
     return item
 
+
 def make_identity_decorator_mock():
     """ Returns a MagicMock with identity_dec as a side-effect. """
     return mock.MagicMock(side_effect=identity_dec)

+ 0 - 12
requirements.txt

@@ -19,23 +19,11 @@ paste
 pbr
 psutil
 pyOpenSSL
-python-cinderclient
-python-glanceclient>=2.0.0
 python-keystoneclient
-python-neutronclient
-python-novaclient
 python-barbicanclient
 python-swiftclient>=3.2
-pyVmomi
 pywinrm
 PyYAML
 requests
 sqlalchemy
 webob
-msrest==0.4.0
-msrestazure==0.4.1
-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