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