Alessandro Pilotti пре 10 година
родитељ
комит
f260ad8102

+ 3 - 0
coriolis/constants.py

@@ -31,3 +31,6 @@ PLATFORM_VMWARE_VSPHERE = "vmware_vsphere"
 TASK_EVENT_INFO = "INFO"
 TASK_EVENT_INFO = "INFO"
 TASK_EVENT_WARNING = "WARNING"
 TASK_EVENT_WARNING = "WARNING"
 TASK_EVENT_ERROR = "ERROR"
 TASK_EVENT_ERROR = "ERROR"
+
+OS_TYPE_LINUX = "linux"
+OS_TYPE_WINDOWS = "windows"

+ 4 - 0
coriolis/exception.py

@@ -233,3 +233,7 @@ class NotSupportedOperation(Invalid):
 
 
 class TaskProcessException(CoriolisException):
 class TaskProcessException(CoriolisException):
     pass
     pass
+
+
+class OperatingSystemNotFound(NotFound):
+    pass

+ 59 - 47
coriolis/osmorphing/base.py

@@ -9,10 +9,8 @@ from coriolis import utils
 class BaseOSMorphingTools(object):
 class BaseOSMorphingTools(object):
     __metaclass__ = abc.ABCMeta
     __metaclass__ = abc.ABCMeta
 
 
-    _packages = {}
-
-    def __init__(self, ssh, os_root_dir, hypervisor, platform, event_manager):
-        self._ssh = ssh
+    def __init__(self, conn, os_root_dir, hypervisor, platform, event_manager):
+        self._conn = conn
         self._os_root_dir = os_root_dir
         self._os_root_dir = os_root_dir
         self._hypervisor = hypervisor
         self._hypervisor = hypervisor
         self._platform = platform
         self._platform = platform
@@ -20,6 +18,63 @@ class BaseOSMorphingTools(object):
         self._version = None
         self._version = None
         self._event_manager = event_manager
         self._event_manager = event_manager
 
 
+    def check_os(self):
+        if not self._distro:
+            os_info = self._check_os()
+            if os_info:
+                self._distro, self._version = os_info
+        if self._distro:
+            return self._distro, self._version
+
+    @abc.abstractmethod
+    def _check_os(self):
+        pass
+
+    @abc.abstractmethod
+    def set_net_config(self, nics_info, dhcp):
+        pass
+
+    def get_packages(self):
+        return [], []
+
+    def pre_packages_install(self):
+        pass
+
+    def install_packages(self, package_names):
+        pass
+
+    def uninstall_packages(self, package_names):
+        pass
+
+    def post_packages_install(self):
+        pass
+
+
+class BaseLinuxOSMorphingTools(BaseOSMorphingTools):
+    __metaclass__ = abc.ABCMeta
+
+    _packages = {}
+
+    def __init__(self, conn, os_root_dir, hypervisor, platform, event_manager):
+        super(BaseLinuxOSMorphingTools, self).__init__(
+            conn, os_root_dir, hypervisor, platform, event_manager)
+        self._ssh = conn
+
+    def get_packages(self):
+        k_add = [(h, p) for (h, p) in self._packages.keys() if
+                 (h is None or h == self._hypervisor) and
+                 (p is None or p == self._platform)]
+
+        add = [p[0] for p in itertools.chain.from_iterable(
+               [l for k, l in self._packages.items() if k in k_add])]
+
+        k_remove = set(self._packages.keys()) - set(k_add)
+        remove = [p[0] for p in itertools.chain.from_iterable(
+                  [l for k, l in self._packages.items() if k in k_remove])
+                  if p[1]]
+
+        return add, remove
+
     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)
@@ -55,46 +110,3 @@ class BaseOSMorphingTools(object):
         self._write_file(tmp_file, content)
         self._write_file(tmp_file, content)
         self._exec_cmd_chroot("cp /%s /%s" % (tmp_file, chroot_path))
         self._exec_cmd_chroot("cp /%s /%s" % (tmp_file, chroot_path))
         self._exec_cmd_chroot("rm /%s" % tmp_file)
         self._exec_cmd_chroot("rm /%s" % tmp_file)
-
-    def check_os(self):
-        if not self._distro:
-            os_info = self._check_os()
-            if os_info:
-                self._distro, self._version = os_info
-        if self._distro:
-            return self._distro, self._version
-
-    @abc.abstractmethod
-    def _check_os(self):
-        pass
-
-    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)]
-
-        add = [p[0] for p in itertools.chain.from_iterable(
-               [l for k, l in self._packages.items() if k in k_add])]
-
-        k_remove = set(self._packages.keys()) - set(k_add)
-        remove = [p[0] for p in itertools.chain.from_iterable(
-                  [l for k, l in self._packages.items() if k in k_remove])
-                  if p[1]]
-
-        return add, remove
-
-    @abc.abstractmethod
-    def set_net_config(self, nics_info, dhcp):
-        pass
-
-    def pre_packages_install(self):
-        pass
-
-    def install_packages(self, package_names):
-        pass
-
-    def uninstall_packages(self, package_names):
-        pass
-
-    def post_packages_install(self):
-        pass

+ 1 - 1
coriolis/osmorphing/debian.py

@@ -5,7 +5,7 @@ from coriolis import constants
 from coriolis.osmorphing import base
 from coriolis.osmorphing import base
 
 
 
 
-class DebianMorphingTools(base.BaseOSMorphingTools):
+class DebianMorphingTools(base.BaseLinuxOSMorphingTools):
     _packages = {
     _packages = {
         (constants.HYPERVISOR_VMWARE, None): [("open-vm-tools", True)],
         (constants.HYPERVISOR_VMWARE, None): [("open-vm-tools", True)],
         # TODO: add cloud-initramfs-growroot
         # TODO: add cloud-initramfs-growroot

+ 17 - 6
coriolis/osmorphing/factory.py

@@ -1,17 +1,28 @@
+import itertools
+
+from coriolis import constants
 from coriolis import exception
 from coriolis import exception
 from coriolis.osmorphing import debian
 from coriolis.osmorphing import debian
 from coriolis.osmorphing import redhat
 from coriolis.osmorphing import redhat
 from coriolis.osmorphing import ubuntu
 from coriolis.osmorphing import ubuntu
+from coriolis.osmorphing import windows
 
 
 
 
-def get_os_morphing_tools(ssh, os_root_dir, target_hypervisor,
+def get_os_morphing_tools(conn, os_type, os_root_dir, target_hypervisor,
                           target_platform, event_manager):
                           target_platform, event_manager):
-    os_morphing_tools_clss = [debian.DebianMorphingTools,
-                              ubuntu.UbuntuMorphingTools,
-                              redhat.RedHatMorphingTools]
+    os_morphing_tools_clss = {
+        constants.OS_TYPE_LINUX: [debian.DebianMorphingTools,
+                                  ubuntu.UbuntuMorphingTools,
+                                  redhat.RedHatMorphingTools],
+        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:
-        tools = cls(ssh, os_root_dir, target_hypervisor, target_platform,
+    for cls in os_morphing_tools_clss.get(
+            os_type, itertools.chain(*os_morphing_tools_clss.values())):
+        tools = cls(conn, os_root_dir, target_hypervisor, target_platform,
                     event_manager)
                     event_manager)
         os_info = tools.check_os()
         os_info = tools.check_os()
         if os_info:
         if os_info:

+ 7 - 16
coriolis/osmorphing/manager.py

@@ -1,5 +1,4 @@
 from oslo_log import log as logging
 from oslo_log import log as logging
-import paramiko
 
 
 from coriolis.osmorphing import factory as osmorphing_factory
 from coriolis.osmorphing import factory as osmorphing_factory
 from coriolis.osmorphing.osmount import factory as osmount_factory
 from coriolis.osmorphing.osmount import factory as osmount_factory
@@ -8,26 +7,18 @@ from coriolis import utils
 LOG = logging.getLogger(__name__)
 LOG = logging.getLogger(__name__)
 
 
 
 
-def morph_image(connection_info, target_hypervisor, target_platform,
+def morph_image(connection_info, os_type, target_hypervisor, target_platform,
                 volume_devs, nics_info, event_manager):
                 volume_devs, nics_info, event_manager):
-    (ip, port, username, pkey) = connection_info
-
-    LOG.info("Waiting for connectivity on host: %(ip)s:%(port)s",
-             {"ip": ip, "port": port})
-    utils.wait_for_port_connectivity(ip, port)
-
-    event_manager.progress_update(
-        "Connecting to host: %(ip)s:%(port)s" % {"ip": ip, "port": port})
-    ssh = paramiko.SSHClient()
-    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
-    ssh.connect(hostname=ip, port=port, username=username, pkey=pkey)
-
-    os_mount_tools = osmount_factory.get_os_mount_tools(ssh, event_manager)
+    os_mount_tools = osmount_factory.get_os_mount_tools(
+        os_type, connection_info, event_manager)
 
 
     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_mount_tools.mount_os(volume_devs)
     os_root_dir, other_mounted_dirs = os_mount_tools.mount_os(volume_devs)
+
+    conn = os_mount_tools.get_connection()
     os_morphing_tools, os_info = osmorphing_factory.get_os_morphing_tools(
     os_morphing_tools, os_info = osmorphing_factory.get_os_morphing_tools(
-        ssh, os_root_dir, target_hypervisor, target_platform, event_manager)
+        conn, os_type, os_root_dir, target_hypervisor, target_platform,
+        event_manager)
 
 
     event_manager.progress_update('OS being migrated: %s' % str(os_info))
     event_manager.progress_update('OS being migrated: %s' % str(os_info))
 
 

+ 37 - 4
coriolis/osmorphing/osmount/base.py

@@ -1,14 +1,24 @@
 import abc
 import abc
 
 
+import paramiko
+
 from coriolis import utils
 from coriolis import utils
 
 
 
 
 class BaseOSMountTools(object):
 class BaseOSMountTools(object):
     __metaclass__ = abc.ABCMeta
     __metaclass__ = abc.ABCMeta
 
 
-    def __init__(self, ssh, event_manager):
-        self._ssh = ssh
+    def __init__(self, connection_info, event_manager):
         self._event_manager = event_manager
         self._event_manager = event_manager
+        self._connect(connection_info)
+
+    @abc.abstractmethod
+    def _connect(self, connection_info):
+        pass
+
+    @abc.abstractmethod
+    def get_connection(self):
+        pass
 
 
     @abc.abstractmethod
     @abc.abstractmethod
     def check_os(self):
     def check_os(self):
@@ -22,5 +32,28 @@ class BaseOSMountTools(object):
     def dismount_os(self, dirs):
     def dismount_os(self, dirs):
         pass
         pass
 
 
-    def _exec_cmd(self, cmd):
-        return utils.exec_ssh_cmd(self._ssh, cmd)
+
+class BaseSSHOSMountTools(object):
+
+    def _connect(self, connection_info):
+        ip = connection_info["ip"]
+        port = connection_info.get("port", 22)
+        username = connection_info["username"]
+        pkey = connection_info.get("pkey")
+        password = connection_info.get("password")
+
+        LOG.info("Waiting for connectivity on host: %(ip)s:%(port)s",
+                 {"ip": ip, "port": port})
+        utils.wait_for_port_connectivity(ip, port)
+
+        self._event_manager.progress_update(
+            "Connecting to SSH host: %(ip)s:%(port)s" %
+            {"ip": ip, "port": port})
+        ssh = paramiko.SSHClient()
+        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+        ssh.connect(hostname=ip, port=port, username=username, pkey=pkey,
+                    password=password)
+        self._ssh = ssh
+
+    def get_connection(self):
+        return self._ssh

+ 13 - 4
coriolis/osmorphing/osmount/factory.py

@@ -1,12 +1,21 @@
+import itertools
+
+from coriolis import constants
 from coriolis import exception
 from coriolis import exception
 from coriolis.osmorphing.osmount import ubuntu
 from coriolis.osmorphing.osmount import ubuntu
+from coriolis.osmorphing.osmount import windows
+
 
 
+def get_os_mount_tools(os_type, connection_info, event_manager):
+    os_mount_tools = {constants.OS_TYPE_LINUX: [ubuntu.UbuntuOSMountTools],
+                      constants.OS_TYPE_WINDOWS: [windows.WindowsMountTools]}
 
 
-def get_os_mount_tools(ssh, event_manager):
-    os_mount_tools = [ubuntu.UbuntuOSMountTools]
+    if os_type and os_type not in os_mount_tools:
+        raise exception.CoriolisException("Unsupported OS type: %s" % os_type)
 
 
-    for cls in os_mount_tools:
-        tools = cls(ssh, event_manager)
+    for cls in os_mount_tools.get(os_type,
+                                  itertools.chain(*os_mount_tools.values())):
+        tools = cls(connection_info, event_manager)
         if tools.check_os():
         if tools.check_os():
             return tools
             return tools
     raise exception.CoriolisException("OS mount tools not found")
     raise exception.CoriolisException("OS mount tools not found")

+ 5 - 2
coriolis/osmorphing/osmount/ubuntu.py

@@ -6,12 +6,15 @@ from coriolis.osmorphing.osmount import base
 from coriolis import utils
 from coriolis import utils
 
 
 
 
-class UbuntuOSMountTools(base.BaseOSMountTools):
+class UbuntuOSMountTools(base.BaseSSHOSMountTools):
     def check_os(self):
     def check_os(self):
         os_info = utils.get_linux_os_info(self._ssh)
         os_info = utils.get_linux_os_info(self._ssh)
         if os_info and os_info[0] == 'Ubuntu':
         if os_info and os_info[0] == 'Ubuntu':
             return True
             return True
 
 
+    def _exec_cmd(self, cmd):
+        return utils.exec_ssh_cmd(self._ssh, cmd)
+
     def _get_vgnames(self):
     def _get_vgnames(self):
         vg_names = []
         vg_names = []
         vgscan_out_lines = self._exec_cmd(
         vgscan_out_lines = self._exec_cmd(
@@ -78,7 +81,7 @@ class UbuntuOSMountTools(base.BaseOSMountTools):
                 break
                 break
 
 
         if not os_root_dir:
         if not os_root_dir:
-            raise exception.CoriolisException("root partition not found")
+            raise exception.OperatingSystemNotFound("root partition not found")
 
 
         for dir in set(dirs).intersection(['proc', 'sys', 'dev', 'run']):
         for dir in set(dirs).intersection(['proc', 'sys', 'dev', 'run']):
             mount_dir = os.path.join(os_root_dir, dir)
             mount_dir = os.path.join(os_root_dir, dir)

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

@@ -0,0 +1,84 @@
+import os
+import re
+
+from oslo_log import log as logging
+
+from coriolis import exception
+from coriolis.osmorphing.osmount import base
+from coriolis import utils
+from coriolis import wsman
+
+LOG = logging.getLogger(__name__)
+
+
+class WindowsMountTools(base.BaseOSMountTools):
+    def _connect(self, connection_info):
+        host = connection_info["ip"]
+        port = connection_info.get("port", 5986)
+        username = connection_info["username"]
+        password = connection_info.get("password")
+        cert_pem = connection_info.get("cert_pem")
+        cert_key_pem = connection_info.get("cert_key_pem")
+        url = "https://%s:%s/wsman" % (host, port)
+
+        LOG.info("Waiting for connectivity on host: %(host)s:%(port)s",
+                 {"host": host, "port": port})
+        utils.wait_for_port_connectivity(host, port)
+
+        self._event_manager.progress_update(
+            "Connecting to WinRM host: %(host)s:%(port)s" %
+            {"host": host, "port": port})
+
+        conn = wsman.WSManConnection()
+        conn.connect(url=url, username=username, password=password,
+                     cert_pem=cert_pem, cert_key_pem=cert_key_pem)
+        self._conn = conn
+
+    def get_connection(self):
+        return self._conn
+
+    def check_os(self):
+        try:
+            version_info = self._conn.exec_ps_command(
+                "(get-ciminstance Win32_OperatingSystem).Caption")
+            LOG.debug("Windows version: %s", version_info)
+            return True
+        except exception.CoriolisException:
+            pass
+
+    def _refresh_storage(self):
+        self._conn.exec_ps_command(
+            "Update-HostStorageCache", ignore_stdout=True)
+
+    def _bring_all_disks_online(self):
+        self._conn.exec_ps_command(
+            "Get-Disk |? IsOffline | Set-Disk -IsOffline $False",
+            ignore_stdout=True)
+
+    def _bring_disk_offline(self, drive_letter):
+        self._conn.exec_ps_command(
+            "Get-Volume |? DriveLetter -eq \"%s\" | Get-Partition | "
+            "Get-Disk | Set-Disk -IsOffline $True" % drive_letter,
+            ignore_stdout=True)
+
+    def _get_system_drive(self):
+        return self._conn.exec_ps_command("$env:SystemDrive")
+
+    def _get_fs_roots(self):
+        return self._conn.exec_ps_command(
+            "(get-psdrive -PSProvider FileSystem).Root").split(self._conn.EOL)
+
+    def mount_os(self, volume_devs):
+        self._refresh_storage()
+        self._bring_all_disks_online()
+        fs_roots = self._get_fs_roots()
+        system_drive = self._get_system_drive()
+
+        for fs_root in [r for r in fs_roots if not r[:-1] == system_drive]:
+            if self._conn.test_path("%sWindows\\System32" % fs_root):
+                return fs_root, []
+
+    def dismount_os(self, dirs):
+        for dir in dirs:
+            drive_letter = dir.split(":")[0]
+            self._bring_disk_offline(drive_letter)

+ 1 - 1
coriolis/osmorphing/redhat.py

@@ -19,7 +19,7 @@ RELEASE_FEDORA = "Fedora"
 DEFAULT_CLOUD_USER = "cloud-user"
 DEFAULT_CLOUD_USER = "cloud-user"
 
 
 
 
-class RedHatMorphingTools(base.BaseOSMorphingTools):
+class RedHatMorphingTools(base.BaseLinuxOSMorphingTools):
     _packages = {
     _packages = {
         (None, None): [("dracut-config-generic", False)],
         (None, None): [("dracut-config-generic", False)],
         (constants.HYPERVISOR_VMWARE, None): [("open-vm-tools", True)],
         (constants.HYPERVISOR_VMWARE, None): [("open-vm-tools", True)],

+ 123 - 0
coriolis/osmorphing/windows.py

@@ -0,0 +1,123 @@
+import os
+import re
+
+from distutils import version
+from oslo_config import cfg
+from oslo_log import log as logging
+
+from coriolis import constants
+from coriolis import exception
+from coriolis.osmorphing import base
+
+opts = [
+    cfg.StrOpt('virtio_iso_url',
+               default='https://fedorapeople.org/groups/virt/virtio-win/'
+               'direct-downloads/latest-virtio/virtio-win.iso',
+               help="Location of the virtio-win ISO"),
+]
+
+CONF = cfg.CONF
+CONF.register_opts(opts, 'windows_images')
+
+LOG = logging.getLogger(__name__)
+
+
+class WindowsMorphingTools(base.BaseOSMorphingTools):
+    def _check_os(self):
+        try:
+            version = self._get_image_version()
+            return ('Windows', version)
+        except exception.CoriolisException:
+            pass
+
+    def pre_packages_install(self):
+        if self._hypervisor == constants.HYPERVISOR_KVM:
+            self._add_virtio_drivers()
+
+        if self._platform == constants.PLATFORM_OPENSTACK:
+            self._add_cloudbase_init()
+
+    def set_net_config(self, nics_info, dhcp):
+        # TODO: implement
+        pass
+
+    def _get_dism_features(self):
+        LOG.info("Getting image features")
+        return self._conn.exec_command(
+            "dism.exe", ["/get-features",  "/image:%s" % self._os_root_dir])
+
+    def _add_dism_driver(self, driver_path):
+        LOG.info("Adding driver: %s" % driver_path)
+        return self._conn.exec_command(
+            "dism.exe /add-driver /image:%s /driver:%s /recurse /forceunsigned"
+            % (self._os_root_dir, driver_path))
+
+    def _get_image_version(self):
+        features = self._get_dism_features()
+        m = re.search(r'^Image Version: (.*)$',
+                      features.replace(self._conn.EOL, os.linesep),
+                      re.MULTILINE)
+        if not m:
+            raise exception.CoriolisException("Could not find OS version")
+        return m.groups()[0]
+
+    def _mount_disk_image(self, path):
+        LOG.info("Mounting disk image: %s" % path)
+        return self._conn.exec_ps_command(
+            "(Mount-DiskImage '%s' -PassThru | Get-Volume).DriveLetter" %
+            path)
+
+    def _dismount_disk_image(self, path):
+        LOG.info("Unmounting disk image: %s" % path)
+        self._conn.exec_ps_command("Dismount-DiskImage '%s'" % path,
+                                   ignore_stdout=True)
+
+    def _add_virtio_drivers(self):
+        self._event_manager.progress_update("Adding virtio-win drivers")
+
+        # TODO: add support for x86
+        arch = "amd64"
+
+        # Ordered by version number
+        # TODO(alexpilotti): distinguish client and server
+        virtio_dirs = [
+            ("xp", version.LooseVersion("5.1")),
+            ("2k3", version.LooseVersion("5.2")),
+            ("2k8", version.LooseVersion("6.0")),
+            ("w7", version.LooseVersion("6.1")),
+            ("2k8R2", version.LooseVersion("6.1")),
+            ("w8", version.LooseVersion("6.2")),
+            ("2k12", version.LooseVersion("6.2")),
+            ("w8.1", version.LooseVersion("6.3")),
+            ("2k12R2", version.LooseVersion("6.3")),
+            ("w10", version.LooseVersion("10.0")),
+            ]
+
+        drivers = ["Balloon", "NetKVM", "qxldod", "pvpanic", "viorng",
+                   "vioscsi", "vioserial", "viostor"]
+
+        virtio_iso_path = "c:\\virtio-win.iso"
+        self._conn.download_file(
+            CONF.windows_images.virtio_iso_url, virtio_iso_path)
+
+        virtio_drive = self._mount_disk_image(virtio_iso_path)
+        try:
+            image_version = version.LooseVersion(self._version)
+
+            for virtio_dir, dir_version in reversed(virtio_dirs):
+                if image_version >= dir_version:
+                    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]
+            for driver_path in driver_paths:
+                self._add_dism_driver(driver_path)
+        finally:
+            self._dismount_disk_image(virtio_iso_path)
+
+    def _add_cloudbase_init(self):
+        self._event_manager.progress_update("Adding cloudbase-init")

+ 10 - 1
coriolis/providers/openstack/__init__.py

@@ -94,7 +94,12 @@ class _MigrationResources(object):
         self._k = k
         self._k = k
 
 
     def get_guest_connection_info(self):
     def get_guest_connection_info(self):
-        return (self._floating_ip.ip, SSH_PORT, MIGR_GUEST_USERNAME, self._k)
+        return {
+            "ip": self._floating_ip.ip,
+            "port": SSH_PORT,
+            "username": MIGR_GUEST_USERNAME,
+            "pkey": self._k,
+            }
 
 
     @utils.retry_on_error()
     @utils.retry_on_error()
     def _wait_for_instance_deletion(self, instance_id):
     def _wait_for_instance_deletion(self, instance_id):
@@ -407,9 +412,13 @@ class ImportProvider(base.BaseImportProvider):
 
 
                 guest_conn_info = migr_resources.get_guest_connection_info()
                 guest_conn_info = migr_resources.get_guest_connection_info()
 
 
+
+            os_type = None
+
             self._event_manager.progress_update(
             self._event_manager.progress_update(
                 "Preparing instance for target platform")
                 "Preparing instance for target platform")
             osmorphing_manager.morph_image(guest_conn_info,
             osmorphing_manager.morph_image(guest_conn_info,
+                                           os_type,
                                            hypervisor_type,
                                            hypervisor_type,
                                            constants.PLATFORM_OPENSTACK,
                                            constants.PLATFORM_OPENSTACK,
                                            volume_devs,
                                            volume_devs,

+ 105 - 0
coriolis/wsman.py

@@ -0,0 +1,105 @@
+from oslo_log import log as logging
+from winrm import protocol
+
+from coriolis import exception
+from coriolis import utils
+
+AUTH_BASIC = "basic"
+AUTH_KERBEROS = "kerberos"
+AUTH_CERTIFICATE = "certificate"
+
+CODEPAGE_UTF8 = 65001
+
+LOG = logging.getLogger(__name__)
+
+
+class WSManConnection(object):
+    def __init__(self):
+        self._protocol = None
+        self._shell_id = None
+
+    EOL = "\r\n"
+
+    @utils.retry_on_error()
+    def connect(self, url, username, auth=None, password=None,
+                cert_pem=None, cert_key_pem=None):
+        protocol.Protocol.DEFAULT_TIMEOUT = 3600
+
+        if not auth:
+            if cert_pem:
+                auth = AUTH_CERTIFICATE
+            else:
+                auth = AUTH_BASIC
+
+        auth_transport_map = {AUTH_BASIC: 'plaintext',
+                              AUTH_KERBEROS: 'kerberos',
+                              AUTH_CERTIFICATE: 'ssl'}
+
+        self._protocol = protocol.Protocol(
+            endpoint=url,
+            transport=auth_transport_map[auth],
+            username=username,
+            password=password,
+            cert_pem=cert_pem,
+            cert_key_pem=cert_key_pem)
+
+        self._shell_id = self._protocol.open_shell(codepage=CODEPAGE_UTF8)
+
+    def disconnect(self):
+        self._protocol.close_shell(self._shell_id)
+        self._shell_id = None
+        self._protocol = None
+
+    @utils.retry_on_error()
+    def _exec_command(self, cmd, args=[]):
+        command_id = self._protocol.run_command(self._shell_id, cmd, args)
+        try:
+            std_out, std_err, exit_code = self._protocol.get_command_output(
+                self._shell_id, command_id)
+        finally:
+            self._protocol.cleanup_command(self._shell_id, command_id)
+
+        return (std_out, std_err, exit_code)
+
+    def exec_command(self, cmd, args=[]):
+        LOG.debug("Executing WSMAN command: %s", str([cmd] + args))
+        std_out, std_err, exit_code = self._exec_command(cmd, args)
+
+        if exit_code:
+            raise exception.CoriolisException(
+                "Command \"%s\" failed with exit code: %s\n"
+                "stdout: %s\nstd_err: %s" %
+                (str([cmd] + args), exit_code, std_out, std_err))
+
+        return std_out
+
+    def exec_ps_command(self, cmd, ignore_stdout=False):
+        # This is needed to avoid Nano Server's output formatting
+        if not ignore_stdout:
+            cmd_fmt = "\"%s | out-file out.txt\""
+        else:
+            cmd_fmt = "\"%s\""
+
+        std_out = self.exec_command("powershell.exe", [cmd_fmt % cmd])
+
+        if not ignore_stdout:
+            return self.exec_command("cmd.exe", ["/c", "type", "out.txt"])[:-2]
+
+    def test_path(self, remote_path):
+        ret_val = self.exec_ps_command("Test-Path -Path \"%s\"" % remote_path)
+        return ret_val == "True"
+
+    def download_file(self, url, remote_path):
+        LOG.debug("Downloading: \"%(url)s\" to \"%(path)s\"",
+                  {"url": url, "path": remote_path})
+        # Nano Server does not have Invoke-WebRequest and additionally
+        # this is also faster
+        self.exec_ps_command(
+            "if(!([System.Management.Automation.PSTypeName]'"
+            "System.Net.Http.HttpClient').Type) {$assembly = "
+            "[System.Reflection.Assembly]::LoadWithPartialName("
+            "'System.Net.Http')}; (new-object System.Net.Http.HttpClient)."
+            "GetStreamAsync('%(url)s').Result.CopyTo("
+            "(New-Object IO.FileStream '%(outfile)s', Create, Write, None), "
+            "1MB)" % {"url": url, "outfile": remote_path},
+            ignore_stdout=True)