1313from requests .packages .urllib3 .exceptions import InsecureRequestWarning
1414from requests .packages .urllib3 .exceptions import SubjectAltNameWarning
1515from requests .auth import HTTPBasicAuth
16-
17-
16+ from retrying import RetryError
17+ from retrying import retry
18+
19+ NS_USERNAME_FILE = '/mnt/nslogin/username'
20+ NS_PASSWORD_FILE = '/mnt/nslogin/password'
21+ DEPLOYMENT_WITH_CPX = 'sidecar'
22+ CPX_CRED_DIR = '/var/deviceinfo'
23+ CPX_CRED_FILE = '/var/deviceinfo/random_id'
24+
1825def parseConfig (args ):
1926 '''Parses the config file for specified metrics.'''
2027
@@ -92,7 +99,10 @@ def check_nitro_access(protocol, nsip, username, password, ns_cert):
9299 logger .error ('Invalid username or password for Citrix Adc!, Unaurthorized Err : {}' .format (response .status_code ))
93100 return False
94101 except requests .exceptions .RequestException as err :
95- logger .error ('{}' .format (err ))
102+ logger .error ('Nitroc Access Error {}' .format (err ))
103+ return False
104+ except Exception as e :
105+ logger .error ("Unable to authenticated ADC nitro credentials {}" .format (e ))
96106 return False
97107 return True
98108
@@ -132,14 +142,53 @@ def verify_ns_stats_access(nsip, ns_protocol, ns_user, ns_password, timeout, ns_
132142 '''Validates if exporter is able to fetch stats from ADC.'''
133143
134144 ns_stat_access = False
135- logger .info ('Verifing stat acces for citrix adc with ip {}' .format (nsip ))
145+ logger .info ('Verifying stat acces for citrix adc with ip {}' .format (nsip ))
136146 while not ns_stat_access :
137147 ns_stat_access = get_sslcertkey_stats (ns_protocol , nsip , ns_user , ns_password , timeout , ns_cert )
138148 if ns_stat_access is False :
139149 logger .info ('Retrying to verify stat access for citrix adc with ip {}' .format (nsip ))
140150 time .sleep (4 )
141151 logger .info ('Exporter able to acces stats for citrix adc {}' .format (nsip ))
142152
153+
154+ def retry_cpx_password_read (ns_password ):
155+ if ns_password is not None :
156+ return False
157+ return True
158+
159+ # Generally in the side car mode, credentials should be immediately available.
160+ # Credential file availability cannot take more than a minute in SIDECAR mode even when nodes are highly engaged.
161+ # Wait for credentials max upto 120 seconds.
162+ # There is no need to wait indefinetely even if credentials are not available after two minutes.
163+ @retry (stop_max_attempt_number = 120 , wait_fixed = 1000 , retry_on_result = retry_cpx_password_read )
164+ def read_cpx_credentials (ns_password ):
165+ if os .path .isdir (CPX_CRED_DIR ):
166+ if os .path .isfile (CPX_CRED_FILE ) and os .path .getsize (CPX_CRED_FILE ):
167+ try :
168+ with open (CPX_CRED_FILE , 'r' ) as fr :
169+ ns_password = fr .read ()
170+ if ns_password is not None :
171+ logger .info ("SIDECAR Mode: Successfully read crendetials for CPX" )
172+ else :
173+ logger .debug ("SIDECAR Mode: None password while reading CPX crednetials from file" )
174+ except IOError as e :
175+ logger .debug ("SIDECAR Mode: IOError {}, while reading CPX crednetials from file" .format (e ))
176+ return ns_password
177+
178+
179+ def get_cpx_credentials (ns_user , ns_password ):
180+ 'Get ns credenttials when CPX mode'
181+
182+ logger .info ("SIDECAR Mode: Trying to get credentials for CPX" )
183+ try :
184+ ns_password = read_cpx_credentials (ns_password )
185+ except RetryError :
186+ logger .error ("SIDECAR Mode: Unable to fetch CPX credentials" )
187+
188+ if ns_password is not None :
189+ ns_user = 'nsroot'
190+ return ns_user , ns_password
191+
143192# Priority order for credentials follows the order config.yaml input > env variables
144193# First env values are populated which can then be overwritten by config values if present.
145194def get_login_credentials (args ):
@@ -148,20 +197,31 @@ def get_login_credentials(args):
148197 ns_user = os .environ .get ("NS_USER" )
149198 ns_password = os .environ .get ("NS_PASSWORD" )
150199
200+ deployment_mode = os .environ .get ("NS_DEPLOYMENT_MODE" , "" )
201+ if deployment_mode .lower () == 'sidecar' :
202+ logger .info ('ADC is running as sidecar' )
203+ else :
204+ logger .info ('ADC is running as standalone' )
205+
151206 if os .environ .get ('KUBERNETES_SERVICE_HOST' ) is not None :
152- if os .path .isfile ("/mnt/nslogin/username" ):
207+ if os .path .isfile (NS_USERNAME_FILE ):
153208 try :
154- with open ("/mnt/nslogin/username" , 'r' ) as f :
209+ with open (NS_USERNAME_FILE , 'r' ) as f :
155210 ns_user = f .read ().rstrip ()
156211 except Exception as e :
157212 logger .error ('Error while reading secret. Verify if secret is property mounted::%s' , e )
158213
159- if os .path .isfile ("/mnt/nslogin/password" ):
214+ if os .path .isfile (NS_PASSWORD_FILE ):
160215 try :
161- with open ("/mnt/nslogin/password" , 'r' ) as f :
216+ with open (NS_PASSWORD_FILE , 'r' ) as f :
162217 ns_password = f .read ().rstrip ()
163218 except Exception as e :
164219 logger .error ('Error while reading secret. Verify if secret is property mounted::%s' , e )
220+
221+ if ns_user is None and ns_password is None :
222+ if deployment_mode .lower () == DEPLOYMENT_WITH_CPX :
223+ ns_user , ns_password = get_cpx_credentials (ns_user , ns_password )
224+
165225 else :
166226 if hasattr (args , 'username' ):
167227 ns_user = args .username
@@ -171,6 +231,7 @@ def get_login_credentials(args):
171231
172232 return ns_user , ns_password
173233
234+
174235def get_ns_session_protocol (args ):
175236 'Get ns session protocol to access ADC'
176237 secure = args .secure .lower ()
@@ -180,6 +241,7 @@ def get_ns_session_protocol(args):
180241 ns_protocol = 'http'
181242 return ns_protocol
182243
244+
183245def get_ns_cert_path (args ):
184246 'Get ns cert path if protocol is secure option is set'
185247 if args .cacert_path :
@@ -229,18 +291,22 @@ def __init__(self, nsip, metrics, username, password, protocol,
229291 self .nitro_timeout = nitro_timeout
230292 self .k8s_cic_prefix = k8s_cic_prefix
231293 self .ns_cert = ns_cert
232-
294+ self .ns_session = requests .Session ()
295+
233296 # Collect metrics from Citrix ADC
234297 def collect (self ):
235298 nsip = self .nsip
236299 data = {}
300+ self .ns_session_login ()
301+
237302 for entity in self .metrics .keys ():
238303 logger .info ('Collecting metric %s for %s' % (entity , nsip ))
239304 try :
240305 data [entity ] = self .collect_data (entity )
241306 except Exception as e :
242307 logger .warning ('Could not collect metric: ' + str (e ))
243308
309+ self .ns_session_logout ()
244310 # Add labels to metrics and provide to Prometheus
245311 log_prefix_match = True
246312
@@ -396,15 +462,16 @@ def get_lbvs_bindings_status(self):
396462 def get_entity_stat (self , url ):
397463 '''Fetches stats from ADC using nitro using for a particular entity.'''
398464
399- headers = {'X-NITRO-USER' : self .username , 'X-NITRO-PASS' : self .password }
400465 try :
401- r = requests . get (url , headers = headers , verify = self .ns_cert , timeout = self .nitro_timeout )
466+ r = self . ns_session . get (url , verify = self .ns_cert , timeout = self .nitro_timeout )
402467 data = r .json ()
403468 if data ['errorcode' ] == 0 :
404469 return data
405- except Exception as e :
406- logger .error ("Unable to access stats from ADC" )
407- return None
470+ except requests .exceptions .RequestException as err :
471+ logger .error ('Stat Access Error {}' .format (err ))
472+ except Exception as e :
473+ logger .error ("Unable to access stats from ADC {}" .format (e ))
474+
408475
409476 def update_lbvs_label (self , label_values , ns_metric_name , log_prefix_match ):
410477 '''Updates lbvserver lables for ingress and services for k8s_cic_ingress_service_stat dashboard.'''
@@ -441,7 +508,54 @@ def update_lbvs_label(self, label_values, ns_metric_name, log_prefix_match):
441508 logger .error ('Unable to update k8s label: (%s)' , e )
442509 return False
443510
511+ def ns_session_login (self ):
512+ ''' Login to ADC and get a session id for stat access'''
444513
514+ payload = {"login" : {'username' : self .username , 'password' : self .password }}
515+ url = '%s://%s/nitro/v1/config/login' % (self .protocol , self .nsip )
516+ ns_login = False
517+ while not ns_login :
518+ try :
519+ response = self .ns_session .post (url , json = payload , verify = ns_cert )
520+ data = response .json ()
521+ if data ['errorcode' ] == 0 :
522+ logger .info ("ADC Session Login Successful" )
523+ ns_login = True
524+ else :
525+ logger .error ("ADC Session Login Failed" )
526+ except requests .exceptions .RequestException as err :
527+ logger .error ('Session Login Error {}' .format (err ))
528+ except Exception as e :
529+ logger .error ("Login Session Try Failed{}" .format (e ))
530+ if ns_login is False :
531+ logger .info ('Retrying to Login to citrix adc' )
532+ time .sleep (1 )
533+
534+ def ns_session_logout (self ):
535+ ''' Logout of ADC session'''
536+
537+ payload = {"logout" : {}}
538+ url = '%s://%s/nitro/v1/config/logout' % (self .protocol , self .nsip )
539+ ns_logout = False
540+ while not ns_logout :
541+ try :
542+ response = self .ns_session .post (url , json = payload , verify = ns_cert )
543+ if response .status_code == 201 or response .status_code == 200 :
544+ ns_logout = True
545+ self .ns_session .close ()
546+ logger .info ("ADC Session Logout Successful" )
547+ break
548+ else :
549+ logger .error ("ADC Session Logout Failed" )
550+ except requests .exceptions .RequestException as err :
551+ logger .error ('Session Logout Error {}' .format (err ))
552+ except Exception as e :
553+ logger .error ("Logout Session Try Failed{}" .format (e ))
554+ if ns_logout is False :
555+ logger .info ('Retrying to Logout of citrix adc' )
556+ time .sleep (1 )
557+
558+
445559if __name__ == '__main__' :
446560
447561 parser = argparse .ArgumentParser ()
0 commit comments