Skip to content

Commit 0b6bb8b

Browse files
kremerspryorda
authored andcommitted
feat(lots-of-stuff): Idle connections, Index, Get_bool_env. Read the commit message
* added a landing page, with a link to metrics. to make it more in line like other prometheus exporters * added test case to test landing page (index page) functionality * added code to return the proper response code * Fixed index page registration and added tests for routing Signed-off-by: Martin Kremers <[email protected]> * fix(idle_connections): Fixing idle connections and formatting Precommit-Verified: 1d89f3b71592aabf931814b50d0088977367f936c12a6819f45e9b1b8d6ec2c5 * fix(stall_connections) Fixing idle connections and formatting Signed-off-by: Martin Kremers <[email protected]> * added additional test case to test get_bool_env when an env variable with value = False is passed. #102 * fix(get_bool_env) Making get_bool_env more solid Signed-off-by: Martin Kremers <[email protected]>
1 parent 19135a6 commit 0b6bb8b

File tree

4 files changed

+127
-53
lines changed

4 files changed

+127
-53
lines changed

tests/unit/test_helpers.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,26 @@ def Destroy(self):
1616
pass
1717

1818

19-
def test_get_bool_env_with_default_value():
20-
value = get_bool_env('INEXISTENT_ENV', True)
21-
22-
assert value
23-
24-
25-
def test_get_bool_env_with_a_valid_env():
26-
key = "TEST_BOOLEAN_VALUE"
27-
28-
os.environ[key] = "True"
29-
30-
value = get_bool_env(key, False)
31-
32-
assert value
19+
def test_get_bool_env():
20+
# Expected behaviour
21+
assert get_bool_env('NON_EXISTENT_ENV', True)
22+
23+
# #102 'bool("False") will evaluate to True in Python'
24+
os.environ['VSPHERE_COLLECT_VMS'] = "False"
25+
assert not get_bool_env('VSPHERE_COLLECT_VMS', True)
26+
27+
# Environment is higher prio than defaults
28+
os.environ['ENVHIGHERPRIO'] = "True"
29+
assert get_bool_env('ENVHIGHERPRIO', False)
30+
assert get_bool_env('ENVHIGHERPRIO', True)
31+
32+
os.environ['ENVHIGHERPRIO_F'] = "False"
33+
assert not get_bool_env('ENVHIGHERPRIO_F', False)
34+
assert not get_bool_env('ENVHIGHERPRIO_F', True)
35+
36+
# Accent upper and lower case in env vars
37+
os.environ['ENVHIGHERPRIO_F'] = "false"
38+
assert not get_bool_env('ENVHIGHERPRIO_F', True)
3339

3440

3541
def test_batch_fetch_properties():

tests/unit/test_vmware_exporter.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@
99
from twisted.internet import defer
1010
from twisted.internet.error import ReactorAlreadyRunning
1111
from twisted.web.server import NOT_DONE_YET
12+
from twisted.web.resource import Resource
1213

13-
from vmware_exporter.vmware_exporter import main, HealthzResource, VmwareCollector, VMWareMetricsResource
14+
15+
from vmware_exporter.vmware_exporter import main, registerEndpoints
16+
from vmware_exporter.vmware_exporter import HealthzResource, VmwareCollector, VMWareMetricsResource, IndexResource
1417
from vmware_exporter.defer import BranchingDeferred
1518

1619

@@ -482,7 +485,7 @@ def test_collect():
482485
).return_value = _succeed(True)
483486
stack.enter_context(mock.patch.object(collector, '_vmware_get_datastores')).return_value = _succeed(True)
484487
stack.enter_context(mock.patch.object(collector, '_vmware_get_hosts')).return_value = _succeed(True)
485-
stack.enter_context(mock.patch.object(collector, '_vmware_disconnect')).return_value = _succeed(None)
488+
stack.enter_context(mock.patch.object(collector, '_vmware_disconnect')).return_value = _succeed(True)
486489
metrics = yield collector.collect()
487490

488491
assert metrics[0].name == 'vmware_vm_power_state'
@@ -726,6 +729,35 @@ def test_healthz():
726729
assert response == b'Server is UP'
727730

