Add utility function to start daemon
[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 sys
32 import time
33 import subprocess
34 import re
35 import socket
36 import tempfile
37 import shutil
38 import errno
39 import pwd
40 import itertools
41 import select
42 import fcntl
43 import resource
44 import logging
45 import logging.handlers
46 import signal
47 import OpenSSL
48
49 from cStringIO import StringIO
50
51 try:
52   from hashlib import sha1
53 except ImportError:
54   import sha
55   sha1 = sha.new
56
57 from ganeti import errors
58 from ganeti import constants
59
60
61 _locksheld = []
62 _re_shell_unquoted = re.compile('^[-.,=:/_+@A-Za-z0-9]+$')
63
64 debug_locks = False
65
66 #: when set to True, L{RunCmd} is disabled
67 no_fork = False
68
69 _RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
70
71
72 class RunResult(object):
73   """Holds the result of running external programs.
74
75   @type exit_code: int
76   @ivar exit_code: the exit code of the program, or None (if the program
77       didn't exit())
78   @type signal: int or None
79   @ivar signal: the signal that caused the program to finish, or None
80       (if the program wasn't terminated by a signal)
81   @type stdout: str
82   @ivar stdout: the standard output of the program
83   @type stderr: str
84   @ivar stderr: the standard error of the program
85   @type failed: boolean
86   @ivar failed: True in case the program was
87       terminated by a signal or exited with a non-zero exit code
88   @ivar fail_reason: a string detailing the termination reason
89
90   """
91   __slots__ = ["exit_code", "signal", "stdout", "stderr",
92                "failed", "fail_reason", "cmd"]
93
94
95   def __init__(self, exit_code, signal_, stdout, stderr, cmd):
96     self.cmd = cmd
97     self.exit_code = exit_code
98     self.signal = signal_
99     self.stdout = stdout
100     self.stderr = stderr
101     self.failed = (signal_ is not None or exit_code != 0)
102
103     if self.signal is not None:
104       self.fail_reason = "terminated by signal %s" % self.signal
105     elif self.exit_code is not None:
106       self.fail_reason = "exited with exit code %s" % self.exit_code
107     else:
108       self.fail_reason = "unable to determine termination reason"
109
110     if self.failed:
111       logging.debug("Command '%s' failed (%s); output: %s",
112                     self.cmd, self.fail_reason, self.output)
113
114   def _GetOutput(self):
115     """Returns the combined stdout and stderr for easier usage.
116
117     """
118     return self.stdout + self.stderr
119
120   output = property(_GetOutput, None, None, "Return full output")
121
122
123 def _BuildCmdEnvironment(env):
124   """Builds the environment for an external program.
125
126   """
127   cmd_env = os.environ.copy()
128   cmd_env["LC_ALL"] = "C"
129   if env is not None:
130     cmd_env.update(env)
131   return cmd_env
132
133
134 def RunCmd(cmd, env=None, output=None, cwd="/"):
135   """Execute a (shell) command.
136
137   The command should not read from its standard input, as it will be
138   closed.
139
140   @type cmd: string or list
141   @param cmd: Command to run
142   @type env: dict
143   @param env: Additional environment variables
144   @type output: str
145   @param output: if desired, the output of the command can be
146       saved in a file instead of the RunResult instance; this
147       parameter denotes the file name (if not None)
148   @type cwd: string
149   @param cwd: if specified, will be used as the working
150       directory for the command; the default will be /
151   @rtype: L{RunResult}
152   @return: RunResult instance
153   @raise errors.ProgrammerError: if we call this when forks are disabled
154
155   """
156   if no_fork:
157     raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
158
159   if isinstance(cmd, basestring):
160     strcmd = cmd
161     shell = True
162   else:
163     cmd = [str(val) for val in cmd]
164     strcmd = ShellQuoteArgs(cmd)
165     shell = False
166
167   if output:
168     logging.debug("RunCmd %s, output file '%s'", strcmd, output)
169   else:
170     logging.debug("RunCmd %s", strcmd)
171
172   cmd_env = _BuildCmdEnvironment(env)
173
174   try:
175     if output is None:
176       out, err, status = _RunCmdPipe(cmd, cmd_env, shell, cwd)
177     else:
178       status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
179       out = err = ""
180   except OSError, err:
181     if err.errno == errno.ENOENT:
182       raise errors.OpExecError("Can't execute '%s': not found (%s)" %
183                                (strcmd, err))
184     else:
185       raise
186
187   if status >= 0:
188     exitcode = status
189     signal_ = None
190   else:
191     exitcode = None
192     signal_ = -status
193
194   return RunResult(exitcode, signal_, out, err, strcmd)
195
196
197 def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None,
198                 pidfile=None):
199   """Start a daemon process after forking twice.
200
201   @type cmd: string or list
202   @param cmd: Command to run
203   @type env: dict
204   @param env: Additional environment variables
205   @type cwd: string
206   @param cwd: Working directory for the program
207   @type output: string
208   @param output: Path to file in which to save the output
209   @type output_fd: int
210   @param output_fd: File descriptor for output
211   @type pidfile: string
212   @param pidfile: Process ID file
213   @rtype: int
214   @return: Daemon process ID
215   @raise errors.ProgrammerError: if we call this when forks are disabled
216
217   """
218   if no_fork:
219     raise errors.ProgrammerError("utils.StartDaemon() called with fork()"
220                                  " disabled")
221
222   if output and not (bool(output) ^ (output_fd is not None)):
223     raise errors.ProgrammerError("Only one of 'output' and 'output_fd' can be"
224                                  " specified")
225
226   if isinstance(cmd, basestring):
227     cmd = ["/bin/sh", "-c", cmd]
228
229   strcmd = ShellQuoteArgs(cmd)
230
231   if output:
232     logging.debug("StartDaemon %s, output file '%s'", strcmd, output)
233   else:
234     logging.debug("StartDaemon %s", strcmd)
235
236   cmd_env = _BuildCmdEnvironment(env)
237
238   # Create pipe for sending PID back
239   (pidpipe_read, pidpipe_write) = os.pipe()
240   try:
241     try:
242       # Create pipe for sending error messages
243       (errpipe_read, errpipe_write) = os.pipe()
244       try:
245         try:
246           # First fork
247           pid = os.fork()
248           if pid == 0:
249             try:
250               # Child process, won't return
251               _RunCmdDaemonChild(errpipe_read, errpipe_write,
252                                  pidpipe_read, pidpipe_write,
253                                  cmd, cmd_env, cwd,
254                                  output, output_fd, pidfile)
255             finally:
256               # Well, maybe child process failed
257               os._exit(1)
258         finally:
259           _CloseFDNoErr(errpipe_write)
260
261         # Wait for daemon to be started (or an error message to arrive) and read
262         # up to 100 KB as an error message
263         errormsg = RetryOnSignal(os.read, errpipe_read, 100 * 1024)
264       finally:
265         _CloseFDNoErr(errpipe_read)
266     finally:
267       _CloseFDNoErr(pidpipe_write)
268
269     # Read up to 128 bytes for PID
270     pidtext = RetryOnSignal(os.read, pidpipe_read, 128)
271   finally:
272     _CloseFDNoErr(pidpipe_read)
273
274   # Try to avoid zombies by waiting for child process
275   try:
276     os.waitpid(pid, 0)
277   except OSError:
278     pass
279
280   if errormsg:
281     raise errors.OpExecError("Error when starting daemon process: %r" %
282                              errormsg)
283
284   try:
285     return int(pidtext)
286   except (ValueError, TypeError), err:
287     raise errors.OpExecError("Error while trying to parse PID %r: %s" %
288                              (pidtext, err))
289
290
291 def _RunCmdDaemonChild(errpipe_read, errpipe_write,
292                        pidpipe_read, pidpipe_write,
293                        args, env, cwd,
294                        output, fd_output, pidfile):
295   """Child process for starting daemon.
296
297   """
298   try:
299     # Close parent's side
300     _CloseFDNoErr(errpipe_read)
301     _CloseFDNoErr(pidpipe_read)
302
303     # First child process
304     os.chdir("/")
305     os.umask(077)
306     os.setsid()
307
308     # And fork for the second time
309     pid = os.fork()
310     if pid != 0:
311       # Exit first child process
312       os._exit(0) # pylint: disable-msg=W0212
313
314     # Make sure pipe is closed on execv* (and thereby notifies original process)
315     SetCloseOnExecFlag(errpipe_write, True)
316
317     # List of file descriptors to be left open
318     noclose_fds = [errpipe_write]
319
320     # Open PID file
321     if pidfile:
322       try:
323         # TODO: Atomic replace with another locked file instead of writing into
324         # it after creating
325         fd_pidfile = os.open(pidfile, os.O_WRONLY | os.O_CREAT, 0600)
326
327         # Lock the PID file (and fail if not possible to do so). Any code
328         # wanting to send a signal to the daemon should try to lock the PID
329         # file before reading it. If acquiring the lock succeeds, the daemon is
330         # no longer running and the signal should not be sent.
331         LockFile(fd_pidfile)
332
333         os.write(fd_pidfile, "%d\n" % os.getpid())
334       except Exception, err:
335         raise Exception("Creating and locking PID file failed: %s" % err)
336
337       # Keeping the file open to hold the lock
338       noclose_fds.append(fd_pidfile)
339
340       SetCloseOnExecFlag(fd_pidfile, False)
341     else:
342       fd_pidfile = None
343
344     # Open /dev/null
345     fd_devnull = os.open(os.devnull, os.O_RDWR)
346
347     assert not output or (bool(output) ^ (fd_output is not None))
348
349     if fd_output is not None:
350       pass
351     elif output:
352       # Open output file
353       try:
354         # TODO: Implement flag to set append=yes/no
355         fd_output = os.open(output, os.O_WRONLY | os.O_CREAT, 0600)
356       except EnvironmentError, err:
357         raise Exception("Opening output file failed: %s" % err)
358     else:
359       fd_output = fd_devnull
360
361     # Redirect standard I/O
362     os.dup2(fd_devnull, 0)
363     os.dup2(fd_output, 1)
364     os.dup2(fd_output, 2)
365
366     # Send daemon PID to parent
367     RetryOnSignal(os.write, pidpipe_write, str(os.getpid()))
368
369     # Close all file descriptors except stdio and error message pipe
370     CloseFDs(noclose_fds=noclose_fds)
371
372     # Change working directory
373     os.chdir(cwd)
374
375     if env is None:
376       os.execvp(args[0], args)
377     else:
378       os.execvpe(args[0], args, env)
379   except: # pylint: disable-msg=W0702
380     try:
381       # Report errors to original process
382       buf = str(sys.exc_info()[1])
383
384       RetryOnSignal(os.write, errpipe_write, buf)
385     except: # pylint: disable-msg=W0702
386       # Ignore errors in error handling
387       pass
388
389   os._exit(1) # pylint: disable-msg=W0212
390
391
392 def _RunCmdPipe(cmd, env, via_shell, cwd):
393   """Run a command and return its output.
394
395   @type  cmd: string or list
396   @param cmd: Command to run
397   @type env: dict
398   @param env: The environment to use
399   @type via_shell: bool
400   @param via_shell: if we should run via the shell
401   @type cwd: string
402   @param cwd: the working directory for the program
403   @rtype: tuple
404   @return: (out, err, status)
405
406   """
407   poller = select.poll()
408   child = subprocess.Popen(cmd, shell=via_shell,
409                            stderr=subprocess.PIPE,
410                            stdout=subprocess.PIPE,
411                            stdin=subprocess.PIPE,
412                            close_fds=True, env=env,
413                            cwd=cwd)
414
415   child.stdin.close()
416   poller.register(child.stdout, select.POLLIN)
417   poller.register(child.stderr, select.POLLIN)
418   out = StringIO()
419   err = StringIO()
420   fdmap = {
421     child.stdout.fileno(): (out, child.stdout),
422     child.stderr.fileno(): (err, child.stderr),
423     }
424   for fd in fdmap:
425     SetNonblockFlag(fd, True)
426
427   while fdmap:
428     pollresult = RetryOnSignal(poller.poll)
429
430     for fd, event in pollresult:
431       if event & select.POLLIN or event & select.POLLPRI:
432         data = fdmap[fd][1].read()
433         # no data from read signifies EOF (the same as POLLHUP)
434         if not data:
435           poller.unregister(fd)
436           del fdmap[fd]
437           continue
438         fdmap[fd][0].write(data)
439       if (event & select.POLLNVAL or event & select.POLLHUP or
440           event & select.POLLERR):
441         poller.unregister(fd)
442         del fdmap[fd]
443
444   out = out.getvalue()
445   err = err.getvalue()
446
447   status = child.wait()
448   return out, err, status
449
450
451 def _RunCmdFile(cmd, env, via_shell, output, cwd):
452   """Run a command and save its output to a file.
453
454   @type  cmd: string or list
455   @param cmd: Command to run
456   @type env: dict
457   @param env: The environment to use
458   @type via_shell: bool
459   @param via_shell: if we should run via the shell
460   @type output: str
461   @param output: the filename in which to save the output
462   @type cwd: string
463   @param cwd: the working directory for the program
464   @rtype: int
465   @return: the exit status
466
467   """
468   fh = open(output, "a")
469   try:
470     child = subprocess.Popen(cmd, shell=via_shell,
471                              stderr=subprocess.STDOUT,
472                              stdout=fh,
473                              stdin=subprocess.PIPE,
474                              close_fds=True, env=env,
475                              cwd=cwd)
476
477     child.stdin.close()
478     status = child.wait()
479   finally:
480     fh.close()
481   return status
482
483
484 def SetCloseOnExecFlag(fd, enable):
485   """Sets or unsets the close-on-exec flag on a file descriptor.
486
487   @type fd: int
488   @param fd: File descriptor
489   @type enable: bool
490   @param enable: Whether to set or unset it.
491
492   """
493   flags = fcntl.fcntl(fd, fcntl.F_GETFD)
494
495   if enable:
496     flags |= fcntl.FD_CLOEXEC
497   else:
498     flags &= ~fcntl.FD_CLOEXEC
499
500   fcntl.fcntl(fd, fcntl.F_SETFD, flags)
501
502
503 def SetNonblockFlag(fd, enable):
504   """Sets or unsets the O_NONBLOCK flag on on a file descriptor.
505
506   @type fd: int
507   @param fd: File descriptor
508   @type enable: bool
509   @param enable: Whether to set or unset it
510
511   """
512   flags = fcntl.fcntl(fd, fcntl.F_GETFL)
513
514   if enable:
515     flags |= os.O_NONBLOCK
516   else:
517     flags &= ~os.O_NONBLOCK
518
519   fcntl.fcntl(fd, fcntl.F_SETFL, flags)
520
521
522 def RetryOnSignal(fn, *args, **kwargs):
523   """Calls a function again if it failed due to EINTR.
524
525   """
526   while True:
527     try:
528       return fn(*args, **kwargs)
529     except EnvironmentError, err:
530       if err.errno != errno.EINTR:
531         raise
532     except select.error, err:
533       if not (err.args and err.args[0] == errno.EINTR):
534         raise
535
536
537 def RemoveFile(filename):
538   """Remove a file ignoring some errors.
539
540   Remove a file, ignoring non-existing ones or directories. Other
541   errors are passed.
542
543   @type filename: str
544   @param filename: the file to be removed
545
546   """
547   try:
548     os.unlink(filename)
549   except OSError, err:
550     if err.errno not in (errno.ENOENT, errno.EISDIR):
551       raise
552
553
554 def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
555   """Renames a file.
556
557   @type old: string
558   @param old: Original path
559   @type new: string
560   @param new: New path
561   @type mkdir: bool
562   @param mkdir: Whether to create target directory if it doesn't exist
563   @type mkdir_mode: int
564   @param mkdir_mode: Mode for newly created directories
565
566   """
567   try:
568     return os.rename(old, new)
569   except OSError, err:
570     # In at least one use case of this function, the job queue, directory
571     # creation is very rare. Checking for the directory before renaming is not
572     # as efficient.
573     if mkdir and err.errno == errno.ENOENT:
574       # Create directory and try again
575       dirname = os.path.dirname(new)
576       try:
577         os.makedirs(dirname, mode=mkdir_mode)
578       except OSError, err:
579         # Ignore EEXIST. This is only handled in os.makedirs as included in
580         # Python 2.5 and above.
581         if err.errno != errno.EEXIST or not os.path.exists(dirname):
582           raise
583
584       return os.rename(old, new)
585
586     raise
587
588
589 def ResetTempfileModule():
590   """Resets the random name generator of the tempfile module.
591
592   This function should be called after C{os.fork} in the child process to
593   ensure it creates a newly seeded random generator. Otherwise it would
594   generate the same random parts as the parent process. If several processes
595   race for the creation of a temporary file, this could lead to one not getting
596   a temporary name.
597
598   """
599   # pylint: disable-msg=W0212
600   if hasattr(tempfile, "_once_lock") and hasattr(tempfile, "_name_sequence"):
601     tempfile._once_lock.acquire()
602     try:
603       # Reset random name generator
604       tempfile._name_sequence = None
605     finally:
606       tempfile._once_lock.release()
607   else:
608     logging.critical("The tempfile module misses at least one of the"
609                      " '_once_lock' and '_name_sequence' attributes")
610
611
612 def _FingerprintFile(filename):
613   """Compute the fingerprint of a file.
614
615   If the file does not exist, a None will be returned
616   instead.
617
618   @type filename: str
619   @param filename: the filename to checksum
620   @rtype: str
621   @return: the hex digest of the sha checksum of the contents
622       of the file
623
624   """
625   if not (os.path.exists(filename) and os.path.isfile(filename)):
626     return None
627
628   f = open(filename)
629
630   fp = sha1()
631   while True:
632     data = f.read(4096)
633     if not data:
634       break
635
636     fp.update(data)
637
638   return fp.hexdigest()
639
640
641 def FingerprintFiles(files):
642   """Compute fingerprints for a list of files.
643
644   @type files: list
645   @param files: the list of filename to fingerprint
646   @rtype: dict
647   @return: a dictionary filename: fingerprint, holding only
648       existing files
649
650   """
651   ret = {}
652
653   for filename in files:
654     cksum = _FingerprintFile(filename)
655     if cksum:
656       ret[filename] = cksum
657
658   return ret
659
660
661 def ForceDictType(target, key_types, allowed_values=None):
662   """Force the values of a dict to have certain types.
663
664   @type target: dict
665   @param target: the dict to update
666   @type key_types: dict
667   @param key_types: dict mapping target dict keys to types
668                     in constants.ENFORCEABLE_TYPES
669   @type allowed_values: list
670   @keyword allowed_values: list of specially allowed values
671
672   """
673   if allowed_values is None:
674     allowed_values = []
675
676   if not isinstance(target, dict):
677     msg = "Expected dictionary, got '%s'" % target
678     raise errors.TypeEnforcementError(msg)
679
680   for key in target:
681     if key not in key_types:
682       msg = "Unknown key '%s'" % key
683       raise errors.TypeEnforcementError(msg)
684
685     if target[key] in allowed_values:
686       continue
687
688     ktype = key_types[key]
689     if ktype not in constants.ENFORCEABLE_TYPES:
690       msg = "'%s' has non-enforceable type %s" % (key, ktype)
691       raise errors.ProgrammerError(msg)
692
693     if ktype == constants.VTYPE_STRING:
694       if not isinstance(target[key], basestring):
695         if isinstance(target[key], bool) and not target[key]:
696           target[key] = ''
697         else:
698           msg = "'%s' (value %s) is not a valid string" % (key, target[key])
699           raise errors.TypeEnforcementError(msg)
700     elif ktype == constants.VTYPE_BOOL:
701       if isinstance(target[key], basestring) and target[key]:
702         if target[key].lower() == constants.VALUE_FALSE:
703           target[key] = False
704         elif target[key].lower() == constants.VALUE_TRUE:
705           target[key] = True
706         else:
707           msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
708           raise errors.TypeEnforcementError(msg)
709       elif target[key]:
710         target[key] = True
711       else:
712         target[key] = False
713     elif ktype == constants.VTYPE_SIZE:
714       try:
715         target[key] = ParseUnit(target[key])
716       except errors.UnitParseError, err:
717         msg = "'%s' (value %s) is not a valid size. error: %s" % \
718               (key, target[key], err)
719         raise errors.TypeEnforcementError(msg)
720     elif ktype == constants.VTYPE_INT:
721       try:
722         target[key] = int(target[key])
723       except (ValueError, TypeError):
724         msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
725         raise errors.TypeEnforcementError(msg)
726
727
728 def IsProcessAlive(pid):
729   """Check if a given pid exists on the system.
730
731   @note: zombie status is not handled, so zombie processes
732       will be returned as alive
733   @type pid: int
734   @param pid: the process ID to check
735   @rtype: boolean
736   @return: True if the process exists
737
738   """
739   if pid <= 0:
740     return False
741
742   try:
743     os.stat("/proc/%d/status" % pid)
744     return True
745   except EnvironmentError, err:
746     if err.errno in (errno.ENOENT, errno.ENOTDIR):
747       return False
748     raise
749
750
751 def ReadPidFile(pidfile):
752   """Read a pid from a file.
753
754   @type  pidfile: string
755   @param pidfile: path to the file containing the pid
756   @rtype: int
757   @return: The process id, if the file exists and contains a valid PID,
758            otherwise 0
759
760   """
761   try:
762     raw_data = ReadFile(pidfile)
763   except EnvironmentError, err:
764     if err.errno != errno.ENOENT:
765       logging.exception("Can't read pid file")
766     return 0
767
768   try:
769     pid = int(raw_data)
770   except (TypeError, ValueError), err:
771     logging.info("Can't parse pid file contents", exc_info=True)
772     return 0
773
774   return pid
775
776
777 def MatchNameComponent(key, name_list, case_sensitive=True):
778   """Try to match a name against a list.
779
780   This function will try to match a name like test1 against a list
781   like C{['test1.example.com', 'test2.example.com', ...]}. Against
782   this list, I{'test1'} as well as I{'test1.example'} will match, but
783   not I{'test1.ex'}. A multiple match will be considered as no match
784   at all (e.g. I{'test1'} against C{['test1.example.com',
785   'test1.example.org']}), except when the key fully matches an entry
786   (e.g. I{'test1'} against C{['test1', 'test1.example.com']}).
787
788   @type key: str
789   @param key: the name to be searched
790   @type name_list: list
791   @param name_list: the list of strings against which to search the key
792   @type case_sensitive: boolean
793   @param case_sensitive: whether to provide a case-sensitive match
794
795   @rtype: None or str
796   @return: None if there is no match I{or} if there are multiple matches,
797       otherwise the element from the list which matches
798
799   """
800   if key in name_list:
801     return key
802
803   re_flags = 0
804   if not case_sensitive:
805     re_flags |= re.IGNORECASE
806     key = key.upper()
807   mo = re.compile("^%s(\..*)?$" % re.escape(key), re_flags)
808   names_filtered = []
809   string_matches = []
810   for name in name_list:
811     if mo.match(name) is not None:
812       names_filtered.append(name)
813       if not case_sensitive and key == name.upper():
814         string_matches.append(name)
815
816   if len(string_matches) == 1:
817     return string_matches[0]
818   if len(names_filtered) == 1:
819     return names_filtered[0]
820   return None
821
822
823 class HostInfo:
824   """Class implementing resolver and hostname functionality
825
826   """
827   def __init__(self, name=None):
828     """Initialize the host name object.
829
830     If the name argument is not passed, it will use this system's
831     name.
832
833     """
834     if name is None:
835       name = self.SysName()
836
837     self.query = name
838     self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
839     self.ip = self.ipaddrs[0]
840
841   def ShortName(self):
842     """Returns the hostname without domain.
843
844     """
845     return self.name.split('.')[0]
846
847   @staticmethod
848   def SysName():
849     """Return the current system's name.
850
851     This is simply a wrapper over C{socket.gethostname()}.
852
853     """
854     return socket.gethostname()
855
856   @staticmethod
857   def LookupHostname(hostname):
858     """Look up hostname
859
860     @type hostname: str
861     @param hostname: hostname to look up
862
863     @rtype: tuple
864     @return: a tuple (name, aliases, ipaddrs) as returned by
865         C{socket.gethostbyname_ex}
866     @raise errors.ResolverError: in case of errors in resolving
867
868     """
869     try:
870       result = socket.gethostbyname_ex(hostname)
871     except socket.gaierror, err:
872       # hostname not found in DNS
873       raise errors.ResolverError(hostname, err.args[0], err.args[1])
874
875     return result
876
877
878 def GetHostInfo(name=None):
879   """Lookup host name and raise an OpPrereqError for failures"""
880
881   try:
882     return HostInfo(name)
883   except errors.ResolverError, err:
884     raise errors.OpPrereqError("The given name (%s) does not resolve: %s" %
885                                (err[0], err[2]), errors.ECODE_RESOLVER)
886
887
888 def ListVolumeGroups():
889   """List volume groups and their size
890
891   @rtype: dict
892   @return:
893        Dictionary with keys volume name and values
894        the size of the volume
895
896   """
897   command = "vgs --noheadings --units m --nosuffix -o name,size"
898   result = RunCmd(command)
899   retval = {}
900   if result.failed:
901     return retval
902
903   for line in result.stdout.splitlines():
904     try:
905       name, size = line.split()
906       size = int(float(size))
907     except (IndexError, ValueError), err:
908       logging.error("Invalid output from vgs (%s): %s", err, line)
909       continue
910
911     retval[name] = size
912
913   return retval
914
915
916 def BridgeExists(bridge):
917   """Check whether the given bridge exists in the system
918
919   @type bridge: str
920   @param bridge: the bridge name to check
921   @rtype: boolean
922   @return: True if it does
923
924   """
925   return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
926
927
928 def NiceSort(name_list):
929   """Sort a list of strings based on digit and non-digit groupings.
930
931   Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
932   will sort the list in the logical order C{['a1', 'a2', 'a10',
933   'a11']}.
934
935   The sort algorithm breaks each name in groups of either only-digits
936   or no-digits. Only the first eight such groups are considered, and
937   after that we just use what's left of the string.
938
939   @type name_list: list
940   @param name_list: the names to be sorted
941   @rtype: list
942   @return: a copy of the name list sorted with our algorithm
943
944   """
945   _SORTER_BASE = "(\D+|\d+)"
946   _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
947                                                   _SORTER_BASE, _SORTER_BASE,
948                                                   _SORTER_BASE, _SORTER_BASE,
949                                                   _SORTER_BASE, _SORTER_BASE)
950   _SORTER_RE = re.compile(_SORTER_FULL)
951   _SORTER_NODIGIT = re.compile("^\D*$")
952   def _TryInt(val):
953     """Attempts to convert a variable to integer."""
954     if val is None or _SORTER_NODIGIT.match(val):
955       return val
956     rval = int(val)
957     return rval
958
959   to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
960              for name in name_list]
961   to_sort.sort()
962   return [tup[1] for tup in to_sort]
963
964
965 def TryConvert(fn, val):
966   """Try to convert a value ignoring errors.
967
968   This function tries to apply function I{fn} to I{val}. If no
969   C{ValueError} or C{TypeError} exceptions are raised, it will return
970   the result, else it will return the original value. Any other
971   exceptions are propagated to the caller.
972
973   @type fn: callable
974   @param fn: function to apply to the value
975   @param val: the value to be converted
976   @return: The converted value if the conversion was successful,
977       otherwise the original value.
978
979   """
980   try:
981     nv = fn(val)
982   except (ValueError, TypeError):
983     nv = val
984   return nv
985
986
987 def IsValidIP(ip):
988   """Verifies the syntax of an IPv4 address.
989
990   This function checks if the IPv4 address passes is valid or not based
991   on syntax (not IP range, class calculations, etc.).
992
993   @type ip: str
994   @param ip: the address to be checked
995   @rtype: a regular expression match object
996   @return: a regular expression match object, or None if the
997       address is not valid
998
999   """
1000   unit = "(0|[1-9]\d{0,2})"
1001   #TODO: convert and return only boolean
1002   return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
1003
1004
1005 def IsValidShellParam(word):
1006   """Verifies is the given word is safe from the shell's p.o.v.
1007
1008   This means that we can pass this to a command via the shell and be
1009   sure that it doesn't alter the command line and is passed as such to
1010   the actual command.
1011
1012   Note that we are overly restrictive here, in order to be on the safe
1013   side.
1014
1015   @type word: str
1016   @param word: the word to check
1017   @rtype: boolean
1018   @return: True if the word is 'safe'
1019
1020   """
1021   return bool(re.match("^[-a-zA-Z0-9._+/:%@]+$", word))
1022
1023
1024 def BuildShellCmd(template, *args):
1025   """Build a safe shell command line from the given arguments.
1026
1027   This function will check all arguments in the args list so that they
1028   are valid shell parameters (i.e. they don't contain shell
1029   metacharacters). If everything is ok, it will return the result of
1030   template % args.
1031
1032   @type template: str
1033   @param template: the string holding the template for the
1034       string formatting
1035   @rtype: str
1036   @return: the expanded command line
1037
1038   """
1039   for word in args:
1040     if not IsValidShellParam(word):
1041       raise errors.ProgrammerError("Shell argument '%s' contains"
1042                                    " invalid characters" % word)
1043   return template % args
1044
1045
1046 def FormatUnit(value, units):
1047   """Formats an incoming number of MiB with the appropriate unit.
1048
1049   @type value: int
1050   @param value: integer representing the value in MiB (1048576)
1051   @type units: char
1052   @param units: the type of formatting we should do:
1053       - 'h' for automatic scaling
1054       - 'm' for MiBs
1055       - 'g' for GiBs
1056       - 't' for TiBs
1057   @rtype: str
1058   @return: the formatted value (with suffix)
1059
1060   """
1061   if units not in ('m', 'g', 't', 'h'):
1062     raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
1063
1064   suffix = ''
1065
1066   if units == 'm' or (units == 'h' and value < 1024):
1067     if units == 'h':
1068       suffix = 'M'
1069     return "%d%s" % (round(value, 0), suffix)
1070
1071   elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
1072     if units == 'h':
1073       suffix = 'G'
1074     return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
1075
1076   else:
1077     if units == 'h':
1078       suffix = 'T'
1079     return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
1080
1081
1082 def ParseUnit(input_string):
1083   """Tries to extract number and scale from the given string.
1084
1085   Input must be in the format C{NUMBER+ [DOT NUMBER+] SPACE*
1086   [UNIT]}. If no unit is specified, it defaults to MiB. Return value
1087   is always an int in MiB.
1088
1089   """
1090   m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', str(input_string))
1091   if not m:
1092     raise errors.UnitParseError("Invalid format")
1093
1094   value = float(m.groups()[0])
1095
1096   unit = m.groups()[1]
1097   if unit:
1098     lcunit = unit.lower()
1099   else:
1100     lcunit = 'm'
1101
1102   if lcunit in ('m', 'mb', 'mib'):
1103     # Value already in MiB
1104     pass
1105
1106   elif lcunit in ('g', 'gb', 'gib'):
1107     value *= 1024
1108
1109   elif lcunit in ('t', 'tb', 'tib'):
1110     value *= 1024 * 1024
1111
1112   else:
1113     raise errors.UnitParseError("Unknown unit: %s" % unit)
1114
1115   # Make sure we round up
1116   if int(value) < value:
1117     value += 1
1118
1119   # Round up to the next multiple of 4
1120   value = int(value)
1121   if value % 4:
1122     value += 4 - value % 4
1123
1124   return value
1125
1126
1127 def AddAuthorizedKey(file_name, key):
1128   """Adds an SSH public key to an authorized_keys file.
1129
1130   @type file_name: str
1131   @param file_name: path to authorized_keys file
1132   @type key: str
1133   @param key: string containing key
1134
1135   """
1136   key_fields = key.split()
1137
1138   f = open(file_name, 'a+')
1139   try:
1140     nl = True
1141     for line in f:
1142       # Ignore whitespace changes
1143       if line.split() == key_fields:
1144         break
1145       nl = line.endswith('\n')
1146     else:
1147       if not nl:
1148         f.write("\n")
1149       f.write(key.rstrip('\r\n'))
1150       f.write("\n")
1151       f.flush()
1152   finally:
1153     f.close()
1154
1155
1156 def RemoveAuthorizedKey(file_name, key):
1157   """Removes an SSH public key from an authorized_keys file.
1158
1159   @type file_name: str
1160   @param file_name: path to authorized_keys file
1161   @type key: str
1162   @param key: string containing key
1163
1164   """
1165   key_fields = key.split()
1166
1167   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1168   try:
1169     out = os.fdopen(fd, 'w')
1170     try:
1171       f = open(file_name, 'r')
1172       try:
1173         for line in f:
1174           # Ignore whitespace changes while comparing lines
1175           if line.split() != key_fields:
1176             out.write(line)
1177
1178         out.flush()
1179         os.rename(tmpname, file_name)
1180       finally:
1181         f.close()
1182     finally:
1183       out.close()
1184   except:
1185     RemoveFile(tmpname)
1186     raise
1187
1188
1189 def SetEtcHostsEntry(file_name, ip, hostname, aliases):
1190   """Sets the name of an IP address and hostname in /etc/hosts.
1191
1192   @type file_name: str
1193   @param file_name: path to the file to modify (usually C{/etc/hosts})
1194   @type ip: str
1195   @param ip: the IP address
1196   @type hostname: str
1197   @param hostname: the hostname to be added
1198   @type aliases: list
1199   @param aliases: the list of aliases to add for the hostname
1200
1201   """
1202   # FIXME: use WriteFile + fn rather than duplicating its efforts
1203   # Ensure aliases are unique
1204   aliases = UniqueSequence([hostname] + aliases)[1:]
1205
1206   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1207   try:
1208     out = os.fdopen(fd, 'w')
1209     try:
1210       f = open(file_name, 'r')
1211       try:
1212         for line in f:
1213           fields = line.split()
1214           if fields and not fields[0].startswith('#') and ip == fields[0]:
1215             continue
1216           out.write(line)
1217
1218         out.write("%s\t%s" % (ip, hostname))
1219         if aliases:
1220           out.write(" %s" % ' '.join(aliases))
1221         out.write('\n')
1222
1223         out.flush()
1224         os.fsync(out)
1225         os.chmod(tmpname, 0644)
1226         os.rename(tmpname, file_name)
1227       finally:
1228         f.close()
1229     finally:
1230       out.close()
1231   except:
1232     RemoveFile(tmpname)
1233     raise
1234
1235
1236 def AddHostToEtcHosts(hostname):
1237   """Wrapper around SetEtcHostsEntry.
1238
1239   @type hostname: str
1240   @param hostname: a hostname that will be resolved and added to
1241       L{constants.ETC_HOSTS}
1242
1243   """
1244   hi = HostInfo(name=hostname)
1245   SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
1246
1247
1248 def RemoveEtcHostsEntry(file_name, hostname):
1249   """Removes a hostname from /etc/hosts.
1250
1251   IP addresses without names are removed from the file.
1252
1253   @type file_name: str
1254   @param file_name: path to the file to modify (usually C{/etc/hosts})
1255   @type hostname: str
1256   @param hostname: the hostname to be removed
1257
1258   """
1259   # FIXME: use WriteFile + fn rather than duplicating its efforts
1260   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1261   try:
1262     out = os.fdopen(fd, 'w')
1263     try:
1264       f = open(file_name, 'r')
1265       try:
1266         for line in f:
1267           fields = line.split()
1268           if len(fields) > 1 and not fields[0].startswith('#'):
1269             names = fields[1:]
1270             if hostname in names:
1271               while hostname in names:
1272                 names.remove(hostname)
1273               if names:
1274                 out.write("%s %s\n" % (fields[0], ' '.join(names)))
1275               continue
1276
1277           out.write(line)
1278
1279         out.flush()
1280         os.fsync(out)
1281         os.chmod(tmpname, 0644)
1282         os.rename(tmpname, file_name)
1283       finally:
1284         f.close()
1285     finally:
1286       out.close()
1287   except:
1288     RemoveFile(tmpname)
1289     raise
1290
1291
1292 def RemoveHostFromEtcHosts(hostname):
1293   """Wrapper around RemoveEtcHostsEntry.
1294
1295   @type hostname: str
1296   @param hostname: hostname that will be resolved and its
1297       full and shot name will be removed from
1298       L{constants.ETC_HOSTS}
1299
1300   """
1301   hi = HostInfo(name=hostname)
1302   RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
1303   RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
1304
1305
1306 def CreateBackup(file_name):
1307   """Creates a backup of a file.
1308
1309   @type file_name: str
1310   @param file_name: file to be backed up
1311   @rtype: str
1312   @return: the path to the newly created backup
1313   @raise errors.ProgrammerError: for invalid file names
1314
1315   """
1316   if not os.path.isfile(file_name):
1317     raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
1318                                 file_name)
1319
1320   prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
1321   dir_name = os.path.dirname(file_name)
1322
1323   fsrc = open(file_name, 'rb')
1324   try:
1325     (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
1326     fdst = os.fdopen(fd, 'wb')
1327     try:
1328       shutil.copyfileobj(fsrc, fdst)
1329     finally:
1330       fdst.close()
1331   finally:
1332     fsrc.close()
1333
1334   return backup_name
1335
1336
1337 def ShellQuote(value):
1338   """Quotes shell argument according to POSIX.
1339
1340   @type value: str
1341   @param value: the argument to be quoted
1342   @rtype: str
1343   @return: the quoted value
1344
1345   """
1346   if _re_shell_unquoted.match(value):
1347     return value
1348   else:
1349     return "'%s'" % value.replace("'", "'\\''")
1350
1351
1352 def ShellQuoteArgs(args):
1353   """Quotes a list of shell arguments.
1354
1355   @type args: list
1356   @param args: list of arguments to be quoted
1357   @rtype: str
1358   @return: the quoted arguments concatenated with spaces
1359
1360   """
1361   return ' '.join([ShellQuote(i) for i in args])
1362
1363
1364 def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
1365   """Simple ping implementation using TCP connect(2).
1366
1367   Check if the given IP is reachable by doing attempting a TCP connect
1368   to it.
1369
1370   @type target: str
1371   @param target: the IP or hostname to ping
1372   @type port: int
1373   @param port: the port to connect to
1374   @type timeout: int
1375   @param timeout: the timeout on the connection attempt
1376   @type live_port_needed: boolean
1377   @param live_port_needed: whether a closed port will cause the
1378       function to return failure, as if there was a timeout
1379   @type source: str or None
1380   @param source: if specified, will cause the connect to be made
1381       from this specific source address; failures to bind other
1382       than C{EADDRNOTAVAIL} will be ignored
1383
1384   """
1385   sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1386
1387   success = False
1388
1389   if source is not None:
1390     try:
1391       sock.bind((source, 0))
1392     except socket.error, (errcode, _):
1393       if errcode == errno.EADDRNOTAVAIL:
1394         success = False
1395
1396   sock.settimeout(timeout)
1397
1398   try:
1399     sock.connect((target, port))
1400     sock.close()
1401     success = True
1402   except socket.timeout:
1403     success = False
1404   except socket.error, (errcode, _):
1405     success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
1406
1407   return success
1408
1409
1410 def OwnIpAddress(address):
1411   """Check if the current host has the the given IP address.
1412
1413   Currently this is done by TCP-pinging the address from the loopback
1414   address.
1415
1416   @type address: string
1417   @param address: the address to check
1418   @rtype: bool
1419   @return: True if we own the address
1420
1421   """
1422   return TcpPing(address, constants.DEFAULT_NODED_PORT,
1423                  source=constants.LOCALHOST_IP_ADDRESS)
1424
1425
1426 def ListVisibleFiles(path):
1427   """Returns a list of visible files in a directory.
1428
1429   @type path: str
1430   @param path: the directory to enumerate
1431   @rtype: list
1432   @return: the list of all files not starting with a dot
1433
1434   """
1435   files = [i for i in os.listdir(path) if not i.startswith(".")]
1436   files.sort()
1437   return files
1438
1439
1440 def GetHomeDir(user, default=None):
1441   """Try to get the homedir of the given user.
1442
1443   The user can be passed either as a string (denoting the name) or as
1444   an integer (denoting the user id). If the user is not found, the
1445   'default' argument is returned, which defaults to None.
1446
1447   """
1448   try:
1449     if isinstance(user, basestring):
1450       result = pwd.getpwnam(user)
1451     elif isinstance(user, (int, long)):
1452       result = pwd.getpwuid(user)
1453     else:
1454       raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1455                                    type(user))
1456   except KeyError:
1457     return default
1458   return result.pw_dir
1459
1460
1461 def NewUUID():
1462   """Returns a random UUID.
1463
1464   @note: This is a Linux-specific method as it uses the /proc
1465       filesystem.
1466   @rtype: str
1467
1468   """
1469   return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
1470
1471
1472 def GenerateSecret(numbytes=20):
1473   """Generates a random secret.
1474
1475   This will generate a pseudo-random secret returning an hex string
1476   (so that it can be used where an ASCII string is needed).
1477
1478   @param numbytes: the number of bytes which will be represented by the returned
1479       string (defaulting to 20, the length of a SHA1 hash)
1480   @rtype: str
1481   @return: an hex representation of the pseudo-random sequence
1482
1483   """
1484   return os.urandom(numbytes).encode('hex')
1485
1486
1487 def EnsureDirs(dirs):
1488   """Make required directories, if they don't exist.
1489
1490   @param dirs: list of tuples (dir_name, dir_mode)
1491   @type dirs: list of (string, integer)
1492
1493   """
1494   for dir_name, dir_mode in dirs:
1495     try:
1496       os.mkdir(dir_name, dir_mode)
1497     except EnvironmentError, err:
1498       if err.errno != errno.EEXIST:
1499         raise errors.GenericError("Cannot create needed directory"
1500                                   " '%s': %s" % (dir_name, err))
1501     if not os.path.isdir(dir_name):
1502       raise errors.GenericError("%s is not a directory" % dir_name)
1503
1504
1505 def ReadFile(file_name, size=-1):
1506   """Reads a file.
1507
1508   @type size: int
1509   @param size: Read at most size bytes (if negative, entire file)
1510   @rtype: str
1511   @return: the (possibly partial) content of the file
1512
1513   """
1514   f = open(file_name, "r")
1515   try:
1516     return f.read(size)
1517   finally:
1518     f.close()
1519
1520
1521 def WriteFile(file_name, fn=None, data=None,
1522               mode=None, uid=-1, gid=-1,
1523               atime=None, mtime=None, close=True,
1524               dry_run=False, backup=False,
1525               prewrite=None, postwrite=None):
1526   """(Over)write a file atomically.
1527
1528   The file_name and either fn (a function taking one argument, the
1529   file descriptor, and which should write the data to it) or data (the
1530   contents of the file) must be passed. The other arguments are
1531   optional and allow setting the file mode, owner and group, and the
1532   mtime/atime of the file.
1533
1534   If the function doesn't raise an exception, it has succeeded and the
1535   target file has the new contents. If the function has raised an
1536   exception, an existing target file should be unmodified and the
1537   temporary file should be removed.
1538
1539   @type file_name: str
1540   @param file_name: the target filename
1541   @type fn: callable
1542   @param fn: content writing function, called with
1543       file descriptor as parameter
1544   @type data: str
1545   @param data: contents of the file
1546   @type mode: int
1547   @param mode: file mode
1548   @type uid: int
1549   @param uid: the owner of the file
1550   @type gid: int
1551   @param gid: the group of the file
1552   @type atime: int
1553   @param atime: a custom access time to be set on the file
1554   @type mtime: int
1555   @param mtime: a custom modification time to be set on the file
1556   @type close: boolean
1557   @param close: whether to close file after writing it
1558   @type prewrite: callable
1559   @param prewrite: function to be called before writing content
1560   @type postwrite: callable
1561   @param postwrite: function to be called after writing content
1562
1563   @rtype: None or int
1564   @return: None if the 'close' parameter evaluates to True,
1565       otherwise the file descriptor
1566
1567   @raise errors.ProgrammerError: if any of the arguments are not valid
1568
1569   """
1570   if not os.path.isabs(file_name):
1571     raise errors.ProgrammerError("Path passed to WriteFile is not"
1572                                  " absolute: '%s'" % file_name)
1573
1574   if [fn, data].count(None) != 1:
1575     raise errors.ProgrammerError("fn or data required")
1576
1577   if [atime, mtime].count(None) == 1:
1578     raise errors.ProgrammerError("Both atime and mtime must be either"
1579                                  " set or None")
1580
1581   if backup and not dry_run and os.path.isfile(file_name):
1582     CreateBackup(file_name)
1583
1584   dir_name, base_name = os.path.split(file_name)
1585   fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
1586   do_remove = True
1587   # here we need to make sure we remove the temp file, if any error
1588   # leaves it in place
1589   try:
1590     if uid != -1 or gid != -1:
1591       os.chown(new_name, uid, gid)
1592     if mode:
1593       os.chmod(new_name, mode)
1594     if callable(prewrite):
1595       prewrite(fd)
1596     if data is not None:
1597       os.write(fd, data)
1598     else:
1599       fn(fd)
1600     if callable(postwrite):
1601       postwrite(fd)
1602     os.fsync(fd)
1603     if atime is not None and mtime is not None:
1604       os.utime(new_name, (atime, mtime))
1605     if not dry_run:
1606       os.rename(new_name, file_name)
1607       do_remove = False
1608   finally:
1609     if close:
1610       os.close(fd)
1611       result = None
1612     else:
1613       result = fd
1614     if do_remove:
1615       RemoveFile(new_name)
1616
1617   return result
1618
1619
1620 def FirstFree(seq, base=0):
1621   """Returns the first non-existing integer from seq.
1622
1623   The seq argument should be a sorted list of positive integers. The
1624   first time the index of an element is smaller than the element
1625   value, the index will be returned.
1626
1627   The base argument is used to start at a different offset,
1628   i.e. C{[3, 4, 6]} with I{offset=3} will return 5.
1629
1630   Example: C{[0, 1, 3]} will return I{2}.
1631
1632   @type seq: sequence
1633   @param seq: the sequence to be analyzed.
1634   @type base: int
1635   @param base: use this value as the base index of the sequence
1636   @rtype: int
1637   @return: the first non-used index in the sequence
1638
1639   """
1640   for idx, elem in enumerate(seq):
1641     assert elem >= base, "Passed element is higher than base offset"
1642     if elem > idx + base:
1643       # idx is not used
1644       return idx + base
1645   return None
1646
1647
1648 def all(seq, pred=bool): # pylint: disable-msg=W0622
1649   "Returns True if pred(x) is True for every element in the iterable"
1650   for _ in itertools.ifilterfalse(pred, seq):
1651     return False
1652   return True
1653
1654
1655 def any(seq, pred=bool): # pylint: disable-msg=W0622
1656   "Returns True if pred(x) is True for at least one element in the iterable"
1657   for _ in itertools.ifilter(pred, seq):
1658     return True
1659   return False
1660
1661
1662 def UniqueSequence(seq):
1663   """Returns a list with unique elements.
1664
1665   Element order is preserved.
1666
1667   @type seq: sequence
1668   @param seq: the sequence with the source elements
1669   @rtype: list
1670   @return: list of unique elements from seq
1671
1672   """
1673   seen = set()
1674   return [i for i in seq if i not in seen and not seen.add(i)]
1675
1676
1677 def NormalizeAndValidateMac(mac):
1678   """Normalizes and check if a MAC address is valid.
1679
1680   Checks whether the supplied MAC address is formally correct, only
1681   accepts colon separated format. Normalize it to all lower.
1682
1683   @type mac: str
1684   @param mac: the MAC to be validated
1685   @rtype: str
1686   @return: returns the normalized and validated MAC.
1687
1688   @raise errors.OpPrereqError: If the MAC isn't valid
1689
1690   """
1691   mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$", re.I)
1692   if not mac_check.match(mac):
1693     raise errors.OpPrereqError("Invalid MAC address specified: %s" %
1694                                mac, errors.ECODE_INVAL)
1695
1696   return mac.lower()
1697
1698
1699 def TestDelay(duration):
1700   """Sleep for a fixed amount of time.
1701
1702   @type duration: float
1703   @param duration: the sleep duration
1704   @rtype: boolean
1705   @return: False for negative value, True otherwise
1706
1707   """
1708   if duration < 0:
1709     return False, "Invalid sleep duration"
1710   time.sleep(duration)
1711   return True, None
1712
1713
1714 def _CloseFDNoErr(fd, retries=5):
1715   """Close a file descriptor ignoring errors.
1716
1717   @type fd: int
1718   @param fd: the file descriptor
1719   @type retries: int
1720   @param retries: how many retries to make, in case we get any
1721       other error than EBADF
1722
1723   """
1724   try:
1725     os.close(fd)
1726   except OSError, err:
1727     if err.errno != errno.EBADF:
1728       if retries > 0:
1729         _CloseFDNoErr(fd, retries - 1)
1730     # else either it's closed already or we're out of retries, so we
1731     # ignore this and go on
1732
1733
1734 def CloseFDs(noclose_fds=None):
1735   """Close file descriptors.
1736
1737   This closes all file descriptors above 2 (i.e. except
1738   stdin/out/err).
1739
1740   @type noclose_fds: list or None
1741   @param noclose_fds: if given, it denotes a list of file descriptor
1742       that should not be closed
1743
1744   """
1745   # Default maximum for the number of available file descriptors.
1746   if 'SC_OPEN_MAX' in os.sysconf_names:
1747     try:
1748       MAXFD = os.sysconf('SC_OPEN_MAX')
1749       if MAXFD < 0:
1750         MAXFD = 1024
1751     except OSError:
1752       MAXFD = 1024
1753   else:
1754     MAXFD = 1024
1755   maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1756   if (maxfd == resource.RLIM_INFINITY):
1757     maxfd = MAXFD
1758
1759   # Iterate through and close all file descriptors (except the standard ones)
1760   for fd in range(3, maxfd):
1761     if noclose_fds and fd in noclose_fds:
1762       continue
1763     _CloseFDNoErr(fd)
1764
1765
1766 def Daemonize(logfile):
1767   """Daemonize the current process.
1768
1769   This detaches the current process from the controlling terminal and
1770   runs it in the background as a daemon.
1771
1772   @type logfile: str
1773   @param logfile: the logfile to which we should redirect stdout/stderr
1774   @rtype: int
1775   @return: the value zero
1776
1777   """
1778   # pylint: disable-msg=W0212
1779   # yes, we really want os._exit
1780   UMASK = 077
1781   WORKDIR = "/"
1782
1783   # this might fail
1784   pid = os.fork()
1785   if (pid == 0):  # The first child.
1786     os.setsid()
1787     # this might fail
1788     pid = os.fork() # Fork a second child.
1789     if (pid == 0):  # The second child.
1790       os.chdir(WORKDIR)
1791       os.umask(UMASK)
1792     else:
1793       # exit() or _exit()?  See below.
1794       os._exit(0) # Exit parent (the first child) of the second child.
1795   else:
1796     os._exit(0) # Exit parent of the first child.
1797
1798   for fd in range(3):
1799     _CloseFDNoErr(fd)
1800   i = os.open("/dev/null", os.O_RDONLY) # stdin
1801   assert i == 0, "Can't close/reopen stdin"
1802   i = os.open(logfile, os.O_WRONLY|os.O_CREAT|os.O_APPEND, 0600) # stdout
1803   assert i == 1, "Can't close/reopen stdout"
1804   # Duplicate standard output to standard error.
1805   os.dup2(1, 2)
1806   return 0
1807
1808
1809 def DaemonPidFileName(name):
1810   """Compute a ganeti pid file absolute path
1811
1812   @type name: str
1813   @param name: the daemon name
1814   @rtype: str
1815   @return: the full path to the pidfile corresponding to the given
1816       daemon name
1817
1818   """
1819   return os.path.join(constants.RUN_GANETI_DIR, "%s.pid" % name)
1820
1821
1822 def WritePidFile(name):
1823   """Write the current process pidfile.
1824
1825   The file will be written to L{constants.RUN_GANETI_DIR}I{/name.pid}
1826
1827   @type name: str
1828   @param name: the daemon name to use
1829   @raise errors.GenericError: if the pid file already exists and
1830       points to a live process
1831
1832   """
1833   pid = os.getpid()
1834   pidfilename = DaemonPidFileName(name)
1835   if IsProcessAlive(ReadPidFile(pidfilename)):
1836     raise errors.GenericError("%s contains a live process" % pidfilename)
1837
1838   WriteFile(pidfilename, data="%d\n" % pid)
1839
1840
1841 def RemovePidFile(name):
1842   """Remove the current process pidfile.
1843
1844   Any errors are ignored.
1845
1846   @type name: str
1847   @param name: the daemon name used to derive the pidfile name
1848
1849   """
1850   pidfilename = DaemonPidFileName(name)
1851   # TODO: we could check here that the file contains our pid
1852   try:
1853     RemoveFile(pidfilename)
1854   except: # pylint: disable-msg=W0702
1855     pass
1856
1857
1858 def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1859                 waitpid=False):
1860   """Kill a process given by its pid.
1861
1862   @type pid: int
1863   @param pid: The PID to terminate.
1864   @type signal_: int
1865   @param signal_: The signal to send, by default SIGTERM
1866   @type timeout: int
1867   @param timeout: The timeout after which, if the process is still alive,
1868                   a SIGKILL will be sent. If not positive, no such checking
1869                   will be done
1870   @type waitpid: boolean
1871   @param waitpid: If true, we should waitpid on this process after
1872       sending signals, since it's our own child and otherwise it
1873       would remain as zombie
1874
1875   """
1876   def _helper(pid, signal_, wait):
1877     """Simple helper to encapsulate the kill/waitpid sequence"""
1878     os.kill(pid, signal_)
1879     if wait:
1880       try:
1881         os.waitpid(pid, os.WNOHANG)
1882       except OSError:
1883         pass
1884
1885   if pid <= 0:
1886     # kill with pid=0 == suicide
1887     raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1888
1889   if not IsProcessAlive(pid):
1890     return
1891
1892   _helper(pid, signal_, waitpid)
1893
1894   if timeout <= 0:
1895     return
1896
1897   def _CheckProcess():
1898     if not IsProcessAlive(pid):
1899       return
1900
1901     try:
1902       (result_pid, _) = os.waitpid(pid, os.WNOHANG)
1903     except OSError:
1904       raise RetryAgain()
1905
1906     if result_pid > 0:
1907       return
1908
1909     raise RetryAgain()
1910
1911   try:
1912     # Wait up to $timeout seconds
1913     Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
1914   except RetryTimeout:
1915     pass
1916
1917   if IsProcessAlive(pid):
1918     # Kill process if it's still alive
1919     _helper(pid, signal.SIGKILL, waitpid)
1920
1921
1922 def FindFile(name, search_path, test=os.path.exists):
1923   """Look for a filesystem object in a given path.
1924
1925   This is an abstract method to search for filesystem object (files,
1926   dirs) under a given search path.
1927
1928   @type name: str
1929   @param name: the name to look for
1930   @type search_path: str
1931   @param search_path: location to start at
1932   @type test: callable
1933   @param test: a function taking one argument that should return True
1934       if the a given object is valid; the default value is
1935       os.path.exists, causing only existing files to be returned
1936   @rtype: str or None
1937   @return: full path to the object if found, None otherwise
1938
1939   """
1940   # validate the filename mask
1941   if constants.EXT_PLUGIN_MASK.match(name) is None:
1942     logging.critical("Invalid value passed for external script name: '%s'",
1943                      name)
1944     return None
1945
1946   for dir_name in search_path:
1947     item_name = os.path.sep.join([dir_name, name])
1948     # check the user test and that we're indeed resolving to the given
1949     # basename
1950     if test(item_name) and os.path.basename(item_name) == name:
1951       return item_name
1952   return None
1953
1954
1955 def CheckVolumeGroupSize(vglist, vgname, minsize):
1956   """Checks if the volume group list is valid.
1957
1958   The function will check if a given volume group is in the list of
1959   volume groups and has a minimum size.
1960
1961   @type vglist: dict
1962   @param vglist: dictionary of volume group names and their size
1963   @type vgname: str
1964   @param vgname: the volume group we should check
1965   @type minsize: int
1966   @param minsize: the minimum size we accept
1967   @rtype: None or str
1968   @return: None for success, otherwise the error message
1969
1970   """
1971   vgsize = vglist.get(vgname, None)
1972   if vgsize is None:
1973     return "volume group '%s' missing" % vgname
1974   elif vgsize < minsize:
1975     return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1976             (vgname, minsize, vgsize))
1977   return None
1978
1979
1980 def SplitTime(value):
1981   """Splits time as floating point number into a tuple.
1982
1983   @param value: Time in seconds
1984   @type value: int or float
1985   @return: Tuple containing (seconds, microseconds)
1986
1987   """
1988   (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1989
1990   assert 0 <= seconds, \
1991     "Seconds must be larger than or equal to 0, but are %s" % seconds
1992   assert 0 <= microseconds <= 999999, \
1993     "Microseconds must be 0-999999, but are %s" % microseconds
1994
1995   return (int(seconds), int(microseconds))
1996
1997
1998 def MergeTime(timetuple):
1999   """Merges a tuple into time as a floating point number.
2000
2001   @param timetuple: Time as tuple, (seconds, microseconds)
2002   @type timetuple: tuple
2003   @return: Time as a floating point number expressed in seconds
2004
2005   """
2006   (seconds, microseconds) = timetuple
2007
2008   assert 0 <= seconds, \
2009     "Seconds must be larger than or equal to 0, but are %s" % seconds
2010   assert 0 <= microseconds <= 999999, \
2011     "Microseconds must be 0-999999, but are %s" % microseconds
2012
2013   return float(seconds) + (float(microseconds) * 0.000001)
2014
2015
2016 def GetDaemonPort(daemon_name):
2017   """Get the daemon port for this cluster.
2018
2019   Note that this routine does not read a ganeti-specific file, but
2020   instead uses C{socket.getservbyname} to allow pre-customization of
2021   this parameter outside of Ganeti.
2022
2023   @type daemon_name: string
2024   @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
2025   @rtype: int
2026
2027   """
2028   if daemon_name not in constants.DAEMONS_PORTS:
2029     raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
2030
2031   (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
2032   try:
2033     port = socket.getservbyname(daemon_name, proto)
2034   except socket.error:
2035     port = default_port
2036
2037   return port
2038
2039
2040 def SetupLogging(logfile, debug=0, stderr_logging=False, program="",
2041                  multithreaded=False, syslog=constants.SYSLOG_USAGE):
2042   """Configures the logging module.
2043
2044   @type logfile: str
2045   @param logfile: the filename to which we should log
2046   @type debug: integer
2047   @param debug: if greater than zero, enable debug messages, otherwise
2048       only those at C{INFO} and above level
2049   @type stderr_logging: boolean
2050   @param stderr_logging: whether we should also log to the standard error
2051   @type program: str
2052   @param program: the name under which we should log messages
2053   @type multithreaded: boolean
2054   @param multithreaded: if True, will add the thread name to the log file
2055   @type syslog: string
2056   @param syslog: one of 'no', 'yes', 'only':
2057       - if no, syslog is not used
2058       - if yes, syslog is used (in addition to file-logging)
2059       - if only, only syslog is used
2060   @raise EnvironmentError: if we can't open the log file and
2061       syslog/stderr logging is disabled
2062
2063   """
2064   fmt = "%(asctime)s: " + program + " pid=%(process)d"
2065   sft = program + "[%(process)d]:"
2066   if multithreaded:
2067     fmt += "/%(threadName)s"
2068     sft += " (%(threadName)s)"
2069   if debug:
2070     fmt += " %(module)s:%(lineno)s"
2071     # no debug info for syslog loggers
2072   fmt += " %(levelname)s %(message)s"
2073   # yes, we do want the textual level, as remote syslog will probably
2074   # lose the error level, and it's easier to grep for it
2075   sft += " %(levelname)s %(message)s"
2076   formatter = logging.Formatter(fmt)
2077   sys_fmt = logging.Formatter(sft)
2078
2079   root_logger = logging.getLogger("")
2080   root_logger.setLevel(logging.NOTSET)
2081
2082   # Remove all previously setup handlers
2083   for handler in root_logger.handlers:
2084     handler.close()
2085     root_logger.removeHandler(handler)
2086
2087   if stderr_logging:
2088     stderr_handler = logging.StreamHandler()
2089     stderr_handler.setFormatter(formatter)
2090     if debug:
2091       stderr_handler.setLevel(logging.NOTSET)
2092     else:
2093       stderr_handler.setLevel(logging.CRITICAL)
2094     root_logger.addHandler(stderr_handler)
2095
2096   if syslog in (constants.SYSLOG_YES, constants.SYSLOG_ONLY):
2097     facility = logging.handlers.SysLogHandler.LOG_DAEMON
2098     syslog_handler = logging.handlers.SysLogHandler(constants.SYSLOG_SOCKET,
2099                                                     facility)
2100     syslog_handler.setFormatter(sys_fmt)
2101     # Never enable debug over syslog
2102     syslog_handler.setLevel(logging.INFO)
2103     root_logger.addHandler(syslog_handler)
2104
2105   if syslog != constants.SYSLOG_ONLY:
2106     # this can fail, if the logging directories are not setup or we have
2107     # a permisssion problem; in this case, it's best to log but ignore
2108     # the error if stderr_logging is True, and if false we re-raise the
2109     # exception since otherwise we could run but without any logs at all
2110     try:
2111       logfile_handler = logging.FileHandler(logfile)
2112       logfile_handler.setFormatter(formatter)
2113       if debug:
2114         logfile_handler.setLevel(logging.DEBUG)
2115       else:
2116         logfile_handler.setLevel(logging.INFO)
2117       root_logger.addHandler(logfile_handler)
2118     except EnvironmentError:
2119       if stderr_logging or syslog == constants.SYSLOG_YES:
2120         logging.exception("Failed to enable logging to file '%s'", logfile)
2121       else:
2122         # we need to re-raise the exception
2123         raise
2124
2125
2126 def IsNormAbsPath(path):
2127   """Check whether a path is absolute and also normalized
2128
2129   This avoids things like /dir/../../other/path to be valid.
2130
2131   """
2132   return os.path.normpath(path) == path and os.path.isabs(path)
2133
2134
2135 def TailFile(fname, lines=20):
2136   """Return the last lines from a file.
2137
2138   @note: this function will only read and parse the last 4KB of
2139       the file; if the lines are very long, it could be that less
2140       than the requested number of lines are returned
2141
2142   @param fname: the file name
2143   @type lines: int
2144   @param lines: the (maximum) number of lines to return
2145
2146   """
2147   fd = open(fname, "r")
2148   try:
2149     fd.seek(0, 2)
2150     pos = fd.tell()
2151     pos = max(0, pos-4096)
2152     fd.seek(pos, 0)
2153     raw_data = fd.read()
2154   finally:
2155     fd.close()
2156
2157   rows = raw_data.splitlines()
2158   return rows[-lines:]
2159
2160
2161 def SafeEncode(text):
2162   """Return a 'safe' version of a source string.
2163
2164   This function mangles the input string and returns a version that
2165   should be safe to display/encode as ASCII. To this end, we first
2166   convert it to ASCII using the 'backslashreplace' encoding which
2167   should get rid of any non-ASCII chars, and then we process it
2168   through a loop copied from the string repr sources in the python; we
2169   don't use string_escape anymore since that escape single quotes and
2170   backslashes too, and that is too much; and that escaping is not
2171   stable, i.e. string_escape(string_escape(x)) != string_escape(x).
2172
2173   @type text: str or unicode
2174   @param text: input data
2175   @rtype: str
2176   @return: a safe version of text
2177
2178   """
2179   if isinstance(text, unicode):
2180     # only if unicode; if str already, we handle it below
2181     text = text.encode('ascii', 'backslashreplace')
2182   resu = ""
2183   for char in text:
2184     c = ord(char)
2185     if char  == '\t':
2186       resu += r'\t'
2187     elif char == '\n':
2188       resu += r'\n'
2189     elif char == '\r':
2190       resu += r'\'r'
2191     elif c < 32 or c >= 127: # non-printable
2192       resu += "\\x%02x" % (c & 0xff)
2193     else:
2194       resu += char
2195   return resu
2196
2197
2198 def UnescapeAndSplit(text, sep=","):
2199   """Split and unescape a string based on a given separator.
2200
2201   This function splits a string based on a separator where the
2202   separator itself can be escape in order to be an element of the
2203   elements. The escaping rules are (assuming coma being the
2204   separator):
2205     - a plain , separates the elements
2206     - a sequence \\\\, (double backslash plus comma) is handled as a
2207       backslash plus a separator comma
2208     - a sequence \, (backslash plus comma) is handled as a
2209       non-separator comma
2210
2211   @type text: string
2212   @param text: the string to split
2213   @type sep: string
2214   @param text: the separator
2215   @rtype: string
2216   @return: a list of strings
2217
2218   """
2219   # we split the list by sep (with no escaping at this stage)
2220   slist = text.split(sep)
2221   # next, we revisit the elements and if any of them ended with an odd
2222   # number of backslashes, then we join it with the next
2223   rlist = []
2224   while slist:
2225     e1 = slist.pop(0)
2226     if e1.endswith("\\"):
2227       num_b = len(e1) - len(e1.rstrip("\\"))
2228       if num_b % 2 == 1:
2229         e2 = slist.pop(0)
2230         # here the backslashes remain (all), and will be reduced in
2231         # the next step
2232         rlist.append(e1 + sep + e2)
2233         continue
2234     rlist.append(e1)
2235   # finally, replace backslash-something with something
2236   rlist = [re.sub(r"\\(.)", r"\1", v) for v in rlist]
2237   return rlist
2238
2239
2240 def CommaJoin(names):
2241   """Nicely join a set of identifiers.
2242
2243   @param names: set, list or tuple
2244   @return: a string with the formatted results
2245
2246   """
2247   return ", ".join([str(val) for val in names])
2248
2249
2250 def BytesToMebibyte(value):
2251   """Converts bytes to mebibytes.
2252
2253   @type value: int
2254   @param value: Value in bytes
2255   @rtype: int
2256   @return: Value in mebibytes
2257
2258   """
2259   return int(round(value / (1024.0 * 1024.0), 0))
2260
2261
2262 def CalculateDirectorySize(path):
2263   """Calculates the size of a directory recursively.
2264
2265   @type path: string
2266   @param path: Path to directory
2267   @rtype: int
2268   @return: Size in mebibytes
2269
2270   """
2271   size = 0
2272
2273   for (curpath, _, files) in os.walk(path):
2274     for filename in files:
2275       st = os.lstat(os.path.join(curpath, filename))
2276       size += st.st_size
2277
2278   return BytesToMebibyte(size)
2279
2280
2281 def GetFilesystemStats(path):
2282   """Returns the total and free space on a filesystem.
2283
2284   @type path: string
2285   @param path: Path on filesystem to be examined
2286   @rtype: int
2287   @return: tuple of (Total space, Free space) in mebibytes
2288
2289   """
2290   st = os.statvfs(path)
2291
2292   fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
2293   tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
2294   return (tsize, fsize)
2295
2296
2297 def RunInSeparateProcess(fn):
2298   """Runs a function in a separate process.
2299
2300   Note: Only boolean return values are supported.
2301
2302   @type fn: callable
2303   @param fn: Function to be called
2304   @rtype: tuple of (int/None, int/None)
2305   @return: Exit code and signal number
2306
2307   """
2308   pid = os.fork()
2309   if pid == 0:
2310     # Child process
2311     try:
2312       # In case the function uses temporary files
2313       ResetTempfileModule()
2314
2315       # Call function
2316       result = int(bool(fn()))
2317       assert result in (0, 1)
2318     except: # pylint: disable-msg=W0702
2319       logging.exception("Error while calling function in separate process")
2320       # 0 and 1 are reserved for the return value
2321       result = 33
2322
2323     os._exit(result) # pylint: disable-msg=W0212
2324
2325   # Parent process
2326
2327   # Avoid zombies and check exit code
2328   (_, status) = os.waitpid(pid, 0)
2329
2330   if os.WIFSIGNALED(status):
2331     exitcode = None
2332     signum = os.WTERMSIG(status)
2333   else:
2334     exitcode = os.WEXITSTATUS(status)
2335     signum = None
2336
2337   if not (exitcode in (0, 1) and signum is None):
2338     raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
2339                               (exitcode, signum))
2340
2341   return bool(exitcode)
2342
2343
2344 def LockedMethod(fn):
2345   """Synchronized object access decorator.
2346
2347   This decorator is intended to protect access to an object using the
2348   object's own lock which is hardcoded to '_lock'.
2349
2350   """
2351   def _LockDebug(*args, **kwargs):
2352     if debug_locks:
2353       logging.debug(*args, **kwargs)
2354
2355   def wrapper(self, *args, **kwargs):
2356     # pylint: disable-msg=W0212
2357     assert hasattr(self, '_lock')
2358     lock = self._lock
2359     _LockDebug("Waiting for %s", lock)
2360     lock.acquire()
2361     try:
2362       _LockDebug("Acquired %s", lock)
2363       result = fn(self, *args, **kwargs)
2364     finally:
2365       _LockDebug("Releasing %s", lock)
2366       lock.release()
2367       _LockDebug("Released %s", lock)
2368     return result
2369   return wrapper
2370
2371
2372 def LockFile(fd):
2373   """Locks a file using POSIX locks.
2374
2375   @type fd: int
2376   @param fd: the file descriptor we need to lock
2377
2378   """
2379   try:
2380     fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
2381   except IOError, err:
2382     if err.errno == errno.EAGAIN:
2383       raise errors.LockError("File already locked")
2384     raise
2385
2386
2387 def FormatTime(val):
2388   """Formats a time value.
2389
2390   @type val: float or None
2391   @param val: the timestamp as returned by time.time()
2392   @return: a string value or N/A if we don't have a valid timestamp
2393
2394   """
2395   if val is None or not isinstance(val, (int, float)):
2396     return "N/A"
2397   # these two codes works on Linux, but they are not guaranteed on all
2398   # platforms
2399   return time.strftime("%F %T", time.localtime(val))
2400
2401
2402 def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
2403   """Reads the watcher pause file.
2404
2405   @type filename: string
2406   @param filename: Path to watcher pause file
2407   @type now: None, float or int
2408   @param now: Current time as Unix timestamp
2409   @type remove_after: int
2410   @param remove_after: Remove watcher pause file after specified amount of
2411     seconds past the pause end time
2412
2413   """
2414   if now is None:
2415     now = time.time()
2416
2417   try:
2418     value = ReadFile(filename)
2419   except IOError, err:
2420     if err.errno != errno.ENOENT:
2421       raise
2422     value = None
2423
2424   if value is not None:
2425     try:
2426       value = int(value)
2427     except ValueError:
2428       logging.warning(("Watcher pause file (%s) contains invalid value,"
2429                        " removing it"), filename)
2430       RemoveFile(filename)
2431       value = None
2432
2433     if value is not None:
2434       # Remove file if it's outdated
2435       if now > (value + remove_after):
2436         RemoveFile(filename)
2437         value = None
2438
2439       elif now > value:
2440         value = None
2441
2442   return value
2443
2444
2445 class RetryTimeout(Exception):
2446   """Retry loop timed out.
2447
2448   """
2449
2450
2451 class RetryAgain(Exception):
2452   """Retry again.
2453
2454   """
2455
2456
2457 class _RetryDelayCalculator(object):
2458   """Calculator for increasing delays.
2459
2460   """
2461   __slots__ = [
2462     "_factor",
2463     "_limit",
2464     "_next",
2465     "_start",
2466     ]
2467
2468   def __init__(self, start, factor, limit):
2469     """Initializes this class.
2470
2471     @type start: float
2472     @param start: Initial delay
2473     @type factor: float
2474     @param factor: Factor for delay increase
2475     @type limit: float or None
2476     @param limit: Upper limit for delay or None for no limit
2477
2478     """
2479     assert start > 0.0
2480     assert factor >= 1.0
2481     assert limit is None or limit >= 0.0
2482
2483     self._start = start
2484     self._factor = factor
2485     self._limit = limit
2486
2487     self._next = start
2488
2489   def __call__(self):
2490     """Returns current delay and calculates the next one.
2491
2492     """
2493     current = self._next
2494
2495     # Update for next run
2496     if self._limit is None or self._next < self._limit:
2497       self._next = min(self._limit, self._next * self._factor)
2498
2499     return current
2500
2501
2502 #: Special delay to specify whole remaining timeout
2503 RETRY_REMAINING_TIME = object()
2504
2505
2506 def Retry(fn, delay, timeout, args=None, wait_fn=time.sleep,
2507           _time_fn=time.time):
2508   """Call a function repeatedly until it succeeds.
2509
2510   The function C{fn} is called repeatedly until it doesn't throw L{RetryAgain}
2511   anymore. Between calls a delay, specified by C{delay}, is inserted. After a
2512   total of C{timeout} seconds, this function throws L{RetryTimeout}.
2513
2514   C{delay} can be one of the following:
2515     - callable returning the delay length as a float
2516     - Tuple of (start, factor, limit)
2517     - L{RETRY_REMAINING_TIME} to sleep until the timeout expires (this is
2518       useful when overriding L{wait_fn} to wait for an external event)
2519     - A static delay as a number (int or float)
2520
2521   @type fn: callable
2522   @param fn: Function to be called
2523   @param delay: Either a callable (returning the delay), a tuple of (start,
2524                 factor, limit) (see L{_RetryDelayCalculator}),
2525                 L{RETRY_REMAINING_TIME} or a number (int or float)
2526   @type timeout: float
2527   @param timeout: Total timeout
2528   @type wait_fn: callable
2529   @param wait_fn: Waiting function
2530   @return: Return value of function
2531
2532   """
2533   assert callable(fn)
2534   assert callable(wait_fn)
2535   assert callable(_time_fn)
2536
2537   if args is None:
2538     args = []
2539
2540   end_time = _time_fn() + timeout
2541
2542   if callable(delay):
2543     # External function to calculate delay
2544     calc_delay = delay
2545
2546   elif isinstance(delay, (tuple, list)):
2547     # Increasing delay with optional upper boundary
2548     (start, factor, limit) = delay
2549     calc_delay = _RetryDelayCalculator(start, factor, limit)
2550
2551   elif delay is RETRY_REMAINING_TIME:
2552     # Always use the remaining time
2553     calc_delay = None
2554
2555   else:
2556     # Static delay
2557     calc_delay = lambda: delay
2558
2559   assert calc_delay is None or callable(calc_delay)
2560
2561   while True:
2562     try:
2563       # pylint: disable-msg=W0142
2564       return fn(*args)
2565     except RetryAgain:
2566       pass
2567
2568     remaining_time = end_time - _time_fn()
2569
2570     if remaining_time < 0.0:
2571       raise RetryTimeout()
2572
2573     assert remaining_time >= 0.0
2574
2575     if calc_delay is None:
2576       wait_fn(remaining_time)
2577     else:
2578       current_delay = calc_delay()
2579       if current_delay > 0.0:
2580         wait_fn(current_delay)
2581
2582
2583 def GetClosedTempfile(*args, **kwargs):
2584   """Creates a temporary file and returns its path.
2585
2586   """
2587   (fd, path) = tempfile.mkstemp(*args, **kwargs)
2588   _CloseFDNoErr(fd)
2589   return path
2590
2591
2592 def GenerateSelfSignedX509Cert(common_name, validity):
2593   """Generates a self-signed X509 certificate.
2594
2595   @type common_name: string
2596   @param common_name: commonName value
2597   @type validity: int
2598   @param validity: Validity for certificate in seconds
2599
2600   """
2601   # Create private and public key
2602   key = OpenSSL.crypto.PKey()
2603   key.generate_key(OpenSSL.crypto.TYPE_RSA, constants.RSA_KEY_BITS)
2604
2605   # Create self-signed certificate
2606   cert = OpenSSL.crypto.X509()
2607   if common_name:
2608     cert.get_subject().CN = common_name
2609   cert.set_serial_number(1)
2610   cert.gmtime_adj_notBefore(0)
2611   cert.gmtime_adj_notAfter(validity)
2612   cert.set_issuer(cert.get_subject())
2613   cert.set_pubkey(key)
2614   cert.sign(key, constants.X509_CERT_SIGN_DIGEST)
2615
2616   key_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key)
2617   cert_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
2618
2619   return (key_pem, cert_pem)
2620
2621
2622 def GenerateSelfSignedSslCert(filename, validity=(5 * 365)):
2623   """Legacy function to generate self-signed X509 certificate.
2624
2625   """
2626   (key_pem, cert_pem) = GenerateSelfSignedX509Cert(None,
2627                                                    validity * 24 * 60 * 60)
2628
2629   WriteFile(filename, mode=0400, data=key_pem + cert_pem)
2630
2631
2632 class FileLock(object):
2633   """Utility class for file locks.
2634
2635   """
2636   def __init__(self, filename):
2637     """Constructor for FileLock.
2638
2639     This will open the file denoted by the I{filename} argument.
2640
2641     @type filename: str
2642     @param filename: path to the file to be locked
2643
2644     """
2645     self.filename = filename
2646     self.fd = open(self.filename, "w")
2647
2648   def __del__(self):
2649     self.Close()
2650
2651   def Close(self):
2652     """Close the file and release the lock.
2653
2654     """
2655     if hasattr(self, "fd") and self.fd:
2656       self.fd.close()
2657       self.fd = None
2658
2659   def _flock(self, flag, blocking, timeout, errmsg):
2660     """Wrapper for fcntl.flock.
2661
2662     @type flag: int
2663     @param flag: operation flag
2664     @type blocking: bool
2665     @param blocking: whether the operation should be done in blocking mode.
2666     @type timeout: None or float
2667     @param timeout: for how long the operation should be retried (implies
2668                     non-blocking mode).
2669     @type errmsg: string
2670     @param errmsg: error message in case operation fails.
2671
2672     """
2673     assert self.fd, "Lock was closed"
2674     assert timeout is None or timeout >= 0, \
2675       "If specified, timeout must be positive"
2676
2677     if timeout is not None:
2678       flag |= fcntl.LOCK_NB
2679       timeout_end = time.time() + timeout
2680
2681     # Blocking doesn't have effect with timeout
2682     elif not blocking:
2683       flag |= fcntl.LOCK_NB
2684       timeout_end = None
2685
2686     # TODO: Convert to utils.Retry
2687
2688     retry = True
2689     while retry:
2690       try:
2691         fcntl.flock(self.fd, flag)
2692         retry = False
2693       except IOError, err:
2694         if err.errno in (errno.EAGAIN, ):
2695           if timeout_end is not None and time.time() < timeout_end:
2696             # Wait before trying again
2697             time.sleep(max(0.1, min(1.0, timeout)))
2698           else:
2699             raise errors.LockError(errmsg)
2700         else:
2701           logging.exception("fcntl.flock failed")
2702           raise
2703
2704   def Exclusive(self, blocking=False, timeout=None):
2705     """Locks the file in exclusive mode.
2706
2707     @type blocking: boolean
2708     @param blocking: whether to block and wait until we
2709         can lock the file or return immediately
2710     @type timeout: int or None
2711     @param timeout: if not None, the duration to wait for the lock
2712         (in blocking mode)
2713
2714     """
2715     self._flock(fcntl.LOCK_EX, blocking, timeout,
2716                 "Failed to lock %s in exclusive mode" % self.filename)
2717
2718   def Shared(self, blocking=False, timeout=None):
2719     """Locks the file in shared mode.
2720
2721     @type blocking: boolean
2722     @param blocking: whether to block and wait until we
2723         can lock the file or return immediately
2724     @type timeout: int or None
2725     @param timeout: if not None, the duration to wait for the lock
2726         (in blocking mode)
2727
2728     """
2729     self._flock(fcntl.LOCK_SH, blocking, timeout,
2730                 "Failed to lock %s in shared mode" % self.filename)
2731
2732   def Unlock(self, blocking=True, timeout=None):
2733     """Unlocks the file.
2734
2735     According to C{flock(2)}, unlocking can also be a nonblocking
2736     operation::
2737
2738       To make a non-blocking request, include LOCK_NB with any of the above
2739       operations.
2740
2741     @type blocking: boolean
2742     @param blocking: whether to block and wait until we
2743         can lock the file or return immediately
2744     @type timeout: int or None
2745     @param timeout: if not None, the duration to wait for the lock
2746         (in blocking mode)
2747
2748     """
2749     self._flock(fcntl.LOCK_UN, blocking, timeout,
2750                 "Failed to unlock %s" % self.filename)
2751
2752
2753 def SignalHandled(signums):
2754   """Signal Handled decoration.
2755
2756   This special decorator installs a signal handler and then calls the target
2757   function. The function must accept a 'signal_handlers' keyword argument,
2758   which will contain a dict indexed by signal number, with SignalHandler
2759   objects as values.
2760
2761   The decorator can be safely stacked with iself, to handle multiple signals
2762   with different handlers.
2763
2764   @type signums: list
2765   @param signums: signals to intercept
2766
2767   """
2768   def wrap(fn):
2769     def sig_function(*args, **kwargs):
2770       assert 'signal_handlers' not in kwargs or \
2771              kwargs['signal_handlers'] is None or \
2772              isinstance(kwargs['signal_handlers'], dict), \
2773              "Wrong signal_handlers parameter in original function call"
2774       if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
2775         signal_handlers = kwargs['signal_handlers']
2776       else:
2777         signal_handlers = {}
2778         kwargs['signal_handlers'] = signal_handlers
2779       sighandler = SignalHandler(signums)
2780       try:
2781         for sig in signums:
2782           signal_handlers[sig] = sighandler
2783         return fn(*args, **kwargs)
2784       finally:
2785         sighandler.Reset()
2786     return sig_function
2787   return wrap
2788
2789
2790 class SignalHandler(object):
2791   """Generic signal handler class.
2792
2793   It automatically restores the original handler when deconstructed or
2794   when L{Reset} is called. You can either pass your own handler
2795   function in or query the L{called} attribute to detect whether the
2796   signal was sent.
2797
2798   @type signum: list
2799   @ivar signum: the signals we handle
2800   @type called: boolean
2801   @ivar called: tracks whether any of the signals have been raised
2802
2803   """
2804   def __init__(self, signum, handler_fn=None):
2805     """Constructs a new SignalHandler instance.
2806
2807     @type signum: int or list of ints
2808     @param signum: Single signal number or set of signal numbers
2809     @type handler_fn: callable
2810     @param handler_fn: Signal handling function
2811
2812     """
2813     assert handler_fn is None or callable(handler_fn)
2814
2815     self.signum = set(signum)
2816     self.called = False
2817
2818     self._handler_fn = handler_fn
2819
2820     self._previous = {}
2821     try:
2822       for signum in self.signum:
2823         # Setup handler
2824         prev_handler = signal.signal(signum, self._HandleSignal)
2825         try:
2826           self._previous[signum] = prev_handler
2827         except:
2828           # Restore previous handler
2829           signal.signal(signum, prev_handler)
2830           raise
2831     except:
2832       # Reset all handlers
2833       self.Reset()
2834       # Here we have a race condition: a handler may have already been called,
2835       # but there's not much we can do about it at this point.
2836       raise
2837
2838   def __del__(self):
2839     self.Reset()
2840
2841   def Reset(self):
2842     """Restore previous handler.
2843
2844     This will reset all the signals to their previous handlers.
2845
2846     """
2847     for signum, prev_handler in self._previous.items():
2848       signal.signal(signum, prev_handler)
2849       # If successful, remove from dict
2850       del self._previous[signum]
2851
2852   def Clear(self):
2853     """Unsets the L{called} flag.
2854
2855     This function can be used in case a signal may arrive several times.
2856
2857     """
2858     self.called = False
2859
2860   def _HandleSignal(self, signum, frame):
2861     """Actual signal handling function.
2862
2863     """
2864     # This is not nice and not absolutely atomic, but it appears to be the only
2865     # solution in Python -- there are no atomic types.
2866     self.called = True
2867
2868     if self._handler_fn:
2869       self._handler_fn(signum, frame)
2870
2871
2872 class FieldSet(object):
2873   """A simple field set.
2874
2875   Among the features are:
2876     - checking if a string is among a list of static string or regex objects
2877     - checking if a whole list of string matches
2878     - returning the matching groups from a regex match
2879
2880   Internally, all fields are held as regular expression objects.
2881
2882   """
2883   def __init__(self, *items):
2884     self.items = [re.compile("^%s$" % value) for value in items]
2885
2886   def Extend(self, other_set):
2887     """Extend the field set with the items from another one"""
2888     self.items.extend(other_set.items)
2889
2890   def Matches(self, field):
2891     """Checks if a field matches the current set
2892
2893     @type field: str
2894     @param field: the string to match
2895     @return: either None or a regular expression match object
2896
2897     """
2898     for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
2899       return m
2900     return None
2901
2902   def NonMatching(self, items):
2903     """Returns the list of fields not matching the current set
2904
2905     @type items: list
2906     @param items: the list of fields to check
2907     @rtype: list
2908     @return: list of non-matching fields
2909
2910     """
2911     return [val for val in items if not self.Matches(val)]