sailesh1993 / rpms / cloud-init

Forked from rpms/cloud-init a year ago
Clone
ca58b3
From b11937faf800b0ae8054dfd64ce50dc92bbb7f80 Mon Sep 17 00:00:00 2001
ca58b3
From: Eduardo Otubo <otubo@redhat.com>
ca58b3
Date: Wed, 26 Sep 2018 13:57:41 +0200
ca58b3
Subject: [PATCH 3/4] azure: Add reported ready marker file.
ca58b3
ca58b3
RH-Author: Eduardo Otubo <otubo@redhat.com>
ca58b3
Message-id: <20180926135742.11140-4-otubo@redhat.com>
ca58b3
Patchwork-id: 82298
ca58b3
O-Subject: [RHEL-7.6 cloud-init PATCHv2 3/4] azure: Add reported ready marker file.
ca58b3
Bugzilla: 1560415
ca58b3
RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
ca58b3
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
ca58b3
ca58b3
commit aae494c39f4c6f625e7409ca262e657d085dd5d1
ca58b3
Author: Joshua Chan <jocha@microsoft.com>
ca58b3
Date:   Thu May 3 14:50:16 2018 -0600
ca58b3
ca58b3
    azure: Add reported ready marker file.
ca58b3
ca58b3
    This change is for Azure VM Preprovisioning. A bug was found when after
ca58b3
    azure VMs report ready the first time, during the time when VM is polling
ca58b3
    indefinitely for the new ovf-env.xml from Instance Metadata Service
ca58b3
    (IMDS), if a reboot happens, we send another report ready signal to the
ca58b3
    fabric, which deletes the reprovisioning data on the node.
ca58b3
ca58b3
    This marker file is used to fix this issue so that we will only send a
ca58b3
    report ready signal to the fabric when no marker file is present. Then,
ca58b3
    create a marker file so that when a reboot does occur, we check if a
ca58b3
    marker file has been created and decide whether we would like to send the
ca58b3
    repot ready signal.
ca58b3
ca58b3
    LP: #1765214
ca58b3
ca58b3
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
ca58b3
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
ca58b3
---
ca58b3
 cloudinit/sources/DataSourceAzure.py          |  21 +++-
ca58b3
 tests/unittests/test_datasource/test_azure.py | 170 ++++++++++++++++++--------
ca58b3
 2 files changed, 134 insertions(+), 57 deletions(-)
ca58b3
ca58b3
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
ca58b3
index 7e49455..46d5744 100644
ca58b3
--- a/cloudinit/sources/DataSourceAzure.py
ca58b3
+++ b/cloudinit/sources/DataSourceAzure.py
ca58b3
@@ -48,6 +48,7 @@ DEFAULT_FS = 'ext4'
ca58b3
 # DMI chassis-asset-tag is set static for all azure instances
ca58b3
 AZURE_CHASSIS_ASSET_TAG = '7783-7084-3265-9085-8269-3286-77'
ca58b3
 REPROVISION_MARKER_FILE = "/var/lib/cloud/data/poll_imds"
ca58b3
+REPORTED_READY_MARKER_FILE = "/var/lib/cloud/data/reported_ready"
ca58b3
 IMDS_URL = "http://169.254.169.254/metadata/reprovisiondata"
ca58b3
 
ca58b3
 
ca58b3
@@ -439,11 +440,12 @@ class DataSourceAzure(sources.DataSource):
ca58b3
             LOG.debug("negotiating already done for %s",
ca58b3
                       self.get_instance_id())
ca58b3
 
ca58b3
-    def _poll_imds(self, report_ready=True):
ca58b3
+    def _poll_imds(self):
ca58b3
         """Poll IMDS for the new provisioning data until we get a valid
ca58b3
         response. Then return the returned JSON object."""
ca58b3
         url = IMDS_URL + "?api-version=2017-04-02"
ca58b3
         headers = {"Metadata": "true"}
ca58b3
+        report_ready = bool(not os.path.isfile(REPORTED_READY_MARKER_FILE))
ca58b3
         LOG.debug("Start polling IMDS")
ca58b3
 
ca58b3
         def exc_cb(msg, exception):
ca58b3
@@ -453,13 +455,17 @@ class DataSourceAzure(sources.DataSource):
ca58b3
             # call DHCP and setup the ephemeral network to acquire the new IP.
