2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Miscellaneous utility functions, wrappers around some subprocess procedures.
|
|
|
|
"""
|
|
|
|
|
2018-02-05 17:01:49 +00:00
|
|
|
import importlib
|
|
|
|
import inspect
|
2017-04-25 16:45:34 +01:00
|
|
|
import os
|
2018-03-01 21:21:25 +00:00
|
|
|
import shlex
|
2017-04-25 16:45:34 +01:00
|
|
|
import subprocess
|
2018-02-05 17:01:49 +00:00
|
|
|
import sys
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2016-01-29 23:21:49 +00:00
|
|
|
import fcntl
|
2017-05-04 18:36:13 +01:00
|
|
|
import resource
|
2016-01-29 23:21:49 +00:00
|
|
|
|
2017-08-07 19:58:51 +01:00
|
|
|
from core import logger
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2018-03-02 00:23:58 +00:00
|
|
|
DEVNULL = open(os.devnull, "wb")
|
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2018-03-02 17:15:52 +00:00
|
|
|
def _detach_init():
|
2017-05-04 18:36:13 +01:00
|
|
|
"""
|
2018-03-02 17:15:52 +00:00
|
|
|
Fork a child process and exit.
|
2017-05-04 18:36:13 +01:00
|
|
|
|
|
|
|
:return: nothing
|
|
|
|
"""
|
2018-03-02 17:15:52 +00:00
|
|
|
if os.fork():
|
|
|
|
# parent exits
|
|
|
|
os._exit(0)
|
|
|
|
os.setsid()
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2018-03-02 17:15:52 +00:00
|
|
|
def _valid_module(path, file_name):
|
2017-05-04 18:36:13 +01:00
|
|
|
"""
|
2018-03-02 17:15:52 +00:00
|
|
|
Check if file is a valid python module.
|
2017-05-04 18:36:13 +01:00
|
|
|
|
2018-03-02 17:15:52 +00:00
|
|
|
:param str path: path to file
|
|
|
|
:param str file_name: file name to check
|
|
|
|
:return: True if a valid python module file, False otherwise
|
|
|
|
:rtype: bool
|
2017-05-04 18:36:13 +01:00
|
|
|
"""
|
2018-03-02 17:15:52 +00:00
|
|
|
file_path = os.path.join(path, file_name)
|
|
|
|
if not os.path.isfile(file_path):
|
|
|
|
return False
|
|
|
|
|
|
|
|
if file_name.startswith("_"):
|
|
|
|
return False
|
|
|
|
|
|
|
|
if not file_name.endswith(".py"):
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def _is_class(module, member, clazz):
|
|
|
|
"""
|
|
|
|
Validates if a module member is a class and an instance of a CoreService.
|
|
|
|
|
|
|
|
:param module: module to validate for service
|
|
|
|
:param member: member to validate for service
|
|
|
|
:param clazz: clazz type to check for validation
|
|
|
|
:return: True if a valid service, False otherwise
|
|
|
|
:rtype: bool
|
|
|
|
"""
|
|
|
|
if not inspect.isclass(member):
|
|
|
|
return False
|
|
|
|
|
|
|
|
if not issubclass(member, clazz):
|
|
|
|
return False
|
|
|
|
|
|
|
|
if member.__module__ != module.__name__:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2018-03-02 17:15:52 +00:00
|
|
|
def _is_exe(file_path):
|
2017-05-04 18:36:13 +01:00
|
|
|
"""
|
|
|
|
Check if a given file path exists and is an executable file.
|
|
|
|
|
|
|
|
:param str file_path: file path to check
|
|
|
|
:return: True if the file is considered and executable file, False otherwise
|
|
|
|
:rtype: bool
|
|
|
|
"""
|
|
|
|
return os.path.isfile(file_path) and os.access(file_path, os.X_OK)
|
|
|
|
|
|
|
|
|
2018-03-02 17:15:52 +00:00
|
|
|
def close_onexec(fd):
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2018-03-02 17:15:52 +00:00
|
|
|
Close on execution of a shell process.
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2018-03-02 17:15:52 +00:00
|
|
|
:param fd: file descriptor to close
|
|
|
|
:return: nothing
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2018-03-02 17:15:52 +00:00
|
|
|
fdflags = fcntl.fcntl(fd, fcntl.F_GETFD)
|
|
|
|
fcntl.fcntl(fd, fcntl.F_SETFD, fdflags | fcntl.FD_CLOEXEC)
|
2013-09-12 20:07:41 +01:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2018-03-02 17:15:52 +00:00
|
|
|
def check_executables(executables):
|
2017-05-04 18:36:13 +01:00
|
|
|
"""
|
2018-03-02 17:15:52 +00:00
|
|
|
Check executables, verify they exist and are executable.
|
2017-05-04 18:36:13 +01:00
|
|
|
|
2018-03-02 17:15:52 +00:00
|
|
|
:param list[str] executables: executable to check
|
2017-05-04 18:36:13 +01:00
|
|
|
:return: nothing
|
2018-03-02 17:15:52 +00:00
|
|
|
:raises EnvironmentError: when an executable doesn't exist or is not executable
|
2017-05-04 18:36:13 +01:00
|
|
|
"""
|
2018-03-02 17:15:52 +00:00
|
|
|
for executable in executables:
|
|
|
|
if not _is_exe(executable):
|
|
|
|
raise EnvironmentError("executable not found: %s" % executable)
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2018-03-02 17:15:52 +00:00
|
|
|
def make_tuple(obj):
|
2017-05-04 18:36:13 +01:00
|
|
|
"""
|
|
|
|
Create a tuple from an object, or return the object itself.
|
|
|
|
|
|
|
|
:param obj: object to convert to a tuple
|
|
|
|
:return: converted tuple or the object itself
|
|
|
|
:rtype: tuple
|
|
|
|
"""
|
2013-08-29 15:21:13 +01:00
|
|
|
if hasattr(obj, "__iter__"):
|
|
|
|
return tuple(obj)
|
|
|
|
else:
|
2017-04-25 16:45:34 +01:00
|
|
|
return obj,
|
|
|
|
|
|
|
|
|
2018-03-02 17:15:52 +00:00
|
|
|
def make_tuple_fromstr(s, value_type):
|
2017-05-04 18:36:13 +01:00
|
|
|
"""
|
|
|
|
Create a tuple from a string.
|
|
|
|
|
|
|
|
:param str s: string to convert to a tuple
|
2017-07-10 17:25:33 +01:00
|
|
|
:param value_type: type of values to be contained within tuple
|
2017-05-04 18:36:13 +01:00
|
|
|
:return: tuple from string
|
|
|
|
:rtype: tuple
|
|
|
|
"""
|
2017-08-02 22:07:56 +01:00
|
|
|
# remove tuple braces and strip commands and space from all values in the tuple string
|
|
|
|
values = [x.strip("' ") for x in s.strip("(), ").split(",")]
|
|
|
|
return tuple(value_type(i) for i in values)
|
2013-08-29 15:21:13 +01:00
|
|
|
|
|
|
|
|
2018-03-02 00:23:58 +00:00
|
|
|
def split_args(args):
|
2018-03-01 21:21:25 +00:00
|
|
|
"""
|
|
|
|
Convenience method for splitting potential string commands into a shell-like syntax list.
|
|
|
|
|
2018-03-02 00:23:58 +00:00
|
|
|
:param list/str args: command list or string
|
2018-03-01 21:21:25 +00:00
|
|
|
:return: shell-like syntax list
|
|
|
|
:rtype: list
|
|
|
|
"""
|
2018-03-02 00:23:58 +00:00
|
|
|
if type(args) == str:
|
|
|
|
args = shlex.split(args)
|
|
|
|
return args
|
2018-03-01 21:21:25 +00:00
|
|
|
|
|
|
|
|
2018-03-02 17:15:52 +00:00
|
|
|
def mute_detach(args, **kwargs):
|
2017-05-04 18:36:13 +01:00
|
|
|
"""
|
|
|
|
Run a muted detached process by forking it.
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2018-03-02 17:15:52 +00:00
|
|
|
:param list[str]|str args: arguments for the command
|
2017-05-04 18:36:13 +01:00
|
|
|
:param dict kwargs: keyword arguments for the command
|
|
|
|
:return: process id of the command
|
|
|
|
:rtype: int
|
|
|
|
"""
|
2018-03-02 17:15:52 +00:00
|
|
|
args = split_args(args)
|
|
|
|
kwargs["preexec_fn"] = _detach_init
|
2018-03-02 00:23:58 +00:00
|
|
|
kwargs["stdout"] = DEVNULL
|
2017-05-04 18:36:13 +01:00
|
|
|
kwargs["stderr"] = subprocess.STDOUT
|
2018-03-02 17:20:50 +00:00
|
|
|
return subprocess.Popen(args, **kwargs).pid
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2018-03-02 00:23:58 +00:00
|
|
|
def cmd(args, wait=True):
|
|
|
|
"""
|
|
|
|
Runs a command on and returns the exit status.
|
|
|
|
|
|
|
|
:param list[str]|str args: command arguments
|
|
|
|
:param bool wait: wait for command to end or not
|
|
|
|
:return: command status
|
|
|
|
:rtype: int
|
|
|
|
"""
|
|
|
|
args = split_args(args)
|
|
|
|
try:
|
|
|
|
p = subprocess.Popen(args)
|
|
|
|
if not wait:
|
|
|
|
return 0
|
|
|
|
return p.wait()
|
|
|
|
except OSError:
|
|
|
|
raise subprocess.CalledProcessError(-1, args)
|
|
|
|
|
|
|
|
|
|
|
|
def cmd_output(args):
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2017-05-04 18:36:13 +01:00
|
|
|
Execute a command on the host and return a tuple containing the exit status and result string. stderr output
|
2017-04-25 16:45:34 +01:00
|
|
|
is folded into the stdout result string.
|
2017-05-04 18:36:13 +01:00
|
|
|
|
2018-03-02 00:23:58 +00:00
|
|
|
:param list[str]|str args: command arguments
|
2017-05-04 18:36:13 +01:00
|
|
|
:return: command status and stdout
|
|
|
|
:rtype: tuple[int, str]
|
2018-03-02 00:23:58 +00:00
|
|
|
:raises subprocess.CalledProcessError: when the file to execute is not found
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2018-03-02 00:23:58 +00:00
|
|
|
args = split_args(args)
|
|
|
|
try:
|
|
|
|
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
|
|
stdout, _ = p.communicate()
|
|
|
|
status = p.wait()
|
2018-03-02 21:39:44 +00:00
|
|
|
return status, stdout.strip()
|
2018-03-02 00:23:58 +00:00
|
|
|
except OSError:
|
|
|
|
raise subprocess.CalledProcessError(-1, args)
|
2018-03-01 21:21:25 +00:00
|
|
|
|
|
|
|
|
2018-03-02 00:23:58 +00:00
|
|
|
def check_cmd(args, **kwargs):
|
2018-03-01 21:21:25 +00:00
|
|
|
"""
|
|
|
|
Execute a command on the host and return a tuple containing the exit status and result string. stderr output
|
|
|
|
is folded into the stdout result string.
|
|
|
|
|
2018-03-02 00:23:58 +00:00
|
|
|
:param list[str]|str args: command arguments
|
2018-03-01 21:21:25 +00:00
|
|
|
:param dict kwargs: keyword arguments to pass to subprocess.Popen
|
2018-03-02 21:57:50 +00:00
|
|
|
:return: combined stdout and stderr
|
|
|
|
:rtype: str
|
2018-03-02 00:23:58 +00:00
|
|
|
:raises subprocess.CalledProcessError: when there is a non-zero exit status or the file to execute is not found
|
2018-03-01 21:21:25 +00:00
|
|
|
"""
|
|
|
|
kwargs["stdout"] = subprocess.PIPE
|
|
|
|
kwargs["stderr"] = subprocess.STDOUT
|
2018-03-02 00:23:58 +00:00
|
|
|
args = split_args(args)
|
|
|
|
try:
|
|
|
|
p = subprocess.Popen(args, **kwargs)
|
|
|
|
stdout, _ = p.communicate()
|
|
|
|
status = p.wait()
|
2018-03-02 21:39:44 +00:00
|
|
|
if status != 0:
|
2018-03-02 00:23:58 +00:00
|
|
|
raise subprocess.CalledProcessError(status, args, stdout)
|
2018-03-02 21:57:50 +00:00
|
|
|
return stdout.strip()
|
2018-03-02 00:23:58 +00:00
|
|
|
except OSError:
|
|
|
|
raise subprocess.CalledProcessError(-1, args)
|
2013-10-22 15:32:42 +01:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2018-03-02 17:15:52 +00:00
|
|
|
def hex_dump(s, bytes_per_word=2, words_per_line=8):
|
2017-05-04 18:36:13 +01:00
|
|
|
"""
|
|
|
|
Hex dump of a string.
|
|
|
|
|
|
|
|
:param str s: string to hex dump
|
|
|
|
:param bytes_per_word: number of bytes per word
|
|
|
|
:param words_per_line: number of words per line
|
|
|
|
:return: hex dump of string
|
|
|
|
"""
|
2013-08-29 15:21:13 +01:00
|
|
|
dump = ""
|
|
|
|
count = 0
|
2018-03-02 17:15:52 +00:00
|
|
|
total_bytes = bytes_per_word * words_per_line
|
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
while s:
|
2018-03-02 17:15:52 +00:00
|
|
|
line = s[:total_bytes]
|
|
|
|
s = s[total_bytes:]
|
2013-08-29 15:21:13 +01:00
|
|
|
tmp = map(lambda x: ("%02x" * bytes_per_word) % x,
|
|
|
|
zip(*[iter(map(ord, line))] * bytes_per_word))
|
|
|
|
if len(line) % 2:
|
|
|
|
tmp.append("%x" % ord(line[-1]))
|
|
|
|
dump += "0x%08x: %s\n" % (count, " ".join(tmp))
|
|
|
|
count += len(line)
|
|
|
|
return dump[:-1]
|
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2018-03-02 17:15:52 +00:00
|
|
|
def file_munge(pathname, header, text):
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Insert text at the end of a file, surrounded by header comments.
|
2017-05-04 18:36:13 +01:00
|
|
|
|
|
|
|
:param str pathname: file path to add text to
|
|
|
|
:param str header: header text comments
|
|
|
|
:param str text: text to append to file
|
|
|
|
:return: nothing
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
# prevent duplicates
|
2018-03-02 17:15:52 +00:00
|
|
|
file_demunge(pathname, header)
|
|
|
|
|
|
|
|
with open(pathname, "a") as append_file:
|
|
|
|
append_file.write("# BEGIN %s\n" % header)
|
|
|
|
append_file.write(text)
|
|
|
|
append_file.write("# END %s\n" % header)
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
|
2018-03-02 17:15:52 +00:00
|
|
|
def file_demunge(pathname, header):
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Remove text that was inserted in a file surrounded by header comments.
|
2017-05-04 18:36:13 +01:00
|
|
|
|
|
|
|
:param str pathname: file path to open for removing a header
|
|
|
|
:param str header: header text to target for removal
|
|
|
|
:return: nothing
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2018-03-02 17:15:52 +00:00
|
|
|
with open(pathname, "r") as read_file:
|
|
|
|
lines = read_file.readlines()
|
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
start = None
|
|
|
|
end = None
|
2018-03-02 17:15:52 +00:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
for i in range(len(lines)):
|
|
|
|
if lines[i] == "# BEGIN %s\n" % header:
|
|
|
|
start = i
|
|
|
|
elif lines[i] == "# END %s\n" % header:
|
|
|
|
end = i + 1
|
2018-03-02 17:15:52 +00:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
if start is None or end is None:
|
|
|
|
return
|
2018-03-02 17:15:52 +00:00
|
|
|
|
|
|
|
with open(pathname, "w") as write_file:
|
|
|
|
lines = lines[:start] + lines[end:]
|
|
|
|
write_file.write("".join(lines))
|
2017-04-25 16:45:34 +01:00
|
|
|
|
|
|
|
|
2018-03-02 17:15:52 +00:00
|
|
|
def expand_corepath(pathname, session=None, node=None):
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Expand a file path given session information.
|
2017-05-04 18:36:13 +01:00
|
|
|
|
|
|
|
:param str pathname: file path to expand
|
|
|
|
:param core.session.Session session: core session object to expand path with
|
|
|
|
:param core.netns.LxcNode node: node to expand path with
|
|
|
|
:return: expanded path
|
|
|
|
:rtype: str
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2013-08-29 15:21:13 +01:00
|
|
|
if session is not None:
|
2017-05-04 18:36:13 +01:00
|
|
|
pathname = pathname.replace("~", "/home/%s" % session.user)
|
|
|
|
pathname = pathname.replace("%SESSION%", str(session.session_id))
|
|
|
|
pathname = pathname.replace("%SESSION_DIR%", session.session_dir)
|
|
|
|
pathname = pathname.replace("%SESSION_USER%", session.user)
|
2018-03-02 17:15:52 +00:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
if node is not None:
|
2017-05-04 18:36:13 +01:00
|
|
|
pathname = pathname.replace("%NODE%", str(node.objid))
|
|
|
|
pathname = pathname.replace("%NODENAME%", node.name)
|
2018-03-02 17:15:52 +00:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
return pathname
|
2017-04-25 16:45:34 +01:00
|
|
|
|
|
|
|
|
2018-03-02 17:15:52 +00:00
|
|
|
def sysctl_devname(devname):
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
|
|
|
Translate a device name to the name used with sysctl.
|
2017-05-04 18:36:13 +01:00
|
|
|
|
|
|
|
:param str devname: device name to translate
|
|
|
|
:return: translated device name
|
|
|
|
:rtype: str
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2013-08-29 15:21:13 +01:00
|
|
|
if devname is None:
|
|
|
|
return None
|
|
|
|
return devname.replace(".", "/")
|
|
|
|
|
2017-04-25 16:45:34 +01:00
|
|
|
|
|
|
|
def daemonize(rootdir="/", umask=0, close_fds=False, dontclose=(),
|
|
|
|
stdin=os.devnull, stdout=os.devnull, stderr=os.devnull,
|
|
|
|
stdoutmode=0644, stderrmode=0644, pidfilename=None,
|
|
|
|
defaultmaxfd=1024):
|
|
|
|
"""
|
|
|
|
Run the background process as a daemon.
|
2017-05-04 18:36:13 +01:00
|
|
|
|
|
|
|
:param str rootdir: root directory for daemon
|
|
|
|
:param int umask: umask for daemon
|
|
|
|
:param bool close_fds: flag to close file descriptors
|
|
|
|
:param dontclose: dont close options
|
|
|
|
:param stdin: stdin for daemon
|
|
|
|
:param stdout: stdout for daemon
|
|
|
|
:param stderr: stderr for daemon
|
|
|
|
:param int stdoutmode: stdout mode
|
|
|
|
:param int stderrmode: stderr mode
|
|
|
|
:param str pidfilename: pid file name
|
|
|
|
:param int defaultmaxfd: default max file descriptors
|
|
|
|
:return: nothing
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2013-08-29 15:21:13 +01:00
|
|
|
if not hasattr(dontclose, "__contains__"):
|
|
|
|
if not isinstance(dontclose, int):
|
2017-05-04 18:36:13 +01:00
|
|
|
raise TypeError("dontclose must be an integer")
|
2013-08-29 15:21:13 +01:00
|
|
|
dontclose = (int(dontclose),)
|
|
|
|
else:
|
|
|
|
for fd in dontclose:
|
|
|
|
if not isinstance(fd, int):
|
2017-05-04 18:36:13 +01:00
|
|
|
raise TypeError("dontclose must contain only integers")
|
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
# redirect stdin
|
|
|
|
if stdin:
|
|
|
|
fd = os.open(stdin, os.O_RDONLY)
|
|
|
|
os.dup2(fd, 0)
|
|
|
|
os.close(fd)
|
2017-05-04 18:36:13 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
# redirect stdout
|
|
|
|
if stdout:
|
|
|
|
fd = os.open(stdout, os.O_WRONLY | os.O_CREAT | os.O_APPEND,
|
|
|
|
stdoutmode)
|
|
|
|
os.dup2(fd, 1)
|
2017-04-25 16:45:34 +01:00
|
|
|
if stdout == stderr:
|
2013-08-29 15:21:13 +01:00
|
|
|
os.dup2(1, 2)
|
|
|
|
os.close(fd)
|
2017-05-04 18:36:13 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
# redirect stderr
|
|
|
|
if stderr and (stderr != stdout):
|
|
|
|
fd = os.open(stderr, os.O_WRONLY | os.O_CREAT | os.O_APPEND,
|
|
|
|
stderrmode)
|
|
|
|
os.dup2(fd, 2)
|
|
|
|
os.close(fd)
|
2017-05-04 18:36:13 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
if os.fork():
|
2017-04-25 16:45:34 +01:00
|
|
|
# parent exits
|
|
|
|
os._exit(0)
|
2017-05-04 18:36:13 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
os.setsid()
|
|
|
|
pid = os.fork()
|
|
|
|
if pid:
|
|
|
|
if pidfilename:
|
|
|
|
try:
|
|
|
|
f = open(pidfilename, "w")
|
|
|
|
f.write("%s\n" % pid)
|
|
|
|
f.close()
|
2017-04-25 16:45:34 +01:00
|
|
|
except IOError:
|
|
|
|
logger.exception("error writing to file: %s", pidfilename)
|
|
|
|
# parent exits
|
|
|
|
os._exit(0)
|
2017-05-04 18:36:13 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
if rootdir:
|
|
|
|
os.chdir(rootdir)
|
2017-05-04 18:36:13 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
os.umask(umask)
|
|
|
|
if close_fds:
|
|
|
|
try:
|
|
|
|
maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
|
|
|
|
if maxfd == resource.RLIM_INFINITY:
|
|
|
|
raise ValueError
|
|
|
|
except:
|
|
|
|
maxfd = defaultmaxfd
|
2017-05-04 18:36:13 +01:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
for fd in xrange(3, maxfd):
|
|
|
|
if fd in dontclose:
|
|
|
|
continue
|
|
|
|
try:
|
|
|
|
os.close(fd)
|
2017-05-04 18:36:13 +01:00
|
|
|
except IOError:
|
2017-04-25 16:45:34 +01:00
|
|
|
logger.exception("error closing file descriptor")
|
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
|
2018-03-02 17:15:52 +00:00
|
|
|
def load_config(filename, d):
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2017-05-04 18:36:13 +01:00
|
|
|
Read key=value pairs from a file, into a dict. Skip comments; strip newline characters and spacing.
|
|
|
|
|
|
|
|
:param str filename: file to read into a dictionary
|
|
|
|
:param dict d: dictionary to read file into
|
|
|
|
:return: nothing
|
2017-04-25 16:45:34 +01:00
|
|
|
"""
|
2017-05-04 18:36:13 +01:00
|
|
|
with open(filename, "r") as f:
|
2013-08-29 15:21:13 +01:00
|
|
|
lines = f.readlines()
|
2018-03-02 17:15:52 +00:00
|
|
|
|
|
|
|
for line in lines:
|
|
|
|
if line[:1] == "#":
|
2013-08-29 15:21:13 +01:00
|
|
|
continue
|
2018-03-02 17:15:52 +00:00
|
|
|
|
2013-08-29 15:21:13 +01:00
|
|
|
try:
|
2018-03-02 17:15:52 +00:00
|
|
|
key, value = line.split("=", 1)
|
2013-08-29 15:21:13 +01:00
|
|
|
d[key] = value.strip()
|
|
|
|
except ValueError:
|
2017-04-25 16:45:34 +01:00
|
|
|
logger.exception("error reading file to dict: %s", filename)
|
2013-10-22 15:32:42 +01:00
|
|
|
|
|
|
|
|
2018-02-05 17:01:49 +00:00
|
|
|
def load_classes(path, clazz):
|
|
|
|
"""
|
|
|
|
Dynamically load classes for use within CORE.
|
|
|
|
|
|
|
|
:param path: path to load classes from
|
|
|
|
:param clazz: class type expected to be inherited from for loading
|
|
|
|
:return: list of classes loaded
|
|
|
|
"""
|
|
|
|
# validate path exists
|
|
|
|
logger.info("attempting to load modules from path: %s", path)
|
|
|
|
if not os.path.isdir(path):
|
|
|
|
logger.warn("invalid custom module directory specified" ": %s" % path)
|
|
|
|
# check if path is in sys.path
|
|
|
|
parent_path = os.path.dirname(path)
|
|
|
|
if parent_path not in sys.path:
|
|
|
|
logger.info("adding parent path to allow imports: %s", parent_path)
|
|
|
|
sys.path.append(parent_path)
|
|
|
|
|
|
|
|
# retrieve potential service modules, and filter out invalid modules
|
|
|
|
base_module = os.path.basename(path)
|
|
|
|
module_names = os.listdir(path)
|
|
|
|
module_names = filter(lambda x: _valid_module(path, x), module_names)
|
|
|
|
module_names = map(lambda x: x[:-3], module_names)
|
|
|
|
|
|
|
|
# import and add all service modules in the path
|
|
|
|
classes = []
|
|
|
|
for module_name in module_names:
|
|
|
|
import_statement = "%s.%s" % (base_module, module_name)
|
|
|
|
logger.info("importing custom module: %s", import_statement)
|
|
|
|
try:
|
|
|
|
module = importlib.import_module(import_statement)
|
|
|
|
members = inspect.getmembers(module, lambda x: _is_class(module, x, clazz))
|
|
|
|
for member in members:
|
2018-02-05 19:22:01 +00:00
|
|
|
valid_class = member[1]
|
|
|
|
classes.append(valid_class)
|
2018-02-05 17:01:49 +00:00
|
|
|
except:
|
|
|
|
logger.exception("unexpected error during import, skipping: %s", import_statement)
|
|
|
|
|
|
|
|
return classes
|