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