Kaynağa Gözat

OSMorphing refactoring

Alessandro Pilotti 9 yıl önce
ebeveyn
işleme
8729e71d28
33 değiştirilmiş dosya ile 1096 ekleme ve 641 silme
  1. 0 1
      coriolis/api/v1/views/migration_view.py
  2. 12 1
      coriolis/api/v1/views/replica_tasks_execution_view.py
  3. 40 8
      coriolis/conductor/rpc/server.py
  4. 8 0
      coriolis/constants.py
  5. 4 0
      coriolis/exception.py
  6. 22 13
      coriolis/osmorphing/base.py
  7. 26 0
      coriolis/osmorphing/coreos.py
  8. 3 9
      coriolis/osmorphing/debian.py
  9. 0 45
      coriolis/osmorphing/factory.py
  10. 60 22
      coriolis/osmorphing/manager.py
  11. 27 0
      coriolis/osmorphing/openwrt.py
  12. 1 44
      coriolis/osmorphing/oracle.py
  13. 9 63
      coriolis/osmorphing/redhat.py
  14. 2 45
      coriolis/osmorphing/suse.py
  15. 1 11
      coriolis/osmorphing/ubuntu.py
  16. 1 210
      coriolis/osmorphing/windows.py
  17. 48 0
      coriolis/providers/base.py
  18. 10 7
      coriolis/providers/factory.py
  19. 213 127
      coriolis/providers/openstack/imp.py
  20. 0 0
      coriolis/providers/openstack/osmorphing/__init__.py
  21. 8 0
      coriolis/providers/openstack/osmorphing/coreos.py
  22. 13 0
      coriolis/providers/openstack/osmorphing/debian.py
  23. 8 0
      coriolis/providers/openstack/osmorphing/openwrt.py
  24. 49 0
      coriolis/providers/openstack/osmorphing/oracle.py
  25. 80 0
      coriolis/providers/openstack/osmorphing/redhat.py
  26. 48 0
      coriolis/providers/openstack/osmorphing/suse.py
  27. 16 0
      coriolis/providers/openstack/osmorphing/ubuntu.py
  28. 228 0
      coriolis/providers/openstack/osmorphing/windows.py
  29. 28 0
      coriolis/tasks/base.py
  30. 11 0
      coriolis/tasks/factory.py
  31. 39 1
      coriolis/tasks/migration_tasks.py
  32. 33 0
      coriolis/tasks/osmorphing_tasks.py
  33. 48 34
      coriolis/tasks/replica_tasks.py

+ 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

+ 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"

+ 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')

+ 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)

+ 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__):

+ 213 - 127
coriolis/providers/openstack/imp.py

@@ -1,7 +1,6 @@
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
 
-import collections
 import math
 import os
 import tempfile
@@ -19,9 +18,16 @@ 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.providers.openstack.osmorphing import coreos
+from coriolis.providers.openstack.osmorphing import debian
+from coriolis.providers.openstack.osmorphing import openwrt
+from coriolis.providers.openstack.osmorphing import oracle
+from coriolis.providers.openstack.osmorphing import redhat
+from coriolis.providers.openstack.osmorphing import suse
+from coriolis.providers.openstack.osmorphing import ubuntu
+from coriolis.providers.openstack.osmorphing import windows
 from coriolis import schemas
 from coriolis import utils
 
