1 # -*- python-indent: 4 -*-
3 """A few things that didn't seem to fit anywhere else."""
18 PID_FILE = '/var/run/nodemanager.pid'
22 interface = get_if_from_hwaddr(get_hwaddr_from_plnode())
23 if not interface: interface = "eth0"
26 def get_hwaddr_from_plnode():
28 for line in open("/usr/boot/plnode.txt", 'r').readlines():
29 if line.startswith("NET_DEVICE"):
30 return line.split("=")[1].strip().strip('"')
35 def get_if_from_hwaddr(hwaddr):
39 dev_hwaddr = sioc.gifhwaddr(dev)
40 if dev_hwaddr == hwaddr: return dev
45 def as_daemon_thread(run):
46 """Call function <run> with no arguments in its own thread."""
47 thr = threading.Thread(target=run)
51 def close_nonstandard_fds():
52 """Close all open file descriptors other than 0, 1, and 2."""
54 for fd in range(3, os.sysconf(_SC_OPEN_MAX)):
56 except OSError: pass # most likely an fd that isn't open
58 # after http://www.erlenstar.demon.co.uk/unix/faq_2.html
60 """Daemonize the current process."""
61 if os.fork() != 0: os._exit(0)
63 if os.fork() != 0: os._exit(0)
66 devnull = os.open(os.devnull, os.O_RDWR)
68 # xxx fixme - this is just to make sure that nothing gets stupidly lost - should use devnull
69 crashlog = os.open('/var/log/nodemanager.daemon', os.O_RDWR | os.O_APPEND | os.O_CREAT, 0644)
73 def fork_as(su, function, *args):
75 fork(), cd / to avoid keeping unused directories open,
76 close all nonstandard file descriptors (to avoid capturing open sockets),
77 fork() again (to avoid zombies) and call <function>
78 with arguments <args> in the grandchild process.
79 If <su> is not None, set our group and user ids
80 appropriately in the child process.
86 close_nonstandard_fds()
88 pw_ent = pwd.getpwnam(su)
92 if child_pid == 0: function(*args)
94 os.seteuid(os.getuid()) # undo su so we can write the log file
95 os.setegid(os.getgid())
96 logger.log_exc("tools: fork_as")
98 else: os.waitpid(child_pid, 0)
104 We use a pid file to ensure that only one copy of NM is running at a given time.
105 If successful, this function will write a pid file containing the pid of the current process.
106 The return value is the pid of the other running process, or None otherwise.
109 if os.access(PID_FILE, os.F_OK): # check for a pid file
110 handle = open(PID_FILE) # pid file exists, read it
111 other_pid = int(handle.read())
113 # check for a process with that pid by sending signal 0
114 try: os.kill(other_pid, 0)
116 if e.errno == errno.ESRCH: other_pid = None # doesn't exist
117 else: raise # who knows
118 if other_pid == None:
119 # write a new pid file
120 write_file(PID_FILE, lambda f: f.write(str(os.getpid())))
123 def write_file(filename, do_write, **kw_args):
125 Write file <filename> atomically by opening a temporary file,
126 using <do_write> to write that file, and then renaming the temporary file.
128 shutil.move(write_temp_file(do_write, **kw_args), filename)
130 def write_temp_file(do_write, mode=None, uidgid=None):
131 fd, temporary_filename = tempfile.mkstemp()
132 if mode: os.chmod(temporary_filename, mode)
133 if uidgid: os.chown(temporary_filename, *uidgid)
134 f = os.fdopen(fd, 'w')
137 return temporary_filename
139 def replace_file_with_string (target, new_contents, chmod=None, remove_if_empty=False):
141 Replace a target file with a new contents
142 checks for changes: does not do anything if previous state was already right
143 can handle chmod if requested
144 can also remove resulting file if contents are void, if requested
146 writes in a tmp file, which is then renamed (from sliverauth originally)
147 returns True if a change occurred, or the file is deleted
150 current=file(target).read()
153 if current==new_contents:
154 # if turns out to be an empty string, and remove_if_empty is set,
155 # then make sure to trash the file if it exists
156 if remove_if_empty and not new_contents and os.path.isfile(target):
157 logger.verbose("tools.replace_file_with_string: removing file %s"%target)
158 try: os.unlink(target)
161 # overwrite target file: create a temp in the same directory
162 path=os.path.dirname(target) or '.'
163 fd, name = tempfile.mkstemp('','repl',path)
164 os.write(fd,new_contents)
166 if os.path.exists(target):
168 shutil.move(name,target)
169 if chmod: os.chmod(target,chmod)
173 # utilities functions to get (cached) information from the node
175 # get node_id from /etc/planetlab/node_id and cache it
181 _node_id=int(file("/etc/planetlab/node_id").read())
186 _root_context_arch=None
187 def root_context_arch():
188 global _root_context_arch
189 if not _root_context_arch:
190 sp=subprocess.Popen(["uname","-i"],stdout=subprocess.PIPE)
191 (_root_context_arch,_)=sp.communicate()
192 _root_context_arch=_root_context_arch.strip()
193 return _root_context_arch
198 def __init__(self, file):
199 logger.log("tools: Lock %s initialized." % file, 2)
200 self.fd = os.open(file, os.O_RDWR|os.O_CREAT, 0600)
201 flags = fcntl.fcntl(self.fd, fcntl.F_GETFD)
202 flags |= fcntl.FD_CLOEXEC
203 fcntl.fcntl(self.fd, fcntl.F_SETFD, flags)
207 logger.log("tools: Lock acquired.", 2)
208 fcntl.lockf(self.fd, fcntl.LOCK_SH)
210 logger.log("tools: Lock released.", 2)
211 fcntl.lockf(self.fd, fcntl.LOCK_UN)
214 # Utilities for getting the IP address of a LXC/Openvswitch slice. Do this by
215 # running ifconfig inside of the slice's context.
217 def get_sliver_process(slice_name, process_cmdline):
218 """ Utility function to find a process inside of an LXC sliver. Returns
219 (cgroup_fn, pid). cgroup_fn is the filename of the cgroup file for
220 the process, for example /proc/2592/cgroup. Pid is the process id of
221 the process. If the process is not found then (None, None) is returned.
224 cmd = 'grep %s /proc/*/cgroup | grep freezer'%slice_name
225 output = os.popen(cmd).readlines()
227 # the slice couldn't be found
228 logger.log("get_sliver_process: couldn't find slice %s" % slice_name)
236 path = l.split(':')[0]
237 comp = l.rsplit(':')[-1]
238 slice_name_check = comp.rsplit('/')[-1]
240 if (slice_name_check == slice_name):
242 pid = slice_path.split('/')[2]
243 cmdline = open('/proc/%s/cmdline'%pid).read().rstrip('\n\x00')
244 if (cmdline == process_cmdline):
245 cgroup_fn = slice_path
250 if (not cgroup_fn) or (not pid):
251 logger.log("get_sliver_process: process %s not running in slice %s" % (process_cmdline, slice_name))
254 return (cgroup_fn, pid)
256 ###################################################
257 # Added by Guilherme Sperb Machado <gsm@machados.org>
258 ###################################################
264 # TODO: is there anything better to do if the "libvirt", "sliver_libvirt",
265 # and "sliver_lxc" are not in place?
268 from sliver_libvirt import Sliver_Libvirt
271 logger.log("Could not import sliver_lxc or libvirt or sliver_libvirt -- which is required here.")
272 ###################################################
274 ###################################################
275 # Author: Guilherme Sperb Machado <gsm@machados.org>
276 ###################################################
277 # Basically this method is just a copy from "get_sliver_process()", just
278 # adding one more split() to correctly parse the processes for LXC.
280 # TODO: maybe merge both methods, and put the type as an argument, if
281 # it is LXC or vserver
282 ###################################################
283 def get_sliver_process_lxc(slice_name, process_cmdline):
284 """ Utility function to find a process inside of an LXC sliver. Returns
285 (cgroup_fn, pid). cgroup_fn is the filename of the cgroup file for
286 the process, for example /proc/2592/cgroup. Pid is the process id of
287 the process. If the process is not found then (None, None) is returned.
290 cmd = 'grep %s /proc/*/cgroup | grep freezer'%slice_name
291 output = os.popen(cmd).readlines()
293 # the slice couldn't be found
294 logger.log("get_sliver_process: couldn't find slice %s" % slice_name)
302 #logger.log("tools: l=%s" % (l) )
303 path = l.split(':')[0]
304 #logger.log("tools: path=%s" % (path) )
305 comp = l.rsplit(':')[-1]
306 #logger.log("tools: comp=%s" % (comp) )
307 slice_name_check1 = comp.rsplit('/')[-1]
308 #logger.log("tools: slice_name_check1=%s" % (slice_name_check1) )
309 slice_name_check2 = slice_name_check1.rsplit('.')[0]
310 #logger.log("tools: slice_name_check2=%s" % (slice_name_check2) )
312 if (slice_name_check2 == slice_name):
314 pid = slice_path.split('/')[2]
315 #logger.log("tools: pid=%s" % (pid) )
316 cmdline = open('/proc/%s/cmdline'%pid).read().rstrip('\n\x00')
317 #logger.log("tools: cmdline=%s" % (cmdline) )
318 #logger.log("tools: process_cmdline=%s" % (process_cmdline) )
319 if (cmdline == process_cmdline):
320 cgroup_fn = slice_path
323 #logger.log("tools: break!")
326 if (not cgroup_fn) or (not pid):
327 logger.log("get_sliver_process: process %s not running in slice %s" % (process_cmdline, slice_name))
330 return (cgroup_fn, pid)
333 def get_sliver_ifconfig(slice_name, device="eth0"):
334 """ return the output of "ifconfig" run from inside the sliver.
336 side effects: adds "/usr/sbin" to sys.path
339 # See if setns is installed. If it's not then we're probably not running
341 if not os.path.exists("/usr/sbin/setns.so"):
344 # setns is part of lxcsu and is installed to /usr/sbin
345 if not "/usr/sbin" in sys.path:
346 sys.path.append("/usr/sbin")
349 (cgroup_fn, pid) = get_sliver_process(slice_name, "/sbin/init")
350 if (not cgroup_fn) or (not pid):
353 path = '/proc/%s/ns/net'%pid
357 setns.chcontext(path)
359 args = ["/sbin/ifconfig", device]
360 sub = subprocess.Popen(args, stderr = subprocess.PIPE, stdout = subprocess.PIPE)
363 if (sub.returncode != 0):
364 logger.log("get_slice_ifconfig: error in ifconfig: %s" % sub.stderr.read())
366 result = sub.stdout.read()
368 setns.chcontext("/proc/1/ns/net")
372 ###################################################
373 # Author: Guilherme Sperb Machado <gsm@machados.org>
374 ###################################################
375 # Basically this method is just a copy from "get_sliver_ifconfig()", but,
376 # instead, calls the "get_sliver_process_lxc()" method.
378 # TODO: maybe merge both methods, and put the type as an argument, if
379 # it is LXC or vserver
380 ###################################################
381 def get_sliver_ifconfig_lxc(slice_name, device="eth0"):
382 """ return the output of "ifconfig" run from inside the sliver.
384 side effects: adds "/usr/sbin" to sys.path
387 # See if setns is installed. If it's not then we're probably not running
389 if not os.path.exists("/usr/sbin/setns.so"):
392 # setns is part of lxcsu and is installed to /usr/sbin
393 if not "/usr/sbin" in sys.path:
394 sys.path.append("/usr/sbin")
397 (cgroup_fn, pid) = get_sliver_process_lxc(slice_name, "/sbin/init")
398 if (not cgroup_fn) or (not pid):
401 path = '/proc/%s/ns/net'%pid
405 setns.chcontext(path)
407 args = ["/sbin/ifconfig", device]
408 sub = subprocess.Popen(args, stderr = subprocess.PIPE, stdout = subprocess.PIPE)
411 if (sub.returncode != 0):
412 logger.log("get_slice_ifconfig: error in ifconfig: %s" % sub.stderr.read())
414 result = sub.stdout.read()
416 setns.chcontext("/proc/1/ns/net")
421 def get_sliver_ip(slice_name):
422 ifconfig = get_sliver_ifconfig(slice_name)
426 for line in ifconfig.split("\n"):
427 if "inet addr:" in line:
428 # example: ' inet addr:192.168.122.189 Bcast:192.168.122.255 Mask:255.255.255.0'
429 parts = line.strip().split()
430 if len(parts)>=2 and parts[1].startswith("addr:"):
431 return parts[1].split(":")[1]
435 ###################################################
436 # Author: Guilherme Sperb Machado <gsm@machados.org>
437 ###################################################
438 # Get the slice ipv6 address
440 ###################################################
441 def get_sliver_ipv6(slice_name):
442 ifconfig = get_sliver_ifconfig_lxc(slice_name)
446 # example: 'inet6 2001:67c:16dc:1302:5054:ff:fea7:7882 prefixlen 64 scopeid 0x0<global>'
447 prog = re.compile(r'inet6\s+(.*)\s+prefixlen\s+(\d+)\s+scopeid\s+(.+)<global>')
448 for line in ifconfig.split("\n"):
449 search = prog.search(line)
451 ipv6addr = search.group(1)
452 prefixlen = search.group(2)
453 return (ipv6addr,prefixlen)
456 ###################################################
457 # Author: Guilherme Sperb Machado <gsm@machados.org>
458 ###################################################
459 # Check if the address is a AF_INET6 family address
460 ###################################################
461 def isValidIPv6(ipv6addr):
463 socket.inet_pton(socket.AF_INET6, ipv6addr)
468 ### this returns the kind of virtualization on the node
469 # either 'vs' or 'lxc'
470 # also caches it in /etc/planetlab/virt for next calls
471 # could be promoted to core nm if need be
472 virt_stamp="/etc/planetlab/virt"
473 def get_node_virt ():
475 return file(virt_stamp).read().strip()
478 logger.log("Computing virt..")
480 if subprocess.call ([ 'vserver', '--help' ]) ==0: virt='vs'
484 with file(virt_stamp,"w") as f:
488 ### this return True or False to indicate that systemctl is present on that box
489 # cache result in memory as _has_systemctl
491 def has_systemctl ():
492 global _has_systemctl
493 if _has_systemctl is None:
494 _has_systemctl = (subprocess.call([ 'systemctl', '--help' ]) == 0)
495 return _has_systemctl
497 ###################################################
498 # Author: Guilherme Sperb Machado <gsm@machados.org>
499 ###################################################
500 # This method was developed to support the ipv6 plugin
502 ###################################################
503 def reboot_sliver(name):
505 # connecting to the libvirtd
506 connLibvirt = Sliver_Libvirt.getConnection(type)
507 domains = connLibvirt.listAllDomains()
508 for domain in domains:
511 # logger.log("ipv6: " + repr(method))
512 #logger.log("tools: " + str(domain.name()) )
515 logger.log("tools: %s destroyed" % (domain.name()) )
517 logger.log("tools: %s created" % (domain.name()) )
519 logger.log("tools: %s could not be rebooted" % (domain.name()) )
521 ###################################################
522 # Author: Guilherme Sperb Machado <gsm@machados.org>
523 ###################################################
524 # Get the /etc/hosts file path
525 ###################################################
526 def get_hosts_file_path(slicename):
527 containerDir = os.path.join(sliver_lxc.Sliver_LXC.CON_BASE_DIR, slicename)
528 logger.log("tools: %s" % (containerDir) )
529 return os.path.join(containerDir, 'etc', 'hosts')
531 ###################################################
532 # Author: Guilherme Sperb Machado <gsm@machados.org>
533 ###################################################
534 # Search if there is a specific ipv6 address in the /etc/hosts file of a given slice
535 ###################################################
536 def search_ipv6addr_hosts(slicename, ipv6addr):
537 hostsFilePath = get_hosts_file_path(slicename)
540 for line in fileinput.input(r'%s' % (hostsFilePath)):
541 if re.search(r'%s' % (ipv6addr), line):
546 logger.log("tools: error when finding ipv6 address %s in the /etc/hosts file of slice=%s" % (ipv6addr, slicename) )
548 ###################################################
549 # Author: Guilherme Sperb Machado <gsm@machados.org>
550 ###################################################
551 # Removes all ipv6 addresses from the /etc/hosts file of a given slice
552 ###################################################
553 def remove_all_ipv6addr_hosts(slicename, node):
554 hostsFilePath = get_hosts_file_path(slicename)
556 for line in fileinput.input(r'%s' % (hostsFilePath), inplace=True):
557 logger.log("tools: line=%s" % (line) )
558 search = re.search(r'^(.*)\s+(%s|%s)$' % (node,'localhost'), line)
560 ipv6candidate = search.group(1)
561 ipv6candidatestrip = ipv6candidate.strip()
562 logger.log("tools: group1=%s" % (ipv6candidatestrip) )
563 valid = isValidIPv6(ipv6candidatestrip)
565 logger.log("tools: address=%s not valid" % (ipv6candidatestrip) )
569 logger.log("tools: could not delete the ipv6 address from the hosts file of slice=%s" % (slicename) )
571 ###################################################
572 # Author: Guilherme Sperb Machado <gsm@machados.org>
573 ###################################################
574 # Adds an ipv6 address to the /etc/hosts file within a slice
575 ###################################################
576 def add_ipv6addr_hosts_line(slicename, node, ipv6addr):
577 hostsFilePath = get_hosts_file_path(slicename)
578 logger.log("tools: %s" % (hostsFilePath) )
579 # debugging purposes:
580 #string = "127.0.0.1\tlocalhost\n192.168.100.179\tmyplc-node1-vm.mgmt.local\n"
581 #string = "127.0.0.1\tlocalhost\n"
583 with open(hostsFilePath, "a") as file:
584 # debugging purposes only:
586 file.write(ipv6addr + " " + node + "\n")
589 logger.log("tools: could not add the IPv6 address to the hosts file of slice=%s" % (slicename) )
593 # how to run a command in a slice
594 # now this is a painful matter
595 # the problem is with capsh that forces a bash command to be injected in its exec'ed command
596 # so because lxcsu uses capsh, you cannot exec anything else than bash
597 # bottom line is, what actually needs to be called is
598 # vs: vserver exec slicename command and its arguments
599 # lxc: lxcsu slicename "command and its arguments"
600 # which, OK, is no big deal as long as the command is simple enough,
601 # but do not stretch it with arguments that have spaces or need quoting as that will become a nightmare
602 def command_in_slice (slicename, argv):
605 return [ 'vserver', slicename, 'exec', ] + argv
607 # wrap up argv in a single string for -c
608 return [ 'lxcsu', slicename, ] + [ " ".join(argv) ]
609 logger.log("command_in_slice: WARNING: could not find a valid virt")
614 def handler (signum, frame):
615 logger.log("Received signal %d - exiting"%signum)
617 signal.signal(signal.SIGHUP,handler)
618 signal.signal(signal.SIGQUIT,handler)
619 signal.signal(signal.SIGINT,handler)
620 signal.signal(signal.SIGTERM,handler)