Repository: elastic/apm-agent-python

Issue:
[META 690] Ensure a minimum retry interval of 5 seconds in fetching central configuration
See meta issue for the description and details:
- Meta issue: https://github.com/elastic/apm/issues/690



Discussion:

Resolving patch:
diff --git a/elasticapm/transport/http.py b/elasticapm/transport/http.py
--- a/elasticapm/transport/http.py
+++ b/elasticapm/transport/http.py
@@ -164,11 +164,9 @@ def get_config(self, current_version=None, keys=None):
             logger.debug("HTTP error while fetching remote config: %s", str(e))
             return current_version, None, max_age
         body = response.read()
-        if "Cache-Control" in response.headers:
-            try:
-                max_age = int(next(re.finditer(r"max-age=(\d+)", response.headers["Cache-Control"])).groups()[0])
-            except StopIteration:
-                logger.debug("Could not parse Cache-Control header: %s", response.headers["Cache-Control"])
+
+        max_age = self._get_cache_control_max_age(response.headers) or max_age
+
         if response.status == 304:
             # config is unchanged, return
             logger.debug("Configuration unchanged")
@@ -187,6 +185,22 @@ def get_config(self, current_version=None, keys=None):
             logger.warning("Failed decoding APM Server response as JSON: %s", body)
             return current_version, None, max_age
 
+    def _get_cache_control_max_age(self, response_headers):
+        max_age = None
+        if "Cache-Control" in response_headers:
+            try:
+                cc_max_age = int(next(re.finditer(r"max-age=(\d+)", response_headers["Cache-Control"])).groups()[0])
+                if cc_max_age <= 0:
+                    # max_age remains at default value
+                    pass
+                elif cc_max_age < 5:
+                    max_age = 5
+                else:
+                    max_age = cc_max_age
+            except StopIteration:
+                logger.debug("Could not parse Cache-Control header: %s", response_headers["Cache-Control"])
+        return max_age
+
     def _process_queue(self):
         if not self.client.server_version:
             self.fetch_server_info()


Test patch:
diff --git a/tests/transports/test_urllib3.py b/tests/transports/test_urllib3.py
--- a/tests/transports/test_urllib3.py
+++ b/tests/transports/test_urllib3.py
@@ -337,6 +337,39 @@ def test_get_config_bad_cache_control_header(waiting_httpserver, caplog, elastic
     assert record.message == "Could not parse Cache-Control header: max-age=fifty"
 
 
+def test_get_config_cache_control_zero(waiting_httpserver, caplog, elasticapm_client):
+    waiting_httpserver.serve_content(
+        code=200, content=b'{"x": "y"}', headers={"Cache-Control": "max-age=0", "Etag": "2"}
+    )
+    url = waiting_httpserver.url
+    transport = Transport(url + "/" + constants.EVENTS_API_PATH, client=elasticapm_client)
+    max_age = transport.get_config("1", {})[2]
+    assert max_age == 300  # if max-age is 0, we use the default
+
+
+def test_get_config_cache_control_negative(waiting_httpserver, caplog, elasticapm_client):
+    waiting_httpserver.serve_content(
+        code=200, content=b'{"x": "y"}', headers={"Cache-Control": "max-age=-1", "Etag": "2"}
+    )
+    url = waiting_httpserver.url
+    transport = Transport(url + "/" + constants.EVENTS_API_PATH, client=elasticapm_client)
+    with caplog.at_level("DEBUG", "elasticapm.transport.http"):
+        max_age = transport.get_config("1", {})[2]
+    assert max_age == 300  # if max-age is negative, we use the default
+    record = caplog.records[-1]
+    assert record.message == "Could not parse Cache-Control header: max-age=-1"
+
+
+def test_get_config_cache_control_less_than_minimum(waiting_httpserver, caplog, elasticapm_client):
+    waiting_httpserver.serve_content(
+        code=200, content=b'{"x": "y"}', headers={"Cache-Control": "max-age=3", "Etag": "2"}
+    )
+    url = waiting_httpserver.url
+    transport = Transport(url + "/" + constants.EVENTS_API_PATH, client=elasticapm_client)
+    max_age = transport.get_config("1", {})[2]
+    assert max_age == 5  # if max-age is less than 5, we use 5
+
+
 def test_get_config_empty_response(waiting_httpserver, caplog, elasticapm_client):
     waiting_httpserver.serve_content(code=200, content=b"", headers={"Cache-Control": "max-age=5"})
     url = waiting_httpserver.url