Abstract Linux node information in hv_base
[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   do_remove = True
1288   # here we need to make sure we remove the temp file, if any error
1289   # leaves it in place
1290   try:
1291     if uid != -1 or gid != -1:
1292       os.chown(new_name, uid, gid)
1293     if mode:
1294       os.chmod(new_name, mode)
1295     if callable(prewrite):
1296       prewrite(fd)
1297     if data is not None:
1298       os.write(fd, data)
1299     else:
1300       fn(fd)
1301     if callable(postwrite):
1302       postwrite(fd)
1303     os.fsync(fd)
1304     if atime is not None and mtime is not None:
1305       os.utime(new_name, (atime, mtime))
1306     if not dry_run:
1307       os.rename(new_name, file_name)
1308       do_remove = False
1309   finally:
1310     if close:
1311       os.close(fd)
1312       result = None
1313     else:
1314       result = fd
1315     if do_remove:
1316       RemoveFile(new_name)
1317
1318   return result
1319
1320
1321 def FirstFree(seq, base=0):
1322   """Returns the first non-existing integer from seq.
1323
1324   The seq argument should be a sorted list of positive integers. The
1325   first time the index of an element is smaller than the element
1326   value, the index will be returned.
1327
1328   The base argument is used to start at a different offset,
1329   i.e. C{[3, 4, 6]} with I{offset=3} will return 5.
1330
1331   Example: C{[0, 1, 3]} will return I{2}.
1332
1333   @type seq: sequence
1334   @param seq: the sequence to be analyzed.
1335   @type base: int
1336   @param base: use this value as the base index of the sequence
1337   @rtype: int
1338   @return: the first non-used index in the sequence
1339
1340   """
1341   for idx, elem in enumerate(seq):
1342     assert elem >= base, "Passed element is higher than base offset"
1343     if elem > idx + base:
1344       # idx is not used
1345       return idx + base
1346   return None
1347
1348
1349 def all(seq, pred=bool):
1350   "Returns True if pred(x) is True for every element in the iterable"
1351   for elem in itertools.ifilterfalse(pred, seq):
1352     return False
1353   return True
1354
1355
1356 def any(seq, pred=bool):
1357   "Returns True if pred(x) is True for at least one element in the iterable"
1358   for elem in itertools.ifilter(pred, seq):
1359     return True
1360   return False
1361
1362
1363 def UniqueSequence(seq):
1364   """Returns a list with unique elements.
1365
1366   Element order is preserved.
1367
1368   @type seq: sequence
1369   @param seq: the sequence with the source elementes
1370   @rtype: list
1371   @return: list of unique elements from seq
1372
1373   """
1374   seen = set()
1375   return [i for i in seq if i not in seen and not seen.add(i)]
1376
1377
1378 def IsValidMac(mac):
1379   """Predicate to check if a MAC address is valid.
1380
1381   Checks wether the supplied MAC address is formally correct, only
1382   accepts colon separated format.
1383
1384   @type mac: str
1385   @param mac: the MAC to be validated
1386   @rtype: boolean
1387   @return: True is the MAC seems valid
1388
1389   """
1390   mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$")
1391   return mac_check.match(mac) is not None
1392
1393
1394 def TestDelay(duration):
1395   """Sleep for a fixed amount of time.
1396
1397   @type duration: float
1398   @param duration: the sleep duration
1399   @rtype: boolean
1400   @return: False for negative value, True otherwise
1401
1402   """
1403   if duration < 0:
1404     return False
1405   time.sleep(duration)
1406   return True
1407
1408
1409 def _CloseFDNoErr(fd, retries=5):
1410   """Close a file descriptor ignoring errors.
1411
1412   @type fd: int
1413   @param fd: the file descriptor
1414   @type retries: int
1415   @param retries: how many retries to make, in case we get any
1416       other error than EBADF
1417
1418   """
1419   try:
1420     os.close(fd)
1421   except OSError, err:
1422     if err.errno != errno.EBADF:
1423       if retries > 0:
1424         _CloseFDNoErr(fd, retries - 1)
1425     # else either it's closed already or we're out of retries, so we
1426     # ignore this and go on
1427
1428
1429 def CloseFDs(noclose_fds=None):
1430   """Close file descriptors.
1431
1432   This closes all file descriptors above 2 (i.e. except
1433   stdin/out/err).
1434
1435   @type noclose_fds: list or None
1436   @param noclose_fds: if given, it denotes a list of file descriptor
1437       that should not be closed
1438
1439   """
1440   # Default maximum for the number of available file descriptors.
1441   if 'SC_OPEN_MAX' in os.sysconf_names:
1442     try:
1443       MAXFD = os.sysconf('SC_OPEN_MAX')
1444       if MAXFD < 0:
1445         MAXFD = 1024
1446     except OSError:
1447       MAXFD = 1024
1448   else:
1449     MAXFD = 1024
1450   maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1451   if (maxfd == resource.RLIM_INFINITY):
1452     maxfd = MAXFD
1453
1454   # Iterate through and close all file descriptors (except the standard ones)
1455   for fd in range(3, maxfd):
1456     if noclose_fds and fd in noclose_fds:
1457       continue
1458     _CloseFDNoErr(fd)
1459
1460
1461 def Daemonize(logfile):
1462   """Daemonize the current process.
1463
1464   This detaches the current process from the controlling terminal and
1465   runs it in the background as a daemon.
1466
1467   @type logfile: str
1468   @param logfile: the logfile to which we should redirect stdout/stderr
1469   @rtype: int
1470   @return: the value zero
1471
1472   """
1473   UMASK = 077
1474   WORKDIR = "/"
1475
1476   # this might fail
1477   pid = os.fork()
1478   if (pid == 0):  # The first child.
1479     os.setsid()
1480     # this might fail
1481     pid = os.fork() # Fork a second child.
1482     if (pid == 0):  # The second child.
1483       os.chdir(WORKDIR)
1484       os.umask(UMASK)
1485     else:
1486       # exit() or _exit()?  See below.
1487       os._exit(0) # Exit parent (the first child) of the second child.
1488   else:
1489     os._exit(0) # Exit parent of the first child.
1490
1491   for fd in range(3):
1492     _CloseFDNoErr(fd)
1493   i = os.open("/dev/null", os.O_RDONLY) # stdin
1494   assert i == 0, "Can't close/reopen stdin"
1495   i = os.open(logfile, os.O_WRONLY|os.O_CREAT|os.O_APPEND, 0600) # stdout
1496   assert i == 1, "Can't close/reopen stdout"
1497   # Duplicate standard output to standard error.
1498   os.dup2(1, 2)
1499   return 0
1500
1501
1502 def DaemonPidFileName(name):
1503   """Compute a ganeti pid file absolute path
1504
1505   @type name: str
1506   @param name: the daemon name
1507   @rtype: str
1508   @return: the full path to the pidfile corresponding to the given
1509       daemon name
1510
1511   """
1512   return os.path.join(constants.RUN_GANETI_DIR, "%s.pid" % name)
1513
1514
1515 def WritePidFile(name):
1516   """Write the current process pidfile.
1517
1518   The file will be written to L{constants.RUN_GANETI_DIR}I{/name.pid}
1519
1520   @type name: str
1521   @param name: the daemon name to use
1522   @raise errors.GenericError: if the pid file already exists and
1523       points to a live process
1524
1525   """
1526   pid = os.getpid()
1527   pidfilename = DaemonPidFileName(name)
1528   if IsProcessAlive(ReadPidFile(pidfilename)):
1529     raise errors.GenericError("%s contains a live process" % pidfilename)
1530
1531   WriteFile(pidfilename, data="%d\n" % pid)
1532
1533
1534 def RemovePidFile(name):
1535   """Remove the current process pidfile.
1536
1537   Any errors are ignored.
1538
1539   @type name: str
1540   @param name: the daemon name used to derive the pidfile name
1541
1542   """
1543   pid = os.getpid()
1544   pidfilename = DaemonPidFileName(name)
1545   # TODO: we could check here that the file contains our pid
1546   try:
1547     RemoveFile(pidfilename)
1548   except:
1549     pass
1550
1551
1552 def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1553                 waitpid=False):
1554   """Kill a process given by its pid.
1555
1556   @type pid: int
1557   @param pid: The PID to terminate.
1558   @type signal_: int
1559   @param signal_: The signal to send, by default SIGTERM
1560   @type timeout: int
1561   @param timeout: The timeout after which, if the process is still alive,
1562                   a SIGKILL will be sent. If not positive, no such checking
1563                   will be done
1564   @type waitpid: boolean
1565   @param waitpid: If true, we should waitpid on this process after
1566       sending signals, since it's our own child and otherwise it
1567       would remain as zombie
1568
1569   """
1570   def _helper(pid, signal_, wait):
1571     """Simple helper to encapsulate the kill/waitpid sequence"""
1572     os.kill(pid, signal_)
1573     if wait:
1574       try:
1575         os.waitpid(pid, os.WNOHANG)
1576       except OSError:
1577         pass
1578
1579   if pid <= 0:
1580     # kill with pid=0 == suicide
1581     raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1582
1583   if not IsProcessAlive(pid):
1584     return
1585   _helper(pid, signal_, waitpid)
1586   if timeout <= 0:
1587     return
1588
1589   # Wait up to $timeout seconds
1590   end = time.time() + timeout
1591   wait = 0.01
1592   while time.time() < end and IsProcessAlive(pid):
1593     try:
1594       (result_pid, _) = os.waitpid(pid, os.WNOHANG)
1595       if result_pid > 0:
1596         break
1597     except OSError:
1598       pass
1599     time.sleep(wait)
1600     # Make wait time longer for next try
1601     if wait < 0.1:
1602       wait *= 1.5
1603
1604   if IsProcessAlive(pid):
1605     # Kill process if it's still alive
1606     _helper(pid, signal.SIGKILL, waitpid)
1607
1608
1609 def FindFile(name, search_path, test=os.path.exists):
1610   """Look for a filesystem object in a given path.
1611
1612   This is an abstract method to search for filesystem object (files,
1613   dirs) under a given search path.
1614
1615   @type name: str
1616   @param name: the name to look for
1617   @type search_path: str
1618   @param search_path: location to start at
1619   @type test: callable
1620   @param test: a function taking one argument that should return True
1621       if the a given object is valid; the default value is
1622       os.path.exists, causing only existing files to be returned
1623   @rtype: str or None
1624   @return: full path to the object if found, None otherwise
1625
1626   """
1627   for dir_name in search_path:
1628     item_name = os.path.sep.join([dir_name, name])
1629     if test(item_name):
1630       return item_name
1631   return None
1632
1633
1634 def CheckVolumeGroupSize(vglist, vgname, minsize):
1635   """Checks if the volume group list is valid.
1636
1637   The function will check if a given volume group is in the list of
1638   volume groups and has a minimum size.
1639
1640   @type vglist: dict
1641   @param vglist: dictionary of volume group names and their size
1642   @type vgname: str
1643   @param vgname: the volume group we should check
1644   @type minsize: int
1645   @param minsize: the minimum size we accept
1646   @rtype: None or str
1647   @return: None for success, otherwise the error message
1648
1649   """
1650   vgsize = vglist.get(vgname, None)
1651   if vgsize is None:
1652     return "volume group '%s' missing" % vgname
1653   elif vgsize < minsize:
1654     return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1655             (vgname, minsize, vgsize))
1656   return None
1657
1658
1659 def SplitTime(value):
1660   """Splits time as floating point number into a tuple.
1661
1662   @param value: Time in seconds
1663   @type value: int or float
1664   @return: Tuple containing (seconds, microseconds)
1665
1666   """
1667   (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1668
1669   assert 0 <= seconds, \
1670     "Seconds must be larger than or equal to 0, but are %s" % seconds
1671   assert 0 <= microseconds <= 999999, \
1672     "Microseconds must be 0-999999, but are %s" % microseconds
1673
1674   return (int(seconds), int(microseconds))
1675
1676
1677 def MergeTime(timetuple):
1678   """Merges a tuple into time as a floating point number.
1679
1680   @param timetuple: Time as tuple, (seconds, microseconds)
1681   @type timetuple: tuple
1682   @return: Time as a floating point number expressed in seconds
1683
1684   """
1685   (seconds, microseconds) = timetuple
1686
1687   assert 0 <= seconds, \
1688     "Seconds must be larger than or equal to 0, but are %s" % seconds
1689   assert 0 <= microseconds <= 999999, \
1690     "Microseconds must be 0-999999, but are %s" % microseconds
1691
1692   return float(seconds) + (float(microseconds) * 0.000001)
1693
1694
1695 def GetNodeDaemonPort():
1696   """Get the node daemon port for this cluster.
1697
1698   Note that this routine does not read a ganeti-specific file, but
1699   instead uses C{socket.getservbyname} to allow pre-customization of
1700   this parameter outside of Ganeti.
1701
1702   @rtype: int
1703
1704   """
1705   try:
1706     port = socket.getservbyname("ganeti-noded", "tcp")
1707   except socket.error:
1708     port = constants.DEFAULT_NODED_PORT
1709
1710   return port
1711
1712
1713 def SetupLogging(logfile, debug=False, stderr_logging=False, program="",
1714                  multithreaded=False):
1715   """Configures the logging module.
1716
1717   @type logfile: str
1718   @param logfile: the filename to which we should log
1719   @type debug: boolean
1720   @param debug: whether to enable debug messages too or
1721       only those at C{INFO} and above level
1722   @type stderr_logging: boolean
1723   @param stderr_logging: whether we should also log to the standard error
1724   @type program: str
1725   @param program: the name under which we should log messages
1726   @type multithreaded: boolean
1727   @param multithreaded: if True, will add the thread name to the log file
1728   @raise EnvironmentError: if we can't open the log file and
1729       stderr logging is disabled
1730
1731   """
1732   fmt = "%(asctime)s: " + program + " pid=%(process)d"
1733   if multithreaded:
1734     fmt += "/%(threadName)s"
1735   if debug:
1736     fmt += " %(module)s:%(lineno)s"
1737   fmt += " %(levelname)s %(message)s"
1738   formatter = logging.Formatter(fmt)
1739
1740   root_logger = logging.getLogger("")
1741   root_logger.setLevel(logging.NOTSET)
1742
1743   # Remove all previously setup handlers
1744   for handler in root_logger.handlers:
1745     handler.close()
1746     root_logger.removeHandler(handler)
1747
1748   if stderr_logging:
1749     stderr_handler = logging.StreamHandler()
1750     stderr_handler.setFormatter(formatter)
1751     if debug:
1752       stderr_handler.setLevel(logging.NOTSET)
1753     else:
1754       stderr_handler.setLevel(logging.CRITICAL)
1755     root_logger.addHandler(stderr_handler)
1756
1757   # this can fail, if the logging directories are not setup or we have
1758   # a permisssion problem; in this case, it's best to log but ignore
1759   # the error if stderr_logging is True, and if false we re-raise the
1760   # exception since otherwise we could run but without any logs at all
1761   try:
1762     logfile_handler = logging.FileHandler(logfile)
1763     logfile_handler.setFormatter(formatter)
1764     if debug:
1765       logfile_handler.setLevel(logging.DEBUG)
1766     else:
1767       logfile_handler.setLevel(logging.INFO)
1768     root_logger.addHandler(logfile_handler)
1769   except EnvironmentError:
1770     if stderr_logging:
1771       logging.exception("Failed to enable logging to file '%s'", logfile)
1772     else:
1773       # we need to re-raise the exception
1774       raise
1775
1776
1777 def TailFile(fname, lines=20):
1778   """Return the last lines from a file.
1779
1780   @note: this function will only read and parse the last 4KB of
1781       the file; if the lines are very long, it could be that less
1782       than the requested number of lines are returned
1783
1784   @param fname: the file name
1785   @type lines: int
1786   @param lines: the (maximum) number of lines to return
1787
1788   """
1789   fd = open(fname, "r")
1790   try:
1791     fd.seek(0, 2)
1792     pos = fd.tell()
1793     pos = max(0, pos-4096)
1794     fd.seek(pos, 0)
1795     raw_data = fd.read()
1796   finally:
1797     fd.close()
1798
1799   rows = raw_data.splitlines()
1800   return rows[-lines:]
1801
1802
1803 def SafeEncode(text):
1804   """Return a 'safe' version of a source string.
1805
1806   This function mangles the input string and returns a version that
1807   should be safe to disply/encode as ASCII. To this end, we first
1808   convert it to ASCII using the 'backslashreplace' encoding which
1809   should get rid of any non-ASCII chars, and then we again encode it
1810   via 'string_escape' which converts '\n' into '\\n' so that log
1811   messages remain one-line.
1812
1813   @type text: str or unicode
1814   @param text: input data
1815   @rtype: str
1816   @return: a safe version of text
1817
1818   """
1819   text = text.encode('ascii', 'backslashreplace')
1820   text = text.encode('string_escape')
1821   return text
1822
1823
1824 def LockedMethod(fn):
1825   """Synchronized object access decorator.
1826
1827   This decorator is intended to protect access to an object using the
1828   object's own lock which is hardcoded to '_lock'.
1829
1830   """
1831   def _LockDebug(*args, **kwargs):
1832     if debug_locks:
1833       logging.debug(*args, **kwargs)
1834
1835   def wrapper(self, *args, **kwargs):
1836     assert hasattr(self, '_lock')
1837     lock = self._lock
1838     _LockDebug("Waiting for %s", lock)
1839     lock.acquire()
1840     try:
1841       _LockDebug("Acquired %s", lock)
1842       result = fn(self, *args, **kwargs)
1843     finally:
1844       _LockDebug("Releasing %s", lock)
1845       lock.release()
1846       _LockDebug("Released %s", lock)
1847     return result
1848   return wrapper
1849
1850
1851 def LockFile(fd):
1852   """Locks a file using POSIX locks.
1853
1854   @type fd: int
1855   @param fd: the file descriptor we need to lock
1856
1857   """
1858   try:
1859     fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
1860   except IOError, err:
1861     if err.errno == errno.EAGAIN:
1862       raise errors.LockError("File already locked")
1863     raise
1864
1865
1866 class FileLock(object):
1867   """Utility class for file locks.
1868
1869   """
1870   def __init__(self, filename):
1871     """Constructor for FileLock.
1872
1873     This will open the file denoted by the I{filename} argument.
1874
1875     @type filename: str
1876     @param filename: path to the file to be locked
1877
1878     """
1879     self.filename = filename
1880     self.fd = open(self.filename, "w")
1881
1882   def __del__(self):
1883     self.Close()
1884
1885   def Close(self):
1886     """Close the file and release the lock.
1887
1888     """
1889     if self.fd:
1890       self.fd.close()
1891       self.fd = None
1892
1893   def _flock(self, flag, blocking, timeout, errmsg):
1894     """Wrapper for fcntl.flock.
1895
1896     @type flag: int
1897     @param flag: operation flag
1898     @type blocking: bool
1899     @param blocking: whether the operation should be done in blocking mode.
1900     @type timeout: None or float
1901     @param timeout: for how long the operation should be retried (implies
1902                     non-blocking mode).
1903     @type errmsg: string
1904     @param errmsg: error message in case operation fails.
1905
1906     """
1907     assert self.fd, "Lock was closed"
1908     assert timeout is None or timeout >= 0, \
1909       "If specified, timeout must be positive"
1910
1911     if timeout is not None:
1912       flag |= fcntl.LOCK_NB
1913       timeout_end = time.time() + timeout
1914
1915     # Blocking doesn't have effect with timeout
1916     elif not blocking:
1917       flag |= fcntl.LOCK_NB
1918       timeout_end = None
1919
1920     retry = True
1921     while retry:
1922       try:
1923         fcntl.flock(self.fd, flag)
1924         retry = False
1925       except IOError, err:
1926         if err.errno in (errno.EAGAIN, ):
1927           if timeout_end is not None and time.time() < timeout_end:
1928             # Wait before trying again
1929             time.sleep(max(0.1, min(1.0, timeout)))
1930           else:
1931             raise errors.LockError(errmsg)
1932         else:
1933           logging.exception("fcntl.flock failed")
1934           raise
1935
1936   def Exclusive(self, blocking=False, timeout=None):
1937     """Locks the file in exclusive mode.
1938
1939     @type blocking: boolean
1940     @param blocking: whether to block and wait until we
1941         can lock the file or return immediately
1942     @type timeout: int or None
1943     @param timeout: if not None, the duration to wait for the lock
1944         (in blocking mode)
1945
1946     """
1947     self._flock(fcntl.LOCK_EX, blocking, timeout,
1948                 "Failed to lock %s in exclusive mode" % self.filename)
1949
1950   def Shared(self, blocking=False, timeout=None):
1951     """Locks the file in shared mode.
1952
1953     @type blocking: boolean
1954     @param blocking: whether to block and wait until we
1955         can lock the file or return immediately
1956     @type timeout: int or None
1957     @param timeout: if not None, the duration to wait for the lock
1958         (in blocking mode)
1959
1960     """
1961     self._flock(fcntl.LOCK_SH, blocking, timeout,
1962                 "Failed to lock %s in shared mode" % self.filename)
1963
1964   def Unlock(self, blocking=True, timeout=None):
1965     """Unlocks the file.
1966
1967     According to C{flock(2)}, unlocking can also be a nonblocking
1968     operation::
1969
1970       To make a non-blocking request, include LOCK_NB with any of the above
1971       operations.
1972
1973     @type blocking: boolean
1974     @param blocking: whether to block and wait until we
1975         can lock the file or return immediately
1976     @type timeout: int or None
1977     @param timeout: if not None, the duration to wait for the lock
1978         (in blocking mode)
1979
1980     """
1981     self._flock(fcntl.LOCK_UN, blocking, timeout,
1982                 "Failed to unlock %s" % self.filename)
1983
1984
1985 class SignalHandler(object):
1986   """Generic signal handler class.
1987
1988   It automatically restores the original handler when deconstructed or
1989   when L{Reset} is called. You can either pass your own handler
1990   function in or query the L{called} attribute to detect whether the
1991   signal was sent.
1992
1993   @type signum: list
1994   @ivar signum: the signals we handle
1995   @type called: boolean
1996   @ivar called: tracks whether any of the signals have been raised
1997
1998   """
1999   def __init__(self, signum):
2000     """Constructs a new SignalHandler instance.
2001
2002     @type signum: int or list of ints
2003     @param signum: Single signal number or set of signal numbers
2004
2005     """
2006     if isinstance(signum, (int, long)):
2007       self.signum = set([signum])
2008     else:
2009       self.signum = set(signum)
2010
2011     self.called = False
2012
2013     self._previous = {}
2014     try:
2015       for signum in self.signum:
2016         # Setup handler
2017         prev_handler = signal.signal(signum, self._HandleSignal)
2018         try:
2019           self._previous[signum] = prev_handler
2020         except:
2021           # Restore previous handler
2022           signal.signal(signum, prev_handler)
2023           raise
2024     except:
2025       # Reset all handlers
2026       self.Reset()
2027       # Here we have a race condition: a handler may have already been called,
2028       # but there's not much we can do about it at this point.
2029       raise
2030
2031   def __del__(self):
2032     self.Reset()
2033
2034   def Reset(self):
2035     """Restore previous handler.
2036
2037     This will reset all the signals to their previous handlers.
2038
2039     """
2040     for signum, prev_handler in self._previous.items():
2041       signal.signal(signum, prev_handler)
2042       # If successful, remove from dict
2043       del self._previous[signum]
2044
2045   def Clear(self):
2046     """Unsets the L{called} flag.
2047
2048     This function can be used in case a signal may arrive several times.
2049
2050     """
2051     self.called = False
2052
2053   def _HandleSignal(self, signum, frame):
2054     """Actual signal handling function.
2055
2056     """
2057     # This is not nice and not absolutely atomic, but it appears to be the only
2058     # solution in Python -- there are no atomic types.
2059     self.called = True
2060
2061
2062 class FieldSet(object):
2063   """A simple field set.
2064
2065   Among the features are:
2066     - checking if a string is among a list of static string or regex objects
2067     - checking if a whole list of string matches
2068     - returning the matching groups from a regex match
2069
2070   Internally, all fields are held as regular expression objects.
2071
2072   """
2073   def __init__(self, *items):
2074     self.items = [re.compile("^%s$" % value) for value in items]
2075
2076   def Extend(self, other_set):
2077     """Extend the field set with the items from another one"""
2078     self.items.extend(other_set.items)
2079
2080   def Matches(self, field):
2081     """Checks if a field matches the current set
2082
2083     @type field: str
2084     @param field: the string to match
2085     @return: either False or a regular expression match object
2086
2087     """
2088     for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
2089       return m
2090     return False
2091
2092   def NonMatching(self, items):
2093     """Returns the list of fields not matching the current set
2094
2095     @type items: list
2096     @param items: the list of fields to check
2097     @rtype: list
2098     @return: list of non-matching fields
2099
2100     """
2101     return [val for val in items if not self.Matches(val)]