mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-19 02:49:21 +02:00
removed EPP connection pool, simplified epplibwrapper, removed socket
This commit is contained in:
parent
b355f1813d
commit
c5d525bd48
8 changed files with 281 additions and 679 deletions
|
@ -2,10 +2,6 @@
|
|||
|
||||
import logging
|
||||
|
||||
from time import sleep
|
||||
from gevent import Timeout
|
||||
from epplibwrapper.utility.pool_status import PoolStatus
|
||||
|
||||
try:
|
||||
from epplib.client import Client
|
||||
from epplib import commands
|
||||
|
@ -18,8 +14,6 @@ from django.conf import settings
|
|||
|
||||
from .cert import Cert, Key
|
||||
from .errors import ErrorCode, LoginError, RegistryError
|
||||
from .socket import Socket
|
||||
from .utility.pool import EPPConnectionPool
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -43,8 +37,13 @@ class EPPLibWrapper:
|
|||
ATTN: This should not be used directly. Use `Domain` from domain.py.
|
||||
"""
|
||||
|
||||
def __init__(self, start_connection_pool=True) -> None:
|
||||
def __init__(self) -> None:
|
||||
"""Initialize settings which will be used for all connections."""
|
||||
# set _client to None initially. In the event that the __init__ fails
|
||||
# before _client initializes, app should still start and be in a state
|
||||
# that it can attempt _client initialization on send attempts
|
||||
logger.info("__init__ called")
|
||||
self._client = None
|
||||
# prepare (but do not send) a Login command
|
||||
self._login = commands.Login(
|
||||
cl_id=settings.SECRET_REGISTRY_CL_ID,
|
||||
|
@ -54,7 +53,16 @@ class EPPLibWrapper:
|
|||
"urn:ietf:params:xml:ns:contact-1.0",
|
||||
],
|
||||
)
|
||||
try:
|
||||
self._initialize_client()
|
||||
except Exception:
|
||||
logger.warning("Unable to configure epplib. Registrar cannot contact registry.", exc_info=True)
|
||||
|
||||
def _initialize_client(self) -> None:
|
||||
"""Initialize a client, assuming _login defined. Sets _client to initialized
|
||||
client. Raises errors if initialization fails.
|
||||
This method will be called at app initialization, and also during retries."""
|
||||
logger.info("_initialize_client called")
|
||||
# establish a client object with a TCP socket transport
|
||||
self._client = Client(
|
||||
SocketTransport(
|
||||
|
@ -64,50 +72,44 @@ class EPPLibWrapper:
|
|||
password=settings.SECRET_REGISTRY_KEY_PASSPHRASE,
|
||||
)
|
||||
)
|
||||
try:
|
||||
# use the _client object to connect
|
||||
self._client.connect()
|
||||
response = self._client.send(self._login)
|
||||
if response.code >= 2000: # type: ignore
|
||||
self._client.close()
|
||||
raise LoginError(response.msg) # type: ignore
|
||||
except TransportError as err:
|
||||
message = "_initialize_client failed to execute due to a connection error."
|
||||
logger.error(f"{message} Error: {err}", exc_info=True)
|
||||
raise RegistryError(message, code=ErrorCode.TRANSPORT_ERROR) from err
|
||||
except LoginError as err:
|
||||
raise err
|
||||
except Exception as err:
|
||||
message = "_initialize_client failed to execute due to an unknown error."
|
||||
logger.error(f"{message} Error: {err}", exc_info=True)
|
||||
raise RegistryError(message) from err
|
||||
|
||||
self.pool_options = {
|
||||
# Pool size
|
||||
"size": settings.EPP_CONNECTION_POOL_SIZE,
|
||||
# Which errors the pool should look out for.
|
||||
# Avoid changing this unless necessary,
|
||||
# it can and will break things.
|
||||
"exc_classes": (TransportError,),
|
||||
# Occasionally pings the registry to keep the connection alive.
|
||||
# Value in seconds => (keepalive / size)
|
||||
"keepalive": settings.POOL_KEEP_ALIVE,
|
||||
}
|
||||
|
||||
self._pool = None
|
||||
|
||||
# Tracks the status of the pool
|
||||
self.pool_status = PoolStatus()
|
||||
|
||||
if start_connection_pool:
|
||||
self.start_connection_pool()
|
||||
def _disconnect(self) -> None:
|
||||
"""Close the connection."""
|
||||
logger.info("_disconnect called")
|
||||
try:
|
||||
self._client.send(commands.Logout())
|
||||
self._client.close()
|
||||
except Exception:
|
||||
logger.warning("Connection to registry was not cleanly closed.")
|
||||
|
||||
def _send(self, command):
|
||||
"""Helper function used by `send`."""
|
||||
logger.info("_send called")
|
||||
cmd_type = command.__class__.__name__
|
||||
|
||||
# Start a timeout to check if the pool is hanging
|
||||
timeout = Timeout(settings.POOL_TIMEOUT)
|
||||
timeout.start()
|
||||
|
||||
try:
|
||||
if not self.pool_status.connection_success:
|
||||
raise LoginError("Couldn't connect to the registry after three attempts")
|
||||
with self._pool.get() as connection:
|
||||
response = connection.send(command)
|
||||
except Timeout as t:
|
||||
# If more than one pool exists,
|
||||
# multiple timeouts can be floating around.
|
||||
# We need to be specific as to which we are targeting.
|
||||
if t is timeout:
|
||||
# Flag that the pool is frozen,
|
||||
# then restart the pool.
|
||||
self.pool_status.pool_hanging = True
|
||||
logger.error("Pool timed out")
|
||||
self.start_connection_pool()
|
||||
# check for the condition that the _client was not initialized properly
|
||||
# at app initialization
|
||||
if self._client is None:
|
||||
self._initialize_client()
|
||||
response = self._client.send(command)
|
||||
except (ValueError, ParsingError) as err:
|
||||
message = f"{cmd_type} failed to execute due to some syntax error."
|
||||
logger.error(f"{message} Error: {err}", exc_info=True)
|
||||
|
@ -131,109 +133,38 @@ class EPPLibWrapper:
|
|||
raise RegistryError(response.msg, code=response.code)
|
||||
else:
|
||||
return response
|
||||
finally:
|
||||
# Close the timeout no matter what happens
|
||||
timeout.close()
|
||||
|
||||
def send(self, command, *, cleaned=False):
|
||||
"""Login, send the command, then close the connection. Tries 3 times."""
|
||||
def _retry(self, command, *, cleaned=False):
|
||||
"""Retry sending a command through EPP by re-initializing the client
|
||||
and then sending the command."""
|
||||
logger.info("_retry called")
|
||||
# re-initialize by disconnecting and initial
|
||||
self._disconnect()
|
||||
self._initialize_client()
|
||||
# try to prevent use of this method without appropriate safeguards
|
||||
if not cleaned:
|
||||
raise ValueError("Please sanitize user input before sending it.")
|
||||
return self._send(command)
|
||||
|
||||
# Reopen the pool if its closed
|
||||
# Only occurs when a login error is raised, after connection is successful
|
||||
if not self.pool_status.pool_running:
|
||||
# We want to reopen the connection pool,
|
||||
# but we don't want the end user to wait while it opens.
|
||||
# Raise syntax doesn't allow this, so we use a try/catch
|
||||
# block.
|
||||
try:
|
||||
logger.error("Can't contact the Registry. Pool was not running.")
|
||||
raise RegistryError("Can't contact the Registry. Pool was not running.")
|
||||
except RegistryError as err:
|
||||
def send(self, command, *, cleaned=False):
|
||||
"""Login, send the command, then close the connection. Tries 3 times."""
|
||||
logger.info("send called")
|
||||
# try to prevent use of this method without appropriate safeguards
|
||||
if not cleaned:
|
||||
raise ValueError("Please sanitize user input before sending it.")
|
||||
try:
|
||||
return self._send(command)
|
||||
except RegistryError as err:
|
||||
if (
|
||||
err.is_transport_error()
|
||||
or err.is_connection_error()
|
||||
or err.is_session_error()
|
||||
or err.is_server_error()
|
||||
or err.should_retry()
|
||||
):
|
||||
return self._retry(command)
|
||||
else:
|
||||
raise err
|
||||
finally:
|
||||
# Code execution will halt after here.
|
||||
# The end user will need to recall .send.
|
||||
self.start_connection_pool()
|
||||
|
||||
counter = 0 # we'll try 3 times
|
||||
while True:
|
||||
try:
|
||||
return self._send(command)
|
||||
except RegistryError as err:
|
||||
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
|
||||
raise err
|
||||
|
||||
def get_pool(self):
|
||||
"""Get the current pool instance"""
|
||||
return self._pool
|
||||
|
||||
def _create_pool(self, client, login, options):
|
||||
"""Creates and returns new pool instance"""
|
||||
logger.info("New pool was created")
|
||||
return EPPConnectionPool(client, login, options)
|
||||
|
||||
def start_connection_pool(self, restart_pool_if_exists=True):
|
||||
"""Starts a connection pool for the registry.
|
||||
|
||||
restart_pool_if_exists -> bool:
|
||||
If an instance of the pool already exists,
|
||||
then then that instance will be killed first.
|
||||
It is generally recommended to keep this enabled.
|
||||
"""
|
||||
# Since we reuse the same creds for each pool, we can test on
|
||||
# one socket, and if successful, then we know we can connect.
|
||||
if not self._test_registry_connection_success():
|
||||
logger.warning("start_connection_pool() -> Cannot contact the Registry")
|
||||
self.pool_status.connection_success = False
|
||||
else:
|
||||
self.pool_status.connection_success = True
|
||||
|
||||
# If this function is reinvoked, then ensure
|
||||
# that we don't have duplicate data sitting around.
|
||||
if self._pool is not None and restart_pool_if_exists:
|
||||
logger.info("Connection pool restarting...")
|
||||
self.kill_pool()
|
||||
logger.info("Old pool killed")
|
||||
|
||||
self._pool = self._create_pool(self._client, self._login, self.pool_options)
|
||||
|
||||
self.pool_status.pool_running = True
|
||||
self.pool_status.pool_hanging = False
|
||||
|
||||
logger.info("Connection pool started")
|
||||
|
||||
def kill_pool(self):
|
||||
"""Kills the existing pool. Use this instead
|
||||
of self._pool = None, as that doesn't clear
|
||||
gevent instances."""
|
||||
if self._pool is not None:
|
||||
self._pool.kill_all_connections()
|
||||
self._pool = None
|
||||
self.pool_status.pool_running = False
|
||||
return None
|
||||
logger.info("kill_pool() was invoked but there was no pool to delete")
|
||||
|
||||
def _test_registry_connection_success(self):
|
||||
"""Check that determines if our login
|
||||
credentials are valid, and/or if the Registrar
|
||||
can be contacted
|
||||
"""
|
||||
# This is closed in test_connection_success
|
||||
socket = Socket(self._client, self._login)
|
||||
can_login = False
|
||||
|
||||
# Something went wrong if this doesn't exist
|
||||
if hasattr(socket, "test_connection_success"):
|
||||
can_login = socket.test_connection_success()
|
||||
|
||||
return can_login
|
||||
|
||||
|
||||
try:
|
||||
|
@ -241,5 +172,4 @@ try:
|
|||
CLIENT = EPPLibWrapper()
|
||||
logger.info("registry client initialized")
|
||||
except Exception:
|
||||
CLIENT = None # type: ignore
|
||||
logger.warning("Unable to configure epplib. Registrar cannot contact registry.", exc_info=True)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue