فهرست منبع

Add QEMU guest agent installer support

Signed-off-by: Mihaela Balutoiu <mbalutoiu@cloudbasesolutions.com>
Mihaela Balutoiu 2 هفته پیش
والد
کامیت
8e67a6ed55
2فایلهای تغییر یافته به همراه167 افزوده شده و 0 حذف شده
  1. 74 0
      coriolis/osmorphing/windows.py
  2. 93 0
      coriolis/tests/osmorphing/test_windows.py

+ 74 - 0
coriolis/osmorphing/windows.py

@@ -71,7 +71,20 @@ CLOUDBASE_INIT_DEFAULT_METADATA_SVCS = [
 REQUIRED_DETECTED_WINDOWS_OS_FIELDS = [
     "version_number", "edition_id", "installation_type", "product_name"]
 
+QEMU_GUEST_AGENT_INSTALL_SCRIPT_FORMAT = (
+    "msiexec.exe /i '%(agent_msi_path)s' /qn /passive "
+    "/L*V '%(agent_msi_log_dir)s/qemu-guest-agent-msiexec.log'")
+
+QEMU_GUEST_AGENT_INSTALL_FROM_DIR_SCRIPT_FORMAT = (
+    "$dst = 'C:\\Program Files\\Qemu-ga'\n"
+    "New-Item -ItemType Directory -Force -Path $dst | Out-Null\n"
+    "Copy-Item '%(qemu_ga_dir)s\\x64\\*' -Destination $dst -Force\n"
+    "Copy-Item '%(qemu_ga_dir)s\\mingw64\\*' -Destination $dst -Force\n"
+    '& "$dst\\qemu-ga.exe" --service install\n'
+    "Start-Service QEMU-GA")
+
 VIRTIO_WIN_ISO_PATH = "c:\\virtio-win.iso"
+QEMU_GA_MSI_NAME = "qemu-ga.msi"
 
 INTERFACES_PATH_FORMAT = (
     "HKLM:\\%s\\ControlSet001\\Services\\Tcpip\\Parameters\\Interfaces")
@@ -841,6 +854,67 @@ class BaseWindowsMorphingTools(base.BaseOSMorphingTools):
         finally:
             self._revoke_permissions(file_repo_path, "*%s" % sid)
 
+    def _setup_qemu_agent_installation_local_script(
+            self, msi_source_path=None, msi_url=None,
+            qemu_ga_source_dir=None):
+        """Stages the QEMU guest agent in the Cloudbase-Init directory and
+        registers a first-boot script which installs it.
+
+        Two source modes are supported:
+          * MSI: pass 'msi_source_path' or 'msi_url'. The MSI is staged
+            and installed via msiexec on first boot.
+          * Directory: pass 'qemu_ga_source_dir' pointing at a 'qemu-ga'
+            directory (e.g. on the SUSE VMDP ISO) containing 'x64' and
+            'mingw64' subdirs.
+        """
+
+        if not any([msi_source_path, msi_url, qemu_ga_source_dir]):
+            raise exception.CoriolisException(
+                "No QEMU guest agent source provided: one of "
+                "'msi_source_path', 'msi_url' or 'qemu_ga_source_dir' "
+                "must be set.")
+
+        cloudbaseinit_base_dir = self._get_cbslinit_base_dir()
+        guest_cloudbase_init_base_dir = "C%s" % cloudbaseinit_base_dir[1:]
+
+        self._event_manager.progress_update(
+            "Setting up guest local script for installing the "
+            "QEMU guest agent on first boot")
+
+        if qemu_ga_source_dir:
+            qemu_ga_exe_path = "%s\\x64\\qemu-ga.exe" % qemu_ga_source_dir
+            if not self._conn.test_path(qemu_ga_exe_path):
+                LOG.warning(
+                    "qemu-ga is not packaged in the configured source. "
+                    "Skipping qemu-ga installation.")
+                return
+            # Copy qemu-ga/ to the Cloudbase-Init directory on the migrated
+            # disk:
+            self._conn.exec_ps_command(
+                "Copy-Item -Recurse -Force '%s' '%s'" % (
+                    qemu_ga_source_dir, cloudbaseinit_base_dir),
+                ignore_stdout=True)
+            local_script = QEMU_GUEST_AGENT_INSTALL_FROM_DIR_SCRIPT_FORMAT % {
+                "qemu_ga_dir": "%s\\qemu-ga" % guest_cloudbase_init_base_dir}
+        else:
+            msi_dest_path = "%s\\%s" % (
+                cloudbaseinit_base_dir, QEMU_GA_MSI_NAME)
+            if msi_url:
+                utils.retry_on_error(sleep_seconds=5)(
+                    self._conn.download_file)(msi_url, msi_dest_path)
+            else:
+                self._conn.exec_ps_command(
+                    "Copy-Item '%s' -Destination '%s'" % (
+                        msi_source_path, msi_dest_path))
+            local_script = QEMU_GUEST_AGENT_INSTALL_SCRIPT_FORMAT % {
+                "agent_msi_path": "%s\\%s" % (
+                    guest_cloudbase_init_base_dir, QEMU_GA_MSI_NAME),
+                "agent_msi_log_dir": guest_cloudbase_init_base_dir}
+
+        self.register_firstboot_script(
+            local_script, user_provided=False,
+            script_filename="coriolis_qemu_agent_install.ps1")
+
     def get_packages(self):
         return [], []
 

+ 93 - 0
coriolis/tests/osmorphing/test_windows.py

@@ -1275,3 +1275,96 @@ class BaseWindowsMorphingToolsTestCase(test_base.CoriolisBaseTestCase):
         # Permissions must be revoked even if driver installation fails:
         mock_revoke_permissions.assert_called_once_with(
             exp_repo_path, f"*{null_sid}")
+
+    @mock.patch.object(
+        windows.BaseWindowsMorphingTools, "register_firstboot_script")
+    def test_setup_qemu_agent_installation_local_script_from_url(
+            self, mock_register_firstboot_script):
+        fake_msi_url = "fake-qemu-ga-msi-url"
+
+        self.morphing_tools._setup_qemu_agent_installation_local_script(
+            msi_url=fake_msi_url)
+
+        exp_msi_dest_path = "C:\\Cloudbase-Init\\qemu-ga.msi"
+        self.morphing_tools._conn.download_file.assert_called_once_with(
+            fake_msi_url, exp_msi_dest_path)
+        self.morphing_tools._conn.exec_ps_command.assert_not_called()
+
+        exp_script = windows.QEMU_GUEST_AGENT_INSTALL_SCRIPT_FORMAT % {
+            "agent_msi_path": exp_msi_dest_path,
+            "agent_msi_log_dir": "C:\\Cloudbase-Init"}
+        mock_register_firstboot_script.assert_called_once_with(
+            exp_script, user_provided=False,
+            script_filename="coriolis_qemu_agent_install.ps1")
+
+    @mock.patch.object(
+        windows.BaseWindowsMorphingTools, "register_firstboot_script")
+    def test_setup_qemu_agent_installation_local_script_from_path(
+            self, mock_register_firstboot_script):
+        fake_msi_source_path = "e:\\guest-agent\\qemu-ga-x86_64.msi"
+
+        self.morphing_tools._setup_qemu_agent_installation_local_script(
+            msi_source_path=fake_msi_source_path)
+
+        exp_msi_dest_path = "C:\\Cloudbase-Init\\qemu-ga.msi"
+        self.morphing_tools._conn.download_file.assert_not_called()
+        self.morphing_tools._conn.exec_ps_command.assert_called_once_with(
+            "Copy-Item '%s' -Destination '%s'" % (
+                fake_msi_source_path, exp_msi_dest_path))
+
+        exp_script = windows.QEMU_GUEST_AGENT_INSTALL_SCRIPT_FORMAT % {
+            "agent_msi_path": exp_msi_dest_path,
+            "agent_msi_log_dir": "C:\\Cloudbase-Init"}
+        mock_register_firstboot_script.assert_called_once_with(
+            exp_script, user_provided=False,
+            script_filename="coriolis_qemu_agent_install.ps1")
+
+    @mock.patch.object(
+        windows.BaseWindowsMorphingTools, "register_firstboot_script")
+    def test_setup_qemu_agent_installation_local_script_no_source(
+            self, mock_register_firstboot_script):
+        self.assertRaises(
+            exception.CoriolisException,
+            self.morphing_tools._setup_qemu_agent_installation_local_script)
+
+        self.morphing_tools._conn.download_file.assert_not_called()
+        self.morphing_tools._conn.exec_ps_command.assert_not_called()
+        mock_register_firstboot_script.assert_not_called()
+
+    @mock.patch.object(
+        windows.BaseWindowsMorphingTools, "register_firstboot_script")
+    def test_setup_qemu_agent_installation_local_script_from_qemu_ga_dir(
+            self, mock_register_firstboot_script):
+        fake_qemu_ga_source_dir = "f:\\qemu-ga"
+        self.morphing_tools._conn.test_path.return_value = True
+
+        self.morphing_tools._setup_qemu_agent_installation_local_script(
+            qemu_ga_source_dir=fake_qemu_ga_source_dir)
+
+        self.morphing_tools._conn.test_path.assert_called_once_with(
+            "%s\\x64\\qemu-ga.exe" % fake_qemu_ga_source_dir)
+        self.morphing_tools._conn.download_file.assert_not_called()
+        self.morphing_tools._conn.exec_ps_command.assert_called_once_with(
+            "Copy-Item -Recurse -Force '%s' '%s'" % (
+                fake_qemu_ga_source_dir, "C:\\Cloudbase-Init"),
+            ignore_stdout=True)
+
+        exp_script = (
+            windows.QEMU_GUEST_AGENT_INSTALL_FROM_DIR_SCRIPT_FORMAT % {
+                "qemu_ga_dir": "C:\\Cloudbase-Init\\qemu-ga"})
+        mock_register_firstboot_script.assert_called_once_with(
+            exp_script, user_provided=False,
+            script_filename="coriolis_qemu_agent_install.ps1")
+
+    @mock.patch.object(
+        windows.BaseWindowsMorphingTools, "register_firstboot_script")
+    def test_setup_qemu_agent_installation_local_script_qemu_ga_dir_missing(
+            self, mock_register_firstboot_script):
+        self.morphing_tools._conn.test_path.return_value = False
+
+        self.morphing_tools._setup_qemu_agent_installation_local_script(
+            qemu_ga_source_dir="f:\\qemu-ga")
+
+        self.morphing_tools._conn.download_file.assert_not_called()
+        self.morphing_tools._conn.exec_ps_command.assert_not_called()
+        mock_register_firstboot_script.assert_not_called()