mirror of
https://github.com/netdata/netdata.git
synced 2025-04-27 14:16:20 +00:00
207 lines
6.3 KiB
Python
207 lines
6.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Description: isc dhcpd lease netdata python.d module
|
|
# Author: ilyam8
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
import os
|
|
import re
|
|
import time
|
|
|
|
|
|
try:
|
|
import ipaddress
|
|
HAVE_IP_ADDRESS = True
|
|
except ImportError:
|
|
HAVE_IP_ADDRESS = False
|
|
|
|
from collections import defaultdict
|
|
from copy import deepcopy
|
|
|
|
from bases.FrameworkServices.SimpleService import SimpleService
|
|
|
|
|
|
ORDER = [
|
|
'pools_utilization',
|
|
'pools_active_leases',
|
|
'leases_total',
|
|
]
|
|
|
|
CHARTS = {
|
|
'pools_utilization': {
|
|
'options': [None, 'Pools Utilization', 'percentage', 'utilization', 'isc_dhcpd.utilization', 'line'],
|
|
'lines': []
|
|
},
|
|
'pools_active_leases': {
|
|
'options': [None, 'Active Leases Per Pool', 'leases', 'active leases', 'isc_dhcpd.active_leases', 'line'],
|
|
'lines': []
|
|
},
|
|
'leases_total': {
|
|
'options': [None, 'All Active Leases', 'leases', 'active leases', 'isc_dhcpd.leases_total', 'line'],
|
|
'lines': [
|
|
['leases_total', 'leases', 'absolute']
|
|
],
|
|
'variables': [
|
|
['leases_size']
|
|
]
|
|
}
|
|
}
|
|
|
|
|
|
class DhcpdLeasesFile:
|
|
def __init__(self, path):
|
|
self.path = path
|
|
self.mod_time = 0
|
|
self.size = 0
|
|
|
|
def is_valid(self):
|
|
return os.path.isfile(self.path) and os.access(self.path, os.R_OK)
|
|
|
|
def is_changed(self):
|
|
mod_time = os.path.getmtime(self.path)
|
|
if mod_time != self.mod_time:
|
|
self.mod_time = mod_time
|
|
self.size = int(os.path.getsize(self.path) / 1024)
|
|
return True
|
|
return False
|
|
|
|
def get_data(self):
|
|
try:
|
|
with open(self.path) as leases:
|
|
result = defaultdict(dict)
|
|
for row in leases:
|
|
row = row.strip()
|
|
if row.startswith('lease'):
|
|
address = row[6:-2]
|
|
elif row.startswith('iaaddr'):
|
|
address = row[7:-2]
|
|
elif row.startswith('ends'):
|
|
result[address]['ends'] = row[5:-1]
|
|
elif row.startswith('binding state'):
|
|
result[address]['state'] = row[14:-1]
|
|
return dict((k, v) for k, v in result.items() if len(v) == 2)
|
|
except (OSError, IOError):
|
|
return None
|
|
|
|
|
|
class Pool:
|
|
def __init__(self, name, network):
|
|
self.id = re.sub(r'[:/.-]+', '_', name)
|
|
self.name = name
|
|
self.network = ipaddress.ip_network(address=u'%s' % network)
|
|
|
|
def num_hosts(self):
|
|
return self.network.num_addresses - 2
|
|
|
|
def __contains__(self, item):
|
|
return item.address in self.network
|
|
|
|
|
|
class Lease:
|
|
def __init__(self, address, ends, state):
|
|
self.address = ipaddress.ip_address(address=u'%s' % address)
|
|
self.ends = ends
|
|
self.state = state
|
|
|
|
def is_active(self, current_time):
|
|
# lease_end_time might be epoch
|
|
if self.ends.startswith('epoch'):
|
|
epoch = int(self.ends.split()[1].replace(';', ''))
|
|
return epoch - current_time > 0
|
|
# max. int for lease-time causes lease to expire in year 2038.
|
|
# dhcpd puts 'never' in the ends section of active lease
|
|
elif self.ends == 'never':
|
|
return True
|
|
return time.mktime(time.strptime(self.ends, '%w %Y/%m/%d %H:%M:%S')) - current_time > 0
|
|
|
|
def is_valid(self):
|
|
return self.state == 'active'
|
|
|
|
|
|
class Service(SimpleService):
|
|
def __init__(self, configuration=None, name=None):
|
|
SimpleService.__init__(self, configuration=configuration, name=name)
|
|
self.order = ORDER
|
|
self.definitions = deepcopy(CHARTS)
|
|
lease_path = self.configuration.get('leases_path', '/var/lib/dhcp/dhcpd.leases')
|
|
self.dhcpd_leases = DhcpdLeasesFile(path=lease_path)
|
|
self.pools = list()
|
|
self.data = dict()
|
|
|
|
# Will work only with 'default' db-time-format (weekday year/month/day hour:minute:second)
|
|
# TODO: update algorithm to parse correctly 'local' db-time-format
|
|
|
|
def check(self):
|
|
if not HAVE_IP_ADDRESS:
|
|
self.error("'python-ipaddress' package is needed")
|
|
return False
|
|
|
|
if not self.dhcpd_leases.is_valid():
|
|
self.error("Make sure '{path}' is exist and readable by netdata".format(path=self.dhcpd_leases.path))
|
|
return False
|
|
|
|
pools = self.configuration.get('pools')
|
|
if not pools:
|
|
self.error('Pools are not defined')
|
|
return False
|
|
|
|
for pool in pools:
|
|
try:
|
|
new_pool = Pool(name=pool, network=pools[pool])
|
|
except ValueError as error:
|
|
self.error("'{pool}' was removed, error: {error}".format(pool=pools[pool], error=error))
|
|
else:
|
|
self.pools.append(new_pool)
|
|
|
|
self.create_charts()
|
|
return bool(self.pools)
|
|
|
|
def get_data(self):
|
|
"""
|
|
:return: dict
|
|
"""
|
|
if not self.dhcpd_leases.is_changed():
|
|
return self.data
|
|
|
|
raw_leases = self.dhcpd_leases.get_data()
|
|
if not raw_leases:
|
|
self.data = dict()
|
|
return None
|
|
|
|
active_leases = list()
|
|
current_time = time.mktime(time.gmtime())
|
|
|
|
for address in raw_leases:
|
|
try:
|
|
new_lease = Lease(address, **raw_leases[address])
|
|
except ValueError:
|
|
continue
|
|
else:
|
|
if new_lease.is_active(current_time) and new_lease.is_valid():
|
|
active_leases.append(new_lease)
|
|
|
|
for pool in self.pools:
|
|
count = len([ip for ip in active_leases if ip in pool])
|
|
self.data[pool.id + '_active_leases'] = count
|
|
self.data[pool.id + '_utilization'] = float(count) / pool.num_hosts() * 10000
|
|
|
|
self.data['leases_size'] = self.dhcpd_leases.size
|
|
self.data['leases_total'] = len(active_leases)
|
|
|
|
return self.data
|
|
|
|
def create_charts(self):
|
|
for pool in self.pools:
|
|
dim = [
|
|
pool.id + '_utilization',
|
|
pool.name,
|
|
'absolute',
|
|
1,
|
|
100,
|
|
]
|
|
self.definitions['pools_utilization']['lines'].append(dim)
|
|
|
|
dim = [
|
|
pool.id + '_active_leases',
|
|
pool.name,
|
|
]
|
|
self.definitions['pools_active_leases']['lines'].append(dim)
|