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