@@ -359,73 +365,59 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
         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(
+        config = {}
+
+        config["glance_upload"] = target_environment.get(
             "glance_upload", CONF.openstack_migration_provider.glance_upload)
-        config.target_disk_format = target_environment.get(
+        config["target_disk_format"] = target_environment.get(
             "disk_format", CONF.openstack_migration_provider.disk_format)
-        config.container_format = target_environment.get(
+        config["container_format"] = target_environment.get(
             "container_format",
             CONF.openstack_migration_provider.container_format)
-        config.hypervisor_type = target_environment.get(
+        config["hypervisor_type"] = target_environment.get(
             "hypervisor_type",
             CONF.openstack_migration_provider.hypervisor_type)
-        config.fip_pool_name = target_environment.get(
+        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(
+        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["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(
+        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(
+        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(
+        config["migr_fip_pool_name"] = target_environment.get(
             "migr_fip_pool_name",
-            config.fip_pool_name or
+            config["fip_pool_name"] or
             CONF.openstack_migration_provider.fip_pool_name)
-        config.migr_network_name = target_environment.get(
+        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)
+        config["flavor_name"] = target_environment.get(
+            "flavor_name", config["migr_flavor_name"])
 
-        if not config.migr_image_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:
+        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]
+            config["migr_network_name"] = list(
+                config["network_map"].values())[0]
 
         return config
 
@@ -440,13 +432,13 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
                      destination_format))
             utils.convert_disk_format(
                 source_path, destination_path, destination_format)
-        except Exception as ex:
+        except Exception:
             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:
+        if not config["glance_upload"]:
             raise exception.CoriolisException(
                 "Glance upload is currently required for migrations")
 
@@ -458,17 +450,18 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
             disk_file_info = utils.get_disk_info(disk_path)
 
             target_disk_path = disk_path
-            if config.target_disk_format != disk_file_info["format"]:
+            if config["target_disk_format"] != disk_file_info["format"]:
                 target_disk_path = ("%s.%s" % (
-                    os.path.splitext(disk_path)[0], config.target_disk_format))
+                    os.path.splitext(
+                        disk_path)[0], config["target_disk_format"]))
                 self._convert_disk_format(
-                    disk_path, target_disk_path, config.target_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,
+                        disk_path, config["target_disk_format"],
                         target_disk_path))
                 os.remove(disk_path)
 
@@ -482,8 +475,8 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
             image = common.create_image(
                 glance, common.get_unique_name(),
                 target_disk_path, disk_format,
-                config.container_format,
-                config.hypervisor_type)
+                config["container_format"],
+                config["hypervisor_type"])
             images.append(image)
 
             self._event_manager.progress_update(
@@ -498,7 +491,7 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
                 "Creating Cinder volume")
 
             volume_type = self._get_volume_type_for_disk(
-                cinder, disk_info, config.storage_map)
+                cinder, disk_info, config["storage_map"])
 
             volume = common.create_volume(
                 cinder, virtual_disk_size, common.get_unique_name(),
@@ -531,7 +524,7 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
         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)
+                      "Trying to use default volume type of '%s'", default)
             dest_stor = default
 
         # ensure the volume type exists:
@@ -553,11 +546,11 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
             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" % 
+                    "Origin network name not provided for nic: %s, skipping" %
                     nic_info.get("name"))
                 continue
 
-            network_name = config.network_map.get(origin_network_name)
+            network_name = config["network_map"].get(origin_network_name)
             if not network_name:
                 raise exception.CoriolisException(
                     "Network not mapped in network_map: %s" %
@@ -576,16 +569,17 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
         return volumes
 
     @utils.retry_on_error()
-    def _rename_volumes(self, cinder, volumes, instance_name):
-        for i, volume in enumerate(volumes):
+    def _rename_volumes(self, cinder, volume_ids, instance_name):
+        for i, volume_id in enumerate(volume_ids):
             new_volume_name = VOLUME_NAME_FORMAT % {
                 "instance_name": instance_name, "num": i + 1}
-            cinder.volumes.update(volume.id, name=new_volume_name)
+            cinder.volumes.update(volume_id, name=new_volume_name)
 
     @utils.retry_on_error()
-    def _set_bootable_volumes(self, cinder, volumes):
+    def _set_bootable_volumes(self, cinder, volume_ids):
         # TODO: check if just setting the first volume as bootable is enough
-        for volume in volumes:
+        for volume_id in volume_ids:
+            volume = cinder.volumes.get(volume_id)
             if not volume.bootable or volume.bootable == 'false':
                 cinder.volumes.set_bootable(volume, True)
 
@@ -603,9 +597,31 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
             volumes.append(volume)
         return volumes
 
-    def _deploy_instance(self, ctxt, connection_info, target_environment,
-                         instance_name, export_info, volumes_info=None,
-                         clone_disks=False):
+    def get_os_morphing_tools(self, conn, osmorphing_info):
+        os_morphing_tools_clss = {
+            constants.OS_TYPE_LINUX: [coreos.CoreOSMorphingTools,
+                                      debian.DebianMorphingTools,
+                                      ubuntu.UbuntuMorphingTools,
+                                      openwrt.OpenWRTMorphingTools,
+                                      oracle.OracleMorphingTools,
+                                      redhat.RedHatMorphingTools,
+                                      suse.SUSEMorphingTools],
+            constants.OS_TYPE_WINDOWS: [windows.WindowsMorphingTools],
+        }
+
+        hypervisor_type = osmorphing_info['hypervisor_type']
+        os_type = osmorphing_info.get('os_type')
+        os_root_dir = osmorphing_info['os_root_dir']
+        os_root_dev = osmorphing_info['os_root_dev']
+
+        return base.get_os_morphing_tools_helper(
+            conn, os_morphing_tools_clss, hypervisor_type, os_type,
+            os_root_dir, os_root_dev, self._event_manager)
+
+    def _deploy_instance_resources(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",
@@ -625,7 +641,6 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
 
         images = []
         volumes = []
-        ports = []
 
         try:
             if not volumes_info:
@@ -642,67 +657,93 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
                         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)
+                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)
+            for i, volume in enumerate(volumes):
+                common.wait_for_volume(cinder, volume.id)
 
