Skip to content

Commit

Permalink
Remove requests_unixsocket (canonical#604)
Browse files Browse the repository at this point in the history
This includes implementation to connect to a Unix socket without
`requests_unixsocket`. This allows us to remove this dependency along
with the version restrictions for `urllib3` and `requests`;

Fixes canonical#583
canonical#600
  • Loading branch information
simondeziel authored Oct 1, 2024
2 parents 0f102cb + fcae421 commit 93b83d1
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 13 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ dist/
.coverage
.tox

# Tox tests leftovers
build

# Translations
*.mo

Expand Down
76 changes: 69 additions & 7 deletions pylxd/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,21 @@
import json
import os
import re
import socket
from enum import Enum
from typing import NamedTuple
from urllib import parse

import requests
import requests_unixsocket
import requests.adapters
import urllib3
import urllib3.connection
from cryptography import x509
from cryptography.hazmat.primitives import hashes
from ws4py.client import WebSocketBaseClient

from pylxd import exceptions, managers

requests_unixsocket.monkeypatch()

SNAP_ROOT = os.path.expanduser("~/snap/lxd/common/config/")
APT_ROOT = os.path.expanduser("~/.config/lxc/")
CERT_FILE_NAME = "client.crt"
Expand All @@ -51,6 +52,9 @@ class Cert(NamedTuple):
key=os.path.join(CERTS_PATH, KEY_FILE_NAME),
) # pragma: no cover

DEFAULT_SCHEME = "http+unix://"
SOCKET_CONNECTION_TIMEOUT = 60


class EventType(Enum):
All = "all"
Expand All @@ -59,6 +63,65 @@ class EventType(Enum):
Lifecycle = "lifecycle"


class _UnixSocketHTTPConnection(urllib3.connection.HTTPConnection, object):
def __init__(self, unix_socket_url):
super(_UnixSocketHTTPConnection, self).__init__(
"localhost", timeout=SOCKET_CONNECTION_TIMEOUT
)
self.unix_socket_url = unix_socket_url
self.timeout = SOCKET_CONNECTION_TIMEOUT
self.sock = None

def __del__(self):
if self.sock:
self.sock.close()

def connect(self):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.settimeout(self.timeout)
socket_path = parse.unquote(parse.urlparse(self.unix_socket_url).netloc)
sock.connect(socket_path)
self.sock = sock


class _UnixSocketHTTPConnectionPool(urllib3.HTTPConnectionPool):
def __init__(self, socket_path):
super(_UnixSocketHTTPConnectionPool, self).__init__("localhost")
self.socket_path = socket_path

def _new_conn(self):
return _UnixSocketHTTPConnection(self.socket_path)


class _UnixAdapter(requests.adapters.HTTPAdapter):
def __init__(self, pool_connections=25, *args, **kwargs):
super(_UnixAdapter, self).__init__(*args, **kwargs)
self.pools = urllib3._collections.RecentlyUsedContainer(
pool_connections, dispose_func=lambda p: p.close()
)

def get_connection(self, url, proxies):
with self.pools.lock:
conn = self.pools.get(url)
if conn:
return conn

conn = _UnixSocketHTTPConnectionPool(url)
self.pools[url] = conn

return conn

# This method is needed fo compatibility with later requests versions.
def get_connection_with_tls_context(self, request, verify, proxies=None, cert=None):
return self.get_connection(request.url, None)

def request_url(self, request, proxies):
return request.path_url

def close(self):
self.pools.clear()


class LXDSSLAdapter(requests.adapters.HTTPAdapter):
def cert_verify(self, conn, url, verify, cert):
with open(verify, "rb") as fd:
Expand All @@ -74,11 +137,10 @@ def get_session_for_url(url: str, verify=None, cert=None) -> requests.Session:
Call sites can use this to customise the session before passing into a Client.
"""
session: requests.Session
if url.startswith("http+unix://"):
session = requests_unixsocket.Session()
session = requests.Session()
if url.startswith(DEFAULT_SCHEME):
session.mount(DEFAULT_SCHEME, _UnixAdapter())
else:
session = requests.Session()
session.cert = cert
session.verify = verify

Expand Down
10 changes: 7 additions & 3 deletions pylxd/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import pytest
import requests
import requests_unixsocket
import requests.adapters

from pylxd import client, exceptions

Expand Down Expand Up @@ -631,13 +631,17 @@ class TestGetSessionForUrl(TestCase):
def test_session_unix_socket(self):
"""http+unix URL return a requests_unixsocket session."""
session = client.get_session_for_url("http+unix://test.com")
self.assertIsInstance(session, requests_unixsocket.Session)
self.assertIsInstance(
session.get_adapter("http+unix://"), requests.adapters.HTTPAdapter
)

def test_session_http(self):
"""HTTP nodes return the default requests session."""
session = client.get_session_for_url("http://test.com")
self.assertIsInstance(session, requests.Session)
self.assertNotIsInstance(session, requests_unixsocket.Session)
self.assertRaises(
requests.exceptions.InvalidSchema, session.get_adapter, "http+unix://"
)

def test_session_cert(self):
"""If certs are given, they're set on the Session."""
Expand Down
4 changes: 1 addition & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@ packages = find:
install_requires =
cryptography >= 3.2
python-dateutil >= 2.4.2
requests >= 2.20.0, < 2.32.0
requests >= 2.20.0
requests-toolbelt >= 0.8.0
requests-unixsocket >= 0.1.5
urllib3 < 2
ws4py != 0.3.5, >= 0.3.4 # 0.3.5 is broken for websocket support

[options.extras_require]
Expand Down

0 comments on commit 93b83d1

Please sign in to comment.