Repository: Azure/WALinuxAgent
Pull request #857 (2017-08-24 18:40:47)

Align HTTP status code handling with HostPlugin
Align HTTP status code handling with HostPlugin

Signed-off-by: Brendan Dixon <brendandixon@me.com>


Patch:
diff --git a/azurelinuxagent/common/exception.py b/azurelinuxagent/common/exception.py
index 91972ebb..17c6ce05 100644
--- a/azurelinuxagent/common/exception.py
+++ b/azurelinuxagent/common/exception.py
@@ -149,10 +149,10 @@ class UpdateError(AgentError):
         super(UpdateError, self).__init__('000012', msg, inner)
 
 
-class BadRequestError(HttpError):
+class ResourceGoneError(HttpError):
     """
-    The server rejected the request (i.e., status code 400)
+   The requested resource no longer exists (i.e., status code 410)
     """
 
     def __init__(self, msg=None, inner=None):
-        super(BadRequestError, self).__init__(msg, inner)
+        super(ResourceGoneError, self).__init__(msg, inner)
diff --git a/azurelinuxagent/common/protocol/hostplugin.py b/azurelinuxagent/common/protocol/hostplugin.py
index 56bb984f..729d8fb9 100644
--- a/azurelinuxagent/common/protocol/hostplugin.py
+++ b/azurelinuxagent/common/protocol/hostplugin.py
@@ -22,8 +22,8 @@ import json
 import traceback
 
 from azurelinuxagent.common import logger
-from azurelinuxagent.common.exception import BadRequestError, \
-                                            HttpError, ProtocolError
+from azurelinuxagent.common.exception import HttpError, ProtocolError, \
+                                            ResourceGoneError
 from azurelinuxagent.common.future import ustr, httpclient
 from azurelinuxagent.common.utils import restutil
 from azurelinuxagent.common.utils import textutil
@@ -146,7 +146,7 @@ class HostPluginProtocol(object):
         except Exception as e:
             # If the HostPlugin rejects the request,
             # let the error continue, but set to use the HostPlugin
-            if isinstance(e, BadRequestError):
+            if isinstance(e, ResourceGoneError):
                 logger.verbose("HostGAPlugin: Setting host plugin as default channel")
                 HostPluginProtocol.set_default_channel(True)
 
diff --git a/azurelinuxagent/common/protocol/wire.py b/azurelinuxagent/common/protocol/wire.py
index 0794f82c..4f3b7e06 100644
--- a/azurelinuxagent/common/protocol/wire.py
+++ b/azurelinuxagent/common/protocol/wire.py
@@ -26,8 +26,8 @@ import azurelinuxagent.common.conf as conf
 import azurelinuxagent.common.utils.fileutil as fileutil
 import azurelinuxagent.common.utils.textutil as textutil
 
-from azurelinuxagent.common.exception import BadRequestError, \
-                                            ProtocolNotFoundError
+from azurelinuxagent.common.exception import ProtocolNotFoundError, \
+                                            ResourceGoneError
 from azurelinuxagent.common.future import httpclient, bytebuffer
 from azurelinuxagent.common.protocol.hostplugin import HostPluginProtocol
 from azurelinuxagent.common.protocol.restapi import *
@@ -617,7 +617,7 @@ class WireClient(object):
 
                 # If the HostPlugin rejects the request,
                 # let the error continue, but set to use the HostPlugin
-                except BadRequestError:
+                except ResourceGoneError:
                     HostPluginProtocol.set_default_channel(True)
                     raise
 
@@ -653,7 +653,7 @@ class WireClient(object):
         except (HttpError, ProtocolError) as e:
             logger.verbose("Fetch failed from [{0}]: {1}", uri, e)
 
-            if isinstance(e, BadRequestError):
+            if isinstance(e, ResourceGoneError):
                 raise
 
         return None
@@ -808,7 +808,7 @@ class WireClient(object):
                 self.save_cache(local_file, xml_text)
                 return ExtensionManifest(xml_text)
 
-            except BadRequestError:
+            except ResourceGoneError:
                 continue
 
         raise ProtocolError("Failed to retrieve extension manifest")
@@ -829,7 +829,7 @@ class WireClient(object):
                 fileutil.write_file(local_file, xml_text)
                 return ExtensionManifest(xml_text)
 
