2021-08-21 02:46:11 -04:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
# config.py - Utility functions for pvcnoded configuration parsing
|
|
|
|
# Part of the Parallel Virtual Cluster (PVC) system
|
|
|
|
#
|
2022-10-06 11:55:27 -04:00
|
|
|
# Copyright (C) 2018-2022 Joshua M. Boniface <joshua@boniface.me>
|
2021-08-21 02:46:11 -04:00
|
|
|
#
|
|
|
|
# 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 os
|
|
|
|
import subprocess
|
|
|
|
import yaml
|
2021-10-12 12:24:03 -04:00
|
|
|
|
2021-08-21 02:46:11 -04:00
|
|
|
from socket import gethostname
|
|
|
|
from re import findall
|
|
|
|
from psutil import cpu_count
|
|
|
|
from ipaddress import ip_address, ip_network
|
|
|
|
|
|
|
|
|
|
|
|
class MalformedConfigurationError(Exception):
|
|
|
|
"""
|
|
|
|
An except when parsing the PVC Node daemon configuration file
|
|
|
|
"""
|
2021-11-06 03:02:43 -04:00
|
|
|
|
2021-08-21 02:46:11 -04:00
|
|
|
def __init__(self, error=None):
|
2021-11-06 03:02:43 -04:00
|
|
|
self.msg = f"ERROR: Configuration file is malformed: {error}"
|
2021-08-21 02:46:11 -04:00
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return str(self.msg)
|
|
|
|
|
|
|
|
|
|
|
|
def get_static_data():
|
|
|
|
"""
|
|
|
|
Data that is obtained once at node startup for use later
|
|
|
|
"""
|
|
|
|
staticdata = list()
|
|
|
|
staticdata.append(str(cpu_count())) # CPU count
|
|
|
|
staticdata.append(
|
2021-11-06 03:02:43 -04:00
|
|
|
subprocess.run(["uname", "-r"], stdout=subprocess.PIPE)
|
|
|
|
.stdout.decode("ascii")
|
|
|
|
.strip()
|
2021-08-21 02:46:11 -04:00
|
|
|
)
|
|
|
|
staticdata.append(
|
2021-11-06 03:02:43 -04:00
|
|
|
subprocess.run(["uname", "-o"], stdout=subprocess.PIPE)
|
|
|
|
.stdout.decode("ascii")
|
|
|
|
.strip()
|
2021-08-21 02:46:11 -04:00
|
|
|
)
|
|
|
|
staticdata.append(
|
2021-11-06 03:02:43 -04:00
|
|
|
subprocess.run(["uname", "-m"], stdout=subprocess.PIPE)
|
|
|
|
.stdout.decode("ascii")
|
|
|
|
.strip()
|
2021-08-21 02:46:11 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
return staticdata
|
|
|
|
|
|
|
|
|
|
|
|
def get_configuration_path():
|
2023-11-26 15:33:23 -05:00
|
|
|
try:
|
|
|
|
_config_file = os.environ["PVC_CONFIG_FILE"]
|
|
|
|
if not os.path.exists(_config_file):
|
|
|
|
raise
|
|
|
|
config_file = _config_file
|
|
|
|
except Exception:
|
|
|
|
print('ERROR: The "PVC_CONFIG_FILE" environment variable must be set.')
|
2021-08-21 02:46:11 -04:00
|
|
|
os._exit(1)
|
|
|
|
|
2023-12-01 01:25:40 -05:00
|
|
|
return config_file
|
2023-11-26 15:33:23 -05:00
|
|
|
|
2021-08-21 02:46:11 -04:00
|
|
|
|
|
|
|
def get_hostname():
|
|
|
|
node_fqdn = gethostname()
|
2021-11-06 03:02:43 -04:00
|
|
|
node_hostname = node_fqdn.split(".", 1)[0]
|
|
|
|
node_domain = "".join(node_fqdn.split(".", 1)[1:])
|
2021-08-21 02:46:11 -04:00
|
|
|
try:
|
2021-11-06 03:02:43 -04:00
|
|
|
node_id = findall(r"\d+", node_hostname)[-1]
|
2021-08-21 02:46:11 -04:00
|
|
|
except IndexError:
|
|
|
|
node_id = 0
|
|
|
|
|
|
|
|
return node_fqdn, node_hostname, node_domain, node_id
|
|
|
|
|
|
|
|
|
|
|
|
def validate_floating_ip(config, network):
|
2021-11-06 03:02:43 -04:00
|
|
|
if network not in ["cluster", "storage", "upstream"]:
|
2021-08-21 02:46:11 -04:00
|
|
|
return False, f'Specified network type "{network}" is not valid'
|
|
|
|
|
2021-11-06 03:02:43 -04:00
|
|
|
floating_key = f"{network}_floating_ip"
|
|
|
|
network_key = f"{network}_network"
|
2021-08-21 02:46:11 -04:00
|
|
|
|
|
|
|
# Verify the network provided is valid
|
|
|
|
try:
|
|
|
|
network = ip_network(config[network_key])
|
|
|
|
except Exception:
|
2021-11-06 03:02:43 -04:00
|
|
|
return (
|
|
|
|
False,
|
|
|
|
f"Network address {config[network_key]} for {network_key} is not valid",
|
|
|
|
)
|
2021-08-21 02:46:11 -04:00
|
|
|
|
|
|
|
# Verify that the floating IP is valid (and in the network)
|
|
|
|
try:
|
2021-11-06 03:02:43 -04:00
|
|
|
floating_address = ip_address(config[floating_key].split("/")[0])
|
2021-08-21 02:46:11 -04:00
|
|
|
if floating_address not in list(network.hosts()):
|
|
|
|
raise
|
|
|
|
except Exception:
|
2021-11-06 03:02:43 -04:00
|
|
|
return (
|
|
|
|
False,
|
|
|
|
f"Floating address {config[floating_key]} for {floating_key} is not valid",
|
|
|
|
)
|
2021-08-21 02:46:11 -04:00
|
|
|
|
2021-11-06 03:02:43 -04:00
|
|
|
return True, ""
|
2021-08-21 02:46:11 -04:00
|
|
|
|
|
|
|
|
2023-12-01 01:25:40 -05:00
|
|
|
def get_parsed_configuration(config_file):
|
2023-11-26 15:33:23 -05:00
|
|
|
print('Loading configuration from file "{}"'.format(config_file))
|
|
|
|
|
|
|
|
with open(config_file, "r") as cfgfh:
|
|
|
|
try:
|
|
|
|
o_config = yaml.load(cfgfh, Loader=yaml.SafeLoader)
|
|
|
|
except Exception as e:
|
|
|
|
print(f"ERROR: Failed to parse configuration file: {e}")
|
|
|
|
os._exit(1)
|
|
|
|
|
|
|
|
config = dict()
|
|
|
|
|
|
|
|
node_fqdn, node_hostname, node_domain, node_id = get_hostname()
|
|
|
|
|
|
|
|
config_thisnode = {
|
|
|
|
"node": node_hostname,
|
|
|
|
"node_hostname": node_hostname,
|
|
|
|
"node_fqdn": node_fqdn,
|
|
|
|
"node_domain": node_domain,
|
|
|
|
"node_id": node_id,
|
|
|
|
}
|
|
|
|
config = {**config, **config_thisnode}
|
|
|
|
|
|
|
|
try:
|
|
|
|
o_path = o_config["path"]
|
|
|
|
config_path = {
|
|
|
|
"plugin_directory": o_path.get(
|
|
|
|
"plugin_directory", "/usr/share/pvc/plugins"
|
|
|
|
),
|
|
|
|
"dynamic_directory": o_path["dynamic_directory"],
|
2023-11-27 13:12:24 -05:00
|
|
|
"log_directory": o_path["system_log_directory"],
|
2023-11-26 15:33:23 -05:00
|
|
|
"console_log_directory": o_path["console_log_directory"],
|
|
|
|
"ceph_directory": o_path["ceph_directory"],
|
|
|
|
}
|
2023-11-27 13:40:07 -05:00
|
|
|
# Define our dynamic directory schema
|
|
|
|
config_path["dnsmasq_dynamic_directory"] = (
|
|
|
|
config_path["dynamic_directory"] + "/dnsmasq"
|
|
|
|
)
|
|
|
|
config_path["pdns_dynamic_directory"] = (
|
|
|
|
config_path["dynamic_directory"] + "/pdns"
|
|
|
|
)
|
|
|
|
config_path["nft_dynamic_directory"] = config_path["dynamic_directory"] + "/nft"
|
|
|
|
# Define our log directory schema
|
|
|
|
config_path["dnsmasq_log_directory"] = config_path["log_directory"] + "/dnsmasq"
|
|
|
|
config_path["pdns_log_directory"] = config_path["log_directory"] + "/pdns"
|
|
|
|
config_path["nft_log_directory"] = config_path["log_directory"] + "/nft"
|
2023-11-26 15:33:23 -05:00
|
|
|
config = {**config, **config_path}
|
|
|
|
|
2023-11-27 13:32:47 -05:00
|
|
|
o_subsystem = o_config["subsystem"]
|
|
|
|
config_subsystem = {
|
|
|
|
"enable_hypervisor": o_subsystem.get("enable_hypervisor", True),
|
|
|
|
"enable_networking": o_subsystem.get("enable_networking", True),
|
|
|
|
"enable_storage": o_subsystem.get("enable_storage", True),
|
|
|
|
"enable_worker": o_subsystem.get("enable_worker", True),
|
|
|
|
"enable_api": o_subsystem.get("enable_api", True),
|
2023-12-29 09:23:35 -05:00
|
|
|
"enable_prometheus": o_subsystem.get("enable_prometheus", True),
|
2023-11-27 13:32:47 -05:00
|
|
|
}
|
|
|
|
config = {**config, **config_subsystem}
|
|
|
|
|
2023-11-26 15:33:23 -05:00
|
|
|
o_cluster = o_config["cluster"]
|
|
|
|
config_cluster = {
|
|
|
|
"cluster_name": o_cluster["name"],
|
|
|
|
"all_nodes": o_cluster["all_nodes"],
|
2023-11-27 13:12:24 -05:00
|
|
|
"coordinators": o_cluster["coordinator_nodes"],
|
2023-11-26 15:33:23 -05:00
|
|
|
}
|
|
|
|
config = {**config, **config_cluster}
|
|
|
|
|
|
|
|
o_cluster_networks = o_cluster["networks"]
|
|
|
|
for network_type in ["cluster", "storage", "upstream"]:
|
|
|
|
o_cluster_networks_specific = o_cluster_networks[network_type]
|
|
|
|
config_cluster_networks_specific = {
|
|
|
|
f"{network_type}_domain": o_cluster_networks_specific["domain"],
|
|
|
|
f"{network_type}_dev": o_cluster_networks_specific["device"],
|
|
|
|
f"{network_type}_mtu": o_cluster_networks_specific["mtu"],
|
|
|
|
f"{network_type}_network": o_cluster_networks_specific["ipv4"][
|
|
|
|
"network_address"
|
|
|
|
]
|
|
|
|
+ "/"
|
2023-11-27 13:12:24 -05:00
|
|
|
+ str(o_cluster_networks_specific["ipv4"]["netmask"]),
|
2023-11-26 15:33:23 -05:00
|
|
|
f"{network_type}_floating_ip": o_cluster_networks_specific["ipv4"][
|
|
|
|
"floating_address"
|
|
|
|
]
|
|
|
|
+ "/"
|
2023-11-27 13:12:24 -05:00
|
|
|
+ str(o_cluster_networks_specific["ipv4"]["netmask"]),
|
2023-11-26 15:33:23 -05:00
|
|
|
f"{network_type}_node_ip_selection": o_cluster_networks_specific[
|
|
|
|
"node_ip_selection"
|
|
|
|
],
|
|
|
|
}
|
|
|
|
|
2023-11-27 13:12:24 -05:00
|
|
|
if (
|
|
|
|
o_cluster_networks_specific["ipv4"].get("gateway_address", None)
|
|
|
|
is not None
|
|
|
|
):
|
2023-11-27 13:35:20 -05:00
|
|
|
config[f"{network_type}_gateway"] = o_cluster_networks_specific["ipv4"][
|
|
|
|
"gateway_address"
|
|
|
|
]
|
2023-11-27 13:12:24 -05:00
|
|
|
|
2023-11-26 15:33:23 -05:00
|
|
|
result, msg = validate_floating_ip(
|
|
|
|
config_cluster_networks_specific, network_type
|
|
|
|
)
|
|
|
|
if not result:
|
|
|
|
raise MalformedConfigurationError(msg)
|
|
|
|
|
|
|
|
network = ip_network(
|
|
|
|
config_cluster_networks_specific[f"{network_type}_network"]
|
|
|
|
)
|
|
|
|
|
2023-11-27 13:12:24 -05:00
|
|
|
if (
|
|
|
|
config_cluster_networks_specific[f"{network_type}_node_ip_selection"]
|
2023-11-28 15:27:07 -05:00
|
|
|
== "by-id"
|
2023-11-27 13:12:24 -05:00
|
|
|
):
|
2023-11-28 15:27:07 -05:00
|
|
|
address_id = int(node_id) - 1
|
|
|
|
else:
|
|
|
|
# This roundabout solution ensures the given IP is in the subnet and is something valid
|
2023-11-26 15:33:23 -05:00
|
|
|
address_id = [
|
|
|
|
idx
|
|
|
|
for idx, ip in enumerate(list(network.hosts()))
|
2023-11-28 15:27:07 -05:00
|
|
|
if str(ip)
|
|
|
|
== config_cluster_networks_specific[
|
|
|
|
f"{network_type}_node_ip_selection"
|
|
|
|
]
|
2023-11-26 15:33:23 -05:00
|
|
|
][0]
|
|
|
|
|
|
|
|
config_cluster_networks_specific[
|
|
|
|
f"{network_type}_dev_ip"
|
|
|
|
] = f"{list(network.hosts())[address_id]}/{network.prefixlen}"
|
|
|
|
|
|
|
|
config = {**config, **config_cluster_networks_specific}
|
|
|
|
|
|
|
|
o_database = o_config["database"]
|
|
|
|
config_database = {
|
|
|
|
"zookeeper_port": o_database["zookeeper"]["port"],
|
|
|
|
"keydb_port": o_database["keydb"]["port"],
|
|
|
|
"keydb_host": o_database["keydb"]["hostname"],
|
|
|
|
"keydb_path": o_database["keydb"]["path"],
|
2023-11-29 23:35:12 -05:00
|
|
|
"api_postgresql_port": o_database["postgres"]["port"],
|
|
|
|
"api_postgresql_host": o_database["postgres"]["hostname"],
|
|
|
|
"api_postgresql_dbname": o_database["postgres"]["credentials"]["api"][
|
2023-11-26 15:33:23 -05:00
|
|
|
"database"
|
|
|
|
],
|
2023-11-29 23:35:12 -05:00
|
|
|
"api_postgresql_user": o_database["postgres"]["credentials"]["api"][
|
2023-11-26 15:33:23 -05:00
|
|
|
"username"
|
|
|
|
],
|
2023-11-29 23:35:12 -05:00
|
|
|
"api_postgresql_password": o_database["postgres"]["credentials"]["api"][
|
|
|
|
"password"
|
|
|
|
],
|
2023-11-26 15:33:23 -05:00
|
|
|
"pdns_postgresql_port": o_database["postgres"]["port"],
|
|
|
|
"pdns_postgresql_host": o_database["postgres"]["hostname"],
|
|
|
|
"pdns_postgresql_dbname": o_database["postgres"]["credentials"]["dns"][
|
|
|
|
"database"
|
|
|
|
],
|
|
|
|
"pdns_postgresql_user": o_database["postgres"]["credentials"]["dns"][
|
|
|
|
"username"
|
|
|
|
],
|
|
|
|
"pdns_postgresql_password": o_database["postgres"]["credentials"]["dns"][
|
|
|
|
"password"
|
|
|
|
],
|
|
|
|
}
|
|
|
|
config = {**config, **config_database}
|
|
|
|
|
|
|
|
o_timer = o_config["timer"]
|
|
|
|
config_timer = {
|
|
|
|
"vm_shutdown_timeout": int(o_timer.get("vm_shutdown_timeout", 180)),
|
|
|
|
"keepalive_interval": int(o_timer.get("keepalive_interval", 5)),
|
2023-12-01 16:05:00 -05:00
|
|
|
"monitoring_interval": int(o_timer.get("monitoring_interval", 15)),
|
2023-11-26 15:33:23 -05:00
|
|
|
}
|
|
|
|
config = {**config, **config_timer}
|
|
|
|
|
|
|
|
o_fencing = o_config["fencing"]
|
|
|
|
config_fencing = {
|
2023-11-27 13:12:24 -05:00
|
|
|
"disable_on_ipmi_failure": o_fencing["disable_on_ipmi_failure"],
|
2023-11-26 15:33:23 -05:00
|
|
|
"fence_intervals": int(o_fencing["intervals"].get("fence_intervals", 6)),
|
|
|
|
"suicide_intervals": int(o_fencing["intervals"].get("suicide_interval", 0)),
|
|
|
|
"successful_fence": o_fencing["actions"].get("successful_fence", None),
|
|
|
|
"failed_fence": o_fencing["actions"].get("failed_fence", None),
|
2023-11-28 16:09:01 -05:00
|
|
|
"ipmi_hostname": o_fencing["ipmi"]["hostname"].format(node_id=node_id),
|
2023-11-26 15:33:23 -05:00
|
|
|
"ipmi_username": o_fencing["ipmi"]["username"],
|
|
|
|
"ipmi_password": o_fencing["ipmi"]["password"],
|
|
|
|
}
|
|
|
|
config = {**config, **config_fencing}
|
|
|
|
|
|
|
|
o_migration = o_config["migration"]
|
|
|
|
config_migration = {
|
|
|
|
"migration_target_selector": o_migration.get("target_selector", "mem"),
|
|
|
|
}
|
|
|
|
config = {**config, **config_migration}
|
|
|
|
|
|
|
|
o_logging = o_config["logging"]
|
|
|
|
config_logging = {
|
|
|
|
"debug": o_logging.get("debug_logging", False),
|
|
|
|
"file_logging": o_logging.get("file_logging", False),
|
|
|
|
"stdout_logging": o_logging.get("stdout_logging", False),
|
|
|
|
"zookeeper_logging": o_logging.get("zookeeper_logging", False),
|
|
|
|
"log_colours": o_logging.get("log_colours", False),
|
|
|
|
"log_dates": o_logging.get("log_dates", False),
|
|
|
|
"log_keepalives": o_logging.get("log_keepalives", False),
|
|
|
|
"log_keepalive_cluster_details": o_logging.get(
|
|
|
|
"log_cluster_details", False
|
|
|
|
),
|
2023-11-29 23:35:12 -05:00
|
|
|
"log_monitoring_details": o_logging.get("log_monitoring_details", False),
|
2023-11-26 15:33:23 -05:00
|
|
|
"console_log_lines": o_logging.get("console_log_lines", False),
|
|
|
|
"node_log_lines": o_logging.get("node_log_lines", False),
|
|
|
|
}
|
|
|
|
config = {**config, **config_logging}
|
|
|
|
|
|
|
|
o_guest_networking = o_config["guest_networking"]
|
|
|
|
config_guest_networking = {
|
|
|
|
"bridge_dev": o_guest_networking["bridge_device"],
|
|
|
|
"bridge_mtu": o_guest_networking["bridge_mtu"],
|
|
|
|
"enable_sriov": o_guest_networking.get("sriov_enable", False),
|
|
|
|
"sriov_device": o_guest_networking.get("sriov_device", list()),
|
|
|
|
}
|
|
|
|
config = {**config, **config_guest_networking}
|
|
|
|
|
|
|
|
o_ceph = o_config["ceph"]
|
|
|
|
config_ceph = {
|
|
|
|
"ceph_config_file": config["ceph_directory"]
|
|
|
|
+ "/"
|
|
|
|
+ o_ceph["ceph_config_file"],
|
|
|
|
"ceph_admin_keyring": config["ceph_directory"]
|
|
|
|
+ "/"
|
|
|
|
+ o_ceph["ceph_keyring_file"],
|
|
|
|
"ceph_monitor_port": o_ceph["monitor_port"],
|
|
|
|
"ceph_secret_uuid": o_ceph["secret_uuid"],
|
2023-11-29 23:35:12 -05:00
|
|
|
"storage_hosts": o_ceph.get("monitor_hosts", None),
|
2023-11-26 15:33:23 -05:00
|
|
|
}
|
|
|
|
config = {**config, **config_ceph}
|
2021-08-21 02:46:11 -04:00
|
|
|
|
2023-11-29 23:35:12 -05:00
|
|
|
o_api = o_config["api"]
|
|
|
|
|
|
|
|
o_api_listen = o_api["listen"]
|
|
|
|
config_api_listen = {
|
|
|
|
"api_listen_address": o_api_listen["address"],
|
|
|
|
"api_listen_port": o_api_listen["port"],
|
|
|
|
}
|
|
|
|
config = {**config, **config_api_listen}
|
|
|
|
|
|
|
|
o_api_authentication = o_api["authentication"]
|
|
|
|
config_api_authentication = {
|
|
|
|
"api_auth_enabled": o_api_authentication.get("enabled", False),
|
|
|
|
"api_auth_secret_key": o_api_authentication.get("secret_key", ""),
|
|
|
|
"api_auth_source": o_api_authentication.get("source", "token"),
|
|
|
|
}
|
|
|
|
config = {**config, **config_api_authentication}
|
|
|
|
|
|
|
|
o_api_ssl = o_api["ssl"]
|
|
|
|
config_api_ssl = {
|
|
|
|
"api_ssl_enabled": o_api_ssl.get("enabled", False),
|
|
|
|
"api_ssl_cert_file": o_api_ssl.get("certificate", None),
|
|
|
|
"api_ssl_key_file": o_api_ssl.get("private_key", None),
|
|
|
|
}
|
|
|
|
config = {**config, **config_api_ssl}
|
|
|
|
|
|
|
|
# Use coordinators as storage hosts if not explicitly specified
|
|
|
|
if not config["storage_hosts"] or len(config["storage_hosts"]) < 1:
|
|
|
|
config["storage_hosts"] = config["coordinators"]
|
|
|
|
|
|
|
|
# Set up our token list if specified
|
|
|
|
if config["api_auth_source"] == "token":
|
|
|
|
config["api_auth_tokens"] = o_api["token"]
|
|
|
|
else:
|
|
|
|
if config["api_auth_enabled"]:
|
|
|
|
print(
|
|
|
|
"WARNING: No authentication method provided; disabling API authentication."
|
|
|
|
)
|
|
|
|
config["api_auth_enabled"] = False
|
|
|
|
|
2023-11-26 15:33:23 -05:00
|
|
|
# Add our node static data to the config
|
|
|
|
config["static_data"] = get_static_data()
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
raise MalformedConfigurationError(e)
|
|
|
|
|
|
|
|
return config
|
|
|
|
|
|
|
|
|
|
|
|
def get_configuration():
|
|
|
|
"""
|
2023-12-01 01:25:40 -05:00
|
|
|
Get the configuration.
|
2023-11-26 15:33:23 -05:00
|
|
|
"""
|
2023-12-01 01:25:40 -05:00
|
|
|
pvc_config_file = get_configuration_path()
|
|
|
|
config = get_parsed_configuration(pvc_config_file)
|
2023-11-27 13:12:24 -05:00
|
|
|
return config
|
2023-11-26 15:33:23 -05:00
|
|
|
|
|
|
|
|
2021-08-21 02:46:11 -04:00
|
|
|
def validate_directories(config):
|
2021-11-06 03:02:43 -04:00
|
|
|
if not os.path.exists(config["dynamic_directory"]):
|
|
|
|
os.makedirs(config["dynamic_directory"])
|
|
|
|
os.makedirs(config["dnsmasq_dynamic_directory"])
|
|
|
|
os.makedirs(config["pdns_dynamic_directory"])
|
|
|
|
os.makedirs(config["nft_dynamic_directory"])
|
|
|
|
|
|
|
|
if not os.path.exists(config["log_directory"]):
|
|
|
|
os.makedirs(config["log_directory"])
|
|
|
|
os.makedirs(config["dnsmasq_log_directory"])
|
|
|
|
os.makedirs(config["pdns_log_directory"])
|
|
|
|
os.makedirs(config["nft_log_directory"])
|