Răsfoiți Sursa

OSMorphing refactoring

Alessandro Pilotti 9 ani în urmă
părinte
comite
8729e71d28
33 a modificat fișierele cu 1096 adăugiri și 641 ștergeri
  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(
     migration_dict = dict(itertools.chain.from_iterable(
         transform(k, v) for k, v in migration.items()))
         transform(k, v) for k, v in migration.items()))
 
 
-    # Migrations have a single tasks execution
     execution = replica_tasks_execution_view.format_replica_tasks_execution(
     execution = replica_tasks_execution_view.format_replica_tasks_execution(
         req, migration_dict["executions"][0])
         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):
 def _sort_tasks(tasks):
     non_error_only_tasks = [t for t in tasks if
     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
     # Include error only tasks only if executed
     error_only_tasks = [t for t in tasks if t["status"] !=
     error_only_tasks = [t for t in tasks if t["status"] !=
                         constants.TASK_STATUS_ON_ERROR_ONLY and
                         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.depends_on = depends_on
         task.on_error = on_error
         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
             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
         return task
 
 
     def _begin_tasks(self, ctxt, execution, task_info={}):
     def _begin_tasks(self, ctxt, execution, task_info={}):
@@ -349,16 +354,31 @@ class ConductorServerEndpoint(object):
                 instance, constants.TASK_TYPE_DEPLOY_REPLICA_INSTANCE,
                 instance, constants.TASK_TYPE_DEPLOY_REPLICA_INSTANCE,
                 execution, [create_snapshot_task.id])
                 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(
             self._create_task(
                 instance, constants.TASK_TYPE_DELETE_REPLICA_DISK_SNAPSHOTS,
                 instance, constants.TASK_TYPE_DELETE_REPLICA_DISK_SNAPSHOTS,
-                execution, [deploy_replica_task.id],
+                execution, depends_on=[finalize_deployment_task.id],
                 on_error=clone_disks)
                 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:
             if not clone_disks:
                 self._create_task(
                 self._create_task(
                     instance,
                     instance,
                     constants.TASK_TYPE_RESTORE_REPLICA_DISK_SNAPSHOTS,
                     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)
         db_api.add_migration(ctxt, migration)
         LOG.info("Migration created: %s", migration.id)
         LOG.info("Migration created: %s", migration.id)
@@ -382,14 +402,26 @@ class ConductorServerEndpoint(object):
         migration.info = {}
         migration.info = {}
 
 
         for instance in instances:
         for instance in instances:
-
             task_export = self._create_task(
             task_export = self._create_task(
                 instance, constants.TASK_TYPE_EXPORT_INSTANCE, execution)
                 instance, constants.TASK_TYPE_EXPORT_INSTANCE, execution)
 
 
-            self._create_task(
+            task_import = self._create_task(
                 instance, constants.TASK_TYPE_IMPORT_INSTANCE,
                 instance, constants.TASK_TYPE_IMPORT_INSTANCE,
                 execution, depends_on=[task_export.id])
                 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)
         db_api.add_migration(ctxt, migration)
         LOG.info("Migration created: %s", migration.id)
         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_EXPORT_INSTANCE = "EXPORT_INSTANCE"
 TASK_TYPE_IMPORT_INSTANCE = "IMPORT_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_GET_INSTANCE_INFO = "GET_INSTANCE_INFO"
 TASK_TYPE_DEPLOY_REPLICA_DISKS = "DEPLOY_REPLICA_DISKS"
 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_DELETE_REPLICA_TARGET_RESOURCES = "DELETE_REPLICA_TARGET_RESOURCES"
 TASK_TYPE_SHUTDOWN_INSTANCE = "SHUTDOWN_INSTANCE"
 TASK_TYPE_SHUTDOWN_INSTANCE = "SHUTDOWN_INSTANCE"
 TASK_TYPE_DEPLOY_REPLICA_INSTANCE = "DEPLOY_REPLICA_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_CREATE_REPLICA_DISK_SNAPSHOTS = "CREATE_REPLICA_DISK_SNAPSHOTS"
 TASK_TYPE_DELETE_REPLICA_DISK_SNAPSHOTS = "DELETE_REPLICA_DISK_SNAPSHOTS"
 TASK_TYPE_DELETE_REPLICA_DISK_SNAPSHOTS = "DELETE_REPLICA_DISK_SNAPSHOTS"
 TASK_TYPE_RESTORE_REPLICA_DISK_SNAPSHOTS = "RESTORE_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
     safe = True
 
 
 
 
+class OSMorphingToolsNotFound(NotFound):
+    message = _("Couldn't find any morphing tools for this OS.")
+
+
 class FileNotFound(NotFound):
 class FileNotFound(NotFound):
     message = _("File %(file_path)s could not be found.")
     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
     __metaclass__ = abc.ABCMeta
 
 
     def __init__(
     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._conn = conn
         self._os_root_dir = os_root_dir
         self._os_root_dir = os_root_dir
         self._os_root_device = os_root_device
         self._os_root_device = os_root_device
-        self._hypervisor = hypervisor
-        self._platform = platform
         self._distro = None
         self._distro = None
         self._version = None
         self._version = None
+        self._hypervisor = hypervisor
         self._event_manager = event_manager
         self._event_manager = event_manager
 
 
     def check_os(self):
     def check_os(self):
@@ -50,10 +49,16 @@ class BaseOSMorphingTools(object):
     def install_packages(self, package_names):
     def install_packages(self, package_names):
         pass
         pass
 
 
+    def post_packages_install(self, package_names):
+        pass
+
+    def pre_packages_uninstall(self, package_names):
+        pass
+
     def uninstall_packages(self, package_names):
     def uninstall_packages(self, package_names):
         pass
         pass
 
 
-    def post_packages_install(self, package_names):
+    def post_packages_uninstall(self, package_names):
         pass
         pass
 
 
 
 
@@ -62,17 +67,15 @@ class BaseLinuxOSMorphingTools(BaseOSMorphingTools):
 
 
     _packages = {}
     _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__(
         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
         self._ssh = conn
 
 
     def get_packages(self):
     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(
         add = [p[0] for p in itertools.chain.from_iterable(
                [l for k, l in self._packages.items() if k in k_add])]
                [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):
     def post_packages_install(self, package_names):
         self._restore_resolv_conf()
         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):
     def _test_path(self, chroot_path):
         path = os.path.join(self._os_root_dir, chroot_path)
         path = os.path.join(self._os_root_dir, chroot_path)
         return utils.test_ssh_path(self._ssh, path)
         return utils.test_ssh_path(self._ssh, path)
@@ -142,7 +151,7 @@ class BaseLinuxOSMorphingTools(BaseOSMorphingTools):
     def _get_config(self, config_content):
     def _get_config(self, config_content):
         config = {}
         config = {}
         for config_line in config_content.split('\n'):
         for config_line in config_content.split('\n'):
-            m = re.match('(.*)="?([^"]*)"?', config_line)
+            m = re.match('(.*)=(?:"|\')?([^"\']*)(?:"|\')?', config_line)
             if m:
             if m:
                 name, value = m.groups()
                 name, value = m.groups()
                 config[name] = value
                 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
 import os
 
 
-from coriolis import constants
 from coriolis.osmorphing import base
 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):
     def _check_os(self):
         lsb_release_path = "etc/lsb-release"
         lsb_release_path = "etc/lsb-release"
         debian_version_path = "etc/debian_version"
         debian_version_path = "etc/debian_version"
@@ -37,7 +30,8 @@ class DebianMorphingTools(base.BaseLinuxOSMorphingTools):
                            interfaces_path)
                            interfaces_path)
 
 
     def pre_packages_install(self, package_names):
     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:
         if package_names:
             self._event_manager.progress_update("Updating packages list")
             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 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
 from coriolis.osmorphing.osmount import factory as osmount_factory
 
 
 LOG = logging.getLogger(__name__)
 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_mount_tools = osmount_factory.get_os_mount_tools(
         os_type, connection_info, event_manager, ignore_devices)
         os_type, connection_info, event_manager, ignore_devices)
 
 
     event_manager.progress_update("Discovering and mounting OS partitions")
     event_manager.progress_update("Discovering and mounting OS partitions")
     os_root_dir, other_mounted_dirs, os_root_dev = os_mount_tools.mount_os()
     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()
     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(
         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")
     event_manager.progress_update("Dismounting OS partitions")
     os_mount_tools.dismount_os(other_mounted_dirs + [os_root_dir])
     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
 import re
 
 
-from coriolis import constants
-from coriolis import exception
 from coriolis.osmorphing import redhat
 from coriolis.osmorphing import redhat
 
 
 
 
-class OracleMorphingTools(redhat.RedHatMorphingTools):
+class BaseOracleMorphingTools(redhat.BaseRedHatMorphingTools):
     def _check_os(self):
     def _check_os(self):
         oracle_release_path = "etc/oracle-release"
         oracle_release_path = "etc/oracle-release"
         if self._test_path(oracle_release_path):
         if self._test_path(oracle_release_path):
@@ -19,47 +17,6 @@ class OracleMorphingTools(redhat.RedHatMorphingTools):
                 distro, version = m.groups()
                 distro, version = m.groups()
                 return (distro, version)
                 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):
     def _run_dracut(self):
         self._run_dracut_base('kernel')
         self._run_dracut_base('kernel')
         self._run_dracut_base('kernel-uek')
         self._run_dracut_base('kernel-uek')

+ 9 - 63
coriolis/osmorphing/redhat.py

@@ -6,10 +6,7 @@ import re
 import uuid
 import uuid
 
 
 from oslo_log import log as logging
 from oslo_log import log as logging
-import yaml
 
 
-from coriolis import constants
-from coriolis import exception
 from coriolis.osmorphing import base
 from coriolis.osmorphing import base
 from coriolis import utils
 from coriolis import utils
 
 
@@ -22,20 +19,16 @@ RELEASE_FEDORA = "Fedora"
 DEFAULT_CLOUD_USER = "cloud-user"
 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"
     _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):
     def _check_os(self):
         redhat_release_path = "etc/redhat-release"
         redhat_release_path = "etc/redhat-release"
         if self._test_path(redhat_release_path):
         if self._test_path(redhat_release_path):
@@ -137,7 +130,7 @@ class RedHatMorphingTools(base.BaseLinuxOSMorphingTools):
             self._exec_cmd_chroot(yum_cmd)
             self._exec_cmd_chroot(yum_cmd)
 
 
     def install_packages(self, package_names):
     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):
     def uninstall_packages(self, package_names):
         self._yum_uninstall(package_names)
         self._yum_uninstall(package_names)
@@ -158,16 +151,6 @@ class RedHatMorphingTools(base.BaseLinuxOSMorphingTools):
                     "dracut -f /boot/initramfs-%(version)s.img %(version)s" %
                     "dracut -f /boot/initramfs-%(version)s.img %(version)s" %
                     {"version": kernel_version})
                     {"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):
     def _set_network_nozeroconf_config(self):
         network_cfg_file = "etc/sysconfig/network"
         network_cfg_file = "etc/sysconfig/network"
         network_cfg = self._read_config_file(network_cfg_file,
         network_cfg = self._read_config_file(network_cfg_file,
@@ -175,23 +158,6 @@ class RedHatMorphingTools(base.BaseLinuxOSMorphingTools):
         network_cfg["NOZEROCONF"] = "yes"
         network_cfg["NOZEROCONF"] = "yes"
         self._write_config_file(network_cfg_file, network_cfg)
         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):
     def _write_config_file(self, chroot_path, config_data):
         content = self._get_config_file_content(config_data)
         content = self._get_config_file_content(config_data)
         self._write_file_sudo(chroot_path, content)
         self._write_file_sudo(chroot_path, content)
@@ -207,23 +173,3 @@ class RedHatMorphingTools(base.BaseLinuxOSMorphingTools):
 
 
     def _set_selinux_autorelabel(self):
     def _set_selinux_autorelabel(self):
         self._exec_cmd_chroot("touch /.autorelabel")
         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
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
 # All Rights Reserved.
 
 
-import os
 import re
 import re
 
 
-from coriolis import constants
 from coriolis.osmorphing import base
 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):
     def _check_os(self):
         os_release = self._get_os_release()
         os_release = self._get_os_release()
         name = os_release.get("NAME")
         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")
             pretty_name = os_release.get("PRETTY_NAME")
             if name == "openSUSE Tumbleweed":
             if name == "openSUSE Tumbleweed":
                 self._version_id = None
                 self._version_id = None
@@ -40,32 +33,6 @@ class SUSEMorphingTools(base.BaseLinuxOSMorphingTools):
         # TODO: add networking support
         # TODO: add networking support
         pass
         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):
     def _run_dracut(self):
         package_names = self._exec_cmd_chroot(
         package_names = self._exec_cmd_chroot(
             'rpm -q kernel-default').decode().split('\n')[:-1]
             'rpm -q kernel-default').decode().split('\n')[:-1]