-                    self._event_manager.progress_update(
-                        "Attaching volume to worker instance")
+                self._event_manager.progress_update(
+                    "Attaching volume to worker instance")
 
-                    self._attach_volume(
-                        nova, cinder, migr_resources.get_instance(), volume.id)
+                self._attach_volume(
+                    nova, cinder, migr_resources.get_instance(), volume.id)
+        finally:
+            if images:
+                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()
+
+        guest_conn_info = migr_resources.get_guest_connection_info()
+        hypervisor_type = self._get_osmorphing_hypervisor_type(
+            config["hypervisor_type"])
+
+        osmorphing_info = {"os_type": os_type,
+                           "hypervisor_type": hypervisor_type,
+                           "nics_info": nics_info}
+
+        volume_ids = [volume.id for volume in volumes]
+        instance_deployment_info = {
+            "config": config,
+            "migr_resources_dict": migr_resources.get_resources_dict(),
+            "volume_ids": volume_ids,
+            "instance_name": instance_name,
+            "nics_info": nics_info,
+            "os_type": os_type,
+            "clone_disks": clone_disks}
+
+        return {"instance_deployment_info": instance_deployment_info,
+                "osmorphing_connection_info": guest_conn_info,
+                "osmorphing_info": osmorphing_info}
+
+    def _finalize_instance_deployment(self, ctxt, connection_info,
+                                      instance_deployment_info):
+        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)
+        cinder = cinder_client.Client(common.CINDER_API_VERSION,
+                                      session=session)
 
-                    conn_info = migr_resources.get_guest_connection_info()
+        volume_ids = instance_deployment_info["volume_ids"]
+        instance_name = instance_deployment_info["instance_name"]
+        nics_info = instance_deployment_info["nics_info"]
+        config = instance_deployment_info["config"]
+        migr_resources_dict = instance_deployment_info["migr_resources_dict"]
 
-                osmorphing_hv_type = self._get_osmorphing_hypervisor_type(
-                    config.hypervisor_type)
+        migr_resources = _MigrationResources.from_resources_dict(
+            nova, neutron, migr_resources_dict)
 
-                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(
+            "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("Renaming volumes")
+        self._rename_volumes(cinder, volume_ids, instance_name)
 
-            self._event_manager.progress_update(
-                "Ensuring volumes are bootable")
-            self._set_bootable_volumes(cinder, volumes)
+        self._event_manager.progress_update(
+            "Ensuring volumes are bootable")
+        self._set_bootable_volumes(cinder, volume_ids)
 
-            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 Neutron ports for migrated instance")
+        ports = self._create_neutron_ports(neutron, config, nics_info)
 
+        try:
             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)