728731

732+
def test_index_page():
733+
request = mock.Mock()
734+
735+
resource = IndexResource()
736+
response = resource.render_GET(request)
737+
738+
request.setResponseCode.assert_called_with(200)
739+
740+
assert response == b"""<html>
741+
<head><title>VMware Exporter</title></head>
742+
<body>
743+
<h1>VMware Exporter</h1>
744+
<p><a href="/metrics">Metrics</a></p>
745+
</body>
746+
</html>"""
747+
748+
749+
def test_register_endpoints():
750+
args = mock.Mock()
751+
args.config_file = None
752+
753+
registered_routes = [b'', b'metrics', b'healthz']
754+
755+
evaluation_var = registerEndpoints(args)
756+
assert isinstance(evaluation_var, Resource)
757+
for route in registered_routes:
758+
assert evaluation_var.getStaticEntity(route) is not None
759+
760+
729761
def test_vmware_resource():
730762
request = mock.Mock()
731763

vmware_exporter/helpers.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
from pyVmomi import vmodl
44

55

6-
def get_bool_env(key, default=None):
7-
return bool(os.environ.get(key, default))
6+
def get_bool_env(key: str, default: bool):
7+
value = os.environ.get(key, default)
8+
return value if type(value) == bool else value.lower() == 'true'
89

910

1011
def batch_fetch_properties(content, obj_type, properties):

vmware_exporter/vmware_exporter.py

Lines changed: 70 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ def collect(self):
188188

189189
metrics = self._create_metric_containers()
190190

191-
logging.info(f"Start collecting metrics from {vsphere_host}")
191+
logging.info("Start collecting metrics from {vsphere_host}".format(vsphere_host=vsphere_host))
192192

193193
self._labels = {}
194194

@@ -212,9 +212,9 @@ def collect(self):
212212

213213
yield parallelize(*tasks)
214214

215-
yield self._vmware_disconnect
215+
yield self._vmware_disconnect()
216216

217-
logging.info(f"Finished collecting metrics from {vsphere_host}")
217+
logging.info("Finished collecting metrics from {vsphere_host}".format(vsphere_host=vsphere_host))
218218

219219
return list(metrics.values()) # noqa: F705
220220

@@ -243,7 +243,7 @@ def connection(self):
243243
return vmware_connect
244244

245245
except vmodl.MethodFault as error:
246-
logging.error(f"Caught vmodl fault: {error.msg}")
246+
logging.error("Caught vmodl fault: {error}".format(error=error.msg))
247247
return None
248248

249249
@run_once_property
@@ -290,7 +290,8 @@ def datastore_inventory(self):
290290
vim.Datastore,
291291
properties
292292
)
293-
logging.info(f"Fetched vim.Datastore inventory ({datetime.datetime.utcnow() - start})")
293+
fetch_time = datetime.datetime.utcnow() - start
294+
logging.info("Fetched vim.Datastore inventory ({fetch_time})".format(fetch_time=fetch_time))
294295
return datastores
295296

296297
@run_once_property
@@ -316,7 +317,8 @@ def host_system_inventory(self):
316317
vim.HostSystem,
317318
properties,
318319
)
319-
logging.info(f"Fetched vim.HostSystem inventory ({datetime.datetime.utcnow() - start})")
320+
fetch_time = datetime.datetime.utcnow() - start
321+
logging.info("Fetched vim.HostSystem inventory ({fetch_time})".format(fetch_time=fetch_time))
320322
return host_systems
321323

322324
@run_once_property
@@ -353,7 +355,8 @@ def vm_inventory(self):
353355
vim.VirtualMachine,
354356
properties,
355357
)
356-
logging.info(f"Fetched vim.VirtualMachine inventory ({datetime.datetime.utcnow() - start})")
358+
fetch_time = datetime.datetime.utcnow() - start
359+
logging.info("Fetched vim.VirtualMachine inventory ({fetch_time})".format(fetch_time=fetch_time))
357360
return virtual_machines
358361