@@ -86,11 +53,6 @@ class SUSEMorphingTools(base.BaseLinuxOSMorphingTools):
         except:
         except:
             return False
             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):
     def install_packages(self, package_names):
         self._exec_cmd_chroot(
         self._exec_cmd_chroot(
             'zypper --non-interactive install %s' % " ".join(package_names))
             'zypper --non-interactive install %s' % " ".join(package_names))
@@ -98,8 +60,3 @@ class SUSEMorphingTools(base.BaseLinuxOSMorphingTools):
     def uninstall_packages(self, package_names):
     def uninstall_packages(self, package_names):
         self._exec_cmd_chroot(
         self._exec_cmd_chroot(
             'zypper --non-interactive remove %s' % " ".join(package_names))
             '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
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
 # All Rights Reserved.
 
 
-from coriolis import constants
 from coriolis.osmorphing import debian
 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):
     def _check_os(self):
         config = self._read_config_file("etc/lsb-release", check_exists=True)
         config = self._read_config_file("etc/lsb-release", check_exists=True)
         dist_id = config.get('DISTRIB_ID')
         dist_id = config.get('DISTRIB_ID')

+ 1 - 210
coriolis/osmorphing/windows.py

@@ -6,31 +6,11 @@ import re
 import uuid
 import uuid
 
 
 from distutils import version
 from distutils import version
