Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/2373.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Show DHCP stats graphs on VLAN and Prefix pages when recent enough DHCP stats are found
62 changes: 33 additions & 29 deletions python/nav/bin/dhcpstats.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
# along with NAV. If not, see <http://www.gnu.org/licenses/>.
#
"""
Collects statistics from DHCP servers and sends them to the Carbon backend.
Collects statistics from DHCP servers and sends them to the Graphite/Carbon
backend.
"""

import argparse
Expand All @@ -25,6 +26,7 @@

from nav.config import getconfig
from nav.dhcpstats import kea_dhcp
from nav.dhcpstats.common import GraphiteMetric
from nav.dhcpstats.errors import CommunicationError
from nav.errors import ConfigurationError
from nav.logs import init_generic_logging
Expand All @@ -36,7 +38,7 @@
CONFIGFILE = "dhcpstats.conf"
PIDFILE = "dhcpstats.pid"

ENDPOINT_CLIENTS = {
CLIENTS = {
"kea-dhcp4": partial(kea_dhcp.Client, dhcp_version=4),
}

Expand Down Expand Up @@ -66,27 +68,27 @@ def parse_args():
"""
parser = argparse.ArgumentParser(
description="Collects statistics from DHCP servers and sends them to the "
"Carbon backend",
epilog="Statistics are collected from each DHCP API endpoint configured in "
"'CONFDIR/dhcpstats.conf', and then sent to the Carbon backend configured in "
"'CONFDIR/graphite.conf'.",
"Graphite/Carbon backend",
epilog="Statistics are collected from each DHCP server configured in "
"'CONFDIR/dhcpstats.conf', and then sent to the Graphite/Carbon backend "
"configured in 'CONFDIR/graphite.conf'.",
)
return parser.parse_args()


def collect_stats(config):
"""
Collects current stats from each configured endpoint.
Collects current stats from each configured server.

:param config: dhcpstats.conf INI-parsed into a dict specifying
endpoints to collect metrics from.
servers to collect metrics from.
"""

_logger.info("--> Starting stats collection <--")

all_stats = []
all_stats: list[GraphiteMetric] = []

for client in get_endpoint_clients(config):
for client in get_clients(config):
_logger.info(
"Collecting stats using %s...",
client,
Expand All @@ -96,13 +98,13 @@ def collect_stats(config):
fetched_stats = client.fetch_stats()
except ConfigurationError as err:
_logger.warning(
"%s is badly configured: %s, skipping endpoint...",
"%s is badly configured: %s, skipping server...",
client,
err,
)
except CommunicationError as err:
_logger.warning(
"Error while collecting stats using %s: %s, skipping endpoint...",
"Error while collecting stats using %s: %s, skipping server...",
client,
err,
)
Expand All @@ -118,46 +120,48 @@ def collect_stats(config):
_logger.info("--> Stats collection done <--")


def get_endpoint_clients(config):
def get_clients(config):
"""
Yields one client per correctly configured endpoint in config. A section
of the config correctly configures an endpoint if:
Yields one client per correctly configured server in config. A section
of the config correctly configures a server if:

* Its name starts with 'endpoint_'.
* Its name starts with 'endpoint_' or 'server_'.
* It has the mandatory option 'type'.
* The value of the 'type' option is mapped to a client initializer
by ENDPOINT_CLIENTS, and the client doesn't raise a
by the global CLIENTS dictionary, and the client doesn't raise a
ConfigurationError when it is initialized with the rest of the
options of the section as keyword arguments.

:param config: dhcpstats.conf INI-parsed into a dict specifying
endpoints to collect metrics from.
servers to collect metrics from.
"""
for section, options in config.items():
if not section.startswith("endpoint_"):
if section.startswith("endpoint_"):
server_name = section.removeprefix("endpoint_")
elif section.startswith("server_"):
server_name = section.removeprefix("server_")
else:
continue
endpoint_name = section.removeprefix("endpoint_")
endpoint_type = options.get("type")
server_type = options.get("type")
kwargs = {opt: val for opt, val in options.items() if opt != "type"}
try:
cls = ENDPOINT_CLIENTS[endpoint_type]
cls = CLIENTS[server_type]
except KeyError:
_logger.warning(
"Invalid endpoint type '%s' defined in config section [%s], skipping "
"endpoint...",
endpoint_type,
"Invalid server type '%s' defined in config section [%s], skipping "
"server...",
server_type,
section,
)
continue

try:
client = cls(endpoint_name, **kwargs)
client = cls(server_name, **kwargs)
except (ConfigurationError, TypeError) as err:
_logger.warning(
"Endpoint type '%s' defined in config section [%s] is badly "
"configured: %s, skipping endpoint...",
endpoint_type,
"Section [%s] of %s is badly configured: %s, skipping server...",
section,
CONFIGFILE,
err,
)
else:
Expand Down
Loading
Loading