359362
@run_once_property
@@ -373,21 +376,21 @@ def datastore_labels(self):
373376
def _collect(node, level=1, dc=None, storagePod=""):
374377
inventory = {}
375378
if isinstance(node, vim.Folder) and not isinstance(node, vim.StoragePod):
376-
logging.debug(f"[Folder ] {('-' * level).ljust(7)} {node.name}")
379+
logging.debug("[Folder ] {level} {name}".format(name=node.name, level=('-' * level).ljust(7)))
377380
for child in node.childEntity:
378-
inventory.update(_collect(child, level+1, dc))
381+
inventory.update(_collect(child, level + 1, dc))
379382
elif isinstance(node, vim.Datacenter):
380-
logging.debug(f"[Datacenter] {('-' * level).ljust(7)} {node.name}")
381-
inventory.update(_collect(node.datastoreFolder, level+1, node.name))
383+
logging.debug("[Datacenter] {level} {name}".format(name=node.name, level=('-' * level).ljust(7)))
384+
inventory.update(_collect(node.datastoreFolder, level + 1, node.name))
382385
elif isinstance(node, vim.Folder) and isinstance(node, vim.StoragePod):
383-
logging.debug(f"[StoragePod] {('-' * level).ljust(7)} {node.name}")
386+
logging.debug("[StoragePod] {level} {name}".format(name=node.name, level=('-' * level).ljust(7)))
384387
for child in node.childEntity:
385-
inventory.update(_collect(child, level+1, dc, node.name))
388+
inventory.update(_collect(child, level + 1, dc, node.name))
386389
elif isinstance(node, vim.Datastore):
387-
logging.debug(f"[Datastore ] {('-' * level).ljust(7)} {node.name}")
390+
logging.debug("[Datastore ] {level} {name}".format(name=node.name, level=('-' * level).ljust(7)))
388391
inventory[node.name] = [node.name, dc, storagePod]
389392
else:
390-
logging.debug(f"[? ] {('-' * level).ljust(7)} {node}")
393+
logging.debug("[? ] {level} {node}".format(node=node, level=('-' * level).ljust(7)))
391394
return inventory
392395

393396
labels = {}
@@ -404,25 +407,25 @@ def host_labels(self):
404407
def _collect(node, level=1, dc=None, folder=None):
405408
inventory = {}
406409
if isinstance(node, vim.Folder) and not isinstance(node, vim.StoragePod):
407-
logging.debug(f"[Folder ] {('-' * level).ljust(7)} {node.name}")
410+
logging.debug("[Folder ] {level} {name}".format(level=('-' * level).ljust(7), name=node.name))
408411
for child in node.childEntity:
409-
inventory.update(_collect(child, level+1, dc))
412+
inventory.update(_collect(child, level + 1, dc))
410413
elif isinstance(node, vim.Datacenter):
411-
logging.debug(f"[Datacenter] {('-' * level).ljust(7)} {node.name}")
412-
inventory.update(_collect(node.hostFolder, level+1, node.name))
414+
logging.debug("[Datacenter] {level} {name}".format(level=('-' * level).ljust(7), name=node.name))
415+
inventory.update(_collect(node.hostFolder, level + 1, node.name))
413416
elif isinstance(node, vim.ComputeResource):
414-
logging.debug(f"[ComputeRes] {('-' * level).ljust(7)} {node.name}")
417+
logging.debug("[ComputeRes] {level} {name}".format(level=('-' * level).ljust(7), name=node.name))
415418
for host in node.host:
416-
inventory.update(_collect(host, level+1, dc, node))
419+
inventory.update(_collect(host, level + 1, dc, node))
417420
elif isinstance(node, vim.HostSystem):
418-
logging.debug(f"[HostSystem] {('-' * level).ljust(7)} {node.name}")
421+
logging.debug("[HostSystem] {level} {name}".format(level=('-' * level).ljust(7), name=node.name))
419422
inventory[node._moId] = [
420423
node.summary.config.name.rstrip('.'),
421424
dc,
422425
folder.name if isinstance(folder, vim.ClusterComputeResource) else ''
423426
]
424427
else:
425-
logging.debug(f"[? ] {('-' * level).ljust(7)} {node}")
428+
logging.debug("[? ] {level} {node}".format(level=('-' * level).ljust(7), node=node))
426429
return inventory
427430

