fix: add proper discovery of IPv6 and IPv4 addresses (#155)

BREAKING CHANGE: The label `__meta_pve_ip` was removed in favor of `__meta_pve_ipv4` and `__meta_pve_ipv6`.
This commit is contained in:
Mathias Petermann 2022-02-27 12:31:46 +01:00 committed by GitHub
parent 4f4e1950ab
commit d15a01fd6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 51 additions and 23 deletions

View File

@ -25,6 +25,7 @@ loop_delay: 300
service: true service: true
exclude_state: [] exclude_state: []
# needs to be a list of strings
exclude_vmid: [] exclude_vmid: []
pve: pve:

View File

@ -16,7 +16,9 @@ run prometheus-pve-sd -vv --loop-delay 900 -o /etc/prometheus/pve.json
The following list of meta labels can be used to relabel your scrape results: The following list of meta labels can be used to relabel your scrape results:
`__meta_pve_ip` `__meta_pve_ipv4`
`__meta_pve_ipv6`
`__meta_pve_name` `__meta_pve_name`

View File

@ -1,9 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Prometheus Discovery.""" """Prometheus Discovery."""
import ipaddress
import json import json
import re import re
import socket
from collections import defaultdict from collections import defaultdict
import requests import requests
@ -85,19 +85,18 @@ class Discovery():
return variables return variables
def _get_ip_address(self, pve_type, pve_node, vmid): def _get_ip_addresses(self, pve_type, pve_node, vmid):
def validate(address): def validate_ip(address: object) -> object:
try: try:
# IP address validation if not ipaddress.ip_address(address).is_loopback \
if socket.inet_aton(address): and not ipaddress.ip_address(address).is_link_local:
# Ignore localhost return address
if address != "127.0.0.1": except ValueError:
return address
except socket.error:
return False return False
address = False ipv4_address = False
ipv6_address = False
networks = False networks = False
if pve_type == "qemu": if pve_type == "qemu":
# If qemu agent is enabled, try to gather the IP address # If qemu agent is enabled, try to gather the IP address
@ -114,23 +113,46 @@ class Discovery():
if type(networks) is list: if type(networks) is list:
for network in networks: for network in networks:
for ip_address in network["ip-addresses"]: for ip_address in network["ip-addresses"]:
address = validate(ip_address["ip-address"]) if ip_address["ip-address-type"] == "ipv4":
ipv4_address = validate_ip(ip_address["ip-address"])
elif ip_address["ip-address-type"] == "ipv6":
ipv6_address = validate_ip(ip_address["ip-address"])
if not address: if not ipv4_address:
try: try:
PVE_REQUEST_COUNT_TOTAL.inc() PVE_REQUEST_COUNT_TOTAL.inc()
config = self.client.nodes(pve_node).get(pve_type, vmid, "config") config = self.client.nodes(pve_node).get(pve_type, vmid, "config")
sources = [config["net0"], config["ipconfig0"]] if "ipconfig0" in config.keys():
sources = [config["net0"], config["ipconfig0"]]
else:
sources = [config["net0"]]
for s in sources: for s in sources:
find = re.search(r"ip=(\d*\.\d*\.\d*\.\d*)", str(sources)) find = re.search(r"ip=(\d*\.\d*\.\d*\.\d*)", str(s))
if find and find.group(1): if find and find.group(1):
address = find.group(1) ipv4_address = find.group(1)
break break
except Exception: # noqa # nosec except Exception: # noqa # nosec
pass pass
return address if not ipv6_address:
try:
PVE_REQUEST_COUNT_TOTAL.inc()
config = self.client.nodes(pve_node).get(pve_type, vmid, "config")
if "ipconfig0" in config.keys():
sources = [config["net0"], config["ipconfig0"]]
else:
sources = [config["net0"]]
for s in sources:
find = re.search(r"ip=(\d*:\d*:\d*:\d*:\d*:\d*)", str(s))
if find and find.group(1):
ipv6_address = find.group(1)
break
except Exception: # noqa # nosec
pass
return ipv4_address, ipv6_address
def _exclude(self, pve_list): def _exclude(self, pve_list):
filtered = [] filtered = []
@ -196,9 +218,9 @@ class Discovery():
except ValueError: except ValueError:
metadata = {"notes": description} metadata = {"notes": description}
address = self._get_ip_address(pve_type, node, vmid) or host ipv4_address, ipv6_address = self._get_ip_addresses(pve_type, node, vmid) or host
prom_host = Host(vmid, host, address, pve_type) prom_host = Host(vmid, host, ipv4_address, ipv6_address, pve_type)
config_flags = [("cpu", "sockets"), ("cores", "cores"), ("memory", "memory")] config_flags = [("cpu", "sockets"), ("cores", "cores"), ("memory", "memory")]
meta_flags = [("status", "proxmox_status")] meta_flags = [("status", "proxmox_status")]

View File

@ -5,19 +5,22 @@
class Host: class Host:
"""Represents a virtual machine or container in PVE.""" """Represents a virtual machine or container in PVE."""
def __init__(self, vmid, hostname, ip_address, pve_type): def __init__(self, vmid, hostname, ipv4_address, ipv6_address, pve_type):
self.hostname = hostname self.hostname = hostname
self.ip_address = ip_address self.ipv4_address = ipv4_address
self.ipv6_address = ipv6_address
self.vmid = vmid self.vmid = vmid
self.pve_type = pve_type self.pve_type = pve_type
self.labels = {} self.labels = {}
self.labels["__meta_pve_ip"] = ip_address self.labels["__meta_pve_ipv4"] = ipv4_address
self.labels["__meta_pve_ipv6"] = ipv6_address
self.labels["__meta_pve_name"] = hostname self.labels["__meta_pve_name"] = hostname
self.labels["__meta_pve_type"] = pve_type self.labels["__meta_pve_type"] = pve_type
self.labels["__meta_pve_vmid"] = str(vmid) self.labels["__meta_pve_vmid"] = str(vmid)
def __str__(self): def __str__(self):
return f"{self.hostname}({self.vmid}): {self.pve_type} {self.ip_address}" return f"{self.hostname}({self.vmid}): {self.pve_type} \
{self.ipv4_address} {self.ipv6_address}"
def add_label(self, key, value): def add_label(self, key, value):
key = key.replace("-", "_").replace(" ", "_") key = key.replace("-", "_").replace(" ", "_")