-from oslo_config import cfg
 from oslo_log import log as logging
 from oslo_log import log as logging
 
 
-from coriolis import constants
 from coriolis import exception
 from coriolis import exception
 from coriolis.osmorphing import base
 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__)
 LOG = logging.getLogger(__name__)
 
 
 SERVICE_START_AUTO = 2
 SERVICE_START_AUTO = 2
@@ -38,10 +18,9 @@ SERVICE_START_MANUAL = 3
 SERVICE_START_DISABLED = 4
 SERVICE_START_DISABLED = 4
 
 
 SERVICE_PATH_FORMAT = "HKLM:\\%s\\ControlSet001\\Services\\%s"
 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):
     def _check_os(self):
         try:
         try:
             (self._version_number,
             (self._version_number,
@@ -52,16 +31,6 @@ class WindowsMorphingTools(base.BaseOSMorphingTools):
         except exception.CoriolisException as ex:
         except exception.CoriolisException as ex:
             LOG.debug("Exception during OS detection: %s", 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):
     def set_net_config(self, nics_info, dhcp):
         # TODO: implement
         # TODO: implement
         pass
         pass
@@ -157,73 +126,6 @@ class WindowsMorphingTools(base.BaseOSMorphingTools):
         self._conn.exec_ps_command("Dismount-DiskImage '%s'" % path,
         self._conn.exec_ps_command("Dismount-DiskImage '%s'" % path,
                                    ignore_stdout=True)
                                    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):
     def _expand_archive(self, path, destination):
         LOG.info("Expanding archive \"%(path)s\" in \"%(destination)s\"",
         LOG.info("Expanding archive \"%(path)s\" in \"%(destination)s\"",
                  {"path": path, "destination": destination})
                  {"path": path, "destination": destination})
@@ -286,114 +188,3 @@ class WindowsMorphingTools(base.BaseOSMorphingTools):
              "service_account": service_account,
              "service_account": service_account,
              "start_mode": start_mode},
              "start_mode": start_mode},
             ignore_stdout=True)
             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.
 # All Rights Reserved.
 
 
 import abc
 import abc
+import itertools
 
 
+from oslo_log import log as logging
+
+from coriolis import exception
 from coriolis import schemas
 from coriolis import schemas
 
 
+LOG = logging.getLogger(__name__)
+
 
 
 class BaseProvider(object):
 class BaseProvider(object):
     __metaclass__ = abc.ABCMeta
     __metaclass__ = abc.ABCMeta
@@ -30,6 +36,9 @@ class BaseProvider(object):
                 "Error validating provider '%s' connection "
                 "Error validating provider '%s' connection "
                 "info: %s" % str(ex)) from ex
                 "info: %s" % str(ex)) from ex
 
 
