Ensure we remove any existing leases for the MAC first, before adding a new entry for that MAC. Otherwise what dnsmasq returns will not be valid and this can cause CloudInit bootstrap errors.
198 lines
5.8 KiB
Python
Executable File
198 lines
5.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# dnsmasq-zookeeper-leases.py - DNSMASQ leases script for Zookeeper
|
|
# Part of the Parallel Virtual Cluster (PVC) system
|
|
#
|
|
# Copyright (C) 2018-2024 Joshua M. Boniface <joshua@boniface.me>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, version 3.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
#
|
|
###############################################################################
|
|
import argparse
|
|
import os
|
|
import sys
|
|
import kazoo.client
|
|
import re
|
|
import yaml
|
|
|
|
|
|
#
|
|
# Variables
|
|
#
|
|
#
|
|
# General Functions
|
|
#
|
|
def get_zookeeper_key():
|
|
# Get the interface from environment (passed by dnsmasq)
|
|
try:
|
|
interface = os.environ["DNSMASQ_BRIDGE_INTERFACE"]
|
|
except Exception as e:
|
|
print(
|
|
"ERROR: DNSMASQ_BRIDGE_INTERFACE environment variable not found: {}".format(
|
|
e
|
|
),
|
|
file=sys.stderr,
|
|
)
|
|
exit(1)
|
|
# Get the ID of the interface (the digits)
|
|
network_vni = re.findall(r"\d+", interface)[0]
|
|
# Create the key
|
|
zookeeper_key = "/networks/{}/dhcp4_leases".format(network_vni)
|
|
return zookeeper_key
|
|
|
|
|
|
def get_lease_expiry():
|
|
try:
|
|
expiry = os.environ["DNSMASQ_LEASE_EXPIRES"]
|
|
except Exception:
|
|
expiry = "0"
|
|
return expiry
|
|
|
|
|
|
def get_client_id():
|
|
try:
|
|
client_id = os.environ["DNSMASQ_CLIENT_ID"]
|
|
except Exception:
|
|
client_id = "*"
|
|
return client_id
|
|
|
|
|
|
def connect_zookeeper():
|
|
# We expect the environ to contain the config file
|
|
try:
|
|
pvc_config_file = os.environ["PVC_CONFIG_FILE"]
|
|
except Exception:
|
|
# Default place
|
|
pvc_config_file = "/etc/pvc/pvc.conf"
|
|
|
|
with open(pvc_config_file, "r") as cfgfile:
|
|
try:
|
|
o_config = yaml.load(cfgfile, yaml.SafeLoader)
|
|
except Exception as e:
|
|
print(
|
|
"ERROR: Failed to parse configuration file: {}".format(e),
|
|
file=sys.stderr,
|
|
)
|
|
exit(1)
|
|
|
|
try:
|
|
zk_conn = kazoo.client.KazooClient(
|
|
hosts=o_config["cluster"]["coordinator_nodes"]
|
|
)
|
|
zk_conn.start()
|
|
except Exception as e:
|
|
print("ERROR: Failed to connect to Zookeeper: {}".format(e), file=sys.stderr)
|
|
exit(1)
|
|
|
|
return zk_conn
|
|
|
|
|
|
def read_data(zk_conn, key):
|
|
return zk_conn.get(key)[0].decode("ascii")
|
|
|
|
|
|
def get_lease(zk_conn, zk_leases_key, macaddr):
|
|
expiry = read_data(zk_conn, "{}/{}/expiry".format(zk_leases_key, macaddr))
|
|
ipaddr = read_data(zk_conn, "{}/{}/ipaddr".format(zk_leases_key, macaddr))
|
|
hostname = read_data(zk_conn, "{}/{}/hostname".format(zk_leases_key, macaddr))
|
|
clientid = read_data(zk_conn, "{}/{}/clientid".format(zk_leases_key, macaddr))
|
|
return expiry, ipaddr, hostname, clientid
|
|
|
|
|
|
#
|
|
# Command Functions
|
|
#
|
|
def read_lease_database(zk_conn, zk_leases_key):
|
|
leases_list = zk_conn.get_children(zk_leases_key)
|
|
output_list = []
|
|
for macaddr in leases_list:
|
|
expiry, ipaddr, hostname, clientid = get_lease(zk_conn, zk_leases_key, macaddr)
|
|
data_string = "{} {} {} {} {}".format(
|
|
expiry, macaddr, ipaddr, hostname, clientid
|
|
)
|
|
print("Reading lease from Zookeeper: {}".format(data_string), file=sys.stderr)
|
|
output_list.append("{}".format(data_string))
|
|
|
|
# Output list
|
|
print("\n".join(output_list))
|
|
|
|
|
|
def add_lease(zk_conn, zk_leases_key, expiry, macaddr, ipaddr, hostname, clientid):
|
|
if not hostname:
|
|
hostname = ""
|
|
try:
|
|
zk_conn.delete("{}/{}".format(zk_leases_key, macaddr), recursive=True)
|
|
transaction = zk_conn.transaction()
|
|
transaction.create("{}/{}".format(zk_leases_key, macaddr), "".encode("ascii"))
|
|
transaction.create(
|
|
"{}/{}/expiry".format(zk_leases_key, macaddr), expiry.encode("ascii")
|
|
)
|
|
transaction.create(
|
|
"{}/{}/ipaddr".format(zk_leases_key, macaddr), ipaddr.encode("ascii")
|
|
)
|
|
transaction.create(
|
|
"{}/{}/hostname".format(zk_leases_key, macaddr), hostname.encode("ascii")
|
|
)
|
|
transaction.create(
|
|
"{}/{}/clientid".format(zk_leases_key, macaddr), clientid.encode("ascii")
|
|
)
|
|
transaction.commit()
|
|
except Exception as e:
|
|
print(f"FATAL: {e}")
|
|
|
|
|
|
def del_lease(zk_conn, zk_leases_key, macaddr, expiry):
|
|
zk_conn.delete("{}/{}".format(zk_leases_key, macaddr), recursive=True)
|
|
|
|
|
|
#
|
|
# Instantiate the parser
|
|
#
|
|
parser = argparse.ArgumentParser(
|
|
description="Store or retrieve dnsmasq leases in Zookeeper"
|
|
)
|
|
parser.add_argument("action", type=str, help="Action")
|
|
parser.add_argument("macaddr", type=str, help="MAC Address", nargs="?", default=None)
|
|
parser.add_argument("ipaddr", type=str, help="IP Address", nargs="?", default=None)
|
|
parser.add_argument("hostname", type=str, help="Hostname", nargs="?", default=None)
|
|
args = parser.parse_args()
|
|
|
|
action = args.action
|
|
macaddr = args.macaddr
|
|
ipaddr = args.ipaddr
|
|
hostname = args.hostname
|
|
|
|
zk_conn = connect_zookeeper()
|
|
zk_leases_key = get_zookeeper_key()
|
|
|
|
if action == "init":
|
|
read_lease_database(zk_conn, zk_leases_key)
|
|
exit(0)
|
|
|
|
expiry = get_lease_expiry()
|
|
clientid = get_client_id()
|
|
|
|
#
|
|
# Choose action
|
|
#
|
|
print(
|
|
"Lease action - {} {} {} {}".format(action, macaddr, ipaddr, hostname),
|
|
file=sys.stderr,
|
|
)
|
|
if action == "add":
|
|
add_lease(zk_conn, zk_leases_key, expiry, macaddr, ipaddr, hostname, clientid)
|
|
elif action == "del":
|
|
del_lease(zk_conn, zk_leases_key, macaddr, expiry)
|
|
elif action == "old":
|
|
pass
|