428431
labels = {}
@@ -505,7 +508,11 @@ def _vmware_get_datastores(self, ds_metrics):
505508
name = datastore['name']
506509
labels = datastore_labels[name]
507510
except KeyError as e:
508-
logging.info(f"Key error, unable to register datastore {e}, datastores are {datastore_labels}")
511+
logging.info(
512+
"Key error, unable to register datastore {error}, datastores are {datastore_labels}".format(
513+
error=e, datastore_labels=datastore_labels
514+
)
515+
)
509516
continue
510517

511518
ds_capacity = float(datastore.get('summary.capacity', 0))
@@ -522,8 +529,8 @@ def _vmware_get_datastores(self, ds_metrics):
522529
ds_metrics['vmware_datastore_vms'].add_metric(labels, len(datastore.get('vm', [])))
523530

524531
ds_metrics['vmware_datastore_maintenance_mode'].add_metric(
525-
labels + [datastore.get('summary.maintenanceMode', 'unknown')],
526-
1
532+
labels + [datastore.get('summary.maintenanceMode', 'unknown')],
533+
1
527534
)
528535

529536
ds_metrics['vmware_datastore_type'].add_metric(
@@ -689,7 +696,11 @@ def _vmware_get_hosts(self, host_metrics):
689696
try:
690697
labels = host_labels[host_id]
691698
except KeyError as e:
692-
logging.info(f"Key error, unable to register host {e}, host labels are {host_labels}")
699+
logging.info(
700+
"Key error, unable to register host {error}, host labels are {host_labels}".format(
701+
error=e, host_labels=host_labels
702+
)
703+
)
693704
continue
694705

695706
# Power state
@@ -893,6 +904,35 @@ def render_GET(self, request):
893904
return 'Server is UP'.encode()
894905

895906

907+
class IndexResource(Resource):
908+
isLeaf = False
909+
910+
def getChild(self, name, request):
911+
if name == b'':
912+
return self
913+
return Resource.getChild(self, name, request)
914+
915+
def render_GET(self, request):
916+
output = """<html>
917+
<head><title>VMware Exporter</title></head>
918+
<body>
919+
<h1>VMware Exporter</h1>
920+
<p><a href="/metrics">Metrics</a></p>
921+
</body>
922+
</html>"""
923+
request.setHeader("Content-Type", "text/html; charset=UTF-8")
924+
request.setResponseCode(200)
925+
return output.encode()
926+
927+
928+
def registerEndpoints(args):
929+
root = Resource()
930+
root.putChild(b'', IndexResource())
931+
root.putChild(b'metrics', VMWareMetricsResource(args))
932+
root.putChild(b'healthz', HealthzResource())
933+
return root
934+
935+
896936
def main(argv=None):
897937
""" start up twisted reactor """
898938
parser = argparse.ArgumentParser(description='VMWare metrics exporter for Prometheus')
@@ -907,18 +947,13 @@ def main(argv=None):
907947

908948
numeric_level = getattr(logging, args.loglevel.upper(), None)
909949
if not isinstance(numeric_level, int):
910-
raise ValueError(f"Invalid log level: {args.loglevel}")
950+
raise ValueError("Invalid log level: {level}".format(level=args.loglevel))
911951
logging.basicConfig(level=numeric_level, format='%(asctime)s %(levelname)s:%(message)s')
912952

913953
reactor.suggestThreadPoolSize(25)
914954

915-
# Start up the server to expose the metrics.
916-
root = Resource()
917-
root.putChild(b'metrics', VMWareMetricsResource(args))
918-
root.putChild(b'healthz', HealthzResource())
919-
920-
factory = Site(root)
921-
logging.info(f"Starting web server on port {args.port}")
955+
factory = Site(registerEndpoints(args))
956+
logging.info("Starting web server on port {port}".format(port=args.port))
922957
endpoint = endpoints.TCP4ServerEndpoint(reactor, args.port)
923958
endpoint.listen(factory)
924959
reactor.run()

0 commit comments

Comments
 (0)