4eb3b8
From 01489fb91f64f6137ddf88c39feabe4296f3a156 Mon Sep 17 00:00:00 2001
4eb3b8
From: Anh Vo <anhvo@microsoft.com>
4eb3b8
Date: Fri, 23 Apr 2021 10:18:05 -0400
4eb3b8
Subject: [PATCH 4/7] Azure: eject the provisioning iso before reporting ready
4eb3b8
 (#861)
4eb3b8
4eb3b8
RH-Author: Eduardo Otubo <otubo@redhat.com>
4eb3b8
RH-MergeRequest: 45: Add support for userdata on Azure from IMDS
4eb3b8
RH-Commit: [4/7] ba830546a62ac5bea33b91d133d364a897b9f6c0
4eb3b8
RH-Bugzilla: 2023940
4eb3b8
RH-Acked-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
4eb3b8
RH-Acked-by: Mohamed Gamal Morsy <mmorsy@redhat.com>
4eb3b8
4eb3b8
Due to hyper-v implementations, iso ejection is more efficient if performed
4eb3b8
from within the guest. The code will attempt to perform a best-effort ejection.
4eb3b8
Failure during ejection will not prevent reporting ready from happening. If iso
4eb3b8
ejection is successful, later iso ejection from the platform will be a no-op.
4eb3b8
In the event the iso ejection from the guest fails, iso ejection will still happen at
4eb3b8
the platform level.
4eb3b8
---
4eb3b8
 cloudinit/sources/DataSourceAzure.py          | 22 +++++++++++++++---
4eb3b8
 cloudinit/sources/helpers/azure.py            | 23 ++++++++++++++++---
4eb3b8
 .../test_datasource/test_azure_helper.py      | 13 +++++++++--
4eb3b8
 3 files changed, 50 insertions(+), 8 deletions(-)
4eb3b8
4eb3b8
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
4eb3b8
index 020b7006..39e67c4f 100755
4eb3b8
--- a/cloudinit/sources/DataSourceAzure.py
4eb3b8
+++ b/cloudinit/sources/DataSourceAzure.py
4eb3b8
@@ -332,6 +332,7 @@ class DataSourceAzure(sources.DataSource):
4eb3b8
     dsname = 'Azure'
4eb3b8
     _negotiated = False
4eb3b8
     _metadata_imds = sources.UNSET
4eb3b8
+    _ci_pkl_version = 1
4eb3b8
 
4eb3b8
     def __init__(self, sys_cfg, distro, paths):
4eb3b8
         sources.DataSource.__init__(self, sys_cfg, distro, paths)
4eb3b8
@@ -346,8 +347,13 @@ class DataSourceAzure(sources.DataSource):
4eb3b8
         # Regenerate network config new_instance boot and every boot
4eb3b8
         self.update_events['network'].add(EventType.BOOT)
4eb3b8
         self._ephemeral_dhcp_ctx = None
4eb3b8
-
4eb3b8
         self.failed_desired_api_version = False
4eb3b8
+        self.iso_dev = None
4eb3b8
+
4eb3b8
+    def _unpickle(self, ci_pkl_version: int) -> None:
4eb3b8
+        super()._unpickle(ci_pkl_version)
4eb3b8
+        if "iso_dev" not in self.__dict__:
4eb3b8
+            self.iso_dev = None
4eb3b8
 
4eb3b8
     def __str__(self):
4eb3b8
         root = sources.DataSource.__str__(self)
4eb3b8
@@ -459,6 +465,13 @@ class DataSourceAzure(sources.DataSource):
4eb3b8
                     '%s was not mountable' % cdev, logger_func=LOG.warning)
4eb3b8
                 continue
4eb3b8
 
4eb3b8
+            report_diagnostic_event("Found provisioning metadata in %s" % cdev,
4eb3b8
+                                    logger_func=LOG.debug)
4eb3b8
+
4eb3b8
+            # save the iso device for ejection before reporting ready
4eb3b8
+            if cdev.startswith("/dev"):
4eb3b8
+                self.iso_dev = cdev
4eb3b8
+
4eb3b8
             perform_reprovision = reprovision or self._should_reprovision(ret)
