Xen and KVM: correct a typo when checking args
[ganeti-local] / lib / utils.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """Ganeti utility module.
23
24 This module holds functions that can be used in both daemons (all) and
25 the command line scripts.
26
27 """
28
29
30 import sys
31 import os
32 import sha
33 import time
34 import subprocess
35 import re
36 import socket
37 import tempfile
38 import shutil
39 import errno
40 import pwd
41 import itertools
42 import select
43 import fcntl
44 import resource
45 import logging
46 import signal
47
48 from cStringIO import StringIO
49
50 from ganeti import errors
51 from ganeti import constants
52
53
54 _locksheld = []
55 _re_shell_unquoted = re.compile('^[-.,=:/_+@A-Za-z0-9]+$')
56
57 debug = False
58 debug_locks = False
59
60 #: when set to True, L{RunCmd} is disabled
61 no_fork = False
62
63
64 class RunResult(object):
65   """Holds the result of running external programs.
66
67   @type exit_code: int
68   @ivar exit_code: the exit code of the program, or None (if the program
69       didn't exit())
70   @type signal: int or None
71   @ivar signal: the signal that caused the program to finish, or None
72       (if the program wasn't terminated by a signal)
73   @type stdout: str
74   @ivar stdout: the standard output of the program
75   @type stderr: str
76   @ivar stderr: the standard error of the program
77   @type failed: boolean
78   @ivar failed: True in case the program was
79       terminated by a signal or exited with a non-zero exit code
80   @ivar fail_reason: a string detailing the termination reason
81
82   """
83   __slots__ = ["exit_code", "signal", "stdout", "stderr",
84                "failed", "fail_reason", "cmd"]
85
86
87   def __init__(self, exit_code, signal_, stdout, stderr, cmd):
88     self.cmd = cmd
89     self.exit_code = exit_code
90     self.signal = signal_
91     self.stdout = stdout
92     self.stderr = stderr
93     self.failed = (signal_ is not None or exit_code != 0)
94
95     if self.signal is not None:
96       self.fail_reason = "terminated by signal %s" % self.signal
97     elif self.exit_code is not None:
98       self.fail_reason = "exited with exit code %s" % self.exit_code
99     else:
100       self.fail_reason = "unable to determine termination reason"
101
102     if self.failed:
103       logging.debug("Command '%s' failed (%s); output: %s",
104                     self.cmd, self.fail_reason, self.output)
105
106   def _GetOutput(self):
107     """Returns the combined stdout and stderr for easier usage.
108
109     """
110     return self.stdout + self.stderr
111
112   output = property(_GetOutput, None, None, "Return full output")
113
114
115 def RunCmd(cmd, env=None, output=None, cwd='/'):
116   """Execute a (shell) command.
117
118   The command should not read from its standard input, as it will be
119   closed.
120
121   @type  cmd: string or list
122   @param cmd: Command to run
123   @type env: dict
124   @param env: Additional environment
125   @type output: str
126   @param output: if desired, the output of the command can be
127       saved in a file instead of the RunResult instance; this
128       parameter denotes the file name (if not None)
129   @type cwd: string
130   @param cwd: if specified, will be used as the working
131       directory for the command; the default will be /
132   @rtype: L{RunResult}
133   @return: RunResult instance
134   @raise erors.ProgrammerError: if we call this when forks are disabled
135
136   """
137   if no_fork:
138     raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
139
140   if isinstance(cmd, list):
141     cmd = [str(val) for val in cmd]
142     strcmd = " ".join(cmd)
143     shell = False
144   else:
145     strcmd = cmd
146     shell = True
147   logging.debug("RunCmd '%s'", strcmd)
148
149   cmd_env = os.environ.copy()
150   cmd_env["LC_ALL"] = "C"
151   if env is not None:
152     cmd_env.update(env)
153
154   if output is None:
155     out, err, status = _RunCmdPipe(cmd, cmd_env, shell, cwd)
156   else:
157     status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
158     out = err = ""
159
160   if status >= 0:
161     exitcode = status
162     signal_ = None
163   else:
164     exitcode = None
165     signal_ = -status
166
167   return RunResult(exitcode, signal_, out, err, strcmd)
168
169
170 def _RunCmdPipe(cmd, env, via_shell, cwd):
171   """Run a command and return its output.
172
173   @type  cmd: string or list
174   @param cmd: Command to run
175   @type env: dict
176   @param env: The environment to use
177   @type via_shell: bool
178   @param via_shell: if we should run via the shell
179   @type cwd: string
180   @param cwd: the working directory for the program
181   @rtype: tuple
182   @return: (out, err, status)
183
184   """
185   poller = select.poll()
186   child = subprocess.Popen(cmd, shell=via_shell,
187                            stderr=subprocess.PIPE,
188                            stdout=subprocess.PIPE,
189                            stdin=subprocess.PIPE,
190                            close_fds=True, env=env,
191                            cwd=cwd)
192
193   child.stdin.close()
194   poller.register(child.stdout, select.POLLIN)
195   poller.register(child.stderr, select.POLLIN)
196   out = StringIO()
197   err = StringIO()
198   fdmap = {
199     child.stdout.fileno(): (out, child.stdout),
200     child.stderr.fileno(): (err, child.stderr),
201     }
202   for fd in fdmap:
203     status = fcntl.fcntl(fd, fcntl.F_GETFL)
204     fcntl.fcntl(fd, fcntl.F_SETFL, status | os.O_NONBLOCK)
205
206   while fdmap:
207     try:
208       pollresult = poller.poll()
209     except EnvironmentError, eerr:
210       if eerr.errno == errno.EINTR:
211         continue
212       raise
213     except select.error, serr:
214       if serr[0] == errno.EINTR:
215         continue
216       raise
217
218     for fd, event in pollresult:
219       if event & select.POLLIN or event & select.POLLPRI:
220         data = fdmap[fd][1].read()
221         # no data from read signifies EOF (the same as POLLHUP)
222         if not data:
223           poller.unregister(fd)
224           del fdmap[fd]
225           continue
226         fdmap[fd][0].write(data)
227       if (event & select.POLLNVAL or event & select.POLLHUP or
228           event & select.POLLERR):
229         poller.unregister(fd)
230         del fdmap[fd]
231
232   out = out.getvalue()
233   err = err.getvalue()
234
235   status = child.wait()
236   return out, err, status
237
238
239 def _RunCmdFile(cmd, env, via_shell, output, cwd):
240   """Run a command and save its output to a file.
241
242   @type  cmd: string or list
243   @param cmd: Command to run
244   @type env: dict
245   @param env: The environment to use
246   @type via_shell: bool
247   @param via_shell: if we should run via the shell
248   @type output: str
249   @param output: the filename in which to save the output
250   @type cwd: string
251   @param cwd: the working directory for the program
252   @rtype: int
253   @return: the exit status
254
255   """
256   fh = open(output, "a")
257   try:
258     child = subprocess.Popen(cmd, shell=via_shell,
259                              stderr=subprocess.STDOUT,
260                              stdout=fh,
261                              stdin=subprocess.PIPE,
262                              close_fds=True, env=env,
263                              cwd=cwd)
264
265     child.stdin.close()
266     status = child.wait()
267   finally:
268     fh.close()
269   return status
270
271
272 def RemoveFile(filename):
273   """Remove a file ignoring some errors.
274
275   Remove a file, ignoring non-existing ones or directories. Other
276   errors are passed.
277
278   @type filename: str
279   @param filename: the file to be removed
280
281   """
282   try:
283     os.unlink(filename)
284   except OSError, err:
285     if err.errno not in (errno.ENOENT, errno.EISDIR):
286       raise
287
288
289 def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
290   """Renames a file.
291
292   @type old: string
293   @param old: Original path
294   @type new: string
295   @param new: New path
296   @type mkdir: bool
297   @param mkdir: Whether to create target directory if it doesn't exist
298   @type mkdir_mode: int
299   @param mkdir_mode: Mode for newly created directories
300
301   """
302   try:
303     return os.rename(old, new)
304   except OSError, err:
305     # In at least one use case of this function, the job queue, directory
306     # creation is very rare. Checking for the directory before renaming is not
307     # as efficient.
308     if mkdir and err.errno == errno.ENOENT:
309       # Create directory and try again
310       os.makedirs(os.path.dirname(new), mkdir_mode)
311       return os.rename(old, new)
312     raise
313
314
315 def _FingerprintFile(filename):
316   """Compute the fingerprint of a file.
317
318   If the file does not exist, a None will be returned
319   instead.
320
321   @type filename: str
322   @param filename: the filename to checksum
323   @rtype: str
324   @return: the hex digest of the sha checksum of the contents
325       of the file
326
327   """
328   if not (os.path.exists(filename) and os.path.isfile(filename)):
329     return None
330
331   f = open(filename)
332
333   fp = sha.sha()
334   while True:
335     data = f.read(4096)
336     if not data:
337       break
338
339     fp.update(data)
340
341   return fp.hexdigest()
342
343
344 def FingerprintFiles(files):
345   """Compute fingerprints for a list of files.
346
347   @type files: list
348   @param files: the list of filename to fingerprint
349   @rtype: dict
350   @return: a dictionary filename: fingerprint, holding only
351       existing files
352
353   """
354   ret = {}
355
356   for filename in files:
357     cksum = _FingerprintFile(filename)
358     if cksum:
359       ret[filename] = cksum
360
361   return ret
362
363
364 def CheckDict(target, template, logname=None):
365   """Ensure a dictionary has a required set of keys.
366
367   For the given dictionaries I{target} and I{template}, ensure
368   I{target} has all the keys from I{template}. Missing keys are added
369   with values from template.
370
371   @type target: dict
372   @param target: the dictionary to update
373   @type template: dict
374   @param template: the dictionary holding the default values
375   @type logname: str or None
376   @param logname: if not None, causes the missing keys to be
377       logged with this name
378
379   """
380   missing = []
381   for k in template:
382     if k not in target:
383       missing.append(k)
384       target[k] = template[k]
385
386   if missing and logname:
387     logging.warning('%s missing keys %s', logname, ', '.join(missing))
388
389
390 def IsProcessAlive(pid):
391   """Check if a given pid exists on the system.
392
393   @note: zombie status is not handled, so zombie processes
394       will be returned as alive
395   @type pid: int
396   @param pid: the process ID to check
397   @rtype: boolean
398   @return: True if the process exists
399
400   """
401   if pid <= 0:
402     return False
403
404   try:
405     os.stat("/proc/%d/status" % pid)
406     return True
407   except EnvironmentError, err:
408     if err.errno in (errno.ENOENT, errno.ENOTDIR):
409       return False
410     raise
411
412
413 def ReadPidFile(pidfile):
414   """Read a pid from a file.
415
416   @type  pidfile: string
417   @param pidfile: path to the file containing the pid
418   @rtype: int
419   @return: The process id, if the file exista and contains a valid PID,
420            otherwise 0
421
422   """
423   try:
424     pf = open(pidfile, 'r')
425   except EnvironmentError, err:
426     if err.errno != errno.ENOENT:
427       logging.exception("Can't read pid file?!")
428     return 0
429
430   try:
431     pid = int(pf.read())
432   except ValueError, err:
433     logging.info("Can't parse pid file contents", exc_info=True)
434     return 0
435
436   return pid
437
438
439 def MatchNameComponent(key, name_list):
440   """Try to match a name against a list.
441
442   This function will try to match a name like test1 against a list
443   like C{['test1.example.com', 'test2.example.com', ...]}. Against
444   this list, I{'test1'} as well as I{'test1.example'} will match, but
445   not I{'test1.ex'}. A multiple match will be considered as no match
446   at all (e.g. I{'test1'} against C{['test1.example.com',
447   'test1.example.org']}).
448
449   @type key: str
450   @param key: the name to be searched
451   @type name_list: list
452   @param name_list: the list of strings against which to search the key
453
454   @rtype: None or str
455   @return: None if there is no match I{or} if there are multiple matches,
456       otherwise the element from the list which matches
457
458   """
459   mo = re.compile("^%s(\..*)?$" % re.escape(key))
460   names_filtered = [name for name in name_list if mo.match(name) is not None]
461   if len(names_filtered) != 1:
462     return None
463   return names_filtered[0]
464
465
466 class HostInfo:
467   """Class implementing resolver and hostname functionality
468
469   """
470   def __init__(self, name=None):
471     """Initialize the host name object.
472
473     If the name argument is not passed, it will use this system's
474     name.
475
476     """
477     if name is None:
478       name = self.SysName()
479
480     self.query = name
481     self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
482     self.ip = self.ipaddrs[0]
483
484   def ShortName(self):
485     """Returns the hostname without domain.
486
487     """
488     return self.name.split('.')[0]
489
490   @staticmethod
491   def SysName():
492     """Return the current system's name.
493
494     This is simply a wrapper over C{socket.gethostname()}.
495
496     """
497     return socket.gethostname()
498
499   @staticmethod
500   def LookupHostname(hostname):
501     """Look up hostname
502
503     @type hostname: str
504     @param hostname: hostname to look up
505
506     @rtype: tuple
507     @return: a tuple (name, aliases, ipaddrs) as returned by
508         C{socket.gethostbyname_ex}
509     @raise errors.ResolverError: in case of errors in resolving
510
511     """
512     try:
513       result = socket.gethostbyname_ex(hostname)
514     except socket.gaierror, err:
515       # hostname not found in DNS
516       raise errors.ResolverError(hostname, err.args[0], err.args[1])
517
518     return result
519
520
521 def ListVolumeGroups():
522   """List volume groups and their size
523
524   @rtype: dict
525   @return:
526        Dictionary with keys volume name and values
527        the size of the volume
528
529   """
530   command = "vgs --noheadings --units m --nosuffix -o name,size"
531   result = RunCmd(command)
532   retval = {}
533   if result.failed:
534     return retval
535
536   for line in result.stdout.splitlines():
537     try:
538       name, size = line.split()
539       size = int(float(size))
540     except (IndexError, ValueError), err:
541       logging.error("Invalid output from vgs (%s): %s", err, line)
542       continue
543
544     retval[name] = size
545
546   return retval
547
548
549 def BridgeExists(bridge):
550   """Check whether the given bridge exists in the system
551
552   @type bridge: str
553   @param bridge: the bridge name to check
554   @rtype: boolean
555   @return: True if it does
556
557   """
558   return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
559
560
561 def CheckBEParams(beparams):
562   """Checks whether the user-supplied be-params are valid,
563   and converts them from string format where appropriate.
564
565   @type beparams: dict
566   @param beparams: new params dict
567
568   """
569   if beparams:
570     for item in beparams:
571       if item not in constants.BES_PARAMETERS:
572         raise errors.OpPrereqError("Unknown backend parameter %s" % item)
573       if item in (constants.BE_MEMORY, constants.BE_VCPUS):
574         val = beparams[item]
575         if val != constants.VALUE_DEFAULT:
576           try:
577             val = int(val)
578           except ValueError, err:
579             raise errors.OpPrereqError("Invalid %s size: %s" % (item, str(err)))
580           beparams[item] = val
581       if item in (constants.BE_AUTO_BALANCE):
582         val = beparams[item]
583         if not isinstance(val, bool):
584           if val == constants.VALUE_TRUE:
585             beparams[item] = True
586           elif val == constants.VALUE_FALSE:
587             beparams[item] = False
588           else:
589             raise errors.OpPrereqError("Invalid %s value: %s" % (item, val))
590
591
592 def NiceSort(name_list):
593   """Sort a list of strings based on digit and non-digit groupings.
594
595   Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
596   will sort the list in the logical order C{['a1', 'a2', 'a10',
597   'a11']}.
598
599   The sort algorithm breaks each name in groups of either only-digits
600   or no-digits. Only the first eight such groups are considered, and
601   after that we just use what's left of the string.
602
603   @type name_list: list
604   @param name_list: the names to be sorted
605   @rtype: list
606   @return: a copy of the name list sorted with our algorithm
607
608   """
609   _SORTER_BASE = "(\D+|\d+)"
610   _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
611                                                   _SORTER_BASE, _SORTER_BASE,
612                                                   _SORTER_BASE, _SORTER_BASE,
613                                                   _SORTER_BASE, _SORTER_BASE)
614   _SORTER_RE = re.compile(_SORTER_FULL)
615   _SORTER_NODIGIT = re.compile("^\D*$")
616   def _TryInt(val):
617     """Attempts to convert a variable to integer."""
618     if val is None or _SORTER_NODIGIT.match(val):
619       return val
620     rval = int(val)
621     return rval
622
623   to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
624              for name in name_list]
625   to_sort.sort()
626   return [tup[1] for tup in to_sort]
627
628
629 def TryConvert(fn, val):
630   """Try to convert a value ignoring errors.
631
632   This function tries to apply function I{fn} to I{val}. If no
633   C{ValueError} or C{TypeError} exceptions are raised, it will return
634   the result, else it will return the original value. Any other
635   exceptions are propagated to the caller.
636
637   @type fn: callable
638   @param fn: function to apply to the value
639   @param val: the value to be converted
640   @return: The converted value if the conversion was successful,
641       otherwise the original value.
642
643   """
644   try:
645     nv = fn(val)
646   except (ValueError, TypeError), err:
647     nv = val
648   return nv
649
650
651 def IsValidIP(ip):
652   """Verifies the syntax of an IPv4 address.
653
654   This function checks if the IPv4 address passes is valid or not based
655   on syntax (not IP range, class calculations, etc.).
656
657   @type ip: str
658   @param ip: the address to be checked
659   @rtype: a regular expression match object
660   @return: a regular epression match object, or None if the
661       address is not valid
662
663   """
664   unit = "(0|[1-9]\d{0,2})"
665   #TODO: convert and return only boolean
666   return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
667
668
669 def IsValidShellParam(word):
670   """Verifies is the given word is safe from the shell's p.o.v.
671
672   This means that we can pass this to a command via the shell and be
673   sure that it doesn't alter the command line and is passed as such to
674   the actual command.
675
676   Note that we are overly restrictive here, in order to be on the safe
677   side.
678
679   @type word: str
680   @param word: the word to check
681   @rtype: boolean
682   @return: True if the word is 'safe'
683
684   """
685   return bool(re.match("^[-a-zA-Z0-9._+/:%@]+$", word))
686
687
688 def BuildShellCmd(template, *args):
689   """Build a safe shell command line from the given arguments.
690
691   This function will check all arguments in the args list so that they
692   are valid shell parameters (i.e. they don't contain shell
693   metacharaters). If everything is ok, it will return the result of
694   template % args.
695
696   @type template: str
697   @param template: the string holding the template for the
698       string formatting
699   @rtype: str
700   @return: the expanded command line
701
702   """
703   for word in args:
704     if not IsValidShellParam(word):
705       raise errors.ProgrammerError("Shell argument '%s' contains"
706                                    " invalid characters" % word)
707   return template % args
708
709
710 def FormatUnit(value, units):
711   """Formats an incoming number of MiB with the appropriate unit.
712
713   @type value: int
714   @param value: integer representing the value in MiB (1048576)
715   @type units: char
716   @param units: the type of formatting we should do:
717       - 'h' for automatic scaling
718       - 'm' for MiBs
719       - 'g' for GiBs
720       - 't' for TiBs
721   @rtype: str
722   @return: the formatted value (with suffix)
723
724   """
725   if units not in ('m', 'g', 't', 'h'):
726     raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
727
728   suffix = ''
729
730   if units == 'm' or (units == 'h' and value < 1024):
731     if units == 'h':
732       suffix = 'M'
733     return "%d%s" % (round(value, 0), suffix)
734
735   elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
736     if units == 'h':
737       suffix = 'G'
738     return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
739
740   else:
741     if units == 'h':
742       suffix = 'T'
743     return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
744
745
746 def ParseUnit(input_string):
747   """Tries to extract number and scale from the given string.
748
749   Input must be in the format C{NUMBER+ [DOT NUMBER+] SPACE*
750   [UNIT]}. If no unit is specified, it defaults to MiB. Return value
751   is always an int in MiB.
752
753   """
754   m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', str(input_string))
755   if not m:
756     raise errors.UnitParseError("Invalid format")
757
758   value = float(m.groups()[0])
759
760   unit = m.groups()[1]
761   if unit:
762     lcunit = unit.lower()
763   else:
764     lcunit = 'm'
765
766   if lcunit in ('m', 'mb', 'mib'):
767     # Value already in MiB
768     pass
769
770   elif lcunit in ('g', 'gb', 'gib'):
771     value *= 1024
772
773   elif lcunit in ('t', 'tb', 'tib'):
774     value *= 1024 * 1024
775
776   else:
777     raise errors.UnitParseError("Unknown unit: %s" % unit)
778
779   # Make sure we round up
780   if int(value) < value:
781     value += 1
782
783   # Round up to the next multiple of 4
784   value = int(value)
785   if value % 4:
786     value += 4 - value % 4
787
788   return value
789
790
791 def AddAuthorizedKey(file_name, key):
792   """Adds an SSH public key to an authorized_keys file.
793
794   @type file_name: str
795   @param file_name: path to authorized_keys file
796   @type key: str
797   @param key: string containing key
798
799   """
800   key_fields = key.split()
801
802   f = open(file_name, 'a+')
803   try:
804     nl = True
805     for line in f:
806       # Ignore whitespace changes
807       if line.split() == key_fields:
808         break
809       nl = line.endswith('\n')
810     else:
811       if not nl:
812         f.write("\n")
813       f.write(key.rstrip('\r\n'))
814       f.write("\n")
815       f.flush()
816   finally:
817     f.close()
818
819
820 def RemoveAuthorizedKey(file_name, key):
821   """Removes an SSH public key from an authorized_keys file.
822
823   @type file_name: str
824   @param file_name: path to authorized_keys file
825   @type key: str
826   @param key: string containing key
827
828   """
829   key_fields = key.split()
830
831   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
832   try:
833     out = os.fdopen(fd, 'w')
834     try:
835       f = open(file_name, 'r')
836       try:
837         for line in f:
838           # Ignore whitespace changes while comparing lines
839           if line.split() != key_fields:
840             out.write(line)
841
842         out.flush()
843         os.rename(tmpname, file_name)
844       finally:
845         f.close()
846     finally:
847       out.close()
848   except:
849     RemoveFile(tmpname)
850     raise
851
852
853 def SetEtcHostsEntry(file_name, ip, hostname, aliases):
854   """Sets the name of an IP address and hostname in /etc/hosts.
855
856   @type file_name: str
857   @param file_name: path to the file to modify (usually C{/etc/hosts})
858   @type ip: str
859   @param ip: the IP address
860   @type hostname: str
861   @param hostname: the hostname to be added
862   @type aliases: list
863   @param aliases: the list of aliases to add for the hostname
864
865   """
866   # Ensure aliases are unique
867   aliases = UniqueSequence([hostname] + aliases)[1:]
868
869   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
870   try:
871     out = os.fdopen(fd, 'w')
872     try:
873       f = open(file_name, 'r')
874       try:
875         for line in f:
876           fields = line.split()
877           if fields and not fields[0].startswith('#') and ip == fields[0]:
878             continue
879           out.write(line)
880
881         out.write("%s\t%s" % (ip, hostname))
882         if aliases:
883           out.write(" %s" % ' '.join(aliases))
884         out.write('\n')
885
886         out.flush()
887         os.fsync(out)
888         os.rename(tmpname, file_name)
889       finally:
890         f.close()
891     finally:
892       out.close()
893   except:
894     RemoveFile(tmpname)
895     raise
896
897
898 def AddHostToEtcHosts(hostname):
899   """Wrapper around SetEtcHostsEntry.
900
901   @type hostname: str
902   @param hostname: a hostname that will be resolved and added to
903       L{constants.ETC_HOSTS}
904
905   """
906   hi = HostInfo(name=hostname)
907   SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
908
909
910 def RemoveEtcHostsEntry(file_name, hostname):
911   """Removes a hostname from /etc/hosts.
912
913   IP addresses without names are removed from the file.
914
915   @type file_name: str
916   @param file_name: path to the file to modify (usually C{/etc/hosts})
917   @type hostname: str
918   @param hostname: the hostname to be removed
919
920   """
921   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
922   try:
923     out = os.fdopen(fd, 'w')
924     try:
925       f = open(file_name, 'r')
926       try:
927         for line in f:
928           fields = line.split()
929           if len(fields) > 1 and not fields[0].startswith('#'):
930             names = fields[1:]
931             if hostname in names:
932               while hostname in names:
933                 names.remove(hostname)
934               if names:
935                 out.write("%s %s\n" % (fields[0], ' '.join(names)))
936               continue
937
938           out.write(line)
939
940         out.flush()
941         os.fsync(out)
942         os.rename(tmpname, file_name)
943       finally:
944         f.close()
945     finally:
946       out.close()
947   except:
948     RemoveFile(tmpname)
949     raise
950
951
952 def RemoveHostFromEtcHosts(hostname):
953   """Wrapper around RemoveEtcHostsEntry.
954
955   @type hostname: str
956   @param hostname: hostname that will be resolved and its
957       full and shot name will be removed from
958       L{constants.ETC_HOSTS}
959
960   """
961   hi = HostInfo(name=hostname)
962   RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
963   RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
964
965
966 def CreateBackup(file_name):
967   """Creates a backup of a file.
968
969   @type file_name: str
970   @param file_name: file to be backed up
971   @rtype: str
972   @return: the path to the newly created backup
973   @raise errors.ProgrammerError: for invalid file names
974
975   """
976   if not os.path.isfile(file_name):
977     raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
978                                 file_name)
979
980   prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
981   dir_name = os.path.dirname(file_name)
982
983   fsrc = open(file_name, 'rb')
984   try:
985     (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
986     fdst = os.fdopen(fd, 'wb')
987     try:
988       shutil.copyfileobj(fsrc, fdst)
989     finally:
990       fdst.close()
991   finally:
992     fsrc.close()
993
994   return backup_name
995
996
997 def ShellQuote(value):
998   """Quotes shell argument according to POSIX.
999
1000   @type value: str
1001   @param value: the argument to be quoted
1002   @rtype: str
1003   @return: the quoted value
1004
1005   """
1006   if _re_shell_unquoted.match(value):
1007     return value
1008   else:
1009     return "'%s'" % value.replace("'", "'\\''")
1010
1011
1012 def ShellQuoteArgs(args):
1013   """Quotes a list of shell arguments.
1014
1015   @type args: list
1016   @param args: list of arguments to be quoted
1017   @rtype: str
1018   @return: the quoted arguments concatenaned with spaces
1019
1020   """
1021   return ' '.join([ShellQuote(i) for i in args])
1022
1023
1024 def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
1025   """Simple ping implementation using TCP connect(2).
1026
1027   Check if the given IP is reachable by doing attempting a TCP connect
1028   to it.
1029
1030   @type target: str
1031   @param target: the IP or hostname to ping
1032   @type port: int
1033   @param port: the port to connect to
1034   @type timeout: int
1035   @param timeout: the timeout on the connection attemp
1036   @type live_port_needed: boolean
1037   @param live_port_needed: whether a closed port will cause the
1038       function to return failure, as if there was a timeout
1039   @type source: str or None
1040   @param source: if specified, will cause the connect to be made
1041       from this specific source address; failures to bind other
1042       than C{EADDRNOTAVAIL} will be ignored
1043
1044   """
1045   sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1046
1047   success = False
1048
1049   if source is not None:
1050     try:
1051       sock.bind((source, 0))
1052     except socket.error, (errcode, errstring):
1053       if errcode == errno.EADDRNOTAVAIL:
1054         success = False
1055
1056   sock.settimeout(timeout)
1057
1058   try:
1059     sock.connect((target, port))
1060     sock.close()
1061     success = True
1062   except socket.timeout:
1063     success = False
1064   except socket.error, (errcode, errstring):
1065     success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
1066
1067   return success
1068
1069
1070 def OwnIpAddress(address):
1071   """Check if the current host has the the given IP address.
1072
1073   Currently this is done by TCP-pinging the address from the loopback
1074   address.
1075
1076   @type address: string
1077   @param address: the addres to check
1078   @rtype: bool
1079   @return: True if we own the address
1080
1081   """
1082   return TcpPing(address, constants.DEFAULT_NODED_PORT,
1083                  source=constants.LOCALHOST_IP_ADDRESS)
1084
1085
1086 def ListVisibleFiles(path):
1087   """Returns a list of visible files in a directory.
1088
1089   @type path: str
1090   @param path: the directory to enumerate
1091   @rtype: list
1092   @return: the list of all files not starting with a dot
1093
1094   """
1095   files = [i for i in os.listdir(path) if not i.startswith(".")]
1096   files.sort()
1097   return files
1098
1099
1100 def GetHomeDir(user, default=None):
1101   """Try to get the homedir of the given user.
1102
1103   The user can be passed either as a string (denoting the name) or as
1104   an integer (denoting the user id). If the user is not found, the
1105   'default' argument is returned, which defaults to None.
1106
1107   """
1108   try:
1109     if isinstance(user, basestring):
1110       result = pwd.getpwnam(user)
1111     elif isinstance(user, (int, long)):
1112       result = pwd.getpwuid(user)
1113     else:
1114       raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1115                                    type(user))
1116   except KeyError:
1117     return default
1118   return result.pw_dir
1119
1120
1121 def NewUUID():
1122   """Returns a random UUID.
1123
1124   @note: This is a Linux-specific method as it uses the /proc
1125       filesystem.
1126   @rtype: str
1127
1128   """
1129   f = open("/proc/sys/kernel/random/uuid", "r")
1130   try:
1131     return f.read(128).rstrip("\n")
1132   finally:
1133     f.close()
1134
1135
1136 def GenerateSecret():
1137   """Generates a random secret.
1138
1139   This will generate a pseudo-random secret, and return its sha digest
1140   (so that it can be used where an ASCII string is needed).
1141
1142   @rtype: str
1143   @return: a sha1 hexdigest of a block of 64 random bytes
1144
1145   """
1146   return sha.new(os.urandom(64)).hexdigest()
1147
1148
1149 def ReadFile(file_name, size=None):
1150   """Reads a file.
1151
1152   @type size: None or int
1153   @param size: Read at most size bytes
1154   @rtype: str
1155   @return: the (possibly partial) conent of the file
1156
1157   """
1158   f = open(file_name, "r")
1159   try:
1160     if size is None:
1161       return f.read()
1162     else:
1163       return f.read(size)
1164   finally:
1165     f.close()
1166
1167
1168 def WriteFile(file_name, fn=None, data=None,
1169               mode=None, uid=-1, gid=-1,
1170               atime=None, mtime=None, close=True,
1171               dry_run=False, backup=False,
1172               prewrite=None, postwrite=None):
1173   """(Over)write a file atomically.
1174
1175   The file_name and either fn (a function taking one argument, the
1176   file descriptor, and which should write the data to it) or data (the
1177   contents of the file) must be passed. The other arguments are
1178   optional and allow setting the file mode, owner and group, and the
1179   mtime/atime of the file.
1180
1181   If the function doesn't raise an exception, it has succeeded and the
1182   target file has the new contents. If the file has raised an
1183   exception, an existing target file should be unmodified and the
1184   temporary file should be removed.
1185
1186   @type file_name: str
1187   @param file_name: the target filename
1188   @type fn: callable
1189   @param fn: content writing function, called with
1190       file descriptor as parameter
1191   @type data: sr
1192   @param data: contents of the file
1193   @type mode: int
1194   @param mode: file mode
1195   @type uid: int
1196   @param uid: the owner of the file
1197   @type gid: int
1198   @param gid: the group of the file
1199   @type atime: int
1200   @param atime: a custom access time to be set on the file
1201   @type mtime: int
1202   @param mtime: a custom modification time to be set on the file
1203   @type close: boolean
1204   @param close: whether to close file after writing it
1205   @type prewrite: callable
1206   @param prewrite: function to be called before writing content
1207   @type postwrite: callable
1208   @param postwrite: function to be called after writing content
1209
1210   @rtype: None or int
1211   @return: None if the 'close' parameter evaluates to True,
1212       otherwise the file descriptor
1213
1214   @raise errors.ProgrammerError: if an of the arguments are not valid
1215
1216   """
1217   if not os.path.isabs(file_name):
1218     raise errors.ProgrammerError("Path passed to WriteFile is not"
1219                                  " absolute: '%s'" % file_name)
1220
1221   if [fn, data].count(None) != 1:
1222     raise errors.ProgrammerError("fn or data required")
1223
1224   if [atime, mtime].count(None) == 1:
1225     raise errors.ProgrammerError("Both atime and mtime must be either"
1226                                  " set or None")
1227
1228   if backup and not dry_run and os.path.isfile(file_name):
1229     CreateBackup(file_name)
1230
1231   dir_name, base_name = os.path.split(file_name)
1232   fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
1233   # here we need to make sure we remove the temp file, if any error
1234   # leaves it in place
1235   try:
1236     if uid != -1 or gid != -1:
1237       os.chown(new_name, uid, gid)
1238     if mode:
1239       os.chmod(new_name, mode)
1240     if callable(prewrite):
1241       prewrite(fd)
1242     if data is not None:
1243       os.write(fd, data)
1244     else:
1245       fn(fd)
1246     if callable(postwrite):
1247       postwrite(fd)
1248     os.fsync(fd)
1249     if atime is not None and mtime is not None:
1250       os.utime(new_name, (atime, mtime))
1251     if not dry_run:
1252       os.rename(new_name, file_name)
1253   finally:
1254     if close:
1255       os.close(fd)
1256       result = None
1257     else:
1258       result = fd
1259     RemoveFile(new_name)
1260
1261   return result
1262
1263
1264 def FirstFree(seq, base=0):
1265   """Returns the first non-existing integer from seq.
1266
1267   The seq argument should be a sorted list of positive integers. The
1268   first time the index of an element is smaller than the element
1269   value, the index will be returned.
1270
1271   The base argument is used to start at a different offset,
1272   i.e. C{[3, 4, 6]} with I{offset=3} will return 5.
1273
1274   Example: C{[0, 1, 3]} will return I{2}.
1275
1276   @type seq: sequence
1277   @param seq: the sequence to be analyzed.
1278   @type base: int
1279   @param base: use this value as the base index of the sequence
1280   @rtype: int
1281   @return: the first non-used index in the sequence
1282
1283   """
1284   for idx, elem in enumerate(seq):
1285     assert elem >= base, "Passed element is higher than base offset"
1286     if elem > idx + base:
1287       # idx is not used
1288       return idx + base
1289   return None
1290
1291
1292 def all(seq, pred=bool):
1293   "Returns True if pred(x) is True for every element in the iterable"
1294   for elem in itertools.ifilterfalse(pred, seq):
1295     return False
1296   return True
1297
1298
1299 def any(seq, pred=bool):
1300   "Returns True if pred(x) is True for at least one element in the iterable"
1301   for elem in itertools.ifilter(pred, seq):
1302     return True
1303   return False
1304
1305
1306 def UniqueSequence(seq):
1307   """Returns a list with unique elements.
1308
1309   Element order is preserved.
1310
1311   @type seq: sequence
1312   @param seq: the sequence with the source elementes
1313   @rtype: list
1314   @return: list of unique elements from seq
1315
1316   """
1317   seen = set()
1318   return [i for i in seq if i not in seen and not seen.add(i)]
1319
1320
1321 def IsValidMac(mac):
1322   """Predicate to check if a MAC address is valid.
1323
1324   Checks wether the supplied MAC address is formally correct, only
1325   accepts colon separated format.
1326
1327   @type mac: str
1328   @param mac: the MAC to be validated
1329   @rtype: boolean
1330   @return: True is the MAC seems valid
1331
1332   """
1333   mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$")
1334   return mac_check.match(mac) is not None
1335
1336
1337 def TestDelay(duration):
1338   """Sleep for a fixed amount of time.
1339
1340   @type duration: float
1341   @param duration: the sleep duration
1342   @rtype: boolean
1343   @return: False for negative value, True otherwise
1344
1345   """
1346   if duration < 0:
1347     return False
1348   time.sleep(duration)
1349   return True
1350
1351
1352 def _CloseFDNoErr(fd, retries=5):
1353   """Close a file descriptor ignoring errors.
1354
1355   @type fd: int
1356   @param fd: the file descriptor
1357   @type retries: int
1358   @param retries: how many retries to make, in case we get any
1359       other error than EBADF
1360
1361   """
1362   try:
1363     os.close(fd)
1364   except OSError, err:
1365     if err.errno != errno.EBADF:
1366       if retries > 0:
1367         _CloseFDNoErr(fd, retries - 1)
1368     # else either it's closed already or we're out of retries, so we
1369     # ignore this and go on
1370
1371
1372 def CloseFDs(noclose_fds=None):
1373   """Close file descriptors.
1374
1375   This closes all file descriptors above 2 (i.e. except
1376   stdin/out/err).
1377
1378   @type noclose_fds: list or None
1379   @param noclose_fds: if given, it denotes a list of file descriptor
1380       that should not be closed
1381
1382   """
1383   # Default maximum for the number of available file descriptors.
1384   if 'SC_OPEN_MAX' in os.sysconf_names:
1385     try:
1386       MAXFD = os.sysconf('SC_OPEN_MAX')
1387       if MAXFD < 0:
1388         MAXFD = 1024
1389     except OSError:
1390       MAXFD = 1024
1391   else:
1392     MAXFD = 1024
1393   maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1394   if (maxfd == resource.RLIM_INFINITY):
1395     maxfd = MAXFD
1396
1397   # Iterate through and close all file descriptors (except the standard ones)
1398   for fd in range(3, maxfd):
1399     if noclose_fds and fd in noclose_fds:
1400       continue
1401     _CloseFDNoErr(fd)
1402
1403
1404 def Daemonize(logfile):
1405   """Daemonize the current process.
1406
1407   This detaches the current process from the controlling terminal and
1408   runs it in the background as a daemon.
1409
1410   @type logfile: str
1411   @param logfile: the logfile to which we should redirect stdout/stderr
1412   @rtype: int
1413   @returns: the value zero
1414
1415   """
1416   UMASK = 077
1417   WORKDIR = "/"
1418
1419   # this might fail
1420   pid = os.fork()
1421   if (pid == 0):  # The first child.
1422     os.setsid()
1423     # this might fail
1424     pid = os.fork() # Fork a second child.
1425     if (pid == 0):  # The second child.
1426       os.chdir(WORKDIR)
1427       os.umask(UMASK)
1428     else:
1429       # exit() or _exit()?  See below.
1430       os._exit(0) # Exit parent (the first child) of the second child.
1431   else:
1432     os._exit(0) # Exit parent of the first child.
1433
1434   for fd in range(3):
1435     _CloseFDNoErr(fd)
1436   i = os.open("/dev/null", os.O_RDONLY) # stdin
1437   assert i == 0, "Can't close/reopen stdin"
1438   i = os.open(logfile, os.O_WRONLY|os.O_CREAT|os.O_APPEND, 0600) # stdout
1439   assert i == 1, "Can't close/reopen stdout"
1440   # Duplicate standard output to standard error.
1441   os.dup2(1, 2)
1442   return 0
1443
1444
1445 def DaemonPidFileName(name):
1446   """Compute a ganeti pid file absolute path
1447
1448   @type name: str
1449   @param name: the daemon name
1450   @rtype: str
1451   @return: the full path to the pidfile corresponding to the given
1452       daemon name
1453
1454   """
1455   return os.path.join(constants.RUN_GANETI_DIR, "%s.pid" % name)
1456
1457
1458 def WritePidFile(name):
1459   """Write the current process pidfile.
1460
1461   The file will be written to L{constants.RUN_GANETI_DIR}I{/name.pid}
1462
1463   @type name: str
1464   @param name: the daemon name to use
1465   @raise errors.GenericError: if the pid file already exists and
1466       points to a live process
1467
1468   """
1469   pid = os.getpid()
1470   pidfilename = DaemonPidFileName(name)
1471   if IsProcessAlive(ReadPidFile(pidfilename)):
1472     raise errors.GenericError("%s contains a live process" % pidfilename)
1473
1474   WriteFile(pidfilename, data="%d\n" % pid)
1475
1476
1477 def RemovePidFile(name):
1478   """Remove the current process pidfile.
1479
1480   Any errors are ignored.
1481
1482   @type name: str
1483   @param name: the daemon name used to derive the pidfile name
1484
1485   """
1486   pid = os.getpid()
1487   pidfilename = DaemonPidFileName(name)
1488   # TODO: we could check here that the file contains our pid
1489   try:
1490     RemoveFile(pidfilename)
1491   except:
1492     pass
1493
1494
1495 def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1496                 waitpid=False):
1497   """Kill a process given by its pid.
1498
1499   @type pid: int
1500   @param pid: The PID to terminate.
1501   @type signal_: int
1502   @param signal_: The signal to send, by default SIGTERM
1503   @type timeout: int
1504   @param timeout: The timeout after which, if the process is still alive,
1505                   a SIGKILL will be sent. If not positive, no such checking
1506                   will be done
1507   @type waitpid: boolean
1508   @param waitpid: If true, we should waitpid on this process after
1509       sending signals, since it's our own child and otherwise it
1510       would remain as zombie
1511
1512   """
1513   def _helper(pid, signal_, wait):
1514     """Simple helper to encapsulate the kill/waitpid sequence"""
1515     os.kill(pid, signal_)
1516     if wait:
1517       try:
1518         os.waitpid(pid, os.WNOHANG)
1519       except OSError:
1520         pass
1521
1522   if pid <= 0:
1523     # kill with pid=0 == suicide
1524     raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1525
1526   if not IsProcessAlive(pid):
1527     return
1528   _helper(pid, signal_, waitpid)
1529   if timeout <= 0:
1530     return
1531
1532   # Wait up to $timeout seconds
1533   end = time.time() + timeout
1534   wait = 0.01
1535   while time.time() < end and IsProcessAlive(pid):
1536     try:
1537       (result_pid, _) = os.waitpid(pid, os.WNOHANG)
1538       if result_pid > 0:
1539         break
1540     except OSError:
1541       pass
1542     time.sleep(wait)
1543     # Make wait time longer for next try
1544     if wait < 0.1:
1545       wait *= 1.5
1546
1547   if IsProcessAlive(pid):
1548     # Kill process if it's still alive
1549     _helper(pid, signal.SIGKILL, waitpid)
1550
1551
1552 def FindFile(name, search_path, test=os.path.exists):
1553   """Look for a filesystem object in a given path.
1554
1555   This is an abstract method to search for filesystem object (files,
1556   dirs) under a given search path.
1557
1558   @type name: str
1559   @param name: the name to look for
1560   @type search_path: str
1561   @param search_path: location to start at
1562   @type test: callable
1563   @param test: a function taking one argument that should return True
1564       if the a given object is valid; the default value is
1565       os.path.exists, causing only existing files to be returned
1566   @rtype: str or None
1567   @return: full path to the object if found, None otherwise
1568
1569   """
1570   for dir_name in search_path:
1571     item_name = os.path.sep.join([dir_name, name])
1572     if test(item_name):
1573       return item_name
1574   return None
1575
1576
1577 def CheckVolumeGroupSize(vglist, vgname, minsize):
1578   """Checks if the volume group list is valid.
1579
1580   The function will check if a given volume group is in the list of
1581   volume groups and has a minimum size.
1582
1583   @type vglist: dict
1584   @param vglist: dictionary of volume group names and their size
1585   @type vgname: str
1586   @param vgname: the volume group we should check
1587   @type minsize: int
1588   @param minsize: the minimum size we accept
1589   @rtype: None or str
1590   @return: None for success, otherwise the error message
1591
1592   """
1593   vgsize = vglist.get(vgname, None)
1594   if vgsize is None:
1595     return "volume group '%s' missing" % vgname
1596   elif vgsize < minsize:
1597     return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1598             (vgname, minsize, vgsize))
1599   return None
1600
1601
1602 def SplitTime(value):
1603   """Splits time as floating point number into a tuple.
1604
1605   @param value: Time in seconds
1606   @type value: int or float
1607   @return: Tuple containing (seconds, microseconds)
1608
1609   """
1610   (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1611
1612   assert 0 <= seconds, \
1613     "Seconds must be larger than or equal to 0, but are %s" % seconds
1614   assert 0 <= microseconds <= 999999, \
1615     "Microseconds must be 0-999999, but are %s" % microseconds
1616
1617   return (int(seconds), int(microseconds))
1618
1619
1620 def MergeTime(timetuple):
1621   """Merges a tuple into time as a floating point number.
1622
1623   @param timetuple: Time as tuple, (seconds, microseconds)
1624   @type timetuple: tuple
1625   @return: Time as a floating point number expressed in seconds
1626
1627   """
1628   (seconds, microseconds) = timetuple
1629
1630   assert 0 <= seconds, \
1631     "Seconds must be larger than or equal to 0, but are %s" % seconds
1632   assert 0 <= microseconds <= 999999, \
1633     "Microseconds must be 0-999999, but are %s" % microseconds
1634
1635   return float(seconds) + (float(microseconds) * 0.000001)
1636
1637
1638 def GetNodeDaemonPort():
1639   """Get the node daemon port for this cluster.
1640
1641   Note that this routine does not read a ganeti-specific file, but
1642   instead uses C{socket.getservbyname} to allow pre-customization of
1643   this parameter outside of Ganeti.
1644
1645   @rtype: int
1646
1647   """
1648   try:
1649     port = socket.getservbyname("ganeti-noded", "tcp")
1650   except socket.error:
1651     port = constants.DEFAULT_NODED_PORT
1652
1653   return port
1654
1655
1656 def SetupLogging(logfile, debug=False, stderr_logging=False, program="",
1657                  multithreaded=False):
1658   """Configures the logging module.
1659
1660   @type logfile: str
1661   @param logfile: the filename to which we should log
1662   @type debug: boolean
1663   @param debug: whether to enable debug messages too or
1664       only those at C{INFO} and above level
1665   @type stderr_logging: boolean
1666   @param stderr_logging: whether we should also log to the standard error
1667   @type program: str
1668   @param program: the name under which we should log messages
1669   @type multithreaded: boolean
1670   @param multithreaded: if True, will add the thread name to the log file
1671   @raise EnvironmentError: if we can't open the log file and
1672       stderr logging is disabled
1673
1674   """
1675   fmt = "%(asctime)s: " + program + " pid=%(process)d"
1676   if multithreaded:
1677     fmt += "/%(threadName)s"
1678   if debug:
1679     fmt += " %(module)s:%(lineno)s"
1680   fmt += " %(levelname)s %(message)s"
1681   formatter = logging.Formatter(fmt)
1682
1683   root_logger = logging.getLogger("")
1684   root_logger.setLevel(logging.NOTSET)
1685
1686   # Remove all previously setup handlers
1687   for handler in root_logger.handlers:
1688     handler.close()
1689     root_logger.removeHandler(handler)
1690
1691   if stderr_logging:
1692     stderr_handler = logging.StreamHandler()
1693     stderr_handler.setFormatter(formatter)
1694     if debug:
1695       stderr_handler.setLevel(logging.NOTSET)
1696     else:
1697       stderr_handler.setLevel(logging.CRITICAL)
1698     root_logger.addHandler(stderr_handler)
1699
1700   # this can fail, if the logging directories are not setup or we have
1701   # a permisssion problem; in this case, it's best to log but ignore
1702   # the error if stderr_logging is True, and if false we re-raise the
1703   # exception since otherwise we could run but without any logs at all
1704   try:
1705     logfile_handler = logging.FileHandler(logfile)
1706     logfile_handler.setFormatter(formatter)
1707     if debug:
1708       logfile_handler.setLevel(logging.DEBUG)
1709     else:
1710       logfile_handler.setLevel(logging.INFO)
1711     root_logger.addHandler(logfile_handler)
1712   except EnvironmentError:
1713     if stderr_logging:
1714       logging.exception("Failed to enable logging to file '%s'", logfile)
1715     else:
1716       # we need to re-raise the exception
1717       raise
1718
1719
1720 def TailFile(fname, lines=20):
1721   """Return the last lines from a file.
1722
1723   @note: this function will only read and parse the last 4KB of
1724       the file; if the lines are very long, it could be that less
1725       than the requested number of lines are returned
1726
1727   @param fname: the file name
1728   @type lines: int
1729   @param lines: the (maximum) number of lines to return
1730
1731   """
1732   fd = open(fname, "r")
1733   try:
1734     fd.seek(0, 2)
1735     pos = fd.tell()
1736     pos = max(0, pos-4096)
1737     fd.seek(pos, 0)
1738     raw_data = fd.read()
1739   finally:
1740     fd.close()
1741
1742   rows = raw_data.splitlines()
1743   return rows[-lines:]
1744
1745
1746 def LockedMethod(fn):
1747   """Synchronized object access decorator.
1748
1749   This decorator is intended to protect access to an object using the
1750   object's own lock which is hardcoded to '_lock'.
1751
1752   """
1753   def _LockDebug(*args, **kwargs):
1754     if debug_locks:
1755       logging.debug(*args, **kwargs)
1756
1757   def wrapper(self, *args, **kwargs):
1758     assert hasattr(self, '_lock')
1759     lock = self._lock
1760     _LockDebug("Waiting for %s", lock)
1761     lock.acquire()
1762     try:
1763       _LockDebug("Acquired %s", lock)
1764       result = fn(self, *args, **kwargs)
1765     finally:
1766       _LockDebug("Releasing %s", lock)
1767       lock.release()
1768       _LockDebug("Released %s", lock)
1769     return result
1770   return wrapper
1771
1772
1773 def LockFile(fd):
1774   """Locks a file using POSIX locks.
1775
1776   @type fd: int
1777   @param fd: the file descriptor we need to lock
1778
1779   """
1780   try:
1781     fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
1782   except IOError, err:
1783     if err.errno == errno.EAGAIN:
1784       raise errors.LockError("File already locked")
1785     raise
1786
1787
1788 class FileLock(object):
1789   """Utility class for file locks.
1790
1791   """
1792   def __init__(self, filename):
1793     """Constructor for FileLock.
1794
1795     This will open the file denoted by the I{filename} argument.
1796
1797     @type filename: str
1798     @param filename: path to the file to be locked
1799
1800     """
1801     self.filename = filename
1802     self.fd = open(self.filename, "w")
1803
1804   def __del__(self):
1805     self.Close()
1806
1807   def Close(self):
1808     """Close the file and release the lock.
1809
1810     """
1811     if self.fd:
1812       self.fd.close()
1813       self.fd = None
1814
1815   def _flock(self, flag, blocking, timeout, errmsg):
1816     """Wrapper for fcntl.flock.
1817
1818     @type flag: int
1819     @param flag: operation flag
1820     @type blocking: bool
1821     @param blocking: whether the operation should be done in blocking mode.
1822     @type timeout: None or float
1823     @param timeout: for how long the operation should be retried (implies
1824                     non-blocking mode).
1825     @type errmsg: string
1826     @param errmsg: error message in case operation fails.
1827
1828     """
1829     assert self.fd, "Lock was closed"
1830     assert timeout is None or timeout >= 0, \
1831       "If specified, timeout must be positive"
1832
1833     if timeout is not None:
1834       flag |= fcntl.LOCK_NB
1835       timeout_end = time.time() + timeout
1836
1837     # Blocking doesn't have effect with timeout
1838     elif not blocking:
1839       flag |= fcntl.LOCK_NB
1840       timeout_end = None
1841
1842     retry = True
1843     while retry:
1844       try:
1845         fcntl.flock(self.fd, flag)
1846         retry = False
1847       except IOError, err:
1848         if err.errno in (errno.EAGAIN, ):
1849           if timeout_end is not None and time.time() < timeout_end:
1850             # Wait before trying again
1851             time.sleep(max(0.1, min(1.0, timeout)))
1852           else:
1853             raise errors.LockError(errmsg)
1854         else:
1855           logging.exception("fcntl.flock failed")
1856           raise
1857
1858   def Exclusive(self, blocking=False, timeout=None):
1859     """Locks the file in exclusive mode.
1860
1861     @type blocking: boolean
1862     @param blocking: whether to block and wait until we
1863         can lock the file or return immediately
1864     @type timeout: int or None
1865     @param timeout: if not None, the duration to wait for the lock
1866         (in blocking mode)
1867
1868     """
1869     self._flock(fcntl.LOCK_EX, blocking, timeout,
1870                 "Failed to lock %s in exclusive mode" % self.filename)
1871
1872   def Shared(self, blocking=False, timeout=None):
1873     """Locks the file in shared mode.
1874
1875     @type blocking: boolean
1876     @param blocking: whether to block and wait until we
1877         can lock the file or return immediately
1878     @type timeout: int or None
1879     @param timeout: if not None, the duration to wait for the lock
1880         (in blocking mode)
1881
1882     """
1883     self._flock(fcntl.LOCK_SH, blocking, timeout,
1884                 "Failed to lock %s in shared mode" % self.filename)
1885
1886   def Unlock(self, blocking=True, timeout=None):
1887     """Unlocks the file.
1888
1889     According to C{flock(2)}, unlocking can also be a nonblocking
1890     operation::
1891
1892       To make a non-blocking request, include LOCK_NB with any of the above
1893       operations.
1894
1895     @type blocking: boolean
1896     @param blocking: whether to block and wait until we
1897         can lock the file or return immediately
1898     @type timeout: int or None
1899     @param timeout: if not None, the duration to wait for the lock
1900         (in blocking mode)
1901
1902     """
1903     self._flock(fcntl.LOCK_UN, blocking, timeout,
1904                 "Failed to unlock %s" % self.filename)
1905
1906
1907 class SignalHandler(object):
1908   """Generic signal handler class.
1909
1910   It automatically restores the original handler when deconstructed or
1911   when L{Reset} is called. You can either pass your own handler
1912   function in or query the L{called} attribute to detect whether the
1913   signal was sent.
1914
1915   @type signum: list
1916   @ivar signum: the signals we handle
1917   @type called: boolean
1918   @ivar called: tracks whether any of the signals have been raised
1919
1920   """
1921   def __init__(self, signum):
1922     """Constructs a new SignalHandler instance.
1923
1924     @type signum: int or list of ints
1925     @param signum: Single signal number or set of signal numbers
1926
1927     """
1928     if isinstance(signum, (int, long)):
1929       self.signum = set([signum])
1930     else:
1931       self.signum = set(signum)
1932
1933     self.called = False
1934
1935     self._previous = {}
1936     try:
1937       for signum in self.signum:
1938         # Setup handler
1939         prev_handler = signal.signal(signum, self._HandleSignal)
1940         try:
1941           self._previous[signum] = prev_handler
1942         except:
1943           # Restore previous handler
1944           signal.signal(signum, prev_handler)
1945           raise
1946     except:
1947       # Reset all handlers
1948       self.Reset()
1949       # Here we have a race condition: a handler may have already been called,
1950       # but there's not much we can do about it at this point.
1951       raise
1952
1953   def __del__(self):
1954     self.Reset()
1955
1956   def Reset(self):
1957     """Restore previous handler.
1958
1959     This will reset all the signals to their previous handlers.
1960
1961     """
1962     for signum, prev_handler in self._previous.items():
1963       signal.signal(signum, prev_handler)
1964       # If successful, remove from dict
1965       del self._previous[signum]
1966
1967   def Clear(self):
1968     """Unsets the L{called} flag.
1969
1970     This function can be used in case a signal may arrive several times.
1971
1972     """
1973     self.called = False
1974
1975   def _HandleSignal(self, signum, frame):
1976     """Actual signal handling function.
1977
1978     """
1979     # This is not nice and not absolutely atomic, but it appears to be the only
1980     # solution in Python -- there are no atomic types.
1981     self.called = True
1982
1983
1984 class FieldSet(object):
1985   """A simple field set.
1986
1987   Among the features are:
1988     - checking if a string is among a list of static string or regex objects
1989     - checking if a whole list of string matches
1990     - returning the matching groups from a regex match
1991
1992   Internally, all fields are held as regular expression objects.
1993
1994   """
1995   def __init__(self, *items):
1996     self.items = [re.compile("^%s$" % value) for value in items]
1997
1998   def Extend(self, other_set):
1999     """Extend the field set with the items from another one"""
2000     self.items.extend(other_set.items)
2001
2002   def Matches(self, field):
2003     """Checks if a field matches the current set
2004
2005     @type field: str
2006     @param field: the string to match
2007     @return: either False or a regular expression match object
2008
2009     """
2010     for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
2011       return m
2012     return False
2013
2014   def NonMatching(self, items):
2015     """Returns the list of fields not matching the current set
2016
2017     @type items: list
2018     @param items: the list of fields to check
2019     @rtype: list
2020     @return: list of non-matching fields
2021
2022     """
2023     return [val for val in items if not self.Matches(val)]