config/controllerconfig/controllerconfig/controllerconfig/utils.py

409 lines
13 KiB
Python

#
# Copyright (c) 2014-2023 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Utilities
"""
import glob
import os
import shutil
import subprocess
import time
import yaml
import re
import six
import netaddr
from tsconfig import tsconfig
from controllerconfig.common import constants
from controllerconfig.common.exceptions import ValidateFail
from oslo_log import log
LOG = log.getLogger(__name__)
DEVNULL = open(os.devnull, 'w')
def start_service(name):
""" Start a systemd service """
try:
subprocess.check_call(["systemctl", "start", name], stdout=DEVNULL)
except subprocess.CalledProcessError:
LOG.error("Failed to start %s service" % name)
raise
def stop_service(name):
""" Stop a systemd service """
try:
subprocess.check_call(["systemctl", "stop", name], stdout=DEVNULL)
except subprocess.CalledProcessError:
LOG.error("Failed to stop %s service" % name)
raise
def restart_service(name):
""" Restart a systemd service """
try:
subprocess.check_call(["systemctl", "restart", name], stdout=DEVNULL)
except subprocess.CalledProcessError:
LOG.error("Failed to restart %s service" % name)
raise
def check_sm_service(service, state):
""" Check whether an SM service has the supplied state """
try:
output = subprocess.check_output(["sm-query", "service", service],
universal_newlines=True)
return state in output # pylint: disable=unsupported-membership-test
except subprocess.CalledProcessError:
return False
def wait_sm_service(service, timeout=180):
""" Check whether an SM service has been enabled.
:param service: SM service name
:param timeout: timeout in seconds
:return True if the service is enabled, False otherwise
"""
for _ in range(timeout):
if check_sm_service(service, 'enabled-active'):
return True
time.sleep(1)
return False
def get_address_from_hosts_file(hostname):
"""
Get the IP address of a host from the /etc/hosts file
:param hostname: hostname to look up
:return: IP address of host
"""
hosts = open('/etc/hosts')
for line in hosts:
if line.strip() and line.split()[1] == hostname:
return line.split()[0]
raise Exception("Hostname %s not found in /etc/hosts" % hostname)
def write_simplex_flag():
""" Write simplex flag. """
simplex_flag = "/etc/platform/simplex"
try:
open(simplex_flag, 'w')
except IOError:
LOG.error("Failed to open file: %s", simplex_flag)
raise Exception("Failed to write configuration file")
def create_manifest_runtime_config(filename, config):
"""Write the runtime Puppet configuration to a runtime file."""
if not config:
return
try:
with open(filename, 'w') as f:
yaml.dump(config, f, default_flow_style=False)
except Exception:
LOG.exception("failed to write config file: %s" % filename)
raise
def apply_manifest(controller_address_0, personality, manifest, hieradata,
stdout_progress=False, runtime_filename=None):
"""Apply puppet manifest files."""
# FIXME(mpeters): remove once manifests and modules are not dependent
# on checking the primary config condition
os.environ["INITIAL_CONFIG_PRIMARY"] = "true"
cmd = [
"/usr/local/bin/puppet-manifest-apply.sh",
hieradata,
str(controller_address_0),
personality,
manifest
]
if runtime_filename:
cmd.append((runtime_filename))
logfile = "/tmp/apply_manifest.log"
try:
with open(logfile, "w") as flog:
subprocess.check_call(cmd, stdout=flog, stderr=flog)
except subprocess.CalledProcessError:
msg = "Failed to execute %s manifest" % manifest
print(msg)
raise Exception(msg)
def create_system_config():
cmd = ["/usr/bin/sysinv-puppet",
"create-system-config",
constants.HIERADATA_PERMDIR]
try:
subprocess.check_call(cmd)
except subprocess.CalledProcessError:
msg = "Failed to update puppet hiera system config"
print(msg)
raise Exception(msg)
def create_host_config(hostname=None):
cmd = ["/usr/bin/sysinv-puppet",
"create-host-config",
constants.HIERADATA_PERMDIR]
if hostname:
cmd.append(hostname)
try:
subprocess.check_call(cmd)
except subprocess.CalledProcessError:
msg = "Failed to update puppet hiera host config"
print(msg)
raise Exception(msg)
def persist_config():
"""Copy temporary config files into new DRBD filesystem"""
# Persist temporary keyring
try:
if os.path.isdir(constants.KEYRING_WORKDIR):
shutil.move(constants.KEYRING_WORKDIR, constants.KEYRING_PERMDIR)
except IOError:
LOG.error("Failed to persist temporary keyring")
raise Exception("Failed to persist temporary keyring")
# Move puppet working files into permanent directory
try:
# ensure parent directory is present
subprocess.call(["mkdir", "-p", tsconfig.PUPPET_PATH])
# move hiera data to puppet directory
if os.path.isdir(constants.HIERADATA_WORKDIR):
subprocess.check_call(["mv", constants.HIERADATA_WORKDIR,
tsconfig.PUPPET_PATH])
except subprocess.CalledProcessError:
LOG.error("Failed to persist puppet config files")
raise Exception("Failed to persist puppet config files")
# Move config working files into permanent directory
try:
# ensure parent directory is present
subprocess.call(["mkdir", "-p",
os.path.dirname(constants.CONFIG_PERMDIR)])
if os.path.isdir(constants.CONFIG_WORKDIR):
# Remove destination directory in case it was created previously
subprocess.call(["rm", "-rf", constants.CONFIG_PERMDIR])
# move working data to config directory
subprocess.check_call(["mv", constants.CONFIG_WORKDIR,
constants.CONFIG_PERMDIR])
except subprocess.CalledProcessError:
LOG.error("Failed to persist config files")
raise Exception("Failed to persist config files")
# Copy postgres config files for mate
try:
subprocess.check_call(["mkdir",
constants.CONFIG_PERMDIR + "/postgresql"])
except subprocess.CalledProcessError:
LOG.error("Failed to create postgresql dir")
raise Exception("Failed to persist config files")
try:
for f in glob.glob("/etc/postgresql/*.conf"):
subprocess.check_call([
"cp", "-p", f, constants.CONFIG_PERMDIR + "/postgresql/"])
except IOError:
LOG.error("Failed to persist postgresql config files")
raise Exception("Failed to persist config files")
# Set up replicated directory for PXE config files
try:
subprocess.check_call([
"mkdir", "-p", constants.CONFIG_PERMDIR + "/pxelinux.cfg"])
except subprocess.CalledProcessError:
LOG.error("Failed to create persistent pxelinux.cfg directory")
raise Exception("Failed to persist config files")
try:
subprocess.check_call(["ln", "-s", constants.CONFIG_PERMDIR +
"/pxelinux.cfg", "/var/pxeboot/pxelinux.cfg"])
except subprocess.CalledProcessError:
LOG.error("Failed to create pxelinux.cfg symlink")
raise Exception("Failed to persist config files")
# Copy branding tarball for mate
if os.listdir('/opt/branding'):
try:
subprocess.check_call([
"mkdir", constants.CONFIG_PERMDIR + "/branding"])
except subprocess.CalledProcessError:
LOG.error("Failed to create branding dir")
raise Exception("Failed to persist config files")
try:
for f in glob.glob("/opt/branding/*.tgz"):
subprocess.check_call([
"cp", "-p", f, constants.CONFIG_PERMDIR + "/branding/"])
break
except IOError:
LOG.error("Failed to persist branding config files")
raise Exception("Failed to persist config files")
def apply_banner_customization():
""" Apply and Install banners provided by the user """
""" execute: /usr/sbin/apply_banner_customization """
logfile = "/tmp/apply_banner_customization.log"
try:
with open(logfile, "w") as blog:
subprocess.check_call(["/usr/sbin/apply_banner_customization",
"/opt/banner"],
stdout=blog, stderr=blog)
except subprocess.CalledProcessError:
error_text = "Failed to apply banner customization"
print("%s; see %s for detail" % (error_text, logfile))
def mtce_restart():
"""Restart maintenance processes to handle interface changes"""
restart_service("mtcClient")
restart_service("hbsClient")
restart_service("pmon")
def mark_config_complete():
"""Signal initial configuration has been completed"""
try:
subprocess.check_call(["touch",
constants.INITIAL_CONFIG_COMPLETE_FILE])
subprocess.call(["rm", "-rf", constants.KEYRING_WORKDIR])
except subprocess.CalledProcessError:
LOG.error("Failed to mark initial config complete")
raise Exception("Failed to mark initial config complete")
def configure_hostname(hostname):
"""Configure hostname for this host."""
hostname_file = '/etc/hostname'
try:
with open(hostname_file, 'w') as f:
f.write(hostname + "\n")
except IOError:
LOG.error("Failed to update file: %s", hostname_file)
raise Exception("Failed to configure hostname")
try:
subprocess.check_call(["hostname", hostname])
except subprocess.CalledProcessError:
LOG.error("Failed to update hostname %s" % hostname)
raise Exception("Failed to configure hostname")
def touch(fname):
with open(fname, 'a'):
os.utime(fname, None)
def is_ssh_parent():
"""Determine if current process is started from a ssh session"""
command = ('pstree -s %d' % (os.getpid()))
try:
cmd_output = subprocess.check_output(command, shell=True,
universal_newlines=True)
if "ssh" in cmd_output: # pylint: disable=unsupported-membership-test
return True
else:
return False
except subprocess.CalledProcessError:
return False
def is_valid_mac(mac):
"""Verify the format of a MAC addres."""
if not mac:
return False
m = "[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$"
return isinstance(mac, six.string_types) and re.match(m, mac.lower())
def validate_network_str(network_str, minimum_size,
existing_networks=None, multicast=False):
"""Determine whether a network is valid."""
try:
network = netaddr.IPNetwork(network_str)
if network.size < minimum_size:
raise ValidateFail("Subnet too small - must have at least %d "
"addresses" % minimum_size)
elif network.version == 6 and network.prefixlen < 64:
raise ValidateFail("IPv6 minimum prefix length is 64")
elif existing_networks:
if any(network.ip in subnet for subnet in existing_networks):
raise ValidateFail("Subnet overlaps with another "
"configured subnet")
elif multicast and not network.is_multicast():
raise ValidateFail("Invalid subnet - must be multicast")
return network
except netaddr.AddrFormatError:
raise ValidateFail(
"Invalid subnet - not a valid IP subnet")
def is_valid_domain(url_str):
r = re.compile(
r'^(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)' # domain...
r'+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|'
r'[A-Za-z0-9-_]*)' # localhost, hostname
r'(?::\d+)?' # optional port
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
url = r.match(url_str)
if url:
return True
else:
return False
def validate_address_str(ip_address_str, network):
"""Determine whether an address is valid."""
try:
ip_address = netaddr.IPAddress(ip_address_str)
if ip_address.version != network.version:
msg = ("Invalid IP version - must match network version " +
ip_version_to_string(network.version))
raise ValidateFail(msg)
elif ip_address == network:
raise ValidateFail("Cannot use network address")
elif ip_address == network.broadcast:
raise ValidateFail("Cannot use broadcast address")
elif ip_address not in network:
raise ValidateFail(
"Address must be in subnet %s" % str(network))
return ip_address
except netaddr.AddrFormatError:
raise ValidateFail(
"Invalid address - not a valid IP address")
def ip_version_to_string(ip_version):
"""Determine whether a nameserver address is valid."""
if ip_version == 4:
return "IPv4"
elif ip_version == 6:
return "IPv6"
else:
return "IP"