+                nova, config["flavor_name"], instance_name,
+                config["keypair_name"], ports, volume_ids,
+                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
@@ -710,20 +751,39 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
                 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 _cleanup_failed_instance_deployment(self, ctxt, connection_info,
+                                            instance_deployment_info,
+                                            is_replica):
+        session = keystone.create_keystone_session(ctxt, connection_info)
+
+        volume_ids = instance_deployment_info.get("volume_ids", [])
+        clone_disks = instance_deployment_info.get("clone_disks")
+        migr_resources_dict = instance_deployment_info.get(
+            "migr_resources_dict")
+
+        if migr_resources_dict:
+            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)
+
+            @utils.ignore_exceptions
+            def _del_migr_resources():
+                migr_resources.delete()
+            _del_migr_resources()
+
+        # Don't remove replica volumes
+        if volume_ids and (not is_replica or clone_disks):
+            cinder = cinder_client.Client(common.CINDER_API_VERSION,
+                                          session=session)
+            self._event_manager.progress_update("Deleting volumes")
+            for volume_id in volume_ids:
+                @utils.ignore_exceptions
+                def _del_volume():
+                    common.delete_volume(cinder, volume_id)
+                _del_volume()
 
     def _get_osmorphing_hypervisor_type(self, hypervisor_type):
         if (hypervisor_type and
@@ -735,16 +795,16 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
     @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,
+                                keypair_name, ports, volume_ids,
                                 ephemeral_volumes=False):
         flavor = common.get_flavor(nova, flavor_name)
 
         block_device_mapping = {}
-        for i, volume in enumerate(volumes):
+        for i, volume_id in enumerate(volume_ids):
             # Delete volume on termination
             block_device_mapping[
                 'vd%s' % chr(ord('a') + i)] = "%s:volume::%s" % (
-                    volume.id, ephemeral_volumes)
+                    volume_id, ephemeral_volumes)
 
         nics = [{'port-id': p['id']} for p in ports]
 
@@ -766,12 +826,38 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
                 nova.servers.delete(instance)
             raise
 
+    def import_instance(self, ctxt, connection_info, target_environment,
+                        instance_name, export_info):
+        return self._deploy_instance_resources(
+            ctxt, connection_info, target_environment, instance_name,
+            export_info)
+
+    def finalize_import_instance(self, ctxt, connection_info,
+                                 instance_deployment_info):
+        return self._finalize_instance_deployment(
+            ctxt, connection_info, instance_deployment_info)
+
+    def cleanup_failed_import_instance(self, ctxt, connection_info,
+                                       instance_deployment_info):
+        return self._cleanup_failed_instance_deployment(
+            ctxt, connection_info, instance_deployment_info, is_replica=False)
+
     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)
+        return self._deploy_instance_resources(
+            ctxt, connection_info, target_environment, instance_name,
+            export_info, volumes_info, clone_disks)
+
+    def finalize_replica_instance_deployment(self, ctxt, connection_info,
+                                             instance_deployment_info):
+        return self._finalize_instance_deployment(
+            ctxt, connection_info, instance_deployment_info)
+
+    def cleanup_failed_replica_instance_deployment(self, ctxt, connection_info,
+                                                   instance_deployment_info):
+        return self._cleanup_failed_instance_deployment(
+            ctxt, connection_info, instance_deployment_info, is_replica=True)
 
     def _update_existing_disk_volumes(self, cinder, disks_info, volumes_info):
         for disk_info in disks_info:
@@ -906,9 +992,9 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
         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)
+            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):

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


+ 8 - 0
coriolis/providers/openstack/osmorphing/coreos.py

@@ -0,0 +1,8 @@
+# Copyright 2017 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+from coriolis.osmorphing import coreos as base_coreos
+
+
+class CoreOSMorphingTools(base_coreos.BaseCoreOSMorphingTools):
+    pass

+ 13 - 0
coriolis/providers/openstack/osmorphing/debian.py

@@ -0,0 +1,13 @@
+# Copyright 2016 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+from coriolis import constants
+from coriolis.osmorphing import debian as base_debian
+
+
+class DebianMorphingTools(base_debian.BaseDebianMorphingTools):
+    _packages = {
+        constants.HYPERVISOR_VMWARE: [("open-vm-tools", True)],
+        # TODO: add cloud-initramfs-growroot
+        None: [("cloud-init", True)],
+    }

+ 8 - 0
coriolis/providers/openstack/osmorphing/openwrt.py

