11# SPDX-License-Identifier: Apache-2.0
22
3+ import atexit
4+ import threading
35import time
46import uuid
57import os
@@ -90,20 +92,77 @@ def __exit__(self, exc_type, exc_val, exc_tb):
9092class TimeoutHTTPAdapter (HTTPAdapter ):
9193 """HTTPAdapter that sets a default timeout for all requests."""
9294
93- def __init__ (self , timeout = None , * args , ** kwargs ):
95+ def __init__ (
96+ self , timeout = None , pool_connections = 10 , pool_maxsize = 10 , * args , ** kwargs
97+ ):
9498 self .timeout = timeout
95- super ().__init__ (* args , ** kwargs )
99+ super ().__init__ (
100+ pool_connections = pool_connections ,
101+ pool_maxsize = pool_maxsize ,
102+ * args ,
103+ ** kwargs ,
104+ )
96105
97106 def send (self , request , ** kwargs ):
98107 if kwargs .get ("timeout" ) is None and self .timeout is not None :
99108 kwargs ["timeout" ] = self .timeout
100109 return super ().send (request , ** kwargs )
101110
102111
112+ class NetBoxSessionManager :
113+ """Manages a shared HTTP session for all NetBox connections.
114+
115+ This class implements lazy initialization of a single shared session
116+ to prevent file descriptor exhaustion from multiple session instances.
117+ """
118+
119+ _session = None
120+ _lock = None
121+
122+ @classmethod
123+ def get_session (cls , ignore_ssl_errors = False , timeout = 20 ):
124+ """Get or create the shared session (lazy initialization).
125+
126+ Args:
127+ ignore_ssl_errors: Whether to ignore SSL certificate errors
128+ timeout: Request timeout in seconds (default: 20)
129+
130+ Returns:
131+ requests.Session: The shared session instance
132+ """
133+ if cls ._session is None :
134+ if cls ._lock is None :
135+ cls ._lock = threading .Lock ()
136+ with cls ._lock :
137+ if cls ._session is None :
138+ cls ._session = requests .Session ()
139+ adapter = TimeoutHTTPAdapter (
140+ timeout = timeout , pool_connections = 10 , pool_maxsize = 10
141+ )
142+ cls ._session .mount ("http://" , adapter )
143+ cls ._session .mount ("https://" , adapter )
144+ if ignore_ssl_errors :
145+ urllib3 .disable_warnings ()
146+ cls ._session .verify = False
147+ return cls ._session
148+
149+ @classmethod
150+ def close_session (cls ):
151+ """Close the shared session and release resources."""
152+ if cls ._session is not None :
153+ cls ._session .close ()
154+ cls ._session = None
155+
156+
157+ def cleanup_netbox_sessions ():
158+ """Cleanup function to close all NetBox sessions."""
159+ NetBoxSessionManager .close_session ()
160+
161+
103162def get_netbox_connection (
104163 netbox_url , netbox_token , ignore_ssl_errors = False , timeout = 20
105164):
106- """Create a NetBox API connection.
165+ """Create a NetBox API connection with shared session .
107166
108167 Args:
109168 netbox_url: NetBox URL
@@ -118,19 +177,10 @@ def get_netbox_connection(
118177 nb = pynetbox .api (netbox_url , token = netbox_token )
119178
120179 if nb :
121- # Create session with timeout adapter
122- session = requests .Session ()
123-
124- # Mount timeout adapter for both http and https
125- adapter = TimeoutHTTPAdapter (timeout = timeout )
126- session .mount ("http://" , adapter )
127- session .mount ("https://" , adapter )
128-
129- if ignore_ssl_errors :
130- urllib3 .disable_warnings ()
131- session .verify = False
132-
133- nb .http_session = session
180+ # Use shared session instead of creating new one
181+ nb .http_session = NetBoxSessionManager .get_session (
182+ ignore_ssl_errors = ignore_ssl_errors , timeout = timeout
183+ )
134184
135185 else :
136186 nb = None
@@ -207,6 +257,10 @@ def get_netbox_connection(
207257 secondary_nb_list = []
208258
209259
260+ # Register cleanup handler to close sessions on program exit
261+ atexit .register (cleanup_netbox_sessions )
262+
263+
210264def get_openstack_connection ():
211265 try :
212266 conn = openstack .connect ()
0 commit comments