c36ff1
From 3ee42e6e6ca51b3fd0b6461f707d62c89d54e227 Mon Sep 17 00:00:00 2001
c36ff1
From: Johnson Shi <Johnson.Shi@microsoft.com>
c36ff1
Date: Thu, 25 Mar 2021 07:20:10 -0700
c36ff1
Subject: [PATCH 2/7] Azure helper: Ensure Azure http handler sleeps between
c36ff1
 retries (#842)
c36ff1
c36ff1
RH-Author: Eduardo Otubo <otubo@redhat.com>
c36ff1
RH-MergeRequest: 18: Add support for userdata on Azure from IMDS
c36ff1
RH-Commit: [2/7] 65672cdfe2265f32e6d3c440ba5a8accafdb6ca6 (otubo/cloud-init-src)
c36ff1
RH-Bugzilla: 2042351
c36ff1
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
c36ff1
RH-Acked-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
c36ff1
c36ff1
Ensure that the Azure helper's http handler sleeps a fixed duration
c36ff1
between retry failure attempts. The http handler will sleep a fixed
c36ff1
duration between failed attempts regardless of whether the attempt
c36ff1
failed due to (1) request timing out or (2) instant failure (no
c36ff1
timeout).
c36ff1
c36ff1
Due to certain platform issues, the http request to the Azure endpoint
c36ff1
may instantly fail without reaching the http timeout duration. Without
c36ff1
sleeping a fixed duration in between retry attempts, the http handler
c36ff1
will loop through the max retry attempts quickly. This causes the
c36ff1
communication between cloud-init and the Azure platform to be less
c36ff1
resilient due to the short total duration if there is no sleep in
c36ff1
between retries.
c36ff1
---
c36ff1
 cloudinit/sources/helpers/azure.py                   |  2 ++
c36ff1
 tests/unittests/test_datasource/test_azure_helper.py | 11 +++++++++--
c36ff1
 2 files changed, 11 insertions(+), 2 deletions(-)
c36ff1
c36ff1
diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py
c36ff1
index d3055d08..03e7156b 100755
c36ff1
--- a/cloudinit/sources/helpers/azure.py
c36ff1
+++ b/cloudinit/sources/helpers/azure.py
c36ff1
@@ -303,6 +303,7 @@ def http_with_retries(url, **kwargs) -> str:
c36ff1
 
c36ff1
     max_readurl_attempts = 240
c36ff1
     default_readurl_timeout = 5
c36ff1
+    sleep_duration_between_retries = 5
c36ff1
     periodic_logging_attempts = 12
c36ff1
 
c36ff1
     if 'timeout' not in kwargs:
c36ff1
@@ -338,6 +339,7 @@ def http_with_retries(url, **kwargs) -> str:
c36ff1
                     'attempt %d with exception: %s' %
c36ff1
                     (url, attempt, e),
c36ff1
                     logger_func=LOG.debug)
c36ff1
+            time.sleep(sleep_duration_between_retries)
c36ff1
 
c36ff1
     raise exc
c36ff1
 
c36ff1
diff --git a/tests/unittests/test_datasource/test_azure_helper.py b/tests/unittests/test_datasource/test_azure_helper.py
c36ff1
index b8899807..63482c6c 100644
c36ff1
--- a/tests/unittests/test_datasource/test_azure_helper.py
c36ff1
+++ b/tests/unittests/test_datasource/test_azure_helper.py
c36ff1
@@ -384,6 +384,7 @@ class TestAzureHelperHttpWithRetries(CiTestCase):
c36ff1
 
c36ff1
     max_readurl_attempts = 240
c36ff1
     default_readurl_timeout = 5
c36ff1
+    sleep_duration_between_retries = 5
c36ff1
     periodic_logging_attempts = 12
c36ff1
 
c36ff1
     def setUp(self):
c36ff1
@@ -394,8 +395,8 @@ class TestAzureHelperHttpWithRetries(CiTestCase):
c36ff1
         self.m_readurl = patches.enter_context(
c36ff1
             mock.patch.object(
c36ff1
                 azure_helper.url_helper, 'readurl', mock.MagicMock()))
c36ff1
-        patches.enter_context(
c36ff1
-            mock.patch.object(azure_helper.time, 'sleep', mock.MagicMock()))
c36ff1
+        self.m_sleep = patches.enter_context(
c36ff1
+            mock.patch.object(azure_helper.time, 'sleep', autospec=True))
c36ff1
 
c36ff1
     def test_http_with_retries(self):
c36ff1
         self.m_readurl.return_value = 'TestResp'
c36ff1
@@ -438,6 +439,12 @@ class TestAzureHelperHttpWithRetries(CiTestCase):
c36ff1
             self.m_readurl.call_count,
c36ff1
             self.periodic_logging_attempts + 1)
c36ff1
 
c36ff1
+        # Ensure that cloud-init did sleep between each failed request
c36ff1
+        self.assertEqual(
c36ff1
+            self.m_sleep.call_count,
c36ff1
+            self.periodic_logging_attempts)
c36ff1
+        self.m_sleep.assert_called_with(self.sleep_duration_between_retries)
c36ff1
+
c36ff1
     def test_http_with_retries_long_delay_logs_periodic_failure_msg(self):
c36ff1
         self.m_readurl.side_effect = \
c36ff1
             [SentinelException] * self.periodic_logging_attempts + \
c36ff1
-- 
c36ff1
2.27.0
c36ff1