@@ -0,0 +1,8 @@
+# Copyright 2017 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+from coriolis.osmorphing import openwrt as base_openwrt
+
+
+class OpenWRTMorphingTools(base_openwrt.BaseOpenWRTMorphingTools):
+    pass

+ 49 - 0
coriolis/providers/openstack/osmorphing/oracle.py

@@ -0,0 +1,49 @@
+# Copyright 2016 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+from coriolis import constants
+from coriolis import exception
+from coriolis.osmorphing import oracle as base_oracle
+from coriolis.providers.openstack.osmorphing import redhat
+
+
+class OracleMorphingTools(base_oracle.BaseOracleMorphingTools,
+                          redhat.RedHatMorphingTools):
+    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 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()

+ 80 - 0
coriolis/providers/openstack/osmorphing/redhat.py

@@ -0,0 +1,80 @@
+# Copyright 2016 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+import os
+
+from oslo_log import log as logging
+import yaml
+
+from coriolis import constants
+from coriolis import exception
+from coriolis.osmorphing import redhat as base_redhat
+
+LOG = logging.getLogger(__name__)
+
+RELEASE_RHEL = "Red Hat Enterprise Linux Server"
+RELEASE_CENTOS = "CentOS Linux"
+RELEASE_FEDORA = "Fedora"
+
+DEFAULT_CLOUD_USER = "cloud-user"
+
+
+class RedHatMorphingTools(base_redhat.BaseRedHatMorphingTools):
+    _packages = {
+        constants.HYPERVISOR_VMWARE: [("open-vm-tools", True)],
+        constants.HYPERVISOR_HYPERV: [("hyperv-daemons", True)],
+        None: [
+            ("dracut-config-generic", False),
+            ("cloud-init", True),
+            ("cloud-utils", False),
+            ("parted", False),
+            ("git", False),
+            ("cloud-utils-growpart", False)],
+    }
+
+    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 _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 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)

+ 48 - 0
coriolis/providers/openstack/osmorphing/suse.py

@@ -0,0 +1,48 @@
+# Copyright 2016 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+from coriolis import constants
+from coriolis.osmorphing import suse as base_suse
+
+
+class SUSEMorphingTools(base_suse.BaseSUSEMorphingTools):
+    _packages = {
+        constants.HYPERVISOR_VMWARE: [("open-vm-tools", True)],
+        None: [("cloud-init", True)],
+    }
+
+    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 _configure_cloud_init(self):
+        if "cloud-init" in self.get_packages()[0]:
+            if self._has_systemd():
+                self._enable_systemd_service("cloud-init")
+
+    def post_packages_install(self, package_names):
+        self._run_dracut()
+        self._configure_cloud_init()
+        super(SUSEMorphingTools, self).post_packages_install(package_names)

+ 16 - 0
coriolis/providers/openstack/osmorphing/ubuntu.py

@@ -0,0 +1,16 @@
+# Copyright 2016 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+from coriolis import constants
+from coriolis.osmorphing import ubuntu as base_ubuntu
+
+
+class UbuntuMorphingTools(base_ubuntu.BaseUbuntuMorphingTools):
+    _packages = {
+        constants.HYPERVISOR_VMWARE: [("open-vm-tools", True)],
+        # TODO: sudo agt-get install linux-tool-<kernel release>
+        # linux-cloud-tools-<kernel release> -y
+        constants.HYPERVISOR_HYPERV: [("hv-kvp-daemon-init", True)],
+        # TODO: add cloud-initramfs-growroot
+        None: [("cloud-init", True)],
+    }

+ 228 - 0
coriolis/providers/openstack/osmorphing/windows.py

@@ -0,0 +1,228 @@
+# Copyright 2016 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+import uuid
+
+from distutils import version
+from oslo_config import cfg
+from oslo_log import log as logging
+
+from coriolis import constants
+from coriolis.osmorphing import windows as base_windows
+
+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
+SERVICE_START_MANUAL = 3
+SERVICE_START_DISABLED = 4
+
+SERVICE_PATH_FORMAT = "HKLM:\\%s\\ControlSet001\\Services\\%s"
+CLOUDBASEINIT_SERVICE_NAME = "cloudbase-init"
+
+
+class WindowsMorphingTools(base_windows.BaseWindowsMorphingTools):
+    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 _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 _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)

+ 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