Skip to content

Commit 003b151

Browse files
committed
CPX changes
1 parent d136eba commit 003b151

File tree

5 files changed

+146
-25
lines changed

5 files changed

+146
-25
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ FROM alpine:latest
22
RUN apk update
33
RUN apk add python py-pip
44
RUN apk add curl
5-
RUN pip install prometheus_client requests pyyaml
5+
RUN pip install prometheus_client requests pyyaml retrying
66
COPY version/VERSION /exporter/
77
COPY exporter.py /exporter/
88
COPY metrics.json /exporter/

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ To use the exporter as a python script, the ```prometheus_client``` and ```reque
3333
pip install prometheus_client
3434
pip install requests
3535
pip install PyYAML
36+
pip install retrying
3637
```
3738
Now, create a folder ```/exporter``` and copy the ```metrics.json``` file to the folder.
3839
Finally, the exporter can be run as a python script using;
@@ -85,15 +86,15 @@ In config.yaml, '--validate-cert' option should be set to 'yes', and certificate
8586
<summary>Usage as a Container</summary>
8687
<br>
8788

88-
In order to use the exporter as a container, the image ```quay.io/citrix/citrix-adc-metrics-exporter:1.4.2``` will need to be pulled using;
89+
In order to use the exporter as a container, the image ```quay.io/citrix/citrix-adc-metrics-exporter:1.4.3``` will need to be pulled using;
8990
```
90-
docker pull quay.io/citrix/citrix-adc-metrics-exporter:1.4.2
91+
docker pull quay.io/citrix/citrix-adc-metrics-exporter:1.4.3
9192
```
9293
**NOTE:** It can also be build locally using ```docker build -f Dockerfile -t <image_name>:<tag> ./```
9394

9495
Now, the exporter can be run using:
9596
```
96-
docker run -dt -p <host_port>:<container_port> --mount type=bind,source=<host-path-for-config-file>,target=/exporter/config.yaml quay.io/citrix/citrix-adc-metrics-exporter:1.4.2 [flags] --config-file=/exporter/config.yaml
97+
docker run -dt -p <host_port>:<container_port> --mount type=bind,source=<host-path-for-config-file>,target=/exporter/config.yaml quay.io/citrix/citrix-adc-metrics-exporter:1.4.3 [flags] --config-file=/exporter/config.yaml
9798
```
9899
where the flags are:
99100

@@ -116,7 +117,7 @@ flag&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs
116117

117118
To setup the exporter as given in the diagram, the following command can be used:
118119
```
119-
docker run -dt -p 8888:8888 --mount type=bind,source=/path/to/config.yaml,target=/exporter/config.yaml --name citrix-adc-exporter quay.io/citrix/citrix-adc-metrics-exporter:1.4.2 --target-nsip=10.0.0.1 --port=8888 --config-file=/exporter/config.yaml
120+
docker run -dt -p 8888:8888 --mount type=bind,source=/path/to/config.yaml,target=/exporter/config.yaml --name citrix-adc-exporter quay.io/citrix/citrix-adc-metrics-exporter:1.4.3 --target-nsip=10.0.0.1 --port=8888 --config-file=/exporter/config.yaml
120121
```
121122
This directs the exporter container to scrape the 10.0.0.1 IP, and the expose the stats it collects on port 8888.
122123

@@ -138,7 +139,7 @@ In config.yaml, '--validate-cert' option should be set to 'yes', and certificate
138139
Certificate should then be mounted at the '--cacert-path' provided. For instance, if cert is 'cacert.pem' and '--cacert-path' provided in 'config.yaml' is '/exporter/cacert.pem'
139140

140141
```
141-
docker run -dt -p 8888:8888 --mount type=bind,source=/path/to/config.yaml,target=/exporter/config.yaml --mount type=bind,source=/path/to/cacert.pem,target=/exporter/cacert.pem --name citrix-adc-exporter quay.io/citrix/citrix-adc-metrics-exporter:1.4.2 --target-nsip=10.0.0.1 --port=8888 --config-file=/exporter/config.yaml
142+
docker run -dt -p 8888:8888 --mount type=bind,source=/path/to/config.yaml,target=/exporter/config.yaml --mount type=bind,source=/path/to/cacert.pem,target=/exporter/cacert.pem --name citrix-adc-exporter quay.io/citrix/citrix-adc-metrics-exporter:1.4.3 --target-nsip=10.0.0.1 --port=8888 --config-file=/exporter/config.yaml
142143
```
143144
Cert validation options can also be provided using environment variables using NS_VALIDATE_CERT, NS_CACERT_PATH. Thoughconfig file input is the preferred method.
144145

@@ -167,7 +168,7 @@ metadata:
167168
spec:
168169
containers:
169170
- name: exporter
170-
image: quay.io/citrix/citrix-adc-metrics-exporter:1.4.2
171+
image: quay.io/citrix/citrix-adc-metrics-exporter:1.4.3
171172
args:
172173
- "--target-nsip=10.0.0.1"
173174
- "--port=8888"
@@ -240,7 +241,7 @@ metadata:
240241
spec:
241242
containers:
242243
- name: exporter
243-
image: quay.io/citrix/citrix-adc-metrics-exporter:1.4.2
244+
image: quay.io/citrix/citrix-adc-metrics-exporter:1.4.3
244245
args:
245246
- "--target-nsip=10.0.0.1"
246247
- "--port=8888"

exporter.py

Lines changed: 128 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,15 @@
1313
from requests.packages.urllib3.exceptions import InsecureRequestWarning
1414
from requests.packages.urllib3.exceptions import SubjectAltNameWarning
1515
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+
1825
def 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.
145194
def 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+
174235
def 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+
183245
def 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+
445559
if __name__ == '__main__':
446560

447561
parser = argparse.ArgumentParser()

metrics.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
{
22
"system": {
33
"counters": [
4-
["numcpus", "citrixadc_cpu_number"]
4+
["numcpus", "citrixadc_cpu_number"],
5+
["disk1avail", "citrixadc_var_partition_free_mb"],
6+
["disk1used", "citrixadc_var_partition_used_mb"],
7+
["disk0avail", "citrixadc_flash_partition_free_mb"],
8+
["disk0used", "citrixadc_flash_partition_used_mb"]
59
],
610

711
"gauges": [
812
["cpuusagepcnt", "citrixadc_cpu_usage_percent"],
913
["memusagepcnt", "citrixadc_memory_usage_percent"],
1014
["mgmtcpuusagepcnt", "citrixadc_management_cpu_usage_percent"],
1115
["pktcpuusagepcnt", "citrixadc_packet_cpu_usage_percent"],
12-
["rescpuusagepcnt", "citrixadc_res_cpu_usage_percent"]
16+
["rescpuusagepcnt", "citrixadc_res_cpu_usage_percent"],
17+
["disk1perusage", "citrixadc_var_partition_used_percent"],
18+
["disk0perusage", "citrixadc_flash_partition_used_percent"]
1319
]
1420
},
1521

version/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.4.2
1+
1.4.3

0 commit comments

Comments
 (0)