ca58b3
             return False
ca58b3
 
ca58b3
-        need_report = report_ready
ca58b3
         while True:
ca58b3
             try:
ca58b3
                 with EphemeralDHCPv4() as lease:
ca58b3
-                    if need_report:
ca58b3
+                    if report_ready:
ca58b3
+                        path = REPORTED_READY_MARKER_FILE
ca58b3
+                        LOG.info(
ca58b3
+                            "Creating a marker file to report ready: %s", path)
ca58b3
+                        util.write_file(path, "{pid}: {time}\n".format(
ca58b3
+                            pid=os.getpid(), time=time()))
ca58b3
                         self._report_ready(lease=lease)
ca58b3
-                        need_report = False
ca58b3
+                        report_ready = False
ca58b3
                     return readurl(url, timeout=1, headers=headers,
ca58b3
                                    exception_cb=exc_cb, infinite=True).contents
ca58b3
             except UrlError:
ca58b3
@@ -493,8 +499,10 @@ class DataSourceAzure(sources.DataSource):
ca58b3
         if (cfg.get('PreprovisionedVm') is True or
ca58b3
                 os.path.isfile(path)):
ca58b3
             if not os.path.isfile(path):
ca58b3
-                LOG.info("Creating a marker file to poll imds")
ca58b3
-                util.write_file(path, "%s: %s\n" % (os.getpid(), time()))
ca58b3
+                LOG.info("Creating a marker file to poll imds: %s",
ca58b3
+                         path)
ca58b3
+                util.write_file(path, "{pid}: {time}\n".format(
ca58b3
+                    pid=os.getpid(), time=time()))
ca58b3
             return True
ca58b3
         return False
ca58b3
 
ca58b3
@@ -529,6 +537,7 @@ class DataSourceAzure(sources.DataSource):
ca58b3
                 "Error communicating with Azure fabric; You may experience."
ca58b3
                 "connectivity issues.", exc_info=True)
ca58b3
             return False
ca58b3
+        util.del_file(REPORTED_READY_MARKER_FILE)
ca58b3
         util.del_file(REPROVISION_MARKER_FILE)
ca58b3
         return fabric_data
ca58b3
 
ca58b3
diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py
ca58b3
index af2c93a..ed810d2 100644
ca58b3
--- a/tests/unittests/test_datasource/test_azure.py
ca58b3
+++ b/tests/unittests/test_datasource/test_azure.py
ca58b3
@@ -1196,19 +1196,9 @@ class TestAzureNetExists(CiTestCase):
ca58b3
         self.assertTrue(hasattr(dsaz, "DataSourceAzureNet"))
ca58b3
 
ca58b3
 
ca58b3
-@mock.patch('cloudinit.sources.DataSourceAzure.util.subp')
ca58b3
-@mock.patch.object(dsaz, 'get_hostname')
ca58b3
-@mock.patch.object(dsaz, 'set_hostname')
ca58b3
-class TestAzureDataSourcePreprovisioning(CiTestCase):
ca58b3
-
ca58b3
-    def setUp(self):
ca58b3
-        super(TestAzureDataSourcePreprovisioning, self).setUp()
ca58b3
-        tmp = self.tmp_dir()
ca58b3
-        self.waagent_d = self.tmp_path('/var/lib/waagent', tmp)
ca58b3
-        self.paths = helpers.Paths({'cloud_dir': tmp})
ca58b3
-        dsaz.BUILTIN_DS_CONFIG['data_dir'] = self.waagent_d
ca58b3
+class TestPreprovisioningReadAzureOvfFlag(CiTestCase):
ca58b3
 
ca58b3
-    def test_read_azure_ovf_with_true_flag(self, *args):
ca58b3
+    def test_read_azure_ovf_with_true_flag(self):
ca58b3
         """The read_azure_ovf method should set the PreprovisionedVM
ca58b3
            cfg flag if the proper setting is present."""
ca58b3
         content = construct_valid_ovf_env(
ca58b3
@@ -1217,7 +1207,7 @@ class TestAzureDataSourcePreprovisioning(CiTestCase):
ca58b3
         cfg = ret[2]
ca58b3
         self.assertTrue(cfg['PreprovisionedVm'])
ca58b3
 
ca58b3
-    def test_read_azure_ovf_with_false_flag(self, *args):
ca58b3
+    def test_read_azure_ovf_with_false_flag(self):
ca58b3
         """The read_azure_ovf method should set the PreprovisionedVM
ca58b3
            cfg flag to false if the proper setting is false."""
ca58b3
         content = construct_valid_ovf_env(
ca58b3
@@ -1226,7 +1216,7 @@ class TestAzureDataSourcePreprovisioning(CiTestCase):
ca58b3
         cfg = ret[2]
ca58b3
         self.assertFalse(cfg['PreprovisionedVm'])
ca58b3
 
ca58b3
-    def test_read_azure_ovf_without_flag(self, *args):
ca58b3
+    def test_read_azure_ovf_without_flag(self):
ca58b3
         """The read_azure_ovf method should not set the
ca58b3
            PreprovisionedVM cfg flag."""
ca58b3
         content = construct_valid_ovf_env()
ca58b3
@@ -1234,12 +1224,121 @@ class TestAzureDataSourcePreprovisioning(CiTestCase):
ca58b3
         cfg = ret[2]
ca58b3
         self.assertFalse(cfg['PreprovisionedVm'])
ca58b3
 
ca58b3
-    @mock.patch('cloudinit.sources.DataSourceAzure.util.is_FreeBSD')
ca58b3
-    @mock.patch('cloudinit.net.dhcp.EphemeralIPv4Network')
ca58b3
-    @mock.patch('cloudinit.net.dhcp.maybe_perform_dhcp_discovery')
ca58b3
-    @mock.patch('requests.Session.request')
ca58b3
+
ca58b3
+@mock.patch('os.path.isfile')
ca58b3
+class TestPreprovisioningShouldReprovision(CiTestCase):
ca58b3
+
ca58b3
+    def setUp(self):
ca58b3
+        super(TestPreprovisioningShouldReprovision, self).setUp()
ca58b3
+        tmp = self.tmp_dir()
ca58b3
+        self.waagent_d = self.tmp_path('/var/lib/waagent', tmp)
ca58b3
+        self.paths = helpers.Paths({'cloud_dir': tmp})
ca58b3
+        dsaz.BUILTIN_DS_CONFIG['data_dir'] = self.waagent_d
ca58b3
+
ca58b3
+    @mock.patch('cloudinit.sources.DataSourceAzure.util.write_file')
ca58b3
+    def test__should_reprovision_with_true_cfg(self, isfile, write_f):
ca58b3
+        """The _should_reprovision method should return true with config
ca58b3
+           flag present."""
ca58b3
+        isfile.return_value = False
ca58b3
+        dsa = dsaz.DataSourceAzure({}, distro=None, paths=self.paths)
ca58b3
+        self.assertTrue(dsa._should_reprovision(
ca58b3
+            (None, None, {'PreprovisionedVm': True}, None)))
ca58b3
+
ca58b3
+    def test__should_reprovision_with_file_existing(self, isfile):
ca58b3
+        """The _should_reprovision method should return True if the sentinal
ca58b3
+           exists."""
ca58b3
+        isfile.return_value = True
ca58b3
+        dsa = dsaz.DataSourceAzure({}, distro=None, paths=self.paths)
ca58b3
+        self.assertTrue(dsa._should_reprovision(
ca58b3
+            (None, None, {'preprovisionedvm': False}, None)))
ca58b3
+
ca58b3
+    def test__should_reprovision_returns_false(self, isfile):
ca58b3
+        """The _should_reprovision method should return False
ca58b3
+           if config and sentinal are not present."""
ca58b3
+        isfile.return_value = False
ca58b3
+        dsa = dsaz.DataSourceAzure({}, distro=None, paths=self.paths)
ca58b3
+        self.assertFalse(dsa._should_reprovision((None, None, {}, None)))
ca58b3
+
ca58b3
+    @mock.patch('cloudinit.sources.DataSourceAzure.DataSourceAzure._poll_imds')
ca58b3
+    def test_reprovision_calls__poll_imds(self, _poll_imds, isfile):
ca58b3
+        """_reprovision will poll IMDS."""
ca58b3
+        isfile.return_value = False
ca58b3
+        hostname = "myhost"
ca58b3
+        username = "myuser"
ca58b3
+        odata = {'HostName': hostname, 'UserName': username}
ca58b3
+        _poll_imds.return_value = construct_valid_ovf_env(data=odata)
ca58b3
+        dsa = dsaz.DataSourceAzure({}, distro=None, paths=self.paths)
ca58b3
+        dsa._reprovision()
ca58b3
+        _poll_imds.assert_called_with()
ca58b3
+
ca58b3
+
ca58b3
+@mock.patch('cloudinit.net.dhcp.EphemeralIPv4Network')
ca58b3
+@mock.patch('cloudinit.net.dhcp.maybe_perform_dhcp_discovery')
ca58b3
+@mock.patch('requests.Session.request')
ca58b3
+@mock.patch(
ca58b3
+    'cloudinit.sources.DataSourceAzure.DataSourceAzure._report_ready')
ca58b3
+class TestPreprovisioningPollIMDS(CiTestCase):
ca58b3
+
ca58b3
+    def setUp(self):
ca58b3
+        super(TestPreprovisioningPollIMDS, self).setUp()
ca58b3
+        self.tmp = self.tmp_dir()
ca58b3
+        self.waagent_d = self.tmp_path('/var/lib/waagent', self.tmp)
ca58b3
+        self.paths = helpers.Paths({'cloud_dir': self.tmp})
ca58b3
+        dsaz.BUILTIN_DS_CONFIG['data_dir'] = self.waagent_d
ca58b3
+
ca58b3
+    @mock.patch('cloudinit.sources.DataSourceAzure.util.write_file')
ca58b3
+    def test_poll_imds_calls_report_ready(self, write_f, report_ready_func,
ca58b3
+                                          fake_resp, m_dhcp, m_net):
ca58b3
+        """The poll_imds will call report_ready after creating marker file."""
ca58b3
+        report_marker = self.tmp_path('report_marker', self.tmp)
ca58b3
+        lease = {
ca58b3
+            'interface': 'eth9', 'fixed-address': '192.168.2.9',
ca58b3
+            'routers': '192.168.2.1', 'subnet-mask': '255.255.255.0',
ca58b3
+            'unknown-245': '624c3620'}
ca58b3
+        m_dhcp.return_value = [lease]
ca58b3
+        dsa = dsaz.DataSourceAzure({}, distro=None, paths=self.paths)
ca58b3
+        mock_path = (
ca58b3
+            'cloudinit.sources.DataSourceAzure.REPORTED_READY_MARKER_FILE')
ca58b3
+        with mock.patch(mock_path, report_marker):
ca58b3
+            dsa._poll_imds()
ca58b3
+        self.assertEqual(report_ready_func.call_count, 1)
ca58b3
+        report_ready_func.assert_called_with(lease=lease)
ca58b3
+
ca58b3
+    def test_poll_imds_report_ready_false(self, report_ready_func,
ca58b3
+                                          fake_resp, m_dhcp, m_net):
ca58b3
+        """The poll_imds should not call reporting ready
ca58b3
+           when flag is false"""
ca58b3
+        report_marker = self.tmp_path('report_marker', self.tmp)
ca58b3
+        write_file(report_marker, content='dont run report_ready :)')
ca58b3
+        m_dhcp.return_value = [{
ca58b3
+            'interface': 'eth9', 'fixed-address': '192.168.2.9',
ca58b3
+            'routers': '192.168.2.1', 'subnet-mask': '255.255.255.0',
ca58b3
+            'unknown-245': '624c3620'}]
ca58b3
+        dsa = dsaz.DataSourceAzure({}, distro=None, paths=self.paths)
ca58b3
+        mock_path = (
ca58b3
+            'cloudinit.sources.DataSourceAzure.REPORTED_READY_MARKER_FILE')
ca58b3
+        with mock.patch(mock_path, report_marker):
ca58b3
+            dsa._poll_imds()
ca58b3
+        self.assertEqual(report_ready_func.call_count, 0)
ca58b3
+
ca58b3
+
ca58b3
+@mock.patch('cloudinit.sources.DataSourceAzure.util.subp')
ca58b3
+@mock.patch('cloudinit.sources.DataSourceAzure.util.write_file')
ca58b3
+@mock.patch('cloudinit.sources.DataSourceAzure.util.is_FreeBSD')
ca58b3
+@mock.patch('cloudinit.net.dhcp.EphemeralIPv4Network')
ca58b3
+@mock.patch('cloudinit.net.dhcp.maybe_perform_dhcp_discovery')
ca58b3
+@mock.patch('requests.Session.request')
ca58b3
+class TestAzureDataSourcePreprovisioning(CiTestCase):
ca58b3
+
ca58b3
+    def setUp(self):
ca58b3
+        super(TestAzureDataSourcePreprovisioning, self).setUp()
ca58b3
+        tmp = self.tmp_dir()
ca58b3
+        self.waagent_d = self.tmp_path('/var/lib/waagent', tmp)
ca58b3
+        self.paths = helpers.Paths({'cloud_dir': tmp})
ca58b3
+        dsaz.BUILTIN_DS_CONFIG['data_dir'] = self.waagent_d
ca58b3
+
ca58b3
     def test_poll_imds_returns_ovf_env(self, fake_resp, m_dhcp, m_net,
ca58b3
-                                       m_is_bsd, *args):
ca58b3
+                                       m_is_bsd, write_f, subp):
ca58b3
         """The _poll_imds method should return the ovf_env.xml."""
ca58b3
         m_is_bsd.return_value = False
ca58b3
         m_dhcp.return_value = [{
ca58b3
@@ -1265,12 +1364,8 @@ class TestAzureDataSourcePreprovisioning(CiTestCase):
ca58b3
             prefix_or_mask='255.255.255.0', router='192.168.2.1')
ca58b3
         self.assertEqual(m_net.call_count, 1)
ca58b3
 
ca58b3
-    @mock.patch('cloudinit.sources.DataSourceAzure.util.is_FreeBSD')
ca58b3
-    @mock.patch('cloudinit.net.dhcp.EphemeralIPv4Network')
ca58b3
-    @mock.patch('cloudinit.net.dhcp.maybe_perform_dhcp_discovery')
ca58b3
-    @mock.patch('requests.Session.request')
ca58b3
     def test__reprovision_calls__poll_imds(self, fake_resp, m_dhcp, m_net,
ca58b3
-                                           m_is_bsd, *args):
ca58b3
+                                           m_is_bsd, write_f, subp):
ca58b3
         """The _reprovision method should call poll IMDS."""
ca58b3
         m_is_bsd.return_value = False
ca58b3
         m_dhcp.return_value = [{
ca58b3
@@ -1302,32 +1397,5 @@ class TestAzureDataSourcePreprovisioning(CiTestCase):
ca58b3
             prefix_or_mask='255.255.255.0', router='192.168.2.1')
ca58b3
         self.assertEqual(m_net.call_count, 1)
ca58b3
 
ca58b3
-    @mock.patch('cloudinit.sources.DataSourceAzure.util.write_file')
ca58b3
-    @mock.patch('os.path.isfile')
ca58b3
-    def test__should_reprovision_with_true_cfg(self, isfile, write_f, *args):
ca58b3
-        """The _should_reprovision method should return true with config
ca58b3
-           flag present."""
ca58b3
-        isfile.return_value = False
ca58b3
-        dsa = dsaz.DataSourceAzure({}, distro=None, paths=self.paths)
ca58b3
-        self.assertTrue(dsa._should_reprovision(
ca58b3
-            (None, None, {'PreprovisionedVm': True}, None)))
ca58b3
-
ca58b3
-    @mock.patch('os.path.isfile')
ca58b3
-    def test__should_reprovision_with_file_existing(self, isfile, *args):
ca58b3
-        """The _should_reprovision method should return True if the sentinal
ca58b3
-           exists."""
ca58b3
-        isfile.return_value = True
ca58b3
-        dsa = dsaz.DataSourceAzure({}, distro=None, paths=self.paths)
ca58b3
-        self.assertTrue(dsa._should_reprovision(
ca58b3
-            (None, None, {'preprovisionedvm': False}, None)))
ca58b3
-
ca58b3
-    @mock.patch('os.path.isfile')
ca58b3
-    def test__should_reprovision_returns_false(self, isfile, *args):
ca58b3
-        """The _should_reprovision method should return False
ca58b3
-           if config and sentinal are not present."""
ca58b3
-        isfile.return_value = False
ca58b3
-        dsa = dsaz.DataSourceAzure({}, distro=None, paths=self.paths)
ca58b3
-        self.assertFalse(dsa._should_reprovision((None, None, {}, None)))
ca58b3
-
ca58b3
 
ca58b3
 # vi: ts=4 expandtab
ca58b3
-- 
ca58b3
1.8.3.1
ca58b3