-            except BadRequestError:
+            except ResourceGoneError:
                 continue
 
         raise ProtocolError("Failed to retrieve GAFamily manifest")
@@ -892,7 +892,7 @@ class WireClient(object):
             except Exception as e:
                 # If the HostPlugin rejects the request,
                 # let the error continue, but set to use the HostPlugin
-                if isinstance(e, BadRequestError):
+                if isinstance(e, ResourceGoneError):
                     HostPluginProtocol.set_default_channel(True)
                     continue
 
@@ -1069,7 +1069,7 @@ class WireClient(object):
 
                 return artifacts_profile
 
-            except BadRequestError:
+            except ResourceGoneError:
                 HostPluginProtocol.set_default_channel(True)
                 continue
 
diff --git a/azurelinuxagent/common/utils/restutil.py b/azurelinuxagent/common/utils/restutil.py
index 5e6c9d15..6ad2479a 100644
--- a/azurelinuxagent/common/utils/restutil.py
+++ b/azurelinuxagent/common/utils/restutil.py
@@ -25,7 +25,7 @@ import azurelinuxagent.common.conf as conf
 import azurelinuxagent.common.logger as logger
 import azurelinuxagent.common.utils.textutil as textutil
 
-from azurelinuxagent.common.exception import BadRequestError, HttpError
+from azurelinuxagent.common.exception import HttpError, ResourceGoneError
 from azurelinuxagent.common.future import httpclient, urlparse, ustr
 from azurelinuxagent.common.version import PY_VERSION_MAJOR
 
@@ -43,9 +43,16 @@ RETRY_CODES = [
     httpclient.FORBIDDEN,
     httpclient.INTERNAL_SERVER_ERROR,
     httpclient.NOT_IMPLEMENTED,
+    httpclient.BAD_GATEWAY,
     httpclient.SERVICE_UNAVAILABLE,
     httpclient.GATEWAY_TIMEOUT,
-    httpclient.INSUFFICIENT_STORAGE
+    httpclient.INSUFFICIENT_STORAGE,
+    429, # Request Rate Limit Exceeded
+]
+
+RESOURCE_GONE_CODES = [
+    httpclient.BAD_REQUEST,
+    httpclient.GONE
 ]
 
 OK_CODES = [
@@ -258,8 +265,8 @@ def http_request(method,
                                         delay, resp.status))
                     continue
 
-            if resp.status == httpclient.BAD_REQUEST:
-                raise BadRequestError()
+            if resp.status in RESOURCE_GONE_CODES:
+                raise ResourceGoneError()
 
             return resp
 
diff --git a/azurelinuxagent/ga/update.py b/azurelinuxagent/ga/update.py
index 13ec99cd..b7ee96ac 100644
--- a/azurelinuxagent/ga/update.py
+++ b/azurelinuxagent/ga/update.py
@@ -41,8 +41,9 @@ import azurelinuxagent.common.utils.textutil as textutil
 from azurelinuxagent.common.event import add_event, add_periodic, \
                                     elapsed_milliseconds, \
                                     WALAEventOperation
-from azurelinuxagent.common.exception import BadRequestError, \
-                                    ProtocolError, UpdateError
+from azurelinuxagent.common.exception import ProtocolError, \
+                                            ResourceGoneError, \
+                                            UpdateError
 from azurelinuxagent.common.future import ustr
 from azurelinuxagent.common.osutil import get_osutil
 from azurelinuxagent.common.protocol import get_protocol_util
@@ -603,7 +604,7 @@ class UpdateHandler(object):
                             self.agents[0].version > base_version
 
             except Exception as e:
-                if isinstance(e, BadRequestError):
+                if isinstance(e, ResourceGoneError):
                     continue
 
                 msg = u"Exception retrieving agent manifests: {0}".format(
@@ -673,7 +674,7 @@ class GuestAgent(object):
             self._ensure_downloaded()
             self._ensure_loaded()
         except Exception as e:
-            if isinstance(e, BadRequestError):
+            if isinstance(e, ResourceGoneError):
                 raise
 
             # Note the failure, blacklist the agent if the package downloaded
@@ -820,7 +821,7 @@ class GuestAgent(object):
 
                 # If the HostPlugin rejects the request,
                 # let the error continue, but set to use the HostPlugin
-                except BadRequestError:
+                except ResourceGoneError:
                     HostPluginProtocol.set_default_channel(True)
                     raise
 
@@ -853,7 +854,7 @@ class GuestAgent(object):
                 logger.verbose("Fetch was unsuccessful [{0}]",
                                restutil.read_response_error(resp))
         except restutil.HttpError as http_error:
-            if isinstance(http_error, BadRequestError):
+            if isinstance(http_error, ResourceGoneError):
                 raise
 
             logger.verbose(u"Agent {0} download from {1} failed [{2}]",


Test patch:
diff --git a/tests/ga/test_update.py b/tests/ga/test_update.py
index a9701d40..59251cbf 100644
--- a/tests/ga/test_update.py
+++ b/tests/ga/test_update.py
@@ -1611,14 +1611,14 @@ class ProtocolMock(object):
         self.call_counts["get_vmagent_manifests"] += 1
         if self.goal_state_is_stale:
             self.goal_state_is_stale = False
-            raise BadRequestError()
+            raise ResourceGoneError()
         return self.agent_manifests, self.etag
 
     def get_vmagent_pkgs(self, manifest):
         self.call_counts["get_vmagent_pkgs"] += 1
         if self.goal_state_is_stale:
             self.goal_state_is_stale = False
-            raise BadRequestError()
+            raise ResourceGoneError()
         return self.agent_packages
 
     def update_goal_state(self, forced=False, max_retry=3):
diff --git a/tests/protocol/mockwiredata.py b/tests/protocol/mockwiredata.py
index 6a70a90f..59247195 100644
--- a/tests/protocol/mockwiredata.py
+++ b/tests/protocol/mockwiredata.py
@@ -16,7 +16,7 @@
 #
 
 from tests.tools import *
-from azurelinuxagent.common.exception import BadRequestError, HttpError
+from azurelinuxagent.common.exception import HttpError, ResourceGoneError
 from azurelinuxagent.common.future import httpclient
 from azurelinuxagent.common.utils.cryptutil import CryptUtil
 
@@ -113,12 +113,12 @@ class WireProtocolData(object):
 
         else:
             # A stale GoalState results in a 400 from the HostPlugin
-            # for which the HTTP handler in restutil raises BadRequestError
+            # for which the HTTP handler in restutil raises ResourceGoneError
             if self.emulate_stale_goal_state:
                 if "extensionArtifact" in url:
                     self.emulate_stale_goal_state = False
                     self.call_counts["extensionArtifact"] += 1
-                    raise BadRequestError()
+                    raise ResourceGoneError()
                 else:
                     raise HttpError()
 
diff --git a/tests/utils/test_rest_util.py b/tests/utils/test_rest_util.py
index 75c75f89..6dfeea21 100644
--- a/tests/utils/test_rest_util.py
+++ b/tests/utils/test_rest_util.py
@@ -18,8 +18,9 @@
 import os
 import unittest
 
-from azurelinuxagent.common.exception import BadRequestError, \
-                                        HttpError, ProtocolError
+from azurelinuxagent.common.exception import HttpError, \
+                                            ProtocolError, \
+                                            ResourceGoneError
 import azurelinuxagent.common.utils.restutil as restutil
 
 from azurelinuxagent.common.future import httpclient, ustr
@@ -272,7 +273,17 @@ class TestHttpOperations(AgentTestCase):
             Mock(status=httpclient.BAD_REQUEST)
         ]
 
-        self.assertRaises(BadRequestError, restutil.http_get, "https://foo.bar")
+        self.assertRaises(ResourceGoneError, restutil.http_get, "https://foo.bar")
+        self.assertEqual(1, _http_request.call_count)
+
+    @patch("time.sleep")
+    @patch("azurelinuxagent.common.utils.restutil._http_request")
+    def test_http_request_raises_for_resource_gone(self, _http_request, _sleep):
+        _http_request.side_effect = [
+            Mock(status=httpclient.GONE)
+        ]
+
+        self.assertRaises(ResourceGoneError, restutil.http_get, "https://foo.bar")
         self.assertEqual(1, _http_request.call_count)
 
     @patch("time.sleep")