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