Files
2022-11-27 19:11:46 +01:00

182 lines
5.8 KiB
Python

"""Specialized Transport Adapter for UNIX domain sockets."""
import collections
import functools
import http.client
import logging
import socket
from typing import Optional, Union
from urllib.parse import unquote, urlparse
try:
import urllib3
except ImportError:
from requests.packages import urllib3
from requests.adapters import DEFAULT_POOLBLOCK, DEFAULT_POOLSIZE, DEFAULT_RETRIES, HTTPAdapter
from ..errors import APIError
from .adapter_utils import _key_normalizer
logger = logging.getLogger("podman.uds_adapter")
class UDSSocket(socket.socket):
"""Specialization of socket.socket for a UNIX domain socket."""
def __init__(self, uds: str):
"""Initialize UDSSocket.
Args:
uds: Full address of a Podman service UNIX domain socket.
Examples:
UDSSocket("http+unix:///run/podman/podman.sock")
"""
super().__init__(socket.AF_UNIX, socket.SOCK_STREAM)
self.uds = uds
def connect(self, **kwargs): # pylint: disable=unused-argument
"""Returns socket for UNIX domain socket."""
netloc = unquote(urlparse(self.uds).netloc)
try:
super().connect(netloc)
except Exception as e:
raise APIError(f"Unable to make connection to UDS '{netloc}'") from e
class UDSConnection(http.client.HTTPConnection):
"""Specialization of HTTPConnection to use a UNIX domain sockets."""
def __init__(
self,
host: str,
port: int,
timeout: Union[float, urllib3.Timeout, None] = None,
strict=False,
**kwargs, # pylint: disable=unused-argument
):
"""Initialize connection to UNIX domain socket for HTTP client.
Args:
host: Ignored.
port: Ignored.
timeout: Time to allow for operation.
strict: Ignored.
Keyword Args:
uds: Full address of a Podman service UNIX domain socket. Required.
"""
connection_kwargs = kwargs.copy()
self.sock: Optional[socket.socket] = None
if timeout is not None:
if isinstance(timeout, urllib3.Timeout):
try:
connection_kwargs["timeout"] = float(timeout.total)
except TypeError:
pass
connection_kwargs["timeout"] = timeout
self.uds = connection_kwargs.pop("uds")
super().__init__(host, **connection_kwargs)
def connect(self) -> None:
"""Connect to Podman service via UNIX domain socket."""
sock = UDSSocket(self.uds)
sock.settimeout(self.timeout)
sock.connect()
self.sock = sock
class UDSConnectionPool(urllib3.HTTPConnectionPool):
"""Specialization of HTTPConnectionPool for holding UNIX domain sockets."""
ConnectionCls = UDSConnection
class UDSPoolManager(urllib3.PoolManager):
"""Specialized PoolManager for tracking UNIX domain socket connections."""
# pylint's special handling for namedtuple does not cover this usage
# pylint: disable=invalid-name
_PoolKey = collections.namedtuple(
"_PoolKey", urllib3.poolmanager.PoolKey._fields + ("key_uds",)
)
# Map supported schemes to Pool Classes
_pool_classes_by_scheme = {
"http": UDSConnectionPool,
"http+ssh": UDSConnectionPool,
}
# Map supported schemes to Pool Key index generator
_key_fn_by_scheme = {
"http": functools.partial(_key_normalizer, _PoolKey),
"http+ssh": functools.partial(_key_normalizer, _PoolKey),
}
def __init__(self, num_pools=10, headers=None, **kwargs):
"""Initialize UDSPoolManager.
Args:
num_pools: Number of UDS Connection pools to maintain.
headers: Additional headers to add to operations.
"""
super().__init__(num_pools, headers, **kwargs)
self.pool_classes_by_scheme = UDSPoolManager._pool_classes_by_scheme
self.key_fn_by_scheme = UDSPoolManager._key_fn_by_scheme
class UDSAdapter(HTTPAdapter):
"""Specialization of requests transport adapter for UNIX domain sockets."""
def __init__(
self,
uds: str,
pool_connections=DEFAULT_POOLSIZE,
pool_maxsize=DEFAULT_POOLSIZE,
max_retries=DEFAULT_RETRIES,
pool_block=DEFAULT_POOLBLOCK,
**kwargs,
):
"""Initialize UDSAdapter.
Args:
uds: Full address of a Podman service UNIX domain socket.
Format, http+unix:///run/podman/podman.sock
max_retries: The maximum number of retries each connection should attempt.
pool_block: Whether the connection pool should block for connections.
pool_connections: The number of connection pools to cache.
pool_maxsize: The maximum number of connections to save in the pool.
Keyword Args:
timeout (float): Time in seconds to wait for response
Examples:
requests.Session.mount(
"http://", UDSAdapater("http+unix:///run/user/1000/podman/podman.sock"))
"""
self.poolmanager: Optional[UDSPoolManager] = None
self._pool_kwargs = {"uds": uds}
if "timeout" in kwargs:
self._pool_kwargs["timeout"] = kwargs.get("timeout")
super().__init__(pool_connections, pool_maxsize, max_retries, pool_block)
def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **kwargs):
"""Initialize UDS Pool Manager.
Args:
connections: The number of urllib3 connection pools to cache.
maxsize: The maximum number of connections to save in the pool.
block: Block when no free connections are available.
"""
pool_kwargs = kwargs.copy()
pool_kwargs.update(self._pool_kwargs)
self.poolmanager = UDSPoolManager(
num_pools=connections, maxsize=maxsize, block=block, **pool_kwargs
)