+    def get_os_morphing_tools(self, conn, osmorphing_info):
+        raise exception.OSMorphingToolsNotFound()
+
 
 
 class BaseImportProvider(BaseProvider):
 class BaseImportProvider(BaseProvider):
     __metaclass__ = abc.ABCMeta
     __metaclass__ = abc.ABCMeta
@@ -60,6 +69,16 @@ class BaseImportProvider(BaseProvider):
         """
         """
         pass
         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):
 class BaseReplicaImportProvider(BaseProvider):
     __metaclass__ = abc.ABCMeta
     __metaclass__ = abc.ABCMeta
@@ -70,6 +89,16 @@ class BaseReplicaImportProvider(BaseProvider):
                                 volumes_info, clone_disks):
                                 volumes_info, clone_disks):
         pass
         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
     @abc.abstractmethod
     def deploy_replica_disks(self, ctxt, connection_info, target_environment,
     def deploy_replica_disks(self, ctxt, connection_info, target_environment,
                              instance_name, export_info, volumes_info):
                              instance_name, export_info, volumes_info):
@@ -142,3 +171,22 @@ class BaseReplicaExportProvider(BaseProvider):
     @abc.abstractmethod
     @abc.abstractmethod
     def shutdown_instance(self, ctxt, connection_info, instance_name):
     def shutdown_instance(self, ctxt, connection_info, instance_name):
         pass
         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
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
 # All Rights Reserved.
 
 
+from oslo_config import cfg
+
 from coriolis import constants
 from coriolis import constants
 from coriolis import exception
 from coriolis import exception
 from coriolis.providers import base
 from coriolis.providers import base
 from coriolis import utils
 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 = {
 PROVIDER_TYPE_MAP = {
     constants.PROVIDER_TYPE_EXPORT: base.BaseExportProvider,
     constants.PROVIDER_TYPE_EXPORT: base.BaseExportProvider,
@@ -23,7 +26,7 @@ PROVIDER_TYPE_MAP = {
 
 
 
 
 def get_provider(platform_name, provider_type, event_handler):
 def get_provider(platform_name, provider_type, event_handler):
-    for provider in PROVIDERS:
+    for provider in CONF.providers:
         cls = utils.load_class(provider)
         cls = utils.load_class(provider)
         if (cls.platform == platform_name and
         if (cls.platform == platform_name and
                 PROVIDER_TYPE_MAP[provider_type] in cls.__bases__):
                 PROVIDER_TYPE_MAP[provider_type] in cls.__bases__):

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

@@ -1,7 +1,6 @@
 # Copyright 2016 Cloudbase Solutions Srl
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
 # All Rights Reserved.
 
 
-import collections
 import math
 import math
 import os
 import os
 import tempfile
 import tempfile
@@ -19,9 +18,16 @@ from coriolis import constants
 from coriolis import events
 from coriolis import events
 from coriolis import exception
 from coriolis import exception
 from coriolis import keystone
 from coriolis import keystone
-from coriolis.osmorphing import manager as osmorphing_manager
 from coriolis.providers import base
 from coriolis.providers import base
 from coriolis.providers.openstack import common
 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 schemas
 from coriolis import utils
 from coriolis import utils
 
 
@@ -359,73 +365,59 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
         return volume
         return volume
 
 
     def _get_import_config(self, target_environment, os_type):
     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)
             "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)
             "disk_format", CONF.openstack_migration_provider.disk_format)
-        config.container_format = target_environment.get(
+        config["container_format"] = target_environment.get(
             "container_format",
             "container_format",
             CONF.openstack_migration_provider.container_format)
             CONF.openstack_migration_provider.container_format)
-        config.hypervisor_type = target_environment.get(
+        config["hypervisor_type"] = target_environment.get(
             "hypervisor_type",
             "hypervisor_type",
             CONF.openstack_migration_provider.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)
             "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",
             "delete_disks_on_vm_termination",
             CONF.openstack_migration_provider.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",
             "migr_image_name",
             target_environment.get("migr_image_name_map", {}).get(
             target_environment.get("migr_image_name_map", {}).get(
                 os_type,
                 os_type,
                 CONF.openstack_migration_provider.migr_image_name_map.get(
                 CONF.openstack_migration_provider.migr_image_name_map.get(
                     os_type)))
                     os_type)))
-        config.migr_flavor_name = target_environment.get(
+        config["migr_flavor_name"] = target_environment.get(
             "migr_flavor_name",
             "migr_flavor_name",
             CONF.openstack_migration_provider.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",
             "migr_fip_pool_name",
-            config.fip_pool_name or
+            config["fip_pool_name"] or
             CONF.openstack_migration_provider.fip_pool_name)
             CONF.openstack_migration_provider.fip_pool_name)
-        config.migr_network_name = target_environment.get(
+        config["migr_network_name"] = target_environment.get(
             "migr_network_name",
             "migr_network_name",
             CONF.openstack_migration_provider.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(
             raise exception.CoriolisException(
                 "No matching migration image type found")
                 "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(
                 raise exception.CoriolisException(
                     'If "migr_network_name" is not provided, "network_map" '
                     'If "migr_network_name" is not provided, "network_map" '
                     'must contain exactly one entry')
                     '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
         return config
 
 
@@ -440,13 +432,13 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
                      destination_format))
                      destination_format))
             utils.convert_disk_format(
             utils.convert_disk_format(
                 source_path, destination_path, destination_format)
                 source_path, destination_path, destination_format)
-        except Exception as ex:
+        except Exception:
             utils.ignore_exceptions(os.remove)(destination_path)
             utils.ignore_exceptions(os.remove)(destination_path)
             raise
             raise
 
 
     def _create_images_and_volumes(self, glance, nova, cinder, config,
     def _create_images_and_volumes(self, glance, nova, cinder, config,
                                    disks_info):
                                    disks_info):
-        if not config.glance_upload:
+        if not config["glance_upload"]:
             raise exception.CoriolisException(
             raise exception.CoriolisException(
                 "Glance upload is currently required for migrations")
                 "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)
             disk_file_info = utils.get_disk_info(disk_path)
 
 
             target_disk_path = 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" % (
                 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(
                 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)
                 disk_file_info = utils.get_disk_info(target_disk_path)
 
 
                 LOG.info(
                 LOG.info(
                     "Succesfully converted '%s' to %s as '%s'. "
                     "Succesfully converted '%s' to %s as '%s'. "
                     "Removing original path." % (
                     "Removing original path." % (
-                        disk_path, config.target_disk_format,
+                        disk_path, config["target_disk_format"],
                         target_disk_path))
                         target_disk_path))
                 os.remove(disk_path)
                 os.remove(disk_path)
 
 
@@ -482,8 +475,8 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
             image = common.create_image(
             image = common.create_image(
                 glance, common.get_unique_name(),
                 glance, common.get_unique_name(),
                 target_disk_path, disk_format,
                 target_disk_path, disk_format,
-                config.container_format,
-                config.hypervisor_type)
+                config["container_format"],
+                config["hypervisor_type"])
             images.append(image)
             images.append(image)
 
 
             self._event_manager.progress_update(
             self._event_manager.progress_update(
@@ -498,7 +491,7 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
                 "Creating Cinder volume")
                 "Creating Cinder volume")
 
 
             volume_type = self._get_volume_type_for_disk(
             volume_type = self._get_volume_type_for_disk(
-                cinder, disk_info, config.storage_map)
+                cinder, disk_info, config["storage_map"])
 
 
             volume = common.create_volume(
             volume = common.create_volume(
                 cinder, virtual_disk_size, common.get_unique_name(),
                 cinder, virtual_disk_size, common.get_unique_name(),
@@ -531,7 +524,7 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
         else:
         else:
             # else if unspecified, just use the default volume type:
             # else if unspecified, just use the default volume type:
             LOG.debug("No 'storage_backend_identifier' provided for disk %s. "
             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
             dest_stor = default
 
 
         # ensure the volume type exists:
         # ensure the volume type exists:
@@ -553,11 +546,11 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
             origin_network_name = nic_info.get("network_name")
             origin_network_name = nic_info.get("network_name")
             if not origin_network_name:
             if not origin_network_name:
                 self._event_manager.progress_update(
                 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"))
                     nic_info.get("name"))
                 continue
                 continue
 
 
-            network_name = config.network_map.get(origin_network_name)
+            network_name = config["network_map"].get(origin_network_name)
             if not network_name:
             if not network_name:
                 raise exception.CoriolisException(
                 raise exception.CoriolisException(
                     "Network not mapped in network_map: %s" %
                     "Network not mapped in network_map: %s" %
@@ -576,16 +569,17 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
         return volumes
         return volumes
 
 
     @utils.retry_on_error()
     @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 % {
             new_volume_name = VOLUME_NAME_FORMAT % {
                 "instance_name": instance_name, "num": i + 1}
                 "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()
     @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
         # 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':
             if not volume.bootable or volume.bootable == 'false':
                 cinder.volumes.set_bootable(volume, True)
                 cinder.volumes.set_bootable(volume, True)
 
 
@@ -603,9 +597,31 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
             volumes.append(volume)
             volumes.append(volume)
         return volumes
         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)
         session = keystone.create_keystone_session(ctxt, connection_info)
 
 
         glance_api_version = connection_info.get("image_api_version",
         glance_api_version = connection_info.get("image_api_version",
@@ -625,7 +641,6 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
 
 
         images = []
         images = []
         volumes = []
         volumes = []
-        ports = []
 
 
         try:
         try:
             if not volumes_info:
             if not volumes_info:
@@ -642,67 +657,93 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
                         cinder, volumes_info)
                         cinder, volumes_info)
 
 
             migr_resources = self._deploy_migration_resources(
             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", [])
             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(
             self._event_manager.progress_update(
                 "Creating migrated instance")
                 "Creating migrated instance")
             self._create_target_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:
         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")
             self._event_manager.progress_update("Deleting Neutron ports")
             for port in ports:
             for port in ports:
                 @utils.ignore_exceptions
                 @utils.ignore_exceptions
@@ -710,20 +751,39 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
                 def _del_port():
                 def _del_port():
                     neutron.delete_port(port["id"])
                     neutron.delete_port(port["id"])
                 _del_port()
                 _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):
     def _get_osmorphing_hypervisor_type(self, hypervisor_type):
         if (hypervisor_type and
         if (hypervisor_type and
@@ -735,16 +795,16 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
     @utils.retry_on_error(max_attempts=10, sleep_seconds=30,
     @utils.retry_on_error(max_attempts=10, sleep_seconds=30,
                           terminal_exceptions=[exception.NotFound])
                           terminal_exceptions=[exception.NotFound])
     def _create_target_instance(self, nova, flavor_name, instance_name,
     def _create_target_instance(self, nova, flavor_name, instance_name,
-                                keypair_name, ports, volumes,
+                                keypair_name, ports, volume_ids,
                                 ephemeral_volumes=False):
                                 ephemeral_volumes=False):
         flavor = common.get_flavor(nova, flavor_name)
         flavor = common.get_flavor(nova, flavor_name)
 
 
         block_device_mapping = {}
         block_device_mapping = {}
-        for i, volume in enumerate(volumes):
+        for i, volume_id in enumerate(volume_ids):
             # Delete volume on termination
             # Delete volume on termination
             block_device_mapping[
             block_device_mapping[
                 'vd%s' % chr(ord('a') + i)] = "%s:volume::%s" % (
                 '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]
         nics = [{'port-id': p['id']} for p in ports]
 
 
@@ -766,12 +826,38 @@ class ImportProvider(base.BaseImportProvider, base.BaseReplicaImportProvider):
                 nova.servers.delete(instance)
                 nova.servers.delete(instance)
             raise
             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,
     def deploy_replica_instance(self, ctxt, connection_info,
                                 target_environment, instance_name, export_info,
                                 target_environment, instance_name, export_info,
                                 volumes_info, clone_disks):
                                 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):
     def _update_existing_disk_volumes(self, cinder, disks_info, volumes_info):
         for disk_info in disks_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)
         config = self._get_import_config(target_environment, os_type)
 
 
         migr_resources = self._deploy_migration_resources(
         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:
         try:
             for i, volume_info in enumerate(volumes_info):
             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
 import abc
 
 
 from coriolis import secrets
 from coriolis import secrets
+from coriolis import utils
 
 
+from oslo_config import cfg
 from oslo_log import log as logging
 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__)
 LOG = logging.getLogger(__name__)
 
 
 
 
@@ -24,3 +34,21 @@ def get_connection_info(ctxt, data):
         LOG.info("Retrieving connection info from secret: %s", secret_ref)
         LOG.info("Retrieving connection info from secret: %s", secret_ref)
         connection_info = secrets.get_secret(ctxt, secret_ref)
         connection_info = secrets.get_secret(ctxt, secret_ref)
     return connection_info
     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 constants
 from coriolis import exception
 from coriolis import exception
 from coriolis.tasks import migration_tasks
 from coriolis.tasks import migration_tasks
+from coriolis.tasks import osmorphing_tasks
 from coriolis.tasks import replica_tasks
 from coriolis.tasks import replica_tasks
 
 
 _TASKS_MAP = {
 _TASKS_MAP = {
@@ -11,6 +12,12 @@ _TASKS_MAP = {
         migration_tasks.ExportInstanceTask,
         migration_tasks.ExportInstanceTask,
     constants.TASK_TYPE_IMPORT_INSTANCE:
     constants.TASK_TYPE_IMPORT_INSTANCE:
         migration_tasks.ImportInstanceTask,
         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:
     constants.TASK_TYPE_GET_INSTANCE_INFO:
         replica_tasks.GetInstanceInfoTask,
         replica_tasks.GetInstanceInfoTask,
     constants.TASK_TYPE_REPLICATE_DISKS:
     constants.TASK_TYPE_REPLICATE_DISKS:
@@ -31,6 +38,10 @@ _TASKS_MAP = {
         replica_tasks.DeleteReplicaSourceResourcesTask,
         replica_tasks.DeleteReplicaSourceResourcesTask,
     constants.TASK_TYPE_DEPLOY_REPLICA_INSTANCE:
     constants.TASK_TYPE_DEPLOY_REPLICA_INSTANCE:
         replica_tasks.DeployReplicaInstanceTask,
         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:
     constants.TASK_TYPE_CREATE_REPLICA_DISK_SNAPSHOTS:
         replica_tasks.CreateReplicaDiskSnapshotsTask,
         replica_tasks.CreateReplicaDiskSnapshotsTask,
     constants.TASK_TYPE_DELETE_REPLICA_DISK_SNAPSHOTS:
     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)
             destination["type"], constants.PROVIDER_TYPE_IMPORT, event_handler)
         connection_info = base.get_connection_info(ctxt, destination)
         connection_info = base.get_connection_info(ctxt, destination)
 
 
-        provider.import_instance(
+        import_info = provider.import_instance(
             ctxt, connection_info, target_environment, instance, export_info)
             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
         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.providers import factory as providers_factory
 from coriolis import schemas
 from coriolis import schemas
 from coriolis.tasks import base
 from coriolis.tasks import base
-from coriolis import utils
 
 
-from oslo_config import cfg
 from oslo_log import log as logging
 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__)
 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):
 def _get_volumes_info(task_info):
     volumes_info = task_info.get("volumes_info")
     volumes_info = task_info.get("volumes_info")
     if not volumes_info:
     if not volumes_info:
@@ -91,10 +62,10 @@ class ReplicateDisksTask(base.TaskRunner):
 
 
         volumes_info = _get_volumes_info(task_info)
         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"])
             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"])
             task_info["migr_target_connection_info"])
 
 
         incremental = task_info.get("incremental", True)
         incremental = task_info.get("incremental", True)
@@ -161,7 +132,7 @@ class DeployReplicaSourceResourcesTask(base.TaskRunner):
 
 
         task_info["migr_source_resources"] = replica_resources_info[
         task_info["migr_source_resources"] = replica_resources_info[
             "migr_resources"]
             "migr_resources"]
-        migr_connection_info = _marshal_migr_conn_info(
+        migr_connection_info = base.marshal_migr_conn_info(
             replica_resources_info["connection_info"])
             replica_resources_info["connection_info"])
         task_info["migr_source_connection_info"] = migr_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[
         task_info["migr_target_resources"] = replica_resources_info[
             "migr_resources"]
             "migr_resources"]
 
 
-        migr_connection_info = _marshal_migr_conn_info(
+        migr_connection_info = base.marshal_migr_conn_info(
             replica_resources_info["connection_info"])
             replica_resources_info["connection_info"])
         task_info["migr_target_connection_info"] = migr_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)
         clone_disks = task_info.get("clone_disks", True)
         LOG.debug("Clone disks: %s", clone_disks)
         LOG.debug("Clone disks: %s", clone_disks)
 
 
-        provider.deploy_replica_instance(
+        import_info = provider.deploy_replica_instance(
             ctxt, connection_info, target_environment, instance,
             ctxt, connection_info, target_environment, instance,
             export_info, volumes_info, clone_disks)
             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
         return task_info