X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=tools.py;h=546288fa48ef9903d050e944b0e3ae1878d09adf;hb=570d234b7d7b40416b296de71225f28c826ee991;hp=b90a59e5c902d2700e82e4fe62de58321746a63c;hpb=1b7d8ef3d9ddf592a8f97ef89b91ed457f1ba6b7;p=nodemanager.git diff --git a/tools.py b/tools.py index b90a59e..546288f 100644 --- a/tools.py +++ b/tools.py @@ -1,3 +1,5 @@ +# -*- python-indent: 4 -*- + """A few things that didn't seem to fit anywhere else.""" import os, os.path @@ -11,24 +13,6 @@ import shutil import sys import signal -################################################### -# Added by Guilherme Sperb Machado -################################################### - -import re -import socket -import fileinput - -# TODO: is there anything better to do if the "libvirt", "sliver_libvirt", -# and "sliver_lxc" are not in place? -try: - import libvirt - from sliver_libvirt import Sliver_Libvirt - import sliver_lxc -except: - logger.log("Could not import sliver_lxc or libvirt or sliver_libvirt -- which is required here.") -################################################### - import logger PID_FILE = '/var/run/nodemanager.pid' @@ -87,7 +71,14 @@ def daemon(): os.dup2(crashlog, 2) def fork_as(su, function, *args): - """fork(), cd / to avoid keeping unused directories open, close all nonstandard file descriptors (to avoid capturing open sockets), fork() again (to avoid zombies) and call with arguments in the grandchild process. If is not None, set our group and user ids appropriately in the child process.""" + """ +fork(), cd / to avoid keeping unused directories open, +close all nonstandard file descriptors (to avoid capturing open sockets), +fork() again (to avoid zombies) and call +with arguments in the grandchild process. +If is not None, set our group and user ids + appropriately in the child process. + """ child_pid = os.fork() if child_pid == 0: try: @@ -109,9 +100,11 @@ def fork_as(su, function, *args): #################### # manage files def pid_file(): - """We use a pid file to ensure that only one copy of NM is running at a given time. + """ +We use a pid file to ensure that only one copy of NM is running at a given time. If successful, this function will write a pid file containing the pid of the current process. -The return value is the pid of the other running process, or None otherwise.""" +The return value is the pid of the other running process, or None otherwise. + """ other_pid = None if os.access(PID_FILE, os.F_OK): # check for a pid file handle = open(PID_FILE) # pid file exists, read it @@ -128,7 +121,10 @@ The return value is the pid of the other running process, or None otherwise.""" return other_pid def write_file(filename, do_write, **kw_args): - """Write file atomically by opening a temporary file, using to write that file, and then renaming the temporary file.""" + """ +Write file atomically by opening a temporary file, +using to write that file, and then renaming the temporary file. + """ shutil.move(write_temp_file(do_write, **kw_args), filename) def write_temp_file(do_write, mode=None, uidgid=None): @@ -140,65 +136,69 @@ def write_temp_file(do_write, mode=None, uidgid=None): finally: f.close() return temporary_filename -# replace a target file with a new contents - checks for changes -# can handle chmod if requested -# can also remove resulting file if contents are void, if requested -# performs atomically: -# writes in a tmp file, which is then renamed (from sliverauth originally) -# returns True if a change occurred, or the file is deleted def replace_file_with_string (target, new_contents, chmod=None, remove_if_empty=False): + """ +Replace a target file with a new contents +checks for changes: does not do anything if previous state was already right +can handle chmod if requested +can also remove resulting file if contents are void, if requested +performs atomically: +writes in a tmp file, which is then renamed (from sliverauth originally) +returns True if a change occurred, or the file is deleted + """ try: - current=file(target).read() + with open(target) as f: + current = f.read() except: - current="" - if current==new_contents: + current = "" + if current == new_contents: # if turns out to be an empty string, and remove_if_empty is set, # then make sure to trash the file if it exists if remove_if_empty and not new_contents and os.path.isfile(target): - logger.verbose("tools.replace_file_with_string: removing file %s"%target) + logger.verbose("tools.replace_file_with_string: removing file {}".format(target)) try: os.unlink(target) finally: return True return False # overwrite target file: create a temp in the same directory - path=os.path.dirname(target) or '.' - fd, name = tempfile.mkstemp('','repl',path) - os.write(fd,new_contents) + path = os.path.dirname(target) or '.' + fd, name = tempfile.mkstemp('', 'repl', path) + os.write(fd, new_contents) os.close(fd) if os.path.exists(target): os.unlink(target) - shutil.move(name,target) - if chmod: os.chmod(target,chmod) + shutil.move(name, target) + if chmod: os.chmod(target, chmod) return True - #################### # utilities functions to get (cached) information from the node # get node_id from /etc/planetlab/node_id and cache it -_node_id=None +_node_id = None def node_id(): global _node_id if _node_id is None: try: - _node_id=int(file("/etc/planetlab/node_id").read()) + with open("/etc/planetlab/node_id") as f: + _node_id = int(f.read()) except: - _node_id="" + _node_id = "" return _node_id -_root_context_arch=None +_root_context_arch = None def root_context_arch(): global _root_context_arch if not _root_context_arch: - sp=subprocess.Popen(["uname","-i"],stdout=subprocess.PIPE) - (_root_context_arch,_)=sp.communicate() - _root_context_arch=_root_context_arch.strip() + sp = subprocess.Popen(["uname", "-i"], stdout=subprocess.PIPE) + (_root_context_arch, _) = sp.communicate() + _root_context_arch = _root_context_arch.strip() return _root_context_arch #################### class NMLock: def __init__(self, file): - logger.log("tools: Lock %s initialized." % file, 2) + logger.log("tools: Lock {} initialized.".format(file), 2) self.fd = os.open(file, os.O_RDWR|os.O_CREAT, 0600) flags = fcntl.fcntl(self.fd, fcntl.F_GETFD) flags |= fcntl.FD_CLOEXEC @@ -217,17 +217,18 @@ class NMLock: # running ifconfig inside of the slice's context. def get_sliver_process(slice_name, process_cmdline): - """ Utility function to find a process inside of an LXC sliver. Returns - (cgroup_fn, pid). cgroup_fn is the filename of the cgroup file for - the process, for example /proc/2592/cgroup. Pid is the process id of - the process. If the process is not found then (None, None) is returned. + """ + Utility function to find a process inside of an LXC sliver. Returns + (cgroup_fn, pid). cgroup_fn is the filename of the cgroup file for + the process, for example /proc/2592/cgroup. Pid is the process id of + the process. If the process is not found then (None, None) is returned. """ try: - cmd = 'grep %s /proc/*/cgroup | grep freezer'%slice_name + cmd = 'grep {} /proc/*/cgroup | grep freezer'.format(slice_name) output = os.popen(cmd).readlines() except: # the slice couldn't be found - logger.log("get_sliver_process: couldn't find slice %s" % slice_name) + logger.log("get_sliver_process: couldn't find slice {}".format(slice_name)) return (None, None) cgroup_fn = None @@ -238,11 +239,31 @@ def get_sliver_process(slice_name, process_cmdline): path = l.split(':')[0] comp = l.rsplit(':')[-1] slice_name_check = comp.rsplit('/')[-1] + # the lines below were added by Guilherme + # due to the LXC requirements + # What we have to consider here is that libervirt on Fedora 18 + # uses the following line: + # /proc/1253/cgroup:6:freezer:/machine.slice/auto_sirius.libvirt-lxc + # While the libvirt on Fedora 20 and 21 uses the following line: + # /proc/1253/cgroup:6:freezer:/machine.slice/machine-lxc\x2del_sirius.scope + # Further documentation on: + # https://libvirt.org/cgroups.html#systemdScope + virt = get_node_virt() + if virt == 'lxc': + # This is for Fedora 20 or later + regexf20orlater = re.compile(r'machine-lxc\\x2d(.+).scope') + isf20orlater = regexf20orlater.search(slice_name_check) + if isf20orlater: + slice_name_check = isf20orlater.group(1) + else: + # This is for Fedora 18 + slice_name_check = slice_name_check.rsplit('.')[0] if (slice_name_check == slice_name): slice_path = path pid = slice_path.split('/')[2] - cmdline = open('/proc/%s/cmdline'%pid).read().rstrip('\n\x00') + with open('/proc/{}/cmdline'.format(pid)) as cmdfile: + cmdline = cmdfile.read().rstrip('\n\x00') if (cmdline == process_cmdline): cgroup_fn = slice_path break @@ -250,122 +271,38 @@ def get_sliver_process(slice_name, process_cmdline): break if (not cgroup_fn) or (not pid): - logger.log("get_sliver_process: process %s not running in slice %s" % (process_cmdline, slice_name)) + logger.log("get_sliver_process: process {} not running in slice {}" + .format(process_cmdline, slice_name)) return (None, None) return (cgroup_fn, pid) ################################################### -# Author: Guilherme Sperb Machado -################################################### -# Basically this method is just a copy from "get_process()", just -# adding one more split() to correctly parse the processes for LXC. -# Only for LXC! -# TODO: maybe merge both methods, and put the type as an argument, if -# it is LXC or vserver +# Added by Guilherme Sperb Machado ################################################### -def get_sliver_process_lxc(slice_name, process_cmdline): - """ Utility function to find a process inside of an LXC sliver. Returns - (cgroup_fn, pid). cgroup_fn is the filename of the cgroup file for - the process, for example /proc/2592/cgroup. Pid is the process id of - the process. If the process is not found then (None, None) is returned. - """ - try: - cmd = 'grep %s /proc/*/cgroup | grep freezer'%slice_name - output = os.popen(cmd).readlines() - except: - # the slice couldn't be found - logger.log("get_sliver_process: couldn't find slice %s" % slice_name) - return (None, None) - - cgroup_fn = None - pid = None - for e in output: - try: - l = e.rstrip() - #logger.log("tools: l=%s" % (l) ) - path = l.split(':')[0] - #logger.log("tools: path=%s" % (path) ) - comp = l.rsplit(':')[-1] - #logger.log("tools: comp=%s" % (comp) ) - slice_name_check1 = comp.rsplit('/')[-1] - #logger.log("tools: slice_name_check1=%s" % (slice_name_check1) ) - slice_name_check2 = slice_name_check1.rsplit('.')[0] - #logger.log("tools: slice_name_check2=%s" % (slice_name_check2) ) - - if (slice_name_check2 == slice_name): - slice_path = path - pid = slice_path.split('/')[2] - #logger.log("tools: pid=%s" % (pid) ) - cmdline = open('/proc/%s/cmdline'%pid).read().rstrip('\n\x00') - #logger.log("tools: cmdline=%s" % (cmdline) ) - #logger.log("tools: process_cmdline=%s" % (process_cmdline) ) - if (cmdline == process_cmdline): - cgroup_fn = slice_path - break - except: - #logger.log("tools: break!") - break - if (not cgroup_fn) or (not pid): - logger.log("get_sliver_process: process %s not running in slice %s" % (process_cmdline, slice_name)) - return (None, None) - - return (cgroup_fn, pid) +try: + import re + import socket + import fileinput +except: + logger.log("Could not import 're', 'socket', or 'fileinput' python packages.") +# TODO: is there anything better to do if the "libvirt", "sliver_libvirt", +# and "sliver_lxc" are not in place? +try: + import libvirt + from sliver_libvirt import Sliver_Libvirt + import sliver_lxc +except: + logger.log("Could not import 'sliver_lxc' or 'libvirt' or 'sliver_libvirt'.") +################################################### def get_sliver_ifconfig(slice_name, device="eth0"): - """ return the output of "ifconfig" run from inside the sliver. - - side effects: adds "/usr/sbin" to sys.path """ + return the output of "ifconfig" run from inside the sliver. - # See if setns is installed. If it's not then we're probably not running - # LXC. - if not os.path.exists("/usr/sbin/setns.so"): - return None - - # setns is part of lxcsu and is installed to /usr/sbin - if not "/usr/sbin" in sys.path: - sys.path.append("/usr/sbin") - import setns - - (cgroup_fn, pid) = get_sliver_process(slice_name, "/sbin/init") - if (not cgroup_fn) or (not pid): - return None - - path = '/proc/%s/ns/net'%pid - - result = None - try: - setns.chcontext(path) - - args = ["/sbin/ifconfig", device] - sub = subprocess.Popen(args, stderr = subprocess.PIPE, stdout = subprocess.PIPE) - sub.wait() - - if (sub.returncode != 0): - logger.log("get_slice_ifconfig: error in ifconfig: %s" % sub.stderr.read()) - - result = sub.stdout.read() - finally: - setns.chcontext("/proc/1/ns/net") - - return result - -################################################### -# Author: Guilherme Sperb Machado -################################################### -# Basically this method is just a copy from "get_sliver_ifconfig()", but, -# instead, calls the "get_sliver_process_lxc()" method. -# Only for LXC! -# TODO: maybe merge both methods, and put the type as an argument, if -# it is LXC or vserver -################################################### -def get_sliver_ifconfig_lxc(slice_name, device="eth0"): - """ return the output of "ifconfig" run from inside the sliver. - - side effects: adds "/usr/sbin" to sys.path + side effects: adds "/usr/sbin" to sys.path """ # See if setns is installed. If it's not then we're probably not running @@ -378,11 +315,11 @@ def get_sliver_ifconfig_lxc(slice_name, device="eth0"): sys.path.append("/usr/sbin") import setns - (cgroup_fn, pid) = get_sliver_process_lxc(slice_name, "/sbin/init") + (cgroup_fn, pid) = get_sliver_process(slice_name, "/sbin/init") if (not cgroup_fn) or (not pid): return None - path = '/proc/%s/ns/net'%pid + path = '/proc/{}/ns/net'.format(pid) result = None try: @@ -393,7 +330,7 @@ def get_sliver_ifconfig_lxc(slice_name, device="eth0"): sub.wait() if (sub.returncode != 0): - logger.log("get_slice_ifconfig: error in ifconfig: %s" % sub.stderr.read()) + logger.log("get_slice_ifconfig: error in ifconfig: {}".format(sub.stderr.read())) result = sub.stdout.read() finally: @@ -401,7 +338,6 @@ def get_sliver_ifconfig_lxc(slice_name, device="eth0"): return result - def get_sliver_ip(slice_name): ifconfig = get_sliver_ifconfig(slice_name) if not ifconfig: @@ -411,7 +347,7 @@ def get_sliver_ip(slice_name): if "inet addr:" in line: # example: ' inet addr:192.168.122.189 Bcast:192.168.122.255 Mask:255.255.255.0' parts = line.strip().split() - if len(parts)>=2 and parts[1].startswith("addr:"): + if len(parts) >= 2 and parts[1].startswith("addr:"): return parts[1].split(":")[1] return None @@ -423,55 +359,55 @@ def get_sliver_ip(slice_name): # Only for LXC! ################################################### def get_sliver_ipv6(slice_name): - ifconfig = get_sliver_ifconfig_lxc(slice_name) + ifconfig = get_sliver_ifconfig(slice_name) if not ifconfig: - return None,None - + return None, None + # example: 'inet6 2001:67c:16dc:1302:5054:ff:fea7:7882 prefixlen 64 scopeid 0x0' prog = re.compile(r'inet6\s+(.*)\s+prefixlen\s+(\d+)\s+scopeid\s+(.+)') for line in ifconfig.split("\n"): - search = prog.search(line) - if search: - ipv6addr = search.group(1) - prefixlen = search.group(2) - return (ipv6addr,prefixlen) - return None,None - -################################################### + search = prog.search(line) + if search: + ipv6addr = search.group(1) + prefixlen = search.group(2) + return (ipv6addr, prefixlen) + return None, None + +################################################### # Author: Guilherme Sperb Machado ################################################### # Check if the address is a AF_INET6 family address ################################################### -def isValidIPv6(ipv6addr): - try: - socket.inet_pton(socket.AF_INET6, ipv6addr) - except socket.error: - return False - return True +def is_valid_ipv6(ipv6addr): + try: + socket.inet_pton(socket.AF_INET6, ipv6addr) + except socket.error: + return False + return True ### this returns the kind of virtualization on the node # either 'vs' or 'lxc' # also caches it in /etc/planetlab/virt for next calls # could be promoted to core nm if need be -virt_stamp="/etc/planetlab/virt" +virt_stamp = "/etc/planetlab/virt" def get_node_virt (): try: - return file(virt_stamp).read().strip() + with open(virt_stamp) as f: + return f.read().strip() except: pass logger.log("Computing virt..") - try: - if subprocess.call ([ 'vserver', '--help' ]) ==0: virt='vs' - else: virt='lxc' + try: + virt = 'vs' if subprocess.call ([ 'vserver', '--help' ]) == 0 else 'lxc' except: virt='lxc' - with file(virt_stamp,"w") as f: + with file(virt_stamp, "w") as f: f.write(virt) return virt ### this return True or False to indicate that systemctl is present on that box # cache result in memory as _has_systemctl -_has_systemctl=None +_has_systemctl = None def has_systemctl (): global _has_systemctl if _has_systemctl is None: @@ -484,23 +420,33 @@ def has_systemctl (): # This method was developed to support the ipv6 plugin # Only for LXC! ################################################### -def reboot_sliver(name): - type = 'sliver.LXC' - # connecting to the libvirtd - connLibvirt = Sliver_Libvirt.getConnection(type) - domains = connLibvirt.listAllDomains() - for domain in domains: - #ret = dir(domain) - #for method in ret: - # logger.log("ipv6: " + repr(method)) - #logger.log("tools: " + str(domain.name()) ) - try: - domain.destroy() - logger.log("tools: %s destroyed" % (domain.name()) ) - domain.create() - logger.log("tools: %s created" % (domain.name()) ) - except: - logger.log("tools: %s could not be rebooted" % (domain.name()) ) +def reboot_slivers(): + type = 'sliver.LXC' + # connecting to the libvirtd + connLibvirt = Sliver_Libvirt.getConnection(type) + domains = connLibvirt.listAllDomains() + for domain in domains: + try: + # set the flag VIR_DOMAIN_REBOOT_INITCTL, which uses "initctl" + result = domain.reboot(0x04) + if result == 0: + logger.log("tools: REBOOT {}".format(domain.name()) ) + else: + raise Exception() + except Exception, e: + logger.log("tools: FAILED to reboot {} ({})".format(domain.name(), e) ) + logger.log("tools: Trying to DESTROY/CREATE {} instead...".format(domain.name()) ) + try: + result = domain.destroy() + if result==0: + logger.log("tools: DESTROYED {}".format(domain.name()) ) + else: logger.log("tools: FAILED in the DESTROY call of {}".format(domain.name()) ) + result = domain.create() + if result==0: + logger.log("tools: CREATED {}".format(domain.name()) ) + else: logger.log("tools: FAILED in the CREATE call of {}".format(domain.name()) ) + except Exception, e: + logger.log("tools: FAILED to DESTROY/CREATE {} ({})".format(domain.name(), e) ) ################################################### # Author: Guilherme Sperb Machado @@ -509,48 +455,61 @@ def reboot_sliver(name): ################################################### def get_hosts_file_path(slicename): containerDir = os.path.join(sliver_lxc.Sliver_LXC.CON_BASE_DIR, slicename) - logger.log("tools: %s" % (containerDir) ) return os.path.join(containerDir, 'etc', 'hosts') ################################################### # Author: Guilherme Sperb Machado ################################################### -# Search if there is a specific ipv6 address in the /etc/hosts file of a given slice +# Search if there is a specific ipv6 address in the +# /etc/hosts file of a given slice +# If the parameter 'ipv6addr' is None, then search +# for any ipv6 address ################################################### def search_ipv6addr_hosts(slicename, ipv6addr): hostsFilePath = get_hosts_file_path(slicename) - found=False + found = False try: - for line in fileinput.input(r'%s' % (hostsFilePath)): - if re.search(r'%s' % (ipv6addr), line): - found=True - fileinput.close() - return found + for line in fileinput.input(r'{}'.format(hostsFilePath)): + if ipv6addr is not None: + if re.search(r'{}'.format(ipv6addr), line): + found = True + else: + search = re.search(r'^(.*)\s+.*$', line) + if search: + ipv6candidate = search.group(1) + ipv6candidatestrip = ipv6candidate.strip() + valid = is_valid_ipv6(ipv6candidatestrip) + if valid: + found = True + fileinput.close() + return found except: - logger.log("tools: error when finding ipv6 address %s in the /etc/hosts file of slice=%s" % (ipv6addr, slicename) ) + logger.log("tools: FAILED to search {} in /etc/hosts file of slice={}" + .format(ipv6addr, slicename)) ################################################### # Author: Guilherme Sperb Machado ################################################### -# Removes all ipv6 addresses from the /etc/hosts file of a given slice +# Removes all ipv6 addresses from the /etc/hosts +# file of a given slice ################################################### def remove_all_ipv6addr_hosts(slicename, node): hostsFilePath = get_hosts_file_path(slicename) try: - for line in fileinput.input(r'%s' % (hostsFilePath), inplace=True): - logger.log("tools: line=%s" % (line) ) - search = re.search(r'^(.*)\s+(%s|%s)$' % (node,'localhost'), line) - if search: - ipv6candidate = search.group(1) - ipv6candidatestrip = ipv6candidate.strip() - logger.log("tools: group1=%s" % (ipv6candidatestrip) ) - valid = isValidIPv6(ipv6candidatestrip) - if not valid: - logger.log("tools: address=%s not valid" % (ipv6candidatestrip) ) - print line, - fileinput.close() + for line in fileinput.input(r'{}'.format(hostsFilePath), inplace=True): + search = re.search(r'^(.*)\s+({}|{})$'.format(node, 'localhost'), line) + if search: + ipv6candidate = search.group(1) + ipv6candidatestrip = ipv6candidate.strip() + valid = is_valid_ipv6(ipv6candidatestrip) + if not valid: + print line, + fileinput.close() + logger.log("tools: REMOVED IPv6 address from /etc/hosts file of slice={}" + .format(slicename) ) except: - logger.log("tools: could not delete the ipv6 address from the hosts file of slice=%s" % (slicename) ) + logger.log("tools: FAILED to remove the IPv6 address from /etc/hosts file of slice={}" + .format(slicename) ) ################################################### # Author: Guilherme Sperb Machado @@ -559,18 +518,19 @@ def remove_all_ipv6addr_hosts(slicename, node): ################################################### def add_ipv6addr_hosts_line(slicename, node, ipv6addr): hostsFilePath = get_hosts_file_path(slicename) - logger.log("tools: %s" % (hostsFilePath) ) + logger.log("tools: {}".format(hostsFilePath) ) # debugging purposes: #string = "127.0.0.1\tlocalhost\n192.168.100.179\tmyplc-node1-vm.mgmt.local\n" #string = "127.0.0.1\tlocalhost\n" try: - with open(hostsFilePath, "a") as file: - # debugging purposes only: - #file.write(string) - file.write(ipv6addr + " " + node + "\n") - file.close() + with open(hostsFilePath, "a") as file: + file.write(ipv6addr + " " + node + "\n") + file.close() + logger.log("tools: ADDED IPv6 address to /etc/hosts file of slice={}" + .format(slicename) ) except: - logger.log("tools: could not add the IPv6 address to the hosts file of slice=%s" % (slicename) ) + logger.log("tools: FAILED to add the IPv6 address to /etc/hosts file of slice={}" + .format(slicename) ) @@ -581,13 +541,13 @@ def add_ipv6addr_hosts_line(slicename, node, ipv6addr): # bottom line is, what actually needs to be called is # vs: vserver exec slicename command and its arguments # lxc: lxcsu slicename "command and its arguments" -# which, OK, is no big deal as long as the command is simple enough, +# which, OK, is no big deal as long as the command is simple enough, # but do not stretch it with arguments that have spaces or need quoting as that will become a nightmare def command_in_slice (slicename, argv): - virt=get_node_virt() - if virt=='vs': + virt = get_node_virt() + if virt == 'vs': return [ 'vserver', slicename, 'exec', ] + argv - elif virt=='lxc': + elif virt == 'lxc': # wrap up argv in a single string for -c return [ 'lxcsu', slicename, ] + [ " ".join(argv) ] logger.log("command_in_slice: WARNING: could not find a valid virt") @@ -596,9 +556,9 @@ def command_in_slice (slicename, argv): #################### def init_signals (): def handler (signum, frame): - logger.log("Received signal %d - exiting"%signum) + logger.log("Received signal {} - exiting".format(signum)) os._exit(1) - signal.signal(signal.SIGHUP,handler) - signal.signal(signal.SIGQUIT,handler) - signal.signal(signal.SIGINT,handler) - signal.signal(signal.SIGTERM,handler) + signal.signal(signal.SIGHUP, handler) + signal.signal(signal.SIGQUIT, handler) + signal.signal(signal.SIGINT, handler) + signal.signal(signal.SIGTERM, handler)