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