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