Skip to content

Commit 9ef867c

Browse files
committed
refactor: Centralize HTTP session management with shared connection pool and token caching
1 parent 0658ee8 commit 9ef867c

File tree

4 files changed

+738
-61
lines changed

4 files changed

+738
-61
lines changed

src/badfish/helpers/http_client.py

Lines changed: 64 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import asyncio
21
import json
32
from typing import Any, Dict, Optional
43

54
import aiohttp
65

76
from src.badfish.helpers.async_lru import alru_cache
87
from src.badfish.helpers.exceptions import BadfishException
8+
from src.badfish.helpers.session_manager import SessionManager
99

1010

1111
class HTTPClient:
@@ -19,10 +19,15 @@ def __init__(self, host: str, username: str, password: str, logger, retries: int
1919
self.host_uri = f"https://{host}"
2020
self.redfish_uri = "/redfish/v1"
2121
self.root_uri = f"{self.host_uri}{self.redfish_uri}"
22-
self.semaphore = asyncio.Semaphore(50)
22+
self.session_manager: Optional[SessionManager] = None
2323
self.token = None
2424
self.session_id = None
2525

26+
async def _ensure_session_manager(self):
27+
"""Lazy initialization of session manager."""
28+
if self.session_manager is None:
29+
self.session_manager = await SessionManager.get_instance()
30+
2631
async def error_handler(self, response: aiohttp.ClientResponse, message: Optional[str] = None) -> None:
2732
try:
2833
raw = await response.text("utf-8", "ignore")
@@ -47,13 +52,13 @@ async def error_handler(self, response: aiohttp.ClientResponse, message: Optiona
4752
@alru_cache(maxsize=64)
4853
async def get_request(self, uri: str, _continue: bool = False, _get_token: bool = False):
4954
return await self.get_raw(uri, _continue, _get_token)
50-
55+
5156
@alru_cache(maxsize=64)
5257
async def get_json(self, uri: str, _continue: bool = False, _get_token: bool = False):
5358
response = await self.get_raw(uri, _continue, _get_token)
5459
if not response:
5560
return None
56-
61+
5762
# Parse JSON from response
5863
try:
5964
raw = await response.text("utf-8", "ignore")
@@ -64,28 +69,30 @@ async def get_json(self, uri: str, _continue: bool = False, _get_token: bool = F
6469
return None
6570

6671
async def get_raw(self, uri: str, _continue: bool = False, _get_token: bool = False):
72+
await self._ensure_session_manager()
73+
74+
# Check token cache first (unless we're getting a new token)
75+
if not _get_token and not self.token:
76+
cached_token = self.session_manager.get_token(self.host)
77+
if cached_token:
78+
self.token = cached_token
79+
cached_session_id = self.session_manager.get_session_id(self.host)
80+
if cached_session_id:
81+
self.session_id = cached_session_id
82+
83+
headers = {}
84+
auth = None
85+
86+
if _get_token:
87+
auth = aiohttp.BasicAuth(self.username, self.password)
88+
elif self.token:
89+
headers["X-Auth-Token"] = self.token
90+
6791
try:
68-
async with self.semaphore:
69-
async with aiohttp.ClientSession() as session:
70-
if not _get_token:
71-
async with session.get(
72-
uri,
73-
headers={"X-Auth-Token": self.token} if self.token else {},
74-
ssl=False,
75-
timeout=60,
76-
) as _response:
77-
await _response.read()
78-
else:
79-
async with session.get(
80-
uri,
81-
auth=aiohttp.BasicAuth(self.username, self.password),
82-
ssl=False,
83-
timeout=60,
84-
) as _response:
85-
await _response.read()
92+
_response = await self.session_manager.get_request(uri, headers, auth)
8693
except (Exception, TimeoutError) as ex:
8794
if _continue:
88-
return
95+
return None
8996
else:
9097
self.logger.debug(f"HTTPClient get_raw exception: {ex}")
9198
self.logger.debug(f"Exception type: {type(ex)}")
@@ -100,39 +107,28 @@ async def post_request(
100107
headers: Dict[str, str],
101108
_get_token: bool = False,
102109
):
110+
await self._ensure_session_manager()
111+
112+
if not _get_token and self.token:
113+
headers = headers.copy() # Avoid mutating caller's dict
114+
headers.update({"X-Auth-Token": self.token})
115+
103116
try:
104-
async with self.semaphore:
105-
async with aiohttp.ClientSession() as session:
106-
if not _get_token and self.token:
107-
headers.update({"X-Auth-Token": self.token})
108-
async with session.post(
109-
uri,
110-
data=json.dumps(payload),
111-
headers=headers,
112-
ssl=False,
113-
) as _response:
114-
if _response.status != 204:
115-
await _response.read()
116-
else:
117-
return _response
117+
_response = await self.session_manager.post_request(uri, payload, headers)
118118
except (Exception, TimeoutError):
119119
raise BadfishException("Failed to communicate with server.")
120120
return _response
121121

122122
async def patch_request(self, uri: str, payload: Dict[str, Any], headers: Dict[str, str], _continue: bool = False):
123+
await self._ensure_session_manager()
124+
125+
if self.token:
126+
headers = headers.copy() # Avoid mutating caller's dict
127+
headers.update({"X-Auth-Token": self.token})
128+
123129
try:
124-
async with self.semaphore:
125-
async with aiohttp.ClientSession() as session:
126-
if self.token:
127-
headers.update({"X-Auth-Token": self.token})
128-
async with session.patch(
129-
uri,
130-
data=json.dumps(payload),
131-
headers=headers,
132-
ssl=False,
133-
) as _response:
134-
raw_data = await _response.read()
135-
return _response
130+
_response = await self.session_manager.patch_request(uri, payload, headers)
131+
return _response
136132
except Exception as ex:
137133
if _continue:
138134
return None
@@ -141,18 +137,15 @@ async def patch_request(self, uri: str, payload: Dict[str, Any], headers: Dict[s
141137
raise BadfishException("Failed to communicate with server.")
142138

143139
async def delete_request(self, uri: str, headers: Dict[str, str]):
140+
await self._ensure_session_manager()
141+
142+
if self.token:
143+
headers = headers.copy() # Avoid mutating caller's dict
144+
headers.update({"X-Auth-Token": self.token})
145+
144146
try:
145-
async with self.semaphore:
146-
async with aiohttp.ClientSession() as session:
147-
if self.token:
148-
headers.update({"X-Auth-Token": self.token})
149-
async with session.delete(
150-
uri,
151-
headers=headers,
152-
ssl=False,
153-
) as _response:
154-
raw_data = await _response.read()
155-
return _response
147+
_response = await self.session_manager.delete_request(uri, headers)
148+
return _response
156149
except (Exception, TimeoutError):
157150
raise BadfishException("Failed to communicate with server.")
158151

@@ -183,6 +176,8 @@ async def find_session_uri(self):
183176
return session_uri
184177

185178
async def validate_credentials(self):
179+
await self._ensure_session_manager()
180+
186181
payload = {"UserName": self.username, "Password": self.password}
187182
headers = {"content-type": "application/json"}
188183
session_uri = await self.find_session_uri()
@@ -200,6 +195,10 @@ async def validate_credentials(self):
200195

201196
self.session_id = _response.headers.get("Location")
202197
token = _response.headers.get("X-Auth-Token")
198+
199+
# Cache token in session manager
200+
self.session_manager.cache_token(self.host, token, self.session_id, ttl_seconds=3600)
201+
203202
return token
204203

205204
async def delete_session(self):
@@ -223,8 +222,13 @@ async def delete_session(self):
223222
except Exception as ex:
224223
self.logger.warning(f"Failed to delete session for {self.host}: {ex}")
225224
finally:
225+
# Invalidate cached token in session manager
226+
if self.session_manager:
227+
self.session_manager.invalidate_token(self.host)
226228
self.session_id = None
227229
self.token = None
228230
except Exception:
231+
if self.session_manager:
232+
self.session_manager.invalidate_token(self.host)
229233
self.session_id = None
230234
self.token = None

src/badfish/helpers/parser.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import argparse
22

3-
from badfish.config import RETRIES
3+
from src.badfish.config import RETRIES
44

55

66
def create_parser():

0 commit comments

Comments
 (0)