13
13
from requests .packages .urllib3 .exceptions import InsecureRequestWarning
14
14
from requests .packages .urllib3 .exceptions import SubjectAltNameWarning
15
15
from 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
+
18
25
def parseConfig (args ):
19
26
'''Parses the config file for specified metrics.'''
20
27
@@ -92,7 +99,10 @@ def check_nitro_access(protocol, nsip, username, password, ns_cert):
92
99
logger .error ('Invalid username or password for Citrix Adc!, Unaurthorized Err : {}' .format (response .status_code ))
93
100
return False
94
101
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 ))
96
106
return False
97
107
return True
98
108
@@ -132,14 +142,53 @@ def verify_ns_stats_access(nsip, ns_protocol, ns_user, ns_password, timeout, ns_
132
142
'''Validates if exporter is able to fetch stats from ADC.'''
133
143
134
144
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 ))
136
146
while not ns_stat_access :
137
147
ns_stat_access = get_sslcertkey_stats (ns_protocol , nsip , ns_user , ns_password , timeout , ns_cert )
138
148
if ns_stat_access is False :
139
149
logger .info ('Retrying to verify stat access for citrix adc with ip {}' .format (nsip ))
140
150
time .sleep (4 )
141
151
logger .info ('Exporter able to acces stats for citrix adc {}' .format (nsip ))
142
152
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
+
143
192
# Priority order for credentials follows the order config.yaml input > env variables
144
193
# First env values are populated which can then be overwritten by config values if present.
145
194
def get_login_credentials (args ):
@@ -148,20 +197,31 @@ def get_login_credentials(args):
148
197
ns_user = os .environ .get ("NS_USER" )
149
198
ns_password = os .environ .get ("NS_PASSWORD" )
150
199
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
+
151
206
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 ):
153
208
try :
154
- with open ("/mnt/nslogin/username" , 'r' ) as f :
209
+ with open (NS_USERNAME_FILE , 'r' ) as f :
155
210
ns_user = f .read ().rstrip ()
156
211
except Exception as e :
157
212
logger .error ('Error while reading secret. Verify if secret is property mounted::%s' , e )
158
213
159
- if os .path .isfile ("/mnt/nslogin/password" ):
214
+ if os .path .isfile (NS_PASSWORD_FILE ):
160
215
try :
161
- with open ("/mnt/nslogin/password" , 'r' ) as f :
216
+ with open (NS_PASSWORD_FILE , 'r' ) as f :
162
217
ns_password = f .read ().rstrip ()
163
218
except Exception as e :
164
219
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
+
165
225
else :
166
226
if hasattr (args , 'username' ):
167
227
ns_user = args .username
@@ -171,6 +231,7 @@ def get_login_credentials(args):
171
231
172
232
return ns_user , ns_password
173
233
234
+
174
235
def get_ns_session_protocol (args ):
175
236
'Get ns session protocol to access ADC'
176
237
secure = args .secure .lower ()
@@ -180,6 +241,7 @@ def get_ns_session_protocol(args):
180
241
ns_protocol = 'http'
181
242
return ns_protocol
182
243
244
+
183
245
def get_ns_cert_path (args ):
184
246
'Get ns cert path if protocol is secure option is set'
185
247
if args .cacert_path :
@@ -229,18 +291,22 @@ def __init__(self, nsip, metrics, username, password, protocol,
229
291
self .nitro_timeout = nitro_timeout
230
292
self .k8s_cic_prefix = k8s_cic_prefix
231
293
self .ns_cert = ns_cert
232
-
294
+ self .ns_session = requests .Session ()
295
+
233
296
# Collect metrics from Citrix ADC
234
297
def collect (self ):
235
298
nsip = self .nsip
236
299
data = {}
300
+ self .ns_session_login ()
301
+
237
302
for entity in self .metrics .keys ():
238
303
logger .info ('Collecting metric %s for %s' % (entity , nsip ))
239
304
try :
240
305
data [entity ] = self .collect_data (entity )
241
306
except Exception as e :
242
307
logger .warning ('Could not collect metric: ' + str (e ))
243
308
309
+ self .ns_session_logout ()
244
310
# Add labels to metrics and provide to Prometheus
245
311
log_prefix_match = True
246
312
@@ -396,15 +462,16 @@ def get_lbvs_bindings_status(self):
396
462
def get_entity_stat (self , url ):
397
463
'''Fetches stats from ADC using nitro using for a particular entity.'''
398
464
399
- headers = {'X-NITRO-USER' : self .username , 'X-NITRO-PASS' : self .password }
400
465
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 )
402
467
data = r .json ()
403
468
if data ['errorcode' ] == 0 :
404
469
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
+
408
475
409
476
def update_lbvs_label (self , label_values , ns_metric_name , log_prefix_match ):
410
477
'''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):
441
508
logger .error ('Unable to update k8s label: (%s)' , e )
442
509
return False
443
510
511
+ def ns_session_login (self ):
512
+ ''' Login to ADC and get a session id for stat access'''
444
513
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
+
445
559
if __name__ == '__main__' :
446
560
447
561
parser = argparse .ArgumentParser ()
0 commit comments