4eb3b8
             perform_reprovision_after_nic_attach = (
4eb3b8
                 reprovision_after_nic_attach or
4eb3b8
@@ -1226,7 +1239,9 @@ class DataSourceAzure(sources.DataSource):
4eb3b8
         @return: The success status of sending the ready signal.
4eb3b8
         """
4eb3b8
         try:
4eb3b8
-            get_metadata_from_fabric(None, lease['unknown-245'])
4eb3b8
+            get_metadata_from_fabric(fallback_lease_file=None,
4eb3b8
+                                     dhcp_opts=lease['unknown-245'],
4eb3b8
+                                     iso_dev=self.iso_dev)
4eb3b8
             return True
4eb3b8
         except Exception as e:
4eb3b8
             report_diagnostic_event(
4eb3b8
@@ -1332,7 +1347,8 @@ class DataSourceAzure(sources.DataSource):
4eb3b8
         metadata_func = partial(get_metadata_from_fabric,
4eb3b8
                                 fallback_lease_file=self.
4eb3b8
                                 dhclient_lease_file,
4eb3b8
-                                pubkey_info=pubkey_info)
4eb3b8
+                                pubkey_info=pubkey_info,
4eb3b8
+                                iso_dev=self.iso_dev)
4eb3b8
 
4eb3b8
         LOG.debug("negotiating with fabric via agent command %s",
4eb3b8
                   self.ds_cfg['agent_command'])
4eb3b8
diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py
4eb3b8
index 03e7156b..ad476076 100755
4eb3b8
--- a/cloudinit/sources/helpers/azure.py
4eb3b8
+++ b/cloudinit/sources/helpers/azure.py
4eb3b8
@@ -865,7 +865,19 @@ class WALinuxAgentShim:
4eb3b8
         return endpoint_ip_address
4eb3b8
 
4eb3b8
     @azure_ds_telemetry_reporter
4eb3b8
-    def register_with_azure_and_fetch_data(self, pubkey_info=None) -> dict:
4eb3b8
+    def eject_iso(self, iso_dev) -> None:
4eb3b8
+        try:
4eb3b8
+            LOG.debug("Ejecting the provisioning iso")
4eb3b8
+            subp.subp(['eject', iso_dev])
4eb3b8
+        except Exception as e:
4eb3b8
+            report_diagnostic_event(
4eb3b8
+                "Failed ejecting the provisioning iso: %s" % e,
4eb3b8
+                logger_func=LOG.debug)
4eb3b8
+
4eb3b8
+    @azure_ds_telemetry_reporter
4eb3b8
+    def register_with_azure_and_fetch_data(self,
4eb3b8
+                                           pubkey_info=None,
4eb3b8
+                                           iso_dev=None) -> dict:
4eb3b8
         """Gets the VM's GoalState from Azure, uses the GoalState information
4eb3b8
         to report ready/send the ready signal/provisioning complete signal to
4eb3b8
         Azure, and then uses pubkey_info to filter and obtain the user's
4eb3b8
@@ -891,6 +903,10 @@ class WALinuxAgentShim:
4eb3b8
             ssh_keys = self._get_user_pubkeys(goal_state, pubkey_info)
4eb3b8
         health_reporter = GoalStateHealthReporter(
4eb3b8
             goal_state, self.azure_endpoint_client, self.endpoint)
4eb3b8
+
4eb3b8
+        if iso_dev is not None:
4eb3b8
+            self.eject_iso(iso_dev)
4eb3b8
+
4eb3b8
         health_reporter.send_ready_signal()
4eb3b8
         return {'public-keys': ssh_keys}
4eb3b8
 
4eb3b8
@@ -1046,11 +1062,12 @@ class WALinuxAgentShim:
4eb3b8
 
4eb3b8
 @azure_ds_telemetry_reporter
4eb3b8
 def get_metadata_from_fabric(fallback_lease_file=None, dhcp_opts=None,
4eb3b8
-                             pubkey_info=None):
4eb3b8
+                             pubkey_info=None, iso_dev=None):
4eb3b8
     shim = WALinuxAgentShim(fallback_lease_file=fallback_lease_file,
4eb3b8
                             dhcp_options=dhcp_opts)
4eb3b8
     try:
4eb3b8
-        return shim.register_with_azure_and_fetch_data(pubkey_info=pubkey_info)
4eb3b8
+        return shim.register_with_azure_and_fetch_data(
4eb3b8
+            pubkey_info=pubkey_info, iso_dev=iso_dev)
4eb3b8
     finally:
4eb3b8
         shim.clean_up()
4eb3b8
 
4eb3b8
diff --git a/tests/unittests/test_datasource/test_azure_helper.py b/tests/unittests/test_datasource/test_azure_helper.py
4eb3b8
index 63482c6c..552c7905 100644
4eb3b8
--- a/tests/unittests/test_datasource/test_azure_helper.py
4eb3b8
+++ b/tests/unittests/test_datasource/test_azure_helper.py
4eb3b8
@@ -1009,6 +1009,14 @@ class TestWALinuxAgentShim(CiTestCase):
4eb3b8
         self.GoalState.return_value.container_id = self.test_container_id
4eb3b8
         self.GoalState.return_value.instance_id = self.test_instance_id
4eb3b8
 
4eb3b8
+    def test_eject_iso_is_called(self):
4eb3b8
+        shim = wa_shim()
4eb3b8
+        with mock.patch.object(
4eb3b8
+            shim, 'eject_iso', autospec=True
4eb3b8
+        ) as m_eject_iso:
4eb3b8
+            shim.register_with_azure_and_fetch_data(iso_dev="/dev/sr0")
4eb3b8
+            m_eject_iso.assert_called_once_with("/dev/sr0")
4eb3b8
+
4eb3b8
     def test_http_client_does_not_use_certificate_for_report_ready(self):
4eb3b8
         shim = wa_shim()
4eb3b8
         shim.register_with_azure_and_fetch_data()
4eb3b8
@@ -1283,13 +1291,14 @@ class TestGetMetadataGoalStateXMLAndReportReadyToFabric(CiTestCase):
4eb3b8
 
4eb3b8
     def test_calls_shim_register_with_azure_and_fetch_data(self):
4eb3b8
         m_pubkey_info = mock.MagicMock()
4eb3b8
-        azure_helper.get_metadata_from_fabric(pubkey_info=m_pubkey_info)
4eb3b8
+        azure_helper.get_metadata_from_fabric(
4eb3b8
+            pubkey_info=m_pubkey_info, iso_dev="/dev/sr0")
4eb3b8
         self.assertEqual(
4eb3b8
             1,
4eb3b8
             self.m_shim.return_value
4eb3b8
                 .register_with_azure_and_fetch_data.call_count)
4eb3b8
         self.assertEqual(
4eb3b8
-            mock.call(pubkey_info=m_pubkey_info),
4eb3b8
+            mock.call(iso_dev="/dev/sr0", pubkey_info=m_pubkey_info),
4eb3b8
             self.m_shim.return_value
4eb3b8
                 .register_with_azure_and_fetch_data.call_args)
4eb3b8
 
4eb3b8
-- 
4eb3b8
2.27.0
4eb3b8