Add --uid-pool option to gnt-cluster init
[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 os
31 import time
32 import subprocess
33 import re
34 import socket
35 import tempfile
36 import shutil
37 import errno
38 import pwd
39 import itertools
40 import select
41 import fcntl
42 import resource
43 import logging
44 import logging.handlers
45 import signal
46 import datetime
47 import calendar
48 import collections
49
50 from cStringIO import StringIO
51
52 try:
53   from hashlib import sha1
54 except ImportError:
55   import sha
56   sha1 = sha.new
57
58 from ganeti import errors
59 from ganeti import constants
60
61
62 _locksheld = []
63 _re_shell_unquoted = re.compile('^[-.,=:/_+@A-Za-z0-9]+$')
64
65 debug_locks = False
66
67 #: when set to True, L{RunCmd} is disabled
68 no_fork = False
69
70 _RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
71
72
73 class RunResult(object):
74   """Holds the result of running external programs.
75
76   @type exit_code: int
77   @ivar exit_code: the exit code of the program, or None (if the program
78       didn't exit())
79   @type signal: int or None
80   @ivar signal: the signal that caused the program to finish, or None
81       (if the program wasn't terminated by a signal)
82   @type stdout: str
83   @ivar stdout: the standard output of the program
84   @type stderr: str
85   @ivar stderr: the standard error of the program
86   @type failed: boolean
87   @ivar failed: True in case the program was
88       terminated by a signal or exited with a non-zero exit code
89   @ivar fail_reason: a string detailing the termination reason
90
91   """
92   __slots__ = ["exit_code", "signal", "stdout", "stderr",
93                "failed", "fail_reason", "cmd"]
94
95
96   def __init__(self, exit_code, signal_, stdout, stderr, cmd):
97     self.cmd = cmd
98     self.exit_code = exit_code
99     self.signal = signal_
100     self.stdout = stdout
101     self.stderr = stderr
102     self.failed = (signal_ is not None or exit_code != 0)
103
104     if self.signal is not None:
105       self.fail_reason = "terminated by signal %s" % self.signal
106     elif self.exit_code is not None:
107       self.fail_reason = "exited with exit code %s" % self.exit_code
108     else:
109       self.fail_reason = "unable to determine termination reason"
110
111     if self.failed:
112       logging.debug("Command '%s' failed (%s); output: %s",
113                     self.cmd, self.fail_reason, self.output)
114
115   def _GetOutput(self):
116     """Returns the combined stdout and stderr for easier usage.
117
118     """
119     return self.stdout + self.stderr
120
121   output = property(_GetOutput, None, None, "Return full output")
122
123
124 def RunCmd(cmd, env=None, output=None, cwd='/', reset_env=False):
125   """Execute a (shell) command.
126
127   The command should not read from its standard input, as it will be
128   closed.
129
130   @type cmd: string or list
131   @param cmd: Command to run
132   @type env: dict
133   @param env: Additional environment
134   @type output: str
135   @param output: if desired, the output of the command can be
136       saved in a file instead of the RunResult instance; this
137       parameter denotes the file name (if not None)
138   @type cwd: string
139   @param cwd: if specified, will be used as the working
140       directory for the command; the default will be /
141   @type reset_env: boolean
142   @param reset_env: whether to reset or keep the default os environment
143   @rtype: L{RunResult}
144   @return: RunResult instance
145   @raise errors.ProgrammerError: if we call this when forks are disabled
146
147   """
148   if no_fork:
149     raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
150
151   if isinstance(cmd, list):
152     cmd = [str(val) for val in cmd]
153     strcmd = " ".join(cmd)
154     shell = False
155   else:
156     strcmd = cmd
157     shell = True
158   logging.debug("RunCmd '%s'", strcmd)
159
160   if not reset_env:
161     cmd_env = os.environ.copy()
162     cmd_env["LC_ALL"] = "C"
163   else:
164     cmd_env = {}
165
166   if env is not None:
167     cmd_env.update(env)
168
169   try:
170     if output is None:
171       out, err, status = _RunCmdPipe(cmd, cmd_env, shell, cwd)
172     else:
173       status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
174       out = err = ""
175   except OSError, err:
176     if err.errno == errno.ENOENT:
177       raise errors.OpExecError("Can't execute '%s': not found (%s)" %
178                                (strcmd, err))
179     else:
180       raise
181
182   if status >= 0:
183     exitcode = status
184     signal_ = None
185   else:
186     exitcode = None
187     signal_ = -status
188
189   return RunResult(exitcode, signal_, out, err, strcmd)
190
191
192 def _RunCmdPipe(cmd, env, via_shell, cwd):
193   """Run a command and return its output.
194
195   @type  cmd: string or list
196   @param cmd: Command to run
197   @type env: dict
198   @param env: The environment to use
199   @type via_shell: bool
200   @param via_shell: if we should run via the shell
201   @type cwd: string
202   @param cwd: the working directory for the program
203   @rtype: tuple
204   @return: (out, err, status)
205
206   """
207   poller = select.poll()
208   child = subprocess.Popen(cmd, shell=via_shell,
209                            stderr=subprocess.PIPE,
210                            stdout=subprocess.PIPE,
211                            stdin=subprocess.PIPE,
212                            close_fds=True, env=env,
213                            cwd=cwd)
214
215   child.stdin.close()
216   poller.register(child.stdout, select.POLLIN)
217   poller.register(child.stderr, select.POLLIN)
218   out = StringIO()
219   err = StringIO()
220   fdmap = {
221     child.stdout.fileno(): (out, child.stdout),
222     child.stderr.fileno(): (err, child.stderr),
223     }
224   for fd in fdmap:
225     status = fcntl.fcntl(fd, fcntl.F_GETFL)
226     fcntl.fcntl(fd, fcntl.F_SETFL, status | os.O_NONBLOCK)
227
228   while fdmap:
229     try:
230       pollresult = poller.poll()
231     except EnvironmentError, eerr:
232       if eerr.errno == errno.EINTR:
233         continue
234       raise
235     except select.error, serr:
236       if serr[0] == errno.EINTR:
237         continue
238       raise
239
240     for fd, event in pollresult:
241       if event & select.POLLIN or event & select.POLLPRI:
242         data = fdmap[fd][1].read()
243         # no data from read signifies EOF (the same as POLLHUP)
244         if not data:
245           poller.unregister(fd)
246           del fdmap[fd]
247           continue
248         fdmap[fd][0].write(data)
249       if (event & select.POLLNVAL or event & select.POLLHUP or
250           event & select.POLLERR):
251         poller.unregister(fd)
252         del fdmap[fd]
253
254   out = out.getvalue()
255   err = err.getvalue()
256
257   status = child.wait()
258   return out, err, status
259
260
261 def _RunCmdFile(cmd, env, via_shell, output, cwd):
262   """Run a command and save its output to a file.
263
264   @type  cmd: string or list
265   @param cmd: Command to run
266   @type env: dict
267   @param env: The environment to use
268   @type via_shell: bool
269   @param via_shell: if we should run via the shell
270   @type output: str
271   @param output: the filename in which to save the output
272   @type cwd: string
273   @param cwd: the working directory for the program
274   @rtype: int
275   @return: the exit status
276
277   """
278   fh = open(output, "a")
279   try:
280     child = subprocess.Popen(cmd, shell=via_shell,
281                              stderr=subprocess.STDOUT,
282                              stdout=fh,
283                              stdin=subprocess.PIPE,
284                              close_fds=True, env=env,
285                              cwd=cwd)
286
287     child.stdin.close()
288     status = child.wait()
289   finally:
290     fh.close()
291   return status
292
293
294 def RunParts(dir_name, env=None, reset_env=False):
295   """Run Scripts or programs in a directory
296
297   @type dir_name: string
298   @param dir_name: absolute path to a directory
299   @type env: dict
300   @param env: The environment to use
301   @type reset_env: boolean
302   @param reset_env: whether to reset or keep the default os environment
303   @rtype: list of tuples
304   @return: list of (name, (one of RUNDIR_STATUS), RunResult)
305
306   """
307   rr = []
308
309   try:
310     dir_contents = ListVisibleFiles(dir_name)
311   except OSError, err:
312     logging.warning("RunParts: skipping %s (cannot list: %s)", dir_name, err)
313     return rr
314
315   for relname in sorted(dir_contents):
316     fname = PathJoin(dir_name, relname)
317     if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
318             constants.EXT_PLUGIN_MASK.match(relname) is not None):
319       rr.append((relname, constants.RUNPARTS_SKIP, None))
320     else:
321       try:
322         result = RunCmd([fname], env=env, reset_env=reset_env)
323       except Exception, err: # pylint: disable-msg=W0703
324         rr.append((relname, constants.RUNPARTS_ERR, str(err)))
325       else:
326         rr.append((relname, constants.RUNPARTS_RUN, result))
327
328   return rr
329
330
331 def RemoveFile(filename):
332   """Remove a file ignoring some errors.
333
334   Remove a file, ignoring non-existing ones or directories. Other
335   errors are passed.
336
337   @type filename: str
338   @param filename: the file to be removed
339
340   """
341   try:
342     os.unlink(filename)
343   except OSError, err:
344     if err.errno not in (errno.ENOENT, errno.EISDIR):
345       raise
346
347
348 def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
349   """Renames a file.
350
351   @type old: string
352   @param old: Original path
353   @type new: string
354   @param new: New path
355   @type mkdir: bool
356   @param mkdir: Whether to create target directory if it doesn't exist
357   @type mkdir_mode: int
358   @param mkdir_mode: Mode for newly created directories
359
360   """
361   try:
362     return os.rename(old, new)
363   except OSError, err:
364     # In at least one use case of this function, the job queue, directory
365     # creation is very rare. Checking for the directory before renaming is not
366     # as efficient.
367     if mkdir and err.errno == errno.ENOENT:
368       # Create directory and try again
369       Makedirs(os.path.dirname(new), mode=mkdir_mode)
370
371       return os.rename(old, new)
372
373     raise
374
375
376 def Makedirs(path, mode=0750):
377   """Super-mkdir; create a leaf directory and all intermediate ones.
378
379   This is a wrapper around C{os.makedirs} adding error handling not implemented
380   before Python 2.5.
381
382   """
383   try:
384     os.makedirs(path, mode)
385   except OSError, err:
386     # Ignore EEXIST. This is only handled in os.makedirs as included in
387     # Python 2.5 and above.
388     if err.errno != errno.EEXIST or not os.path.exists(path):
389       raise
390
391
392 def ResetTempfileModule():
393   """Resets the random name generator of the tempfile module.
394
395   This function should be called after C{os.fork} in the child process to
396   ensure it creates a newly seeded random generator. Otherwise it would
397   generate the same random parts as the parent process. If several processes
398   race for the creation of a temporary file, this could lead to one not getting
399   a temporary name.
400
401   """
402   # pylint: disable-msg=W0212
403   if hasattr(tempfile, "_once_lock") and hasattr(tempfile, "_name_sequence"):
404     tempfile._once_lock.acquire()
405     try:
406       # Reset random name generator
407       tempfile._name_sequence = None
408     finally:
409       tempfile._once_lock.release()
410   else:
411     logging.critical("The tempfile module misses at least one of the"
412                      " '_once_lock' and '_name_sequence' attributes")
413
414
415 def _FingerprintFile(filename):
416   """Compute the fingerprint of a file.
417
418   If the file does not exist, a None will be returned
419   instead.
420
421   @type filename: str
422   @param filename: the filename to checksum
423   @rtype: str
424   @return: the hex digest of the sha checksum of the contents
425       of the file
426
427   """
428   if not (os.path.exists(filename) and os.path.isfile(filename)):
429     return None
430
431   f = open(filename)
432
433   fp = sha1()
434   while True:
435     data = f.read(4096)
436     if not data:
437       break
438
439     fp.update(data)
440
441   return fp.hexdigest()
442
443
444 def FingerprintFiles(files):
445   """Compute fingerprints for a list of files.
446
447   @type files: list
448   @param files: the list of filename to fingerprint
449   @rtype: dict
450   @return: a dictionary filename: fingerprint, holding only
451       existing files
452
453   """
454   ret = {}
455
456   for filename in files:
457     cksum = _FingerprintFile(filename)
458     if cksum:
459       ret[filename] = cksum
460
461   return ret
462
463
464 def ForceDictType(target, key_types, allowed_values=None):
465   """Force the values of a dict to have certain types.
466
467   @type target: dict
468   @param target: the dict to update
469   @type key_types: dict
470   @param key_types: dict mapping target dict keys to types
471                     in constants.ENFORCEABLE_TYPES
472   @type allowed_values: list
473   @keyword allowed_values: list of specially allowed values
474
475   """
476   if allowed_values is None:
477     allowed_values = []
478
479   if not isinstance(target, dict):
480     msg = "Expected dictionary, got '%s'" % target
481     raise errors.TypeEnforcementError(msg)
482
483   for key in target:
484     if key not in key_types:
485       msg = "Unknown key '%s'" % key
486       raise errors.TypeEnforcementError(msg)
487
488     if target[key] in allowed_values:
489       continue
490
491     ktype = key_types[key]
492     if ktype not in constants.ENFORCEABLE_TYPES:
493       msg = "'%s' has non-enforceable type %s" % (key, ktype)
494       raise errors.ProgrammerError(msg)
495
496     if ktype == constants.VTYPE_STRING:
497       if not isinstance(target[key], basestring):
498         if isinstance(target[key], bool) and not target[key]:
499           target[key] = ''
500         else:
501           msg = "'%s' (value %s) is not a valid string" % (key, target[key])
502           raise errors.TypeEnforcementError(msg)
503     elif ktype == constants.VTYPE_BOOL:
504       if isinstance(target[key], basestring) and target[key]:
505         if target[key].lower() == constants.VALUE_FALSE:
506           target[key] = False
507         elif target[key].lower() == constants.VALUE_TRUE:
508           target[key] = True
509         else:
510           msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
511           raise errors.TypeEnforcementError(msg)
512       elif target[key]:
513         target[key] = True
514       else:
515         target[key] = False
516     elif ktype == constants.VTYPE_SIZE:
517       try:
518         target[key] = ParseUnit(target[key])
519       except errors.UnitParseError, err:
520         msg = "'%s' (value %s) is not a valid size. error: %s" % \
521               (key, target[key], err)
522         raise errors.TypeEnforcementError(msg)
523     elif ktype == constants.VTYPE_INT:
524       try:
525         target[key] = int(target[key])
526       except (ValueError, TypeError):
527         msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
528         raise errors.TypeEnforcementError(msg)
529
530
531 def IsProcessAlive(pid):
532   """Check if a given pid exists on the system.
533
534   @note: zombie status is not handled, so zombie processes
535       will be returned as alive
536   @type pid: int
537   @param pid: the process ID to check
538   @rtype: boolean
539   @return: True if the process exists
540
541   """
542   if pid <= 0:
543     return False
544
545   try:
546     os.stat("/proc/%d/status" % pid)
547     return True
548   except EnvironmentError, err:
549     if err.errno in (errno.ENOENT, errno.ENOTDIR):
550       return False
551     raise
552
553
554 def ReadPidFile(pidfile):
555   """Read a pid from a file.
556
557   @type  pidfile: string
558   @param pidfile: path to the file containing the pid
559   @rtype: int
560   @return: The process id, if the file exists and contains a valid PID,
561            otherwise 0
562
563   """
564   try:
565     raw_data = ReadFile(pidfile)
566   except EnvironmentError, err:
567     if err.errno != errno.ENOENT:
568       logging.exception("Can't read pid file")
569     return 0
570
571   try:
572     pid = int(raw_data)
573   except (TypeError, ValueError), err:
574     logging.info("Can't parse pid file contents", exc_info=True)
575     return 0
576
577   return pid
578
579
580 def MatchNameComponent(key, name_list, case_sensitive=True):
581   """Try to match a name against a list.
582
583   This function will try to match a name like test1 against a list
584   like C{['test1.example.com', 'test2.example.com', ...]}. Against
585   this list, I{'test1'} as well as I{'test1.example'} will match, but
586   not I{'test1.ex'}. A multiple match will be considered as no match
587   at all (e.g. I{'test1'} against C{['test1.example.com',
588   'test1.example.org']}), except when the key fully matches an entry
589   (e.g. I{'test1'} against C{['test1', 'test1.example.com']}).
590
591   @type key: str
592   @param key: the name to be searched
593   @type name_list: list
594   @param name_list: the list of strings against which to search the key
595   @type case_sensitive: boolean
596   @param case_sensitive: whether to provide a case-sensitive match
597
598   @rtype: None or str
599   @return: None if there is no match I{or} if there are multiple matches,
600       otherwise the element from the list which matches
601
602   """
603   if key in name_list:
604     return key
605
606   re_flags = 0
607   if not case_sensitive:
608     re_flags |= re.IGNORECASE
609     key = key.upper()
610   mo = re.compile("^%s(\..*)?$" % re.escape(key), re_flags)
611   names_filtered = []
612   string_matches = []
613   for name in name_list:
614     if mo.match(name) is not None:
615       names_filtered.append(name)
616       if not case_sensitive and key == name.upper():
617         string_matches.append(name)
618
619   if len(string_matches) == 1:
620     return string_matches[0]
621   if len(names_filtered) == 1:
622     return names_filtered[0]
623   return None
624
625
626 class HostInfo:
627   """Class implementing resolver and hostname functionality
628
629   """
630   _VALID_NAME_RE = re.compile("^[a-z0-9._-]{1,255}$")
631
632   def __init__(self, name=None):
633     """Initialize the host name object.
634
635     If the name argument is not passed, it will use this system's
636     name.
637
638     """
639     if name is None:
640       name = self.SysName()
641
642     self.query = name
643     self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
644     self.ip = self.ipaddrs[0]
645
646   def ShortName(self):
647     """Returns the hostname without domain.
648
649     """
650     return self.name.split('.')[0]
651
652   @staticmethod
653   def SysName():
654     """Return the current system's name.
655
656     This is simply a wrapper over C{socket.gethostname()}.
657
658     """
659     return socket.gethostname()
660
661   @staticmethod
662   def LookupHostname(hostname):
663     """Look up hostname
664
665     @type hostname: str
666     @param hostname: hostname to look up
667
668     @rtype: tuple
669     @return: a tuple (name, aliases, ipaddrs) as returned by
670         C{socket.gethostbyname_ex}
671     @raise errors.ResolverError: in case of errors in resolving
672
673     """
674     try:
675       result = socket.gethostbyname_ex(hostname)
676     except socket.gaierror, err:
677       # hostname not found in DNS
678       raise errors.ResolverError(hostname, err.args[0], err.args[1])
679
680     return result
681
682   @classmethod
683   def NormalizeName(cls, hostname):
684     """Validate and normalize the given hostname.
685
686     @attention: the validation is a bit more relaxed than the standards
687         require; most importantly, we allow underscores in names
688     @raise errors.OpPrereqError: when the name is not valid
689
690     """
691     hostname = hostname.lower()
692     if (not cls._VALID_NAME_RE.match(hostname) or
693         # double-dots, meaning empty label
694         ".." in hostname or
695         # empty initial label
696         hostname.startswith(".")):
697       raise errors.OpPrereqError("Invalid hostname '%s'" % hostname,
698                                  errors.ECODE_INVAL)
699     if hostname.endswith("."):
700       hostname = hostname.rstrip(".")
701     return hostname
702
703
704 def GetHostInfo(name=None):
705   """Lookup host name and raise an OpPrereqError for failures"""
706
707   try:
708     return HostInfo(name)
709   except errors.ResolverError, err:
710     raise errors.OpPrereqError("The given name (%s) does not resolve: %s" %
711                                (err[0], err[2]), errors.ECODE_RESOLVER)
712
713
714 def ListVolumeGroups():
715   """List volume groups and their size
716
717   @rtype: dict
718   @return:
719        Dictionary with keys volume name and values
720        the size of the volume
721
722   """
723   command = "vgs --noheadings --units m --nosuffix -o name,size"
724   result = RunCmd(command)
725   retval = {}
726   if result.failed:
727     return retval
728
729   for line in result.stdout.splitlines():
730     try:
731       name, size = line.split()
732       size = int(float(size))
733     except (IndexError, ValueError), err:
734       logging.error("Invalid output from vgs (%s): %s", err, line)
735       continue
736
737     retval[name] = size
738
739   return retval
740
741
742 def BridgeExists(bridge):
743   """Check whether the given bridge exists in the system
744
745   @type bridge: str
746   @param bridge: the bridge name to check
747   @rtype: boolean
748   @return: True if it does
749
750   """
751   return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
752
753
754 def NiceSort(name_list):
755   """Sort a list of strings based on digit and non-digit groupings.
756
757   Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
758   will sort the list in the logical order C{['a1', 'a2', 'a10',
759   'a11']}.
760
761   The sort algorithm breaks each name in groups of either only-digits
762   or no-digits. Only the first eight such groups are considered, and
763   after that we just use what's left of the string.
764
765   @type name_list: list
766   @param name_list: the names to be sorted
767   @rtype: list
768   @return: a copy of the name list sorted with our algorithm
769
770   """
771   _SORTER_BASE = "(\D+|\d+)"
772   _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
773                                                   _SORTER_BASE, _SORTER_BASE,
774                                                   _SORTER_BASE, _SORTER_BASE,
775                                                   _SORTER_BASE, _SORTER_BASE)
776   _SORTER_RE = re.compile(_SORTER_FULL)
777   _SORTER_NODIGIT = re.compile("^\D*$")
778   def _TryInt(val):
779     """Attempts to convert a variable to integer."""
780     if val is None or _SORTER_NODIGIT.match(val):
781       return val
782     rval = int(val)
783     return rval
784
785   to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
786              for name in name_list]
787   to_sort.sort()
788   return [tup[1] for tup in to_sort]
789
790
791 def TryConvert(fn, val):
792   """Try to convert a value ignoring errors.
793
794   This function tries to apply function I{fn} to I{val}. If no
795   C{ValueError} or C{TypeError} exceptions are raised, it will return
796   the result, else it will return the original value. Any other
797   exceptions are propagated to the caller.
798
799   @type fn: callable
800   @param fn: function to apply to the value
801   @param val: the value to be converted
802   @return: The converted value if the conversion was successful,
803       otherwise the original value.
804
805   """
806   try:
807     nv = fn(val)
808   except (ValueError, TypeError):
809     nv = val
810   return nv
811
812
813 def IsValidIP(ip):
814   """Verifies the syntax of an IPv4 address.
815
816   This function checks if the IPv4 address passes is valid or not based
817   on syntax (not IP range, class calculations, etc.).
818
819   @type ip: str
820   @param ip: the address to be checked
821   @rtype: a regular expression match object
822   @return: a regular expression match object, or None if the
823       address is not valid
824
825   """
826   unit = "(0|[1-9]\d{0,2})"
827   #TODO: convert and return only boolean
828   return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
829
830
831 def IsValidShellParam(word):
832   """Verifies is the given word is safe from the shell's p.o.v.
833
834   This means that we can pass this to a command via the shell and be
835   sure that it doesn't alter the command line and is passed as such to
836   the actual command.
837
838   Note that we are overly restrictive here, in order to be on the safe
839   side.
840
841   @type word: str
842   @param word: the word to check
843   @rtype: boolean
844   @return: True if the word is 'safe'
845
846   """
847   return bool(re.match("^[-a-zA-Z0-9._+/:%@]+$", word))
848
849
850 def BuildShellCmd(template, *args):
851   """Build a safe shell command line from the given arguments.
852
853   This function will check all arguments in the args list so that they
854   are valid shell parameters (i.e. they don't contain shell
855   metacharacters). If everything is ok, it will return the result of
856   template % args.
857
858   @type template: str
859   @param template: the string holding the template for the
860       string formatting
861   @rtype: str
862   @return: the expanded command line
863
864   """
865   for word in args:
866     if not IsValidShellParam(word):
867       raise errors.ProgrammerError("Shell argument '%s' contains"
868                                    " invalid characters" % word)
869   return template % args
870
871
872 def FormatUnit(value, units):
873   """Formats an incoming number of MiB with the appropriate unit.
874
875   @type value: int
876   @param value: integer representing the value in MiB (1048576)
877   @type units: char
878   @param units: the type of formatting we should do:
879       - 'h' for automatic scaling
880       - 'm' for MiBs
881       - 'g' for GiBs
882       - 't' for TiBs
883   @rtype: str
884   @return: the formatted value (with suffix)
885
886   """
887   if units not in ('m', 'g', 't', 'h'):
888     raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
889
890   suffix = ''
891
892   if units == 'm' or (units == 'h' and value < 1024):
893     if units == 'h':
894       suffix = 'M'
895     return "%d%s" % (round(value, 0), suffix)
896
897   elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
898     if units == 'h':
899       suffix = 'G'
900     return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
901
902   else:
903     if units == 'h':
904       suffix = 'T'
905     return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
906
907
908 def ParseUnit(input_string):
909   """Tries to extract number and scale from the given string.
910
911   Input must be in the format C{NUMBER+ [DOT NUMBER+] SPACE*
912   [UNIT]}. If no unit is specified, it defaults to MiB. Return value
913   is always an int in MiB.
914
915   """
916   m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', str(input_string))
917   if not m:
918     raise errors.UnitParseError("Invalid format")
919
920   value = float(m.groups()[0])
921
922   unit = m.groups()[1]
923   if unit:
924     lcunit = unit.lower()
925   else:
926     lcunit = 'm'
927
928   if lcunit in ('m', 'mb', 'mib'):
929     # Value already in MiB
930     pass
931
932   elif lcunit in ('g', 'gb', 'gib'):
933     value *= 1024
934
935   elif lcunit in ('t', 'tb', 'tib'):
936     value *= 1024 * 1024
937
938   else:
939     raise errors.UnitParseError("Unknown unit: %s" % unit)
940
941   # Make sure we round up
942   if int(value) < value:
943     value += 1
944
945   # Round up to the next multiple of 4
946   value = int(value)
947   if value % 4:
948     value += 4 - value % 4
949
950   return value
951
952
953 def AddAuthorizedKey(file_name, key):
954   """Adds an SSH public key to an authorized_keys file.
955
956   @type file_name: str
957   @param file_name: path to authorized_keys file
958   @type key: str
959   @param key: string containing key
960
961   """
962   key_fields = key.split()
963
964   f = open(file_name, 'a+')
965   try:
966     nl = True
967     for line in f:
968       # Ignore whitespace changes
969       if line.split() == key_fields:
970         break
971       nl = line.endswith('\n')
972     else:
973       if not nl:
974         f.write("\n")
975       f.write(key.rstrip('\r\n'))
976       f.write("\n")
977       f.flush()
978   finally:
979     f.close()
980
981
982 def RemoveAuthorizedKey(file_name, key):
983   """Removes an SSH public key from an authorized_keys file.
984
985   @type file_name: str
986   @param file_name: path to authorized_keys file
987   @type key: str
988   @param key: string containing key
989
990   """
991   key_fields = key.split()
992
993   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
994   try:
995     out = os.fdopen(fd, 'w')
996     try:
997       f = open(file_name, 'r')
998       try:
999         for line in f:
1000           # Ignore whitespace changes while comparing lines
1001           if line.split() != key_fields:
1002             out.write(line)
1003
1004         out.flush()
1005         os.rename(tmpname, file_name)
1006       finally:
1007         f.close()
1008     finally:
1009       out.close()
1010   except:
1011     RemoveFile(tmpname)
1012     raise
1013
1014
1015 def SetEtcHostsEntry(file_name, ip, hostname, aliases):
1016   """Sets the name of an IP address and hostname in /etc/hosts.
1017
1018   @type file_name: str
1019   @param file_name: path to the file to modify (usually C{/etc/hosts})
1020   @type ip: str
1021   @param ip: the IP address
1022   @type hostname: str
1023   @param hostname: the hostname to be added
1024   @type aliases: list
1025   @param aliases: the list of aliases to add for the hostname
1026
1027   """
1028   # FIXME: use WriteFile + fn rather than duplicating its efforts
1029   # Ensure aliases are unique
1030   aliases = UniqueSequence([hostname] + aliases)[1:]
1031
1032   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1033   try:
1034     out = os.fdopen(fd, 'w')
1035     try:
1036       f = open(file_name, 'r')
1037       try:
1038         for line in f:
1039           fields = line.split()
1040           if fields and not fields[0].startswith('#') and ip == fields[0]:
1041             continue
1042           out.write(line)
1043
1044         out.write("%s\t%s" % (ip, hostname))
1045         if aliases:
1046           out.write(" %s" % ' '.join(aliases))
1047         out.write('\n')
1048
1049         out.flush()
1050         os.fsync(out)
1051         os.chmod(tmpname, 0644)
1052         os.rename(tmpname, file_name)
1053       finally:
1054         f.close()
1055     finally:
1056       out.close()
1057   except:
1058     RemoveFile(tmpname)
1059     raise
1060
1061
1062 def AddHostToEtcHosts(hostname):
1063   """Wrapper around SetEtcHostsEntry.
1064
1065   @type hostname: str
1066   @param hostname: a hostname that will be resolved and added to
1067       L{constants.ETC_HOSTS}
1068
1069   """
1070   hi = HostInfo(name=hostname)
1071   SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
1072
1073
1074 def RemoveEtcHostsEntry(file_name, hostname):
1075   """Removes a hostname from /etc/hosts.
1076
1077   IP addresses without names are removed from the file.
1078
1079   @type file_name: str
1080   @param file_name: path to the file to modify (usually C{/etc/hosts})
1081   @type hostname: str
1082   @param hostname: the hostname to be removed
1083
1084   """
1085   # FIXME: use WriteFile + fn rather than duplicating its efforts
1086   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1087   try:
1088     out = os.fdopen(fd, 'w')
1089     try:
1090       f = open(file_name, 'r')
1091       try:
1092         for line in f:
1093           fields = line.split()
1094           if len(fields) > 1 and not fields[0].startswith('#'):
1095             names = fields[1:]
1096             if hostname in names:
1097               while hostname in names:
1098                 names.remove(hostname)
1099               if names:
1100                 out.write("%s %s\n" % (fields[0], ' '.join(names)))
1101               continue
1102
1103           out.write(line)
1104
1105         out.flush()
1106         os.fsync(out)
1107         os.chmod(tmpname, 0644)
1108         os.rename(tmpname, file_name)
1109       finally:
1110         f.close()
1111     finally:
1112       out.close()
1113   except:
1114     RemoveFile(tmpname)
1115     raise
1116
1117
1118 def RemoveHostFromEtcHosts(hostname):
1119   """Wrapper around RemoveEtcHostsEntry.
1120
1121   @type hostname: str
1122   @param hostname: hostname that will be resolved and its
1123       full and shot name will be removed from
1124       L{constants.ETC_HOSTS}
1125
1126   """
1127   hi = HostInfo(name=hostname)
1128   RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
1129   RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
1130
1131
1132 def TimestampForFilename():
1133   """Returns the current time formatted for filenames.
1134
1135   The format doesn't contain colons as some shells and applications them as
1136   separators.
1137
1138   """
1139   return time.strftime("%Y-%m-%d_%H_%M_%S")
1140
1141
1142 def CreateBackup(file_name):
1143   """Creates a backup of a file.
1144
1145   @type file_name: str
1146   @param file_name: file to be backed up
1147   @rtype: str
1148   @return: the path to the newly created backup
1149   @raise errors.ProgrammerError: for invalid file names
1150
1151   """
1152   if not os.path.isfile(file_name):
1153     raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
1154                                 file_name)
1155
1156   prefix = ("%s.backup-%s." %
1157             (os.path.basename(file_name), TimestampForFilename()))
1158   dir_name = os.path.dirname(file_name)
1159
1160   fsrc = open(file_name, 'rb')
1161   try:
1162     (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
1163     fdst = os.fdopen(fd, 'wb')
1164     try:
1165       logging.debug("Backing up %s at %s", file_name, backup_name)
1166       shutil.copyfileobj(fsrc, fdst)
1167     finally:
1168       fdst.close()
1169   finally:
1170     fsrc.close()
1171
1172   return backup_name
1173
1174
1175 def ShellQuote(value):
1176   """Quotes shell argument according to POSIX.
1177
1178   @type value: str
1179   @param value: the argument to be quoted
1180   @rtype: str
1181   @return: the quoted value
1182
1183   """
1184   if _re_shell_unquoted.match(value):
1185     return value
1186   else:
1187     return "'%s'" % value.replace("'", "'\\''")
1188
1189
1190 def ShellQuoteArgs(args):
1191   """Quotes a list of shell arguments.
1192
1193   @type args: list
1194   @param args: list of arguments to be quoted
1195   @rtype: str
1196   @return: the quoted arguments concatenated with spaces
1197
1198   """
1199   return ' '.join([ShellQuote(i) for i in args])
1200
1201
1202 def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
1203   """Simple ping implementation using TCP connect(2).
1204
1205   Check if the given IP is reachable by doing attempting a TCP connect
1206   to it.
1207
1208   @type target: str
1209   @param target: the IP or hostname to ping
1210   @type port: int
1211   @param port: the port to connect to
1212   @type timeout: int
1213   @param timeout: the timeout on the connection attempt
1214   @type live_port_needed: boolean
1215   @param live_port_needed: whether a closed port will cause the
1216       function to return failure, as if there was a timeout
1217   @type source: str or None
1218   @param source: if specified, will cause the connect to be made
1219       from this specific source address; failures to bind other
1220       than C{EADDRNOTAVAIL} will be ignored
1221
1222   """
1223   sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1224
1225   success = False
1226
1227   if source is not None:
1228     try:
1229       sock.bind((source, 0))
1230     except socket.error, (errcode, _):
1231       if errcode == errno.EADDRNOTAVAIL:
1232         success = False
1233
1234   sock.settimeout(timeout)
1235
1236   try:
1237     sock.connect((target, port))
1238     sock.close()
1239     success = True
1240   except socket.timeout:
1241     success = False
1242   except socket.error, (errcode, _):
1243     success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
1244
1245   return success
1246
1247
1248 def OwnIpAddress(address):
1249   """Check if the current host has the the given IP address.
1250
1251   Currently this is done by TCP-pinging the address from the loopback
1252   address.
1253
1254   @type address: string
1255   @param address: the address to check
1256   @rtype: bool
1257   @return: True if we own the address
1258
1259   """
1260   return TcpPing(address, constants.DEFAULT_NODED_PORT,
1261                  source=constants.LOCALHOST_IP_ADDRESS)
1262
1263
1264 def ListVisibleFiles(path):
1265   """Returns a list of visible files in a directory.
1266
1267   @type path: str
1268   @param path: the directory to enumerate
1269   @rtype: list
1270   @return: the list of all files not starting with a dot
1271   @raise ProgrammerError: if L{path} is not an absolue and normalized path
1272
1273   """
1274   if not IsNormAbsPath(path):
1275     raise errors.ProgrammerError("Path passed to ListVisibleFiles is not"
1276                                  " absolute/normalized: '%s'" % path)
1277   files = [i for i in os.listdir(path) if not i.startswith(".")]
1278   files.sort()
1279   return files
1280
1281
1282 def GetHomeDir(user, default=None):
1283   """Try to get the homedir of the given user.
1284
1285   The user can be passed either as a string (denoting the name) or as
1286   an integer (denoting the user id). If the user is not found, the
1287   'default' argument is returned, which defaults to None.
1288
1289   """
1290   try:
1291     if isinstance(user, basestring):
1292       result = pwd.getpwnam(user)
1293     elif isinstance(user, (int, long)):
1294       result = pwd.getpwuid(user)
1295     else:
1296       raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1297                                    type(user))
1298   except KeyError:
1299     return default
1300   return result.pw_dir
1301
1302
1303 def NewUUID():
1304   """Returns a random UUID.
1305
1306   @note: This is a Linux-specific method as it uses the /proc
1307       filesystem.
1308   @rtype: str
1309
1310   """
1311   return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
1312
1313
1314 def GenerateSecret(numbytes=20):
1315   """Generates a random secret.
1316
1317   This will generate a pseudo-random secret returning an hex string
1318   (so that it can be used where an ASCII string is needed).
1319
1320   @param numbytes: the number of bytes which will be represented by the returned
1321       string (defaulting to 20, the length of a SHA1 hash)
1322   @rtype: str
1323   @return: an hex representation of the pseudo-random sequence
1324
1325   """
1326   return os.urandom(numbytes).encode('hex')
1327
1328
1329 def EnsureDirs(dirs):
1330   """Make required directories, if they don't exist.
1331
1332   @param dirs: list of tuples (dir_name, dir_mode)
1333   @type dirs: list of (string, integer)
1334
1335   """
1336   for dir_name, dir_mode in dirs:
1337     try:
1338       os.mkdir(dir_name, dir_mode)
1339     except EnvironmentError, err:
1340       if err.errno != errno.EEXIST:
1341         raise errors.GenericError("Cannot create needed directory"
1342                                   " '%s': %s" % (dir_name, err))
1343     if not os.path.isdir(dir_name):
1344       raise errors.GenericError("%s is not a directory" % dir_name)
1345
1346
1347 def ReadFile(file_name, size=-1):
1348   """Reads a file.
1349
1350   @type size: int
1351   @param size: Read at most size bytes (if negative, entire file)
1352   @rtype: str
1353   @return: the (possibly partial) content of the file
1354
1355   """
1356   f = open(file_name, "r")
1357   try:
1358     return f.read(size)
1359   finally:
1360     f.close()
1361
1362
1363 def WriteFile(file_name, fn=None, data=None,
1364               mode=None, uid=-1, gid=-1,
1365               atime=None, mtime=None, close=True,
1366               dry_run=False, backup=False,
1367               prewrite=None, postwrite=None):
1368   """(Over)write a file atomically.
1369
1370   The file_name and either fn (a function taking one argument, the
1371   file descriptor, and which should write the data to it) or data (the
1372   contents of the file) must be passed. The other arguments are
1373   optional and allow setting the file mode, owner and group, and the
1374   mtime/atime of the file.
1375
1376   If the function doesn't raise an exception, it has succeeded and the
1377   target file has the new contents. If the function has raised an
1378   exception, an existing target file should be unmodified and the
1379   temporary file should be removed.
1380
1381   @type file_name: str
1382   @param file_name: the target filename
1383   @type fn: callable
1384   @param fn: content writing function, called with
1385       file descriptor as parameter
1386   @type data: str
1387   @param data: contents of the file
1388   @type mode: int
1389   @param mode: file mode
1390   @type uid: int
1391   @param uid: the owner of the file
1392   @type gid: int
1393   @param gid: the group of the file
1394   @type atime: int
1395   @param atime: a custom access time to be set on the file
1396   @type mtime: int
1397   @param mtime: a custom modification time to be set on the file
1398   @type close: boolean
1399   @param close: whether to close file after writing it
1400   @type prewrite: callable
1401   @param prewrite: function to be called before writing content
1402   @type postwrite: callable
1403   @param postwrite: function to be called after writing content
1404
1405   @rtype: None or int
1406   @return: None if the 'close' parameter evaluates to True,
1407       otherwise the file descriptor
1408
1409   @raise errors.ProgrammerError: if any of the arguments are not valid
1410
1411   """
1412   if not os.path.isabs(file_name):
1413     raise errors.ProgrammerError("Path passed to WriteFile is not"
1414                                  " absolute: '%s'" % file_name)
1415
1416   if [fn, data].count(None) != 1:
1417     raise errors.ProgrammerError("fn or data required")
1418
1419   if [atime, mtime].count(None) == 1:
1420     raise errors.ProgrammerError("Both atime and mtime must be either"
1421                                  " set or None")
1422
1423   if backup and not dry_run and os.path.isfile(file_name):
1424     CreateBackup(file_name)
1425
1426   dir_name, base_name = os.path.split(file_name)
1427   fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
1428   do_remove = True
1429   # here we need to make sure we remove the temp file, if any error
1430   # leaves it in place
1431   try:
1432     if uid != -1 or gid != -1:
1433       os.chown(new_name, uid, gid)
1434     if mode:
1435       os.chmod(new_name, mode)
1436     if callable(prewrite):
1437       prewrite(fd)
1438     if data is not None:
1439       os.write(fd, data)
1440     else:
1441       fn(fd)
1442     if callable(postwrite):
1443       postwrite(fd)
1444     os.fsync(fd)
1445     if atime is not None and mtime is not None:
1446       os.utime(new_name, (atime, mtime))
1447     if not dry_run:
1448       os.rename(new_name, file_name)
1449       do_remove = False
1450   finally:
1451     if close:
1452       os.close(fd)
1453       result = None
1454     else:
1455       result = fd
1456     if do_remove:
1457       RemoveFile(new_name)
1458
1459   return result
1460
1461
1462 def FirstFree(seq, base=0):
1463   """Returns the first non-existing integer from seq.
1464
1465   The seq argument should be a sorted list of positive integers. The
1466   first time the index of an element is smaller than the element
1467   value, the index will be returned.
1468
1469   The base argument is used to start at a different offset,
1470   i.e. C{[3, 4, 6]} with I{offset=3} will return 5.
1471
1472   Example: C{[0, 1, 3]} will return I{2}.
1473
1474   @type seq: sequence
1475   @param seq: the sequence to be analyzed.
1476   @type base: int
1477   @param base: use this value as the base index of the sequence
1478   @rtype: int
1479   @return: the first non-used index in the sequence
1480
1481   """
1482   for idx, elem in enumerate(seq):
1483     assert elem >= base, "Passed element is higher than base offset"
1484     if elem > idx + base:
1485       # idx is not used
1486       return idx + base
1487   return None
1488
1489
1490 def all(seq, pred=bool): # pylint: disable-msg=W0622
1491   "Returns True if pred(x) is True for every element in the iterable"
1492   for _ in itertools.ifilterfalse(pred, seq):
1493     return False
1494   return True
1495
1496
1497 def any(seq, pred=bool): # pylint: disable-msg=W0622
1498   "Returns True if pred(x) is True for at least one element in the iterable"
1499   for _ in itertools.ifilter(pred, seq):
1500     return True
1501   return False
1502
1503
1504 def SingleWaitForFdCondition(fdobj, event, timeout):
1505   """Waits for a condition to occur on the socket.
1506
1507   Immediately returns at the first interruption.
1508
1509   @type fdobj: integer or object supporting a fileno() method
1510   @param fdobj: entity to wait for events on
1511   @type event: integer
1512   @param event: ORed condition (see select module)
1513   @type timeout: float or None
1514   @param timeout: Timeout in seconds
1515   @rtype: int or None
1516   @return: None for timeout, otherwise occured conditions
1517
1518   """
1519   check = (event | select.POLLPRI |
1520            select.POLLNVAL | select.POLLHUP | select.POLLERR)
1521
1522   if timeout is not None:
1523     # Poller object expects milliseconds
1524     timeout *= 1000
1525
1526   poller = select.poll()
1527   poller.register(fdobj, event)
1528   try:
1529     # TODO: If the main thread receives a signal and we have no timeout, we
1530     # could wait forever. This should check a global "quit" flag or something
1531     # every so often.
1532     io_events = poller.poll(timeout)
1533   except select.error, err:
1534     if err[0] != errno.EINTR:
1535       raise
1536     io_events = []
1537   if io_events and io_events[0][1] & check:
1538     return io_events[0][1]
1539   else:
1540     return None
1541
1542
1543 class FdConditionWaiterHelper(object):
1544   """Retry helper for WaitForFdCondition.
1545
1546   This class contains the retried and wait functions that make sure
1547   WaitForFdCondition can continue waiting until the timeout is actually
1548   expired.
1549
1550   """
1551
1552   def __init__(self, timeout):
1553     self.timeout = timeout
1554
1555   def Poll(self, fdobj, event):
1556     result = SingleWaitForFdCondition(fdobj, event, self.timeout)
1557     if result is None:
1558       raise RetryAgain()
1559     else:
1560       return result
1561
1562   def UpdateTimeout(self, timeout):
1563     self.timeout = timeout
1564
1565
1566 def WaitForFdCondition(fdobj, event, timeout):
1567   """Waits for a condition to occur on the socket.
1568
1569   Retries until the timeout is expired, even if interrupted.
1570
1571   @type fdobj: integer or object supporting a fileno() method
1572   @param fdobj: entity to wait for events on
1573   @type event: integer
1574   @param event: ORed condition (see select module)
1575   @type timeout: float or None
1576   @param timeout: Timeout in seconds
1577   @rtype: int or None
1578   @return: None for timeout, otherwise occured conditions
1579
1580   """
1581   if timeout is not None:
1582     retrywaiter = FdConditionWaiterHelper(timeout)
1583     try:
1584       result = Retry(retrywaiter.Poll, RETRY_REMAINING_TIME, timeout,
1585                      args=(fdobj, event), wait_fn=retrywaiter.UpdateTimeout)
1586     except RetryTimeout:
1587       result = None
1588   else:
1589     result = None
1590     while result is None:
1591       result = SingleWaitForFdCondition(fdobj, event, timeout)
1592   return result
1593
1594
1595 def partition(seq, pred=bool): # # pylint: disable-msg=W0622
1596   "Partition a list in two, based on the given predicate"
1597   return (list(itertools.ifilter(pred, seq)),
1598           list(itertools.ifilterfalse(pred, seq)))
1599
1600
1601 def UniqueSequence(seq):
1602   """Returns a list with unique elements.
1603
1604   Element order is preserved.
1605
1606   @type seq: sequence
1607   @param seq: the sequence with the source elements
1608   @rtype: list
1609   @return: list of unique elements from seq
1610
1611   """
1612   seen = set()
1613   return [i for i in seq if i not in seen and not seen.add(i)]
1614
1615
1616 def NormalizeAndValidateMac(mac):
1617   """Normalizes and check if a MAC address is valid.
1618
1619   Checks whether the supplied MAC address is formally correct, only
1620   accepts colon separated format. Normalize it to all lower.
1621
1622   @type mac: str
1623   @param mac: the MAC to be validated
1624   @rtype: str
1625   @return: returns the normalized and validated MAC.
1626
1627   @raise errors.OpPrereqError: If the MAC isn't valid
1628
1629   """
1630   mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$", re.I)
1631   if not mac_check.match(mac):
1632     raise errors.OpPrereqError("Invalid MAC address specified: %s" %
1633                                mac, errors.ECODE_INVAL)
1634
1635   return mac.lower()
1636
1637
1638 def TestDelay(duration):
1639   """Sleep for a fixed amount of time.
1640
1641   @type duration: float
1642   @param duration: the sleep duration
1643   @rtype: boolean
1644   @return: False for negative value, True otherwise
1645
1646   """
1647   if duration < 0:
1648     return False, "Invalid sleep duration"
1649   time.sleep(duration)
1650   return True, None
1651
1652
1653 def _CloseFDNoErr(fd, retries=5):
1654   """Close a file descriptor ignoring errors.
1655
1656   @type fd: int
1657   @param fd: the file descriptor
1658   @type retries: int
1659   @param retries: how many retries to make, in case we get any
1660       other error than EBADF
1661
1662   """
1663   try:
1664     os.close(fd)
1665   except OSError, err:
1666     if err.errno != errno.EBADF:
1667       if retries > 0:
1668         _CloseFDNoErr(fd, retries - 1)
1669     # else either it's closed already or we're out of retries, so we
1670     # ignore this and go on
1671
1672
1673 def CloseFDs(noclose_fds=None):
1674   """Close file descriptors.
1675
1676   This closes all file descriptors above 2 (i.e. except
1677   stdin/out/err).
1678
1679   @type noclose_fds: list or None
1680   @param noclose_fds: if given, it denotes a list of file descriptor
1681       that should not be closed
1682
1683   """
1684   # Default maximum for the number of available file descriptors.
1685   if 'SC_OPEN_MAX' in os.sysconf_names:
1686     try:
1687       MAXFD = os.sysconf('SC_OPEN_MAX')
1688       if MAXFD < 0:
1689         MAXFD = 1024
1690     except OSError:
1691       MAXFD = 1024
1692   else:
1693     MAXFD = 1024
1694   maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1695   if (maxfd == resource.RLIM_INFINITY):
1696     maxfd = MAXFD
1697
1698   # Iterate through and close all file descriptors (except the standard ones)
1699   for fd in range(3, maxfd):
1700     if noclose_fds and fd in noclose_fds:
1701       continue
1702     _CloseFDNoErr(fd)
1703
1704
1705 def Daemonize(logfile):
1706   """Daemonize the current process.
1707
1708   This detaches the current process from the controlling terminal and
1709   runs it in the background as a daemon.
1710
1711   @type logfile: str
1712   @param logfile: the logfile to which we should redirect stdout/stderr
1713   @rtype: int
1714   @return: the value zero
1715
1716   """
1717   # pylint: disable-msg=W0212
1718   # yes, we really want os._exit
1719   UMASK = 077
1720   WORKDIR = "/"
1721
1722   # this might fail
1723   pid = os.fork()
1724   if (pid == 0):  # The first child.
1725     os.setsid()
1726     # this might fail
1727     pid = os.fork() # Fork a second child.
1728     if (pid == 0):  # The second child.
1729       os.chdir(WORKDIR)
1730       os.umask(UMASK)
1731     else:
1732       # exit() or _exit()?  See below.
1733       os._exit(0) # Exit parent (the first child) of the second child.
1734   else:
1735     os._exit(0) # Exit parent of the first child.
1736
1737   for fd in range(3):
1738     _CloseFDNoErr(fd)
1739   i = os.open("/dev/null", os.O_RDONLY) # stdin
1740   assert i == 0, "Can't close/reopen stdin"
1741   i = os.open(logfile, os.O_WRONLY|os.O_CREAT|os.O_APPEND, 0600) # stdout
1742   assert i == 1, "Can't close/reopen stdout"
1743   # Duplicate standard output to standard error.
1744   os.dup2(1, 2)
1745   return 0
1746
1747
1748 def DaemonPidFileName(name):
1749   """Compute a ganeti pid file absolute path
1750
1751   @type name: str
1752   @param name: the daemon name
1753   @rtype: str
1754   @return: the full path to the pidfile corresponding to the given
1755       daemon name
1756
1757   """
1758   return PathJoin(constants.RUN_GANETI_DIR, "%s.pid" % name)
1759
1760
1761 def EnsureDaemon(name):
1762   """Check for and start daemon if not alive.
1763
1764   """
1765   result = RunCmd([constants.DAEMON_UTIL, "check-and-start", name])
1766   if result.failed:
1767     logging.error("Can't start daemon '%s', failure %s, output: %s",
1768                   name, result.fail_reason, result.output)
1769     return False
1770
1771   return True
1772
1773
1774 def WritePidFile(name):
1775   """Write the current process pidfile.
1776
1777   The file will be written to L{constants.RUN_GANETI_DIR}I{/name.pid}
1778
1779   @type name: str
1780   @param name: the daemon name to use
1781   @raise errors.GenericError: if the pid file already exists and
1782       points to a live process
1783
1784   """
1785   pid = os.getpid()
1786   pidfilename = DaemonPidFileName(name)
1787   if IsProcessAlive(ReadPidFile(pidfilename)):
1788     raise errors.GenericError("%s contains a live process" % pidfilename)
1789
1790   WriteFile(pidfilename, data="%d\n" % pid)
1791
1792
1793 def RemovePidFile(name):
1794   """Remove the current process pidfile.
1795
1796   Any errors are ignored.
1797
1798   @type name: str
1799   @param name: the daemon name used to derive the pidfile name
1800
1801   """
1802   pidfilename = DaemonPidFileName(name)
1803   # TODO: we could check here that the file contains our pid
1804   try:
1805     RemoveFile(pidfilename)
1806   except: # pylint: disable-msg=W0702
1807     pass
1808
1809
1810 def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1811                 waitpid=False):
1812   """Kill a process given by its pid.
1813
1814   @type pid: int
1815   @param pid: The PID to terminate.
1816   @type signal_: int
1817   @param signal_: The signal to send, by default SIGTERM
1818   @type timeout: int
1819   @param timeout: The timeout after which, if the process is still alive,
1820                   a SIGKILL will be sent. If not positive, no such checking
1821                   will be done
1822   @type waitpid: boolean
1823   @param waitpid: If true, we should waitpid on this process after
1824       sending signals, since it's our own child and otherwise it
1825       would remain as zombie
1826
1827   """
1828   def _helper(pid, signal_, wait):
1829     """Simple helper to encapsulate the kill/waitpid sequence"""
1830     os.kill(pid, signal_)
1831     if wait:
1832       try:
1833         os.waitpid(pid, os.WNOHANG)
1834       except OSError:
1835         pass
1836
1837   if pid <= 0:
1838     # kill with pid=0 == suicide
1839     raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1840
1841   if not IsProcessAlive(pid):
1842     return
1843
1844   _helper(pid, signal_, waitpid)
1845
1846   if timeout <= 0:
1847     return
1848
1849   def _CheckProcess():
1850     if not IsProcessAlive(pid):
1851       return
1852
1853     try:
1854       (result_pid, _) = os.waitpid(pid, os.WNOHANG)
1855     except OSError:
1856       raise RetryAgain()
1857
1858     if result_pid > 0:
1859       return
1860
1861     raise RetryAgain()
1862
1863   try:
1864     # Wait up to $timeout seconds
1865     Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
1866   except RetryTimeout:
1867     pass
1868
1869   if IsProcessAlive(pid):
1870     # Kill process if it's still alive
1871     _helper(pid, signal.SIGKILL, waitpid)
1872
1873
1874 def FindFile(name, search_path, test=os.path.exists):
1875   """Look for a filesystem object in a given path.
1876
1877   This is an abstract method to search for filesystem object (files,
1878   dirs) under a given search path.
1879
1880   @type name: str
1881   @param name: the name to look for
1882   @type search_path: str
1883   @param search_path: location to start at
1884   @type test: callable
1885   @param test: a function taking one argument that should return True
1886       if the a given object is valid; the default value is
1887       os.path.exists, causing only existing files to be returned
1888   @rtype: str or None
1889   @return: full path to the object if found, None otherwise
1890
1891   """
1892   # validate the filename mask
1893   if constants.EXT_PLUGIN_MASK.match(name) is None:
1894     logging.critical("Invalid value passed for external script name: '%s'",
1895                      name)
1896     return None
1897
1898   for dir_name in search_path:
1899     # FIXME: investigate switch to PathJoin
1900     item_name = os.path.sep.join([dir_name, name])
1901     # check the user test and that we're indeed resolving to the given
1902     # basename
1903     if test(item_name) and os.path.basename(item_name) == name:
1904       return item_name
1905   return None
1906
1907
1908 def CheckVolumeGroupSize(vglist, vgname, minsize):
1909   """Checks if the volume group list is valid.
1910
1911   The function will check if a given volume group is in the list of
1912   volume groups and has a minimum size.
1913
1914   @type vglist: dict
1915   @param vglist: dictionary of volume group names and their size
1916   @type vgname: str
1917   @param vgname: the volume group we should check
1918   @type minsize: int
1919   @param minsize: the minimum size we accept
1920   @rtype: None or str
1921   @return: None for success, otherwise the error message
1922
1923   """
1924   vgsize = vglist.get(vgname, None)
1925   if vgsize is None:
1926     return "volume group '%s' missing" % vgname
1927   elif vgsize < minsize:
1928     return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1929             (vgname, minsize, vgsize))
1930   return None
1931
1932
1933 def SplitTime(value):
1934   """Splits time as floating point number into a tuple.
1935
1936   @param value: Time in seconds
1937   @type value: int or float
1938   @return: Tuple containing (seconds, microseconds)
1939
1940   """
1941   (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1942
1943   assert 0 <= seconds, \
1944     "Seconds must be larger than or equal to 0, but are %s" % seconds
1945   assert 0 <= microseconds <= 999999, \
1946     "Microseconds must be 0-999999, but are %s" % microseconds
1947
1948   return (int(seconds), int(microseconds))
1949
1950
1951 def MergeTime(timetuple):
1952   """Merges a tuple into time as a floating point number.
1953
1954   @param timetuple: Time as tuple, (seconds, microseconds)
1955   @type timetuple: tuple
1956   @return: Time as a floating point number expressed in seconds
1957
1958   """
1959   (seconds, microseconds) = timetuple
1960
1961   assert 0 <= seconds, \
1962     "Seconds must be larger than or equal to 0, but are %s" % seconds
1963   assert 0 <= microseconds <= 999999, \
1964     "Microseconds must be 0-999999, but are %s" % microseconds
1965
1966   return float(seconds) + (float(microseconds) * 0.000001)
1967
1968
1969 def GetDaemonPort(daemon_name):
1970   """Get the daemon port for this cluster.
1971
1972   Note that this routine does not read a ganeti-specific file, but
1973   instead uses C{socket.getservbyname} to allow pre-customization of
1974   this parameter outside of Ganeti.
1975
1976   @type daemon_name: string
1977   @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
1978   @rtype: int
1979
1980   """
1981   if daemon_name not in constants.DAEMONS_PORTS:
1982     raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
1983
1984   (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
1985   try:
1986     port = socket.getservbyname(daemon_name, proto)
1987   except socket.error:
1988     port = default_port
1989
1990   return port
1991
1992
1993 def SetupLogging(logfile, debug=0, stderr_logging=False, program="",
1994                  multithreaded=False, syslog=constants.SYSLOG_USAGE):
1995   """Configures the logging module.
1996
1997   @type logfile: str
1998   @param logfile: the filename to which we should log
1999   @type debug: integer
2000   @param debug: if greater than zero, enable debug messages, otherwise
2001       only those at C{INFO} and above level
2002   @type stderr_logging: boolean
2003   @param stderr_logging: whether we should also log to the standard error
2004   @type program: str
2005   @param program: the name under which we should log messages
2006   @type multithreaded: boolean
2007   @param multithreaded: if True, will add the thread name to the log file
2008   @type syslog: string
2009   @param syslog: one of 'no', 'yes', 'only':
2010       - if no, syslog is not used
2011       - if yes, syslog is used (in addition to file-logging)
2012       - if only, only syslog is used
2013   @raise EnvironmentError: if we can't open the log file and
2014       syslog/stderr logging is disabled
2015
2016   """
2017   fmt = "%(asctime)s: " + program + " pid=%(process)d"
2018   sft = program + "[%(process)d]:"
2019   if multithreaded:
2020     fmt += "/%(threadName)s"
2021     sft += " (%(threadName)s)"
2022   if debug:
2023     fmt += " %(module)s:%(lineno)s"
2024     # no debug info for syslog loggers
2025   fmt += " %(levelname)s %(message)s"
2026   # yes, we do want the textual level, as remote syslog will probably
2027   # lose the error level, and it's easier to grep for it
2028   sft += " %(levelname)s %(message)s"
2029   formatter = logging.Formatter(fmt)
2030   sys_fmt = logging.Formatter(sft)
2031
2032   root_logger = logging.getLogger("")
2033   root_logger.setLevel(logging.NOTSET)
2034
2035   # Remove all previously setup handlers
2036   for handler in root_logger.handlers:
2037     handler.close()
2038     root_logger.removeHandler(handler)
2039
2040   if stderr_logging:
2041     stderr_handler = logging.StreamHandler()
2042     stderr_handler.setFormatter(formatter)
2043     if debug:
2044       stderr_handler.setLevel(logging.NOTSET)
2045     else:
2046       stderr_handler.setLevel(logging.CRITICAL)
2047     root_logger.addHandler(stderr_handler)
2048
2049   if syslog in (constants.SYSLOG_YES, constants.SYSLOG_ONLY):
2050     facility = logging.handlers.SysLogHandler.LOG_DAEMON
2051     syslog_handler = logging.handlers.SysLogHandler(constants.SYSLOG_SOCKET,
2052                                                     facility)
2053     syslog_handler.setFormatter(sys_fmt)
2054     # Never enable debug over syslog
2055     syslog_handler.setLevel(logging.INFO)
2056     root_logger.addHandler(syslog_handler)
2057
2058   if syslog != constants.SYSLOG_ONLY:
2059     # this can fail, if the logging directories are not setup or we have
2060     # a permisssion problem; in this case, it's best to log but ignore
2061     # the error if stderr_logging is True, and if false we re-raise the
2062     # exception since otherwise we could run but without any logs at all
2063     try:
2064       logfile_handler = logging.FileHandler(logfile)
2065       logfile_handler.setFormatter(formatter)
2066       if debug:
2067         logfile_handler.setLevel(logging.DEBUG)
2068       else:
2069         logfile_handler.setLevel(logging.INFO)
2070       root_logger.addHandler(logfile_handler)
2071     except EnvironmentError:
2072       if stderr_logging or syslog == constants.SYSLOG_YES:
2073         logging.exception("Failed to enable logging to file '%s'", logfile)
2074       else:
2075         # we need to re-raise the exception
2076         raise
2077
2078
2079 def IsNormAbsPath(path):
2080   """Check whether a path is absolute and also normalized
2081
2082   This avoids things like /dir/../../other/path to be valid.
2083
2084   """
2085   return os.path.normpath(path) == path and os.path.isabs(path)
2086
2087
2088 def PathJoin(*args):
2089   """Safe-join a list of path components.
2090
2091   Requirements:
2092       - the first argument must be an absolute path
2093       - no component in the path must have backtracking (e.g. /../),
2094         since we check for normalization at the end
2095
2096   @param args: the path components to be joined
2097   @raise ValueError: for invalid paths
2098
2099   """
2100   # ensure we're having at least one path passed in
2101   assert args
2102   # ensure the first component is an absolute and normalized path name
2103   root = args[0]
2104   if not IsNormAbsPath(root):
2105     raise ValueError("Invalid parameter to PathJoin: '%s'" % str(args[0]))
2106   result = os.path.join(*args)
2107   # ensure that the whole path is normalized
2108   if not IsNormAbsPath(result):
2109     raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
2110   # check that we're still under the original prefix
2111   prefix = os.path.commonprefix([root, result])
2112   if prefix != root:
2113     raise ValueError("Error: path joining resulted in different prefix"
2114                      " (%s != %s)" % (prefix, root))
2115   return result
2116
2117
2118 def TailFile(fname, lines=20):
2119   """Return the last lines from a file.
2120
2121   @note: this function will only read and parse the last 4KB of
2122       the file; if the lines are very long, it could be that less
2123       than the requested number of lines are returned
2124
2125   @param fname: the file name
2126   @type lines: int
2127   @param lines: the (maximum) number of lines to return
2128
2129   """
2130   fd = open(fname, "r")
2131   try:
2132     fd.seek(0, 2)
2133     pos = fd.tell()
2134     pos = max(0, pos-4096)
2135     fd.seek(pos, 0)
2136     raw_data = fd.read()
2137   finally:
2138     fd.close()
2139
2140   rows = raw_data.splitlines()
2141   return rows[-lines:]
2142
2143
2144 def _ParseAsn1Generalizedtime(value):
2145   """Parses an ASN1 GENERALIZEDTIME timestamp as used by pyOpenSSL.
2146
2147   @type value: string
2148   @param value: ASN1 GENERALIZEDTIME timestamp
2149
2150   """
2151   m = re.match(r"^(\d+)([-+]\d\d)(\d\d)$", value)
2152   if m:
2153     # We have an offset
2154     asn1time = m.group(1)
2155     hours = int(m.group(2))
2156     minutes = int(m.group(3))
2157     utcoffset = (60 * hours) + minutes
2158   else:
2159     if not value.endswith("Z"):
2160       raise ValueError("Missing timezone")
2161     asn1time = value[:-1]
2162     utcoffset = 0
2163
2164   parsed = time.strptime(asn1time, "%Y%m%d%H%M%S")
2165
2166   tt = datetime.datetime(*(parsed[:7])) - datetime.timedelta(minutes=utcoffset)
2167
2168   return calendar.timegm(tt.utctimetuple())
2169
2170
2171 def GetX509CertValidity(cert):
2172   """Returns the validity period of the certificate.
2173
2174   @type cert: OpenSSL.crypto.X509
2175   @param cert: X509 certificate object
2176
2177   """
2178   # The get_notBefore and get_notAfter functions are only supported in
2179   # pyOpenSSL 0.7 and above.
2180   try:
2181     get_notbefore_fn = cert.get_notBefore
2182   except AttributeError:
2183     not_before = None
2184   else:
2185     not_before_asn1 = get_notbefore_fn()
2186
2187     if not_before_asn1 is None:
2188       not_before = None
2189     else:
2190       not_before = _ParseAsn1Generalizedtime(not_before_asn1)
2191
2192   try:
2193     get_notafter_fn = cert.get_notAfter
2194   except AttributeError:
2195     not_after = None
2196   else:
2197     not_after_asn1 = get_notafter_fn()
2198
2199     if not_after_asn1 is None:
2200       not_after = None
2201     else:
2202       not_after = _ParseAsn1Generalizedtime(not_after_asn1)
2203
2204   return (not_before, not_after)
2205
2206
2207 def SafeEncode(text):
2208   """Return a 'safe' version of a source string.
2209
2210   This function mangles the input string and returns a version that
2211   should be safe to display/encode as ASCII. To this end, we first
2212   convert it to ASCII using the 'backslashreplace' encoding which
2213   should get rid of any non-ASCII chars, and then we process it
2214   through a loop copied from the string repr sources in the python; we
2215   don't use string_escape anymore since that escape single quotes and
2216   backslashes too, and that is too much; and that escaping is not
2217   stable, i.e. string_escape(string_escape(x)) != string_escape(x).
2218
2219   @type text: str or unicode
2220   @param text: input data
2221   @rtype: str
2222   @return: a safe version of text
2223
2224   """
2225   if isinstance(text, unicode):
2226     # only if unicode; if str already, we handle it below
2227     text = text.encode('ascii', 'backslashreplace')
2228   resu = ""
2229   for char in text:
2230     c = ord(char)
2231     if char  == '\t':
2232       resu += r'\t'
2233     elif char == '\n':
2234       resu += r'\n'
2235     elif char == '\r':
2236       resu += r'\'r'
2237     elif c < 32 or c >= 127: # non-printable
2238       resu += "\\x%02x" % (c & 0xff)
2239     else:
2240       resu += char
2241   return resu
2242
2243
2244 def UnescapeAndSplit(text, sep=","):
2245   """Split and unescape a string based on a given separator.
2246
2247   This function splits a string based on a separator where the
2248   separator itself can be escape in order to be an element of the
2249   elements. The escaping rules are (assuming coma being the
2250   separator):
2251     - a plain , separates the elements
2252     - a sequence \\\\, (double backslash plus comma) is handled as a
2253       backslash plus a separator comma
2254     - a sequence \, (backslash plus comma) is handled as a
2255       non-separator comma
2256
2257   @type text: string
2258   @param text: the string to split
2259   @type sep: string
2260   @param text: the separator
2261   @rtype: string
2262   @return: a list of strings
2263
2264   """
2265   # we split the list by sep (with no escaping at this stage)
2266   slist = text.split(sep)
2267   # next, we revisit the elements and if any of them ended with an odd
2268   # number of backslashes, then we join it with the next
2269   rlist = []
2270   while slist:
2271     e1 = slist.pop(0)
2272     if e1.endswith("\\"):
2273       num_b = len(e1) - len(e1.rstrip("\\"))
2274       if num_b % 2 == 1:
2275         e2 = slist.pop(0)
2276         # here the backslashes remain (all), and will be reduced in
2277         # the next step
2278         rlist.append(e1 + sep + e2)
2279         continue
2280     rlist.append(e1)
2281   # finally, replace backslash-something with something
2282   rlist = [re.sub(r"\\(.)", r"\1", v) for v in rlist]
2283   return rlist
2284
2285
2286 def CommaJoin(names):
2287   """Nicely join a set of identifiers.
2288
2289   @param names: set, list or tuple
2290   @return: a string with the formatted results
2291
2292   """
2293   return ", ".join([str(val) for val in names])
2294
2295
2296 def BytesToMebibyte(value):
2297   """Converts bytes to mebibytes.
2298
2299   @type value: int
2300   @param value: Value in bytes
2301   @rtype: int
2302   @return: Value in mebibytes
2303
2304   """
2305   return int(round(value / (1024.0 * 1024.0), 0))
2306
2307
2308 def CalculateDirectorySize(path):
2309   """Calculates the size of a directory recursively.
2310
2311   @type path: string
2312   @param path: Path to directory
2313   @rtype: int
2314   @return: Size in mebibytes
2315
2316   """
2317   size = 0
2318
2319   for (curpath, _, files) in os.walk(path):
2320     for filename in files:
2321       st = os.lstat(PathJoin(curpath, filename))
2322       size += st.st_size
2323
2324   return BytesToMebibyte(size)
2325
2326
2327 def GetFilesystemStats(path):
2328   """Returns the total and free space on a filesystem.
2329
2330   @type path: string
2331   @param path: Path on filesystem to be examined
2332   @rtype: int
2333   @return: tuple of (Total space, Free space) in mebibytes
2334
2335   """
2336   st = os.statvfs(path)
2337
2338   fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
2339   tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
2340   return (tsize, fsize)
2341
2342
2343 def RunInSeparateProcess(fn, *args):
2344   """Runs a function in a separate process.
2345
2346   Note: Only boolean return values are supported.
2347
2348   @type fn: callable
2349   @param fn: Function to be called
2350   @rtype: bool
2351   @return: Function's result
2352
2353   """
2354   pid = os.fork()
2355   if pid == 0:
2356     # Child process
2357     try:
2358       # In case the function uses temporary files
2359       ResetTempfileModule()
2360
2361       # Call function
2362       result = int(bool(fn(*args)))
2363       assert result in (0, 1)
2364     except: # pylint: disable-msg=W0702
2365       logging.exception("Error while calling function in separate process")
2366       # 0 and 1 are reserved for the return value
2367       result = 33
2368
2369     os._exit(result) # pylint: disable-msg=W0212
2370
2371   # Parent process
2372
2373   # Avoid zombies and check exit code
2374   (_, status) = os.waitpid(pid, 0)
2375
2376   if os.WIFSIGNALED(status):
2377     exitcode = None
2378     signum = os.WTERMSIG(status)
2379   else:
2380     exitcode = os.WEXITSTATUS(status)
2381     signum = None
2382
2383   if not (exitcode in (0, 1) and signum is None):
2384     raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
2385                               (exitcode, signum))
2386
2387   return bool(exitcode)
2388
2389
2390 def LockedMethod(fn):
2391   """Synchronized object access decorator.
2392
2393   This decorator is intended to protect access to an object using the
2394   object's own lock which is hardcoded to '_lock'.
2395
2396   """
2397   def _LockDebug(*args, **kwargs):
2398     if debug_locks:
2399       logging.debug(*args, **kwargs)
2400
2401   def wrapper(self, *args, **kwargs):
2402     # pylint: disable-msg=W0212
2403     assert hasattr(self, '_lock')
2404     lock = self._lock
2405     _LockDebug("Waiting for %s", lock)
2406     lock.acquire()
2407     try:
2408       _LockDebug("Acquired %s", lock)
2409       result = fn(self, *args, **kwargs)
2410     finally:
2411       _LockDebug("Releasing %s", lock)
2412       lock.release()
2413       _LockDebug("Released %s", lock)
2414     return result
2415   return wrapper
2416
2417
2418 def LockFile(fd):
2419   """Locks a file using POSIX locks.
2420
2421   @type fd: int
2422   @param fd: the file descriptor we need to lock
2423
2424   """
2425   try:
2426     fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
2427   except IOError, err:
2428     if err.errno == errno.EAGAIN:
2429       raise errors.LockError("File already locked")
2430     raise
2431
2432
2433 def FormatTime(val):
2434   """Formats a time value.
2435
2436   @type val: float or None
2437   @param val: the timestamp as returned by time.time()
2438   @return: a string value or N/A if we don't have a valid timestamp
2439
2440   """
2441   if val is None or not isinstance(val, (int, float)):
2442     return "N/A"
2443   # these two codes works on Linux, but they are not guaranteed on all
2444   # platforms
2445   return time.strftime("%F %T", time.localtime(val))
2446
2447
2448 def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
2449   """Reads the watcher pause file.
2450
2451   @type filename: string
2452   @param filename: Path to watcher pause file
2453   @type now: None, float or int
2454   @param now: Current time as Unix timestamp
2455   @type remove_after: int
2456   @param remove_after: Remove watcher pause file after specified amount of
2457     seconds past the pause end time
2458
2459   """
2460   if now is None:
2461     now = time.time()
2462
2463   try:
2464     value = ReadFile(filename)
2465   except IOError, err:
2466     if err.errno != errno.ENOENT:
2467       raise
2468     value = None
2469
2470   if value is not None:
2471     try:
2472       value = int(value)
2473     except ValueError:
2474       logging.warning(("Watcher pause file (%s) contains invalid value,"
2475                        " removing it"), filename)
2476       RemoveFile(filename)
2477       value = None
2478
2479     if value is not None:
2480       # Remove file if it's outdated
2481       if now > (value + remove_after):
2482         RemoveFile(filename)
2483         value = None
2484
2485       elif now > value:
2486         value = None
2487
2488   return value
2489
2490
2491 class RetryTimeout(Exception):
2492   """Retry loop timed out.
2493
2494   """
2495
2496
2497 class RetryAgain(Exception):
2498   """Retry again.
2499
2500   """
2501
2502
2503 class _RetryDelayCalculator(object):
2504   """Calculator for increasing delays.
2505
2506   """
2507   __slots__ = [
2508     "_factor",
2509     "_limit",
2510     "_next",
2511     "_start",
2512     ]
2513
2514   def __init__(self, start, factor, limit):
2515     """Initializes this class.
2516
2517     @type start: float
2518     @param start: Initial delay
2519     @type factor: float
2520     @param factor: Factor for delay increase
2521     @type limit: float or None
2522     @param limit: Upper limit for delay or None for no limit
2523
2524     """
2525     assert start > 0.0
2526     assert factor >= 1.0
2527     assert limit is None or limit >= 0.0
2528
2529     self._start = start
2530     self._factor = factor
2531     self._limit = limit
2532
2533     self._next = start
2534
2535   def __call__(self):
2536     """Returns current delay and calculates the next one.
2537
2538     """
2539     current = self._next
2540
2541     # Update for next run
2542     if self._limit is None or self._next < self._limit:
2543       self._next = min(self._limit, self._next * self._factor)
2544
2545     return current
2546
2547
2548 #: Special delay to specify whole remaining timeout
2549 RETRY_REMAINING_TIME = object()
2550
2551
2552 def Retry(fn, delay, timeout, args=None, wait_fn=time.sleep,
2553           _time_fn=time.time):
2554   """Call a function repeatedly until it succeeds.
2555
2556   The function C{fn} is called repeatedly until it doesn't throw L{RetryAgain}
2557   anymore. Between calls a delay, specified by C{delay}, is inserted. After a
2558   total of C{timeout} seconds, this function throws L{RetryTimeout}.
2559
2560   C{delay} can be one of the following:
2561     - callable returning the delay length as a float
2562     - Tuple of (start, factor, limit)
2563     - L{RETRY_REMAINING_TIME} to sleep until the timeout expires (this is
2564       useful when overriding L{wait_fn} to wait for an external event)
2565     - A static delay as a number (int or float)
2566
2567   @type fn: callable
2568   @param fn: Function to be called
2569   @param delay: Either a callable (returning the delay), a tuple of (start,
2570                 factor, limit) (see L{_RetryDelayCalculator}),
2571                 L{RETRY_REMAINING_TIME} or a number (int or float)
2572   @type timeout: float
2573   @param timeout: Total timeout
2574   @type wait_fn: callable
2575   @param wait_fn: Waiting function
2576   @return: Return value of function
2577
2578   """
2579   assert callable(fn)
2580   assert callable(wait_fn)
2581   assert callable(_time_fn)
2582
2583   if args is None:
2584     args = []
2585
2586   end_time = _time_fn() + timeout
2587
2588   if callable(delay):
2589     # External function to calculate delay
2590     calc_delay = delay
2591
2592   elif isinstance(delay, (tuple, list)):
2593     # Increasing delay with optional upper boundary
2594     (start, factor, limit) = delay
2595     calc_delay = _RetryDelayCalculator(start, factor, limit)
2596
2597   elif delay is RETRY_REMAINING_TIME:
2598     # Always use the remaining time
2599     calc_delay = None
2600
2601   else:
2602     # Static delay
2603     calc_delay = lambda: delay
2604
2605   assert calc_delay is None or callable(calc_delay)
2606
2607   while True:
2608     try:
2609       # pylint: disable-msg=W0142
2610       return fn(*args)
2611     except RetryAgain:
2612       pass
2613     except RetryTimeout:
2614       raise errors.ProgrammerError("Nested retry loop detected that didn't"
2615                                    " handle RetryTimeout")
2616
2617     remaining_time = end_time - _time_fn()
2618
2619     if remaining_time < 0.0:
2620       raise RetryTimeout()
2621
2622     assert remaining_time >= 0.0
2623
2624     if calc_delay is None:
2625       wait_fn(remaining_time)
2626     else:
2627       current_delay = calc_delay()
2628       if current_delay > 0.0:
2629         wait_fn(current_delay)
2630
2631
2632 class FileLock(object):
2633   """Utility class for file locks.
2634
2635   """
2636   def __init__(self, fd, filename):
2637     """Constructor for FileLock.
2638
2639     @type fd: file
2640     @param fd: File object
2641     @type filename: str
2642     @param filename: Path of the file opened at I{fd}
2643
2644     """
2645     self.fd = fd
2646     self.filename = filename
2647
2648   @classmethod
2649   def Open(cls, filename):
2650     """Creates and opens a file to be used as a file-based lock.
2651
2652     @type filename: string
2653     @param filename: path to the file to be locked
2654
2655     """
2656     # Using "os.open" is necessary to allow both opening existing file
2657     # read/write and creating if not existing. Vanilla "open" will truncate an
2658     # existing file -or- allow creating if not existing.
2659     return cls(os.fdopen(os.open(filename, os.O_RDWR | os.O_CREAT), "w+"),
2660                filename)
2661
2662   def __del__(self):
2663     self.Close()
2664
2665   def Close(self):
2666     """Close the file and release the lock.
2667
2668     """
2669     if hasattr(self, "fd") and self.fd:
2670       self.fd.close()
2671       self.fd = None
2672
2673   def _flock(self, flag, blocking, timeout, errmsg):
2674     """Wrapper for fcntl.flock.
2675
2676     @type flag: int
2677     @param flag: operation flag
2678     @type blocking: bool
2679     @param blocking: whether the operation should be done in blocking mode.
2680     @type timeout: None or float
2681     @param timeout: for how long the operation should be retried (implies
2682                     non-blocking mode).
2683     @type errmsg: string
2684     @param errmsg: error message in case operation fails.
2685
2686     """
2687     assert self.fd, "Lock was closed"
2688     assert timeout is None or timeout >= 0, \
2689       "If specified, timeout must be positive"
2690     assert not (flag & fcntl.LOCK_NB), "LOCK_NB must not be set"
2691
2692     # When a timeout is used, LOCK_NB must always be set
2693     if not (timeout is None and blocking):
2694       flag |= fcntl.LOCK_NB
2695
2696     if timeout is None:
2697       self._Lock(self.fd, flag, timeout)
2698     else:
2699       try:
2700         Retry(self._Lock, (0.1, 1.2, 1.0), timeout,
2701               args=(self.fd, flag, timeout))
2702       except RetryTimeout:
2703         raise errors.LockError(errmsg)
2704
2705   @staticmethod
2706   def _Lock(fd, flag, timeout):
2707     try:
2708       fcntl.flock(fd, flag)
2709     except IOError, err:
2710       if timeout is not None and err.errno == errno.EAGAIN:
2711         raise RetryAgain()
2712
2713       logging.exception("fcntl.flock failed")
2714       raise
2715
2716   def Exclusive(self, blocking=False, timeout=None):
2717     """Locks the file in exclusive mode.
2718
2719     @type blocking: boolean
2720     @param blocking: whether to block and wait until we
2721         can lock the file or return immediately
2722     @type timeout: int or None
2723     @param timeout: if not None, the duration to wait for the lock
2724         (in blocking mode)
2725
2726     """
2727     self._flock(fcntl.LOCK_EX, blocking, timeout,
2728                 "Failed to lock %s in exclusive mode" % self.filename)
2729
2730   def Shared(self, blocking=False, timeout=None):
2731     """Locks the file in shared mode.
2732
2733     @type blocking: boolean
2734     @param blocking: whether to block and wait until we
2735         can lock the file or return immediately
2736     @type timeout: int or None
2737     @param timeout: if not None, the duration to wait for the lock
2738         (in blocking mode)
2739
2740     """
2741     self._flock(fcntl.LOCK_SH, blocking, timeout,
2742                 "Failed to lock %s in shared mode" % self.filename)
2743
2744   def Unlock(self, blocking=True, timeout=None):
2745     """Unlocks the file.
2746
2747     According to C{flock(2)}, unlocking can also be a nonblocking
2748     operation::
2749
2750       To make a non-blocking request, include LOCK_NB with any of the above
2751       operations.
2752
2753     @type blocking: boolean
2754     @param blocking: whether to block and wait until we
2755         can lock the file or return immediately
2756     @type timeout: int or None
2757     @param timeout: if not None, the duration to wait for the lock
2758         (in blocking mode)
2759
2760     """
2761     self._flock(fcntl.LOCK_UN, blocking, timeout,
2762                 "Failed to unlock %s" % self.filename)
2763
2764
2765 class LineSplitter:
2766   """Splits data chunks into lines separated by newline.
2767
2768   Instances provide a file-like interface.
2769
2770   """
2771   def __init__(self, line_fn, *args):
2772     """Initializes this class.
2773
2774     @type line_fn: callable
2775     @param line_fn: Function called for each line, first parameter is line
2776     @param args: Extra arguments for L{line_fn}
2777
2778     """
2779     assert callable(line_fn)
2780
2781     if args:
2782       # Python 2.4 doesn't have functools.partial yet
2783       self._line_fn = \
2784         lambda line: line_fn(line, *args) # pylint: disable-msg=W0142
2785     else:
2786       self._line_fn = line_fn
2787
2788     self._lines = collections.deque()
2789     self._buffer = ""
2790
2791   def write(self, data):
2792     parts = (self._buffer + data).split("\n")
2793     self._buffer = parts.pop()
2794     self._lines.extend(parts)
2795
2796   def flush(self):
2797     while self._lines:
2798       self._line_fn(self._lines.popleft().rstrip("\r\n"))
2799
2800   def close(self):
2801     self.flush()
2802     if self._buffer:
2803       self._line_fn(self._buffer)
2804
2805
2806 def SignalHandled(signums):
2807   """Signal Handled decoration.
2808
2809   This special decorator installs a signal handler and then calls the target
2810   function. The function must accept a 'signal_handlers' keyword argument,
2811   which will contain a dict indexed by signal number, with SignalHandler
2812   objects as values.
2813
2814   The decorator can be safely stacked with iself, to handle multiple signals
2815   with different handlers.
2816
2817   @type signums: list
2818   @param signums: signals to intercept
2819
2820   """
2821   def wrap(fn):
2822     def sig_function(*args, **kwargs):
2823       assert 'signal_handlers' not in kwargs or \
2824              kwargs['signal_handlers'] is None or \
2825              isinstance(kwargs['signal_handlers'], dict), \
2826              "Wrong signal_handlers parameter in original function call"
2827       if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
2828         signal_handlers = kwargs['signal_handlers']
2829       else:
2830         signal_handlers = {}
2831         kwargs['signal_handlers'] = signal_handlers
2832       sighandler = SignalHandler(signums)
2833       try:
2834         for sig in signums:
2835           signal_handlers[sig] = sighandler
2836         return fn(*args, **kwargs)
2837       finally:
2838         sighandler.Reset()
2839     return sig_function
2840   return wrap
2841
2842
2843 class SignalHandler(object):
2844   """Generic signal handler class.
2845
2846   It automatically restores the original handler when deconstructed or
2847   when L{Reset} is called. You can either pass your own handler
2848   function in or query the L{called} attribute to detect whether the
2849   signal was sent.
2850
2851   @type signum: list
2852   @ivar signum: the signals we handle
2853   @type called: boolean
2854   @ivar called: tracks whether any of the signals have been raised
2855
2856   """
2857   def __init__(self, signum):
2858     """Constructs a new SignalHandler instance.
2859
2860     @type signum: int or list of ints
2861     @param signum: Single signal number or set of signal numbers
2862
2863     """
2864     self.signum = set(signum)
2865     self.called = False
2866
2867     self._previous = {}
2868     try:
2869       for signum in self.signum:
2870         # Setup handler
2871         prev_handler = signal.signal(signum, self._HandleSignal)
2872         try:
2873           self._previous[signum] = prev_handler
2874         except:
2875           # Restore previous handler
2876           signal.signal(signum, prev_handler)
2877           raise
2878     except:
2879       # Reset all handlers
2880       self.Reset()
2881       # Here we have a race condition: a handler may have already been called,
2882       # but there's not much we can do about it at this point.
2883       raise
2884
2885   def __del__(self):
2886     self.Reset()
2887
2888   def Reset(self):
2889     """Restore previous handler.
2890
2891     This will reset all the signals to their previous handlers.
2892
2893     """
2894     for signum, prev_handler in self._previous.items():
2895       signal.signal(signum, prev_handler)
2896       # If successful, remove from dict
2897       del self._previous[signum]
2898
2899   def Clear(self):
2900     """Unsets the L{called} flag.
2901
2902     This function can be used in case a signal may arrive several times.
2903
2904     """
2905     self.called = False
2906
2907   # we don't care about arguments, but we leave them named for the future
2908   def _HandleSignal(self, signum, frame): # pylint: disable-msg=W0613
2909     """Actual signal handling function.
2910
2911     """
2912     # This is not nice and not absolutely atomic, but it appears to be the only
2913     # solution in Python -- there are no atomic types.
2914     self.called = True
2915
2916
2917 class FieldSet(object):
2918   """A simple field set.
2919
2920   Among the features are:
2921     - checking if a string is among a list of static string or regex objects
2922     - checking if a whole list of string matches
2923     - returning the matching groups from a regex match
2924
2925   Internally, all fields are held as regular expression objects.
2926
2927   """
2928   def __init__(self, *items):
2929     self.items = [re.compile("^%s$" % value) for value in items]
2930
2931   def Extend(self, other_set):
2932     """Extend the field set with the items from another one"""
2933     self.items.extend(other_set.items)
2934
2935   def Matches(self, field):
2936     """Checks if a field matches the current set
2937
2938     @type field: str
2939     @param field: the string to match
2940     @return: either None or a regular expression match object
2941
2942     """
2943     for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
2944       return m
2945     return None
2946
2947   def NonMatching(self, items):
2948     """Returns the list of fields not matching the current set
2949
2950     @type items: list
2951     @param items: the list of fields to check
2952     @rtype: list
2953     @return: list of non-matching fields
2954
2955     """
2956     return [val for val in items if not self.Matches(val)]