#!/usr/bin/env python3 """Prometheus Discovery.""" import json import re import socket from collections import defaultdict from prometheuspvesd.config import SingleConfig from prometheuspvesd.model import Host from prometheuspvesd.model import HostList from prometheuspvesd.utils import SingleLog from prometheuspvesd.utils import to_bool try: from proxmoxer import ProxmoxAPI HAS_PROXMOXER = True except ImportError: HAS_PROXMOXER = False class Discovery(): """Prometheus PVE Service Discovery.""" def __init__(self): if not HAS_PROXMOXER: self.logger.error( "The Proxmox VE Prometheus SD requires proxmoxer: " "https://pypi.org/project/proxmoxer/" ) self.config = SingleConfig() self.log = SingleLog() self.logger = SingleLog().logger self.client = self._auth() self.host_list = HostList() def _auth(self): return ProxmoxAPI( self.config.config["pve"]["server"], user=self.config.config["pve"]["user"], password=self.config.config["pve"]["password"], verify_ssl=to_bool(self.config.config["pve"]["verify_ssl"]), timeout=self.config.config["pve"]["auth_timeout"] ) def _get_names(self, pve_list, pve_type): names = [] if pve_type == "node": names = [node["node"] for node in pve_list] elif pve_type == "pool": names = [pool["poolid"] for pool in pve_list] return names def _get_variables(self, pve_list, pve_type): variables = {} if pve_type in ["qemu", "container"]: for vm in pve_list: nested = {} for key, value in vm.items(): nested["proxmox_" + key] = value variables[vm["name"]] = nested return variables def _get_ip_address(self, pve_type, pve_node, vmid): def validate(address): try: # IP address validation if socket.inet_aton(address): # Ignore localhost if address != "127.0.0.1": return address except socket.error: return False address = False networks = False if pve_type == "qemu": # If qemu agent is enabled, try to gather the IP address try: if self.client.nodes(pve_node).get(pve_type, vmid, "agent", "info") is not None: networks = self.client.nodes(pve_node).get( "qemu", vmid, "agent", "network-get-interfaces" )["result"] except Exception: # noqa pass if networks: if type(networks) is list: for network in networks: for ip_address in network["ip-addresses"]: address = validate(ip_address["ip-address"]) if not address: try: config = self.client.nodes(pve_node).get(pve_type, vmid, "config") sources = [config["net0"], config["ipconfig0"]] for s in sources: find = re.search(r"ip=(\d*\.\d*\.\d*\.\d*)", str(sources)) if find and find.group(1): address = find.group(1) break except Exception: # noqa pass return address def _exclude(self, pve_list): filtered = [] for item in pve_list: obj = defaultdict(dict, item) if obj["template"] == 1: continue if obj["status"] in self.config.config["exclude_state"]: continue if obj["vmid"] in self.config.config["exclude_vmid"]: continue filtered.append(item.copy()) return filtered def propagate(self): self.host_list.clear() for node in self._get_names(self.client.nodes.get(), "node"): try: qemu_list = self._exclude(self.client.nodes(node).qemu.get()) container_list = self._exclude(self.client.nodes(node).lxc.get()) except Exception as e: # noqa self.logger.error("Proxmoxer API error: {0}".format(str(e))) # Merge QEMU and Containers lists from this node instances = self._get_variables(qemu_list, "qemu").copy() instances.update(self._get_variables(container_list, "container")) self.logger.info("Found {} targets".format(len(instances))) for host in instances: host_meta = instances[host] vmid = host_meta["proxmox_vmid"] try: pve_type = host_meta["proxmox_type"] except KeyError: pve_type = "qemu" config = self.client.nodes(node).get(pve_type, vmid, "config") try: description = (config["description"]) except KeyError: description = None except Exception as e: # noqa self.logger.error("Proxmoxer API error: {0}".format(str(e))) try: metadata = json.loads(description) except TypeError: metadata = {} except ValueError: metadata = {"notes": description} address = self._get_ip_address(pve_type, node, vmid) or host prom_host = Host(vmid, host, address, pve_type) config_flags = [("cpu", "sockets"), ("cores", "cores"), ("memory", "memory")] meta_flags = [("status", "proxmox_status")] for key, flag in config_flags: if flag in config: prom_host.add_label(key, config[flag]) for key, flag in meta_flags: if flag in host_meta: prom_host.add_label(key, host_meta[flag]) if "groups" in metadata: prom_host.add_label("groups", ",".join(metadata["groups"])) self.host_list.add_host(prom_host) self.logger.debug("Discovered {}".format(prom_host)) for pool in self._get_names(self.client.pools.get(), "pool"): try: pool_list = self._exclude(self.client.pool(pool).get()["members"]) except Exception as e: # noqa self.logger.error("Proxmoxer API error: {0}".format(str(e))) members = [ member["name"] for member in pool_list if (member["type"] == "qemu" or member["type"] == "lxc") ] for member in members: self.inventory.add_host(group=pool, host=member) return self.host_list