Repository: cisagov/manage.get.gov

Issue:
Add a retry on the sending messages to epp
### Issue description

We are getting several errors on stable where a epp message (setting contacts or nameservers) receives an empty response from the registry and hits an error, but the next message goes through. It is suggested we should do a retry with these messages, if a Transport Error occurs.

### Acceptance criteria

- [ ] In the event of a transport error, the epplibwrapper should attempt a retry on sending the message to the registry

### Additional context

https://cisa-corp.slack.com/archives/C05BGB4L5NF/p1701129837064509

Note, the connection pool library does have a retry tag that could be used, though that may do it for all messages and we don't need to retry in case of all failures (just if an empty response is received)
#1378 is determined it may not be an issue with availability but with the epp connection itself where it's getting an empty response. Adding the retry is a possible solution to this.
### Links to other issues
related to #1378 


Discussion:

Resolving patch:
diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py
--- a/src/epplibwrapper/client.py
+++ b/src/epplibwrapper/client.py
@@ -17,7 +17,7 @@
 from django.conf import settings
 
 from .cert import Cert, Key
-from .errors import LoginError, RegistryError
+from .errors import ErrorCode, LoginError, RegistryError
 from .socket import Socket
 from .utility.pool import EPPConnectionPool
 
@@ -115,7 +115,7 @@ def _send(self, command):
         except TransportError as err:
             message = f"{cmd_type} failed to execute due to a connection error."
             logger.error(f"{message} Error: {err}", exc_info=True)
-            raise RegistryError(message) from err
+            raise RegistryError(message, code=ErrorCode.TRANSPORT_ERROR) from err
         except LoginError as err:
             # For linter due to it not liking this line length
             text = "failed to execute due to a registry login error."
@@ -163,7 +163,8 @@ def send(self, command, *, cleaned=False):
             try:
                 return self._send(command)
             except RegistryError as err:
-                if err.should_retry() and counter < 3:
+                if counter < 3 and (err.should_retry() or err.is_transport_error()):
+                    logger.info(f"Retrying transport error. Attempt #{counter+1} of 3.")
                     counter += 1
                     sleep((counter * 50) / 1000)  # sleep 50 ms to 150 ms
                 else:  # don't try again
diff --git a/src/epplibwrapper/errors.py b/src/epplibwrapper/errors.py
--- a/src/epplibwrapper/errors.py
+++ b/src/epplibwrapper/errors.py
@@ -4,13 +4,15 @@
 class ErrorCode(IntEnum):
     """
     Overview of registry response codes from RFC 5730. See RFC 5730 for full text.
-
+        - 0 System connection error
         - 1000 - 1500 Success
         - 2000 - 2308 Registrar did something silly
         - 2400 - 2500 Registry did something silly
         - 2501 - 2502 Something malicious or abusive may have occurred
     """
 
+    TRANSPORT_ERROR = 0
+
     COMMAND_COMPLETED_SUCCESSFULLY = 1000
     COMMAND_COMPLETED_SUCCESSFULLY_ACTION_PENDING = 1001
     COMMAND_COMPLETED_SUCCESSFULLY_NO_MESSAGES = 1300
@@ -67,6 +69,9 @@ def __init__(self, *args, code=None, **kwargs):
     def should_retry(self):
         return self.code == ErrorCode.COMMAND_FAILED
 
+    def is_transport_error(self):
+        return self.code == ErrorCode.TRANSPORT_ERROR
+
     # connection errors have error code of None and [Errno 99] in the err message
     def is_connection_error(self):
         return self.code is None


Test patch: