Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ bf4daac9

History | View | Annotate | Download (69.5 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Ganeti utility module.
23

24
This module holds functions that can be used in both daemons (all) and
25
the command line scripts.
26

27
"""
28

    
29

    
30
import os
31
import time
32
import subprocess
33
import re
34
import socket
35
import tempfile
36
import shutil
37
import errno
38
import pwd
39
import itertools
40
import select
41
import fcntl
42
import resource
43
import logging
44
import logging.handlers
45
import signal
46

    
47
from cStringIO import StringIO
48

    
49
try:
50
  from hashlib import sha1
51
except ImportError:
52
  import sha
53
  sha1 = sha.new
54

    
55
from ganeti import errors
56
from ganeti import constants
57

    
58

    
59
_locksheld = []
60
_re_shell_unquoted = re.compile('^[-.,=:/_+@A-Za-z0-9]+$')
61

    
62
debug_locks = False
63

    
64
#: when set to True, L{RunCmd} is disabled
65
no_fork = False
66

    
67
_RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
68

    
69

    
70
class RunResult(object):
71
  """Holds the result of running external programs.
72

73
  @type exit_code: int
74
  @ivar exit_code: the exit code of the program, or None (if the program
75
      didn't exit())
76
  @type signal: int or None
77
  @ivar signal: the signal that caused the program to finish, or None
78
      (if the program wasn't terminated by a signal)
79
  @type stdout: str
80
  @ivar stdout: the standard output of the program
81
  @type stderr: str
82
  @ivar stderr: the standard error of the program
83
  @type failed: boolean
84
  @ivar failed: True in case the program was
85
      terminated by a signal or exited with a non-zero exit code
86
  @ivar fail_reason: a string detailing the termination reason
87

88
  """
89
  __slots__ = ["exit_code", "signal", "stdout", "stderr",
90
               "failed", "fail_reason", "cmd"]
91

    
92

    
93
  def __init__(self, exit_code, signal_, stdout, stderr, cmd):
94
    self.cmd = cmd
95
    self.exit_code = exit_code
96
    self.signal = signal_
97
    self.stdout = stdout
98
    self.stderr = stderr
99
    self.failed = (signal_ is not None or exit_code != 0)
100

    
101
    if self.signal is not None:
102
      self.fail_reason = "terminated by signal %s" % self.signal
103
    elif self.exit_code is not None:
104
      self.fail_reason = "exited with exit code %s" % self.exit_code
105
    else:
106
      self.fail_reason = "unable to determine termination reason"
107

    
108
    if self.failed:
109
      logging.debug("Command '%s' failed (%s); output: %s",
110
                    self.cmd, self.fail_reason, self.output)
111

    
112
  def _GetOutput(self):
113
    """Returns the combined stdout and stderr for easier usage.
114

115
    """
116
    return self.stdout + self.stderr
117

    
118
  output = property(_GetOutput, None, None, "Return full output")
119

    
120

    
121
def RunCmd(cmd, env=None, output=None, cwd='/', reset_env=False):
122
  """Execute a (shell) command.
123

124
  The command should not read from its standard input, as it will be
125
  closed.
126

127
  @type cmd: string or list
128
  @param cmd: Command to run
129
  @type env: dict
130
  @param env: Additional environment
131
  @type output: str
132
  @param output: if desired, the output of the command can be
133
      saved in a file instead of the RunResult instance; this
134
      parameter denotes the file name (if not None)
135
  @type cwd: string
136
  @param cwd: if specified, will be used as the working
137
      directory for the command; the default will be /
138
  @type reset_env: boolean
139
  @param reset_env: whether to reset or keep the default os environment
140
  @rtype: L{RunResult}
141
  @return: RunResult instance
142
  @raise errors.ProgrammerError: if we call this when forks are disabled
143

144
  """
145
  if no_fork:
146
    raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
147

    
148
  if isinstance(cmd, list):
149
    cmd = [str(val) for val in cmd]
150
    strcmd = " ".join(cmd)
151
    shell = False
152
  else:
153
    strcmd = cmd
154
    shell = True
155
  logging.debug("RunCmd '%s'", strcmd)
156

    
157
  if not reset_env:
158
    cmd_env = os.environ.copy()
159
    cmd_env["LC_ALL"] = "C"
160
  else:
161
    cmd_env = {}
162

    
163
  if env is not None:
164
    cmd_env.update(env)
165

    
166
  try:
167
    if output is None:
168
      out, err, status = _RunCmdPipe(cmd, cmd_env, shell, cwd)
169
    else:
170
      status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
171
      out = err = ""
172
  except OSError, err:
173
    if err.errno == errno.ENOENT:
174
      raise errors.OpExecError("Can't execute '%s': not found (%s)" %
175
                               (strcmd, err))
176
    else:
177
      raise
178

    
179
  if status >= 0:
180
    exitcode = status
181
    signal_ = None
182
  else:
183
    exitcode = None
184
    signal_ = -status
185

    
186
  return RunResult(exitcode, signal_, out, err, strcmd)
187

    
188

    
189
def _RunCmdPipe(cmd, env, via_shell, cwd):
190
  """Run a command and return its output.
191

192
  @type  cmd: string or list
193
  @param cmd: Command to run
194
  @type env: dict
195
  @param env: The environment to use
196
  @type via_shell: bool
197
  @param via_shell: if we should run via the shell
198
  @type cwd: string
199
  @param cwd: the working directory for the program
200
  @rtype: tuple
201
  @return: (out, err, status)
202

203
  """
204
  poller = select.poll()
205
  child = subprocess.Popen(cmd, shell=via_shell,
206
                           stderr=subprocess.PIPE,
207
                           stdout=subprocess.PIPE,
208
                           stdin=subprocess.PIPE,
209
                           close_fds=True, env=env,
210
                           cwd=cwd)
211

    
212
  child.stdin.close()
213
  poller.register(child.stdout, select.POLLIN)
214
  poller.register(child.stderr, select.POLLIN)
215
  out = StringIO()
216
  err = StringIO()
217
  fdmap = {
218
    child.stdout.fileno(): (out, child.stdout),
219
    child.stderr.fileno(): (err, child.stderr),
220
    }
221
  for fd in fdmap:
222
    status = fcntl.fcntl(fd, fcntl.F_GETFL)
223
    fcntl.fcntl(fd, fcntl.F_SETFL, status | os.O_NONBLOCK)
224

    
225
  while fdmap:
226
    try:
227
      pollresult = poller.poll()
228
    except EnvironmentError, eerr:
229
      if eerr.errno == errno.EINTR:
230
        continue
231
      raise
232
    except select.error, serr:
233
      if serr[0] == errno.EINTR:
234
        continue
235
      raise
236

    
237
    for fd, event in pollresult:
238
      if event & select.POLLIN or event & select.POLLPRI:
239
        data = fdmap[fd][1].read()
240
        # no data from read signifies EOF (the same as POLLHUP)
241
        if not data:
242
          poller.unregister(fd)
243
          del fdmap[fd]
244
          continue
245
        fdmap[fd][0].write(data)
246
      if (event & select.POLLNVAL or event & select.POLLHUP or
247
          event & select.POLLERR):
248
        poller.unregister(fd)
249
        del fdmap[fd]
250

    
251
  out = out.getvalue()
252
  err = err.getvalue()
253

    
254
  status = child.wait()
255
  return out, err, status
256

    
257

    
258
def _RunCmdFile(cmd, env, via_shell, output, cwd):
259
  """Run a command and save its output to a file.
260

261
  @type  cmd: string or list
262
  @param cmd: Command to run
263
  @type env: dict
264
  @param env: The environment to use
265
  @type via_shell: bool
266
  @param via_shell: if we should run via the shell
267
  @type output: str
268
  @param output: the filename in which to save the output
269
  @type cwd: string
270
  @param cwd: the working directory for the program
271
  @rtype: int
272
  @return: the exit status
273

274
  """
275
  fh = open(output, "a")
276
  try:
277
    child = subprocess.Popen(cmd, shell=via_shell,
278
                             stderr=subprocess.STDOUT,
279
                             stdout=fh,
280
                             stdin=subprocess.PIPE,
281
                             close_fds=True, env=env,
282
                             cwd=cwd)
283

    
284
    child.stdin.close()
285
    status = child.wait()
286
  finally:
287
    fh.close()
288
  return status
289

    
290

    
291
def RemoveFile(filename):
292
  """Remove a file ignoring some errors.
293

294
  Remove a file, ignoring non-existing ones or directories. Other
295
  errors are passed.
296

297
  @type filename: str
298
  @param filename: the file to be removed
299

300
  """
301
  try:
302
    os.unlink(filename)
303
  except OSError, err:
304
    if err.errno not in (errno.ENOENT, errno.EISDIR):
305
      raise
306

    
307

    
308
def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
309
  """Renames a file.
310

311
  @type old: string
312
  @param old: Original path
313
  @type new: string
314
  @param new: New path
315
  @type mkdir: bool
316
  @param mkdir: Whether to create target directory if it doesn't exist
317
  @type mkdir_mode: int
318
  @param mkdir_mode: Mode for newly created directories
319

320
  """
321
  try:
322
    return os.rename(old, new)
323
  except OSError, err:
324
    # In at least one use case of this function, the job queue, directory
325
    # creation is very rare. Checking for the directory before renaming is not
326
    # as efficient.
327
    if mkdir and err.errno == errno.ENOENT:
328
      # Create directory and try again
329
      dirname = os.path.dirname(new)
330
      try:
331
        os.makedirs(dirname, mode=mkdir_mode)
332
      except OSError, err:
333
        # Ignore EEXIST. This is only handled in os.makedirs as included in
334
        # Python 2.5 and above.
335
        if err.errno != errno.EEXIST or not os.path.exists(dirname):
336
          raise
337

    
338
      return os.rename(old, new)
339

    
340
    raise
341

    
342

    
343
def ResetTempfileModule():
344
  """Resets the random name generator of the tempfile module.
345

346
  This function should be called after C{os.fork} in the child process to
347
  ensure it creates a newly seeded random generator. Otherwise it would
348
  generate the same random parts as the parent process. If several processes
349
  race for the creation of a temporary file, this could lead to one not getting
350
  a temporary name.
351

352
  """
353
  # pylint: disable-msg=W0212
354
  if hasattr(tempfile, "_once_lock") and hasattr(tempfile, "_name_sequence"):
355
    tempfile._once_lock.acquire()
356
    try:
357
      # Reset random name generator
358
      tempfile._name_sequence = None
359
    finally:
360
      tempfile._once_lock.release()
361
  else:
362
    logging.critical("The tempfile module misses at least one of the"
363
                     " '_once_lock' and '_name_sequence' attributes")
364

    
365

    
366
def _FingerprintFile(filename):
367
  """Compute the fingerprint of a file.
368

369
  If the file does not exist, a None will be returned
370
  instead.
371

372
  @type filename: str
373
  @param filename: the filename to checksum
374
  @rtype: str
375
  @return: the hex digest of the sha checksum of the contents
376
      of the file
377

378
  """
379
  if not (os.path.exists(filename) and os.path.isfile(filename)):
380
    return None
381

    
382
  f = open(filename)
383

    
384
  fp = sha1()
385
  while True:
386
    data = f.read(4096)
387
    if not data:
388
      break
389

    
390
    fp.update(data)
391

    
392
  return fp.hexdigest()
393

    
394

    
395
def FingerprintFiles(files):
396
  """Compute fingerprints for a list of files.
397

398
  @type files: list
399
  @param files: the list of filename to fingerprint
400
  @rtype: dict
401
  @return: a dictionary filename: fingerprint, holding only
402
      existing files
403

404
  """
405
  ret = {}
406

    
407
  for filename in files:
408
    cksum = _FingerprintFile(filename)
409
    if cksum:
410
      ret[filename] = cksum
411

    
412
  return ret
413

    
414

    
415
def ForceDictType(target, key_types, allowed_values=None):
416
  """Force the values of a dict to have certain types.
417

418
  @type target: dict
419
  @param target: the dict to update
420
  @type key_types: dict
421
  @param key_types: dict mapping target dict keys to types
422
                    in constants.ENFORCEABLE_TYPES
423
  @type allowed_values: list
424
  @keyword allowed_values: list of specially allowed values
425

426
  """
427
  if allowed_values is None:
428
    allowed_values = []
429

    
430
  if not isinstance(target, dict):
431
    msg = "Expected dictionary, got '%s'" % target
432
    raise errors.TypeEnforcementError(msg)
433

    
434
  for key in target:
435
    if key not in key_types:
436
      msg = "Unknown key '%s'" % key
437
      raise errors.TypeEnforcementError(msg)
438

    
439
    if target[key] in allowed_values:
440
      continue
441

    
442
    ktype = key_types[key]
443
    if ktype not in constants.ENFORCEABLE_TYPES:
444
      msg = "'%s' has non-enforceable type %s" % (key, ktype)
445
      raise errors.ProgrammerError(msg)
446

    
447
    if ktype == constants.VTYPE_STRING:
448
      if not isinstance(target[key], basestring):
449
        if isinstance(target[key], bool) and not target[key]:
450
          target[key] = ''
451
        else:
452
          msg = "'%s' (value %s) is not a valid string" % (key, target[key])
453
          raise errors.TypeEnforcementError(msg)
454
    elif ktype == constants.VTYPE_BOOL:
455
      if isinstance(target[key], basestring) and target[key]:
456
        if target[key].lower() == constants.VALUE_FALSE:
457
          target[key] = False
458
        elif target[key].lower() == constants.VALUE_TRUE:
459
          target[key] = True
460
        else:
461
          msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
462
          raise errors.TypeEnforcementError(msg)
463
      elif target[key]:
464
        target[key] = True
465
      else:
466
        target[key] = False
467
    elif ktype == constants.VTYPE_SIZE:
468
      try:
469
        target[key] = ParseUnit(target[key])
470
      except errors.UnitParseError, err:
471
        msg = "'%s' (value %s) is not a valid size. error: %s" % \
472
              (key, target[key], err)
473
        raise errors.TypeEnforcementError(msg)
474
    elif ktype == constants.VTYPE_INT:
475
      try:
476
        target[key] = int(target[key])
477
      except (ValueError, TypeError):
478
        msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
479
        raise errors.TypeEnforcementError(msg)
480

    
481

    
482
def IsProcessAlive(pid):
483
  """Check if a given pid exists on the system.
484

485
  @note: zombie status is not handled, so zombie processes
486
      will be returned as alive
487
  @type pid: int
488
  @param pid: the process ID to check
489
  @rtype: boolean
490
  @return: True if the process exists
491

492
  """
493
  if pid <= 0:
494
    return False
495

    
496
  try:
497
    os.stat("/proc/%d/status" % pid)
498
    return True
499
  except EnvironmentError, err:
500
    if err.errno in (errno.ENOENT, errno.ENOTDIR):
501
      return False
502
    raise
503

    
504

    
505
def ReadPidFile(pidfile):
506
  """Read a pid from a file.
507

508
  @type  pidfile: string
509
  @param pidfile: path to the file containing the pid
510
  @rtype: int
511
  @return: The process id, if the file exists and contains a valid PID,
512
           otherwise 0
513

514
  """
515
  try:
516
    raw_data = ReadFile(pidfile)
517
  except EnvironmentError, err:
518
    if err.errno != errno.ENOENT:
519
      logging.exception("Can't read pid file")
520
    return 0
521

    
522
  try:
523
    pid = int(raw_data)
524
  except (TypeError, ValueError), err:
525
    logging.info("Can't parse pid file contents", exc_info=True)
526
    return 0
527

    
528
  return pid
529

    
530

    
531
def MatchNameComponent(key, name_list, case_sensitive=True):
532
  """Try to match a name against a list.
533

534
  This function will try to match a name like test1 against a list
535
  like C{['test1.example.com', 'test2.example.com', ...]}. Against
536
  this list, I{'test1'} as well as I{'test1.example'} will match, but
537
  not I{'test1.ex'}. A multiple match will be considered as no match
538
  at all (e.g. I{'test1'} against C{['test1.example.com',
539
  'test1.example.org']}), except when the key fully matches an entry
540
  (e.g. I{'test1'} against C{['test1', 'test1.example.com']}).
541

542
  @type key: str
543
  @param key: the name to be searched
544
  @type name_list: list
545
  @param name_list: the list of strings against which to search the key
546
  @type case_sensitive: boolean
547
  @param case_sensitive: whether to provide a case-sensitive match
548

549
  @rtype: None or str
550
  @return: None if there is no match I{or} if there are multiple matches,
551
      otherwise the element from the list which matches
552

553
  """
554
  if key in name_list:
555
    return key
556

    
557
  re_flags = 0
558
  if not case_sensitive:
559
    re_flags |= re.IGNORECASE
560
    key = key.upper()
561
  mo = re.compile("^%s(\..*)?$" % re.escape(key), re_flags)
562
  names_filtered = []
563
  string_matches = []
564
  for name in name_list:
565
    if mo.match(name) is not None:
566
      names_filtered.append(name)
567
      if not case_sensitive and key == name.upper():
568
        string_matches.append(name)
569

    
570
  if len(string_matches) == 1:
571
    return string_matches[0]
572
  if len(names_filtered) == 1:
573
    return names_filtered[0]
574
  return None
575

    
576

    
577
class HostInfo:
578
  """Class implementing resolver and hostname functionality
579

580
  """
581
  def __init__(self, name=None):
582
    """Initialize the host name object.
583

584
    If the name argument is not passed, it will use this system's
585
    name.
586

587
    """
588
    if name is None:
589
      name = self.SysName()
590

    
591
    self.query = name
592
    self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
593
    self.ip = self.ipaddrs[0]
594

    
595
  def ShortName(self):
596
    """Returns the hostname without domain.
597

598
    """
599
    return self.name.split('.')[0]
600

    
601
  @staticmethod
602
  def SysName():
603
    """Return the current system's name.
604

605
    This is simply a wrapper over C{socket.gethostname()}.
606

607
    """
608
    return socket.gethostname()
609

    
610
  @staticmethod
611
  def LookupHostname(hostname):
612
    """Look up hostname
613

614
    @type hostname: str
615
    @param hostname: hostname to look up
616

617
    @rtype: tuple
618
    @return: a tuple (name, aliases, ipaddrs) as returned by
619
        C{socket.gethostbyname_ex}
620
    @raise errors.ResolverError: in case of errors in resolving
621

622
    """
623
    try:
624
      result = socket.gethostbyname_ex(hostname)
625
    except socket.gaierror, err:
626
      # hostname not found in DNS
627
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
628

    
629
    return result
630

    
631

    
632
def GetHostInfo(name=None):
633
  """Lookup host name and raise an OpPrereqError for failures"""
634

    
635
  try:
636
    return HostInfo(name)
637
  except errors.ResolverError, err:
638
    raise errors.OpPrereqError("The given name (%s) does not resolve: %s" %
639
                               (err[0], err[2]), errors.ECODE_RESOLVER)
640

    
641

    
642
def ListVolumeGroups():
643
  """List volume groups and their size
644

645
  @rtype: dict
646
  @return:
647
       Dictionary with keys volume name and values
648
       the size of the volume
649

650
  """
651
  command = "vgs --noheadings --units m --nosuffix -o name,size"
652
  result = RunCmd(command)
653
  retval = {}
654
  if result.failed:
655
    return retval
656

    
657
  for line in result.stdout.splitlines():
658
    try:
659
      name, size = line.split()
660
      size = int(float(size))
661
    except (IndexError, ValueError), err:
662
      logging.error("Invalid output from vgs (%s): %s", err, line)
663
      continue
664

    
665
    retval[name] = size
666

    
667
  return retval
668

    
669

    
670
def BridgeExists(bridge):
671
  """Check whether the given bridge exists in the system
672

673
  @type bridge: str
674
  @param bridge: the bridge name to check
675
  @rtype: boolean
676
  @return: True if it does
677

678
  """
679
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
680

    
681

    
682
def NiceSort(name_list):
683
  """Sort a list of strings based on digit and non-digit groupings.
684

685
  Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
686
  will sort the list in the logical order C{['a1', 'a2', 'a10',
687
  'a11']}.
688

689
  The sort algorithm breaks each name in groups of either only-digits
690
  or no-digits. Only the first eight such groups are considered, and
691
  after that we just use what's left of the string.
692

693
  @type name_list: list
694
  @param name_list: the names to be sorted
695
  @rtype: list
696
  @return: a copy of the name list sorted with our algorithm
697

698
  """
699
  _SORTER_BASE = "(\D+|\d+)"
700
  _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
701
                                                  _SORTER_BASE, _SORTER_BASE,
702
                                                  _SORTER_BASE, _SORTER_BASE,
703
                                                  _SORTER_BASE, _SORTER_BASE)
704
  _SORTER_RE = re.compile(_SORTER_FULL)
705
  _SORTER_NODIGIT = re.compile("^\D*$")
706
  def _TryInt(val):
707
    """Attempts to convert a variable to integer."""
708
    if val is None or _SORTER_NODIGIT.match(val):
709
      return val
710
    rval = int(val)
711
    return rval
712

    
713
  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
714
             for name in name_list]
715
  to_sort.sort()
716
  return [tup[1] for tup in to_sort]
717

    
718

    
719
def TryConvert(fn, val):
720
  """Try to convert a value ignoring errors.
721

722
  This function tries to apply function I{fn} to I{val}. If no
723
  C{ValueError} or C{TypeError} exceptions are raised, it will return
724
  the result, else it will return the original value. Any other
725
  exceptions are propagated to the caller.
726

727
  @type fn: callable
728
  @param fn: function to apply to the value
729
  @param val: the value to be converted
730
  @return: The converted value if the conversion was successful,
731
      otherwise the original value.
732

733
  """
734
  try:
735
    nv = fn(val)
736
  except (ValueError, TypeError):
737
    nv = val
738
  return nv
739

    
740

    
741
def IsValidIP(ip):
742
  """Verifies the syntax of an IPv4 address.
743

744
  This function checks if the IPv4 address passes is valid or not based
745
  on syntax (not IP range, class calculations, etc.).
746

747
  @type ip: str
748
  @param ip: the address to be checked
749
  @rtype: a regular expression match object
750
  @return: a regular expression match object, or None if the
751
      address is not valid
752

753
  """
754
  unit = "(0|[1-9]\d{0,2})"
755
  #TODO: convert and return only boolean
756
  return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
757

    
758

    
759
def IsValidShellParam(word):
760
  """Verifies is the given word is safe from the shell's p.o.v.
761

762
  This means that we can pass this to a command via the shell and be
763
  sure that it doesn't alter the command line and is passed as such to
764
  the actual command.
765

766
  Note that we are overly restrictive here, in order to be on the safe
767
  side.
768

769
  @type word: str
770
  @param word: the word to check
771
  @rtype: boolean
772
  @return: True if the word is 'safe'
773

774
  """
775
  return bool(re.match("^[-a-zA-Z0-9._+/:%@]+$", word))
776

    
777

    
778
def BuildShellCmd(template, *args):
779
  """Build a safe shell command line from the given arguments.
780

781
  This function will check all arguments in the args list so that they
782
  are valid shell parameters (i.e. they don't contain shell
783
  metacharacters). If everything is ok, it will return the result of
784
  template % args.
785

786
  @type template: str
787
  @param template: the string holding the template for the
788
      string formatting
789
  @rtype: str
790
  @return: the expanded command line
791

792
  """
793
  for word in args:
794
    if not IsValidShellParam(word):
795
      raise errors.ProgrammerError("Shell argument '%s' contains"
796
                                   " invalid characters" % word)
797
  return template % args
798

    
799

    
800
def FormatUnit(value, units):
801
  """Formats an incoming number of MiB with the appropriate unit.
802

803
  @type value: int
804
  @param value: integer representing the value in MiB (1048576)
805
  @type units: char
806
  @param units: the type of formatting we should do:
807
      - 'h' for automatic scaling
808
      - 'm' for MiBs
809
      - 'g' for GiBs
810
      - 't' for TiBs
811
  @rtype: str
812
  @return: the formatted value (with suffix)
813

814
  """
815
  if units not in ('m', 'g', 't', 'h'):
816
    raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
817

    
818
  suffix = ''
819

    
820
  if units == 'm' or (units == 'h' and value < 1024):
821
    if units == 'h':
822
      suffix = 'M'
823
    return "%d%s" % (round(value, 0), suffix)
824

    
825
  elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
826
    if units == 'h':
827
      suffix = 'G'
828
    return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
829

    
830
  else:
831
    if units == 'h':
832
      suffix = 'T'
833
    return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
834

    
835

    
836
def ParseUnit(input_string):
837
  """Tries to extract number and scale from the given string.
838

839
  Input must be in the format C{NUMBER+ [DOT NUMBER+] SPACE*
840
  [UNIT]}. If no unit is specified, it defaults to MiB. Return value
841
  is always an int in MiB.
842

843
  """
844
  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', str(input_string))
845
  if not m:
846
    raise errors.UnitParseError("Invalid format")
847

    
848
  value = float(m.groups()[0])
849

    
850
  unit = m.groups()[1]
851
  if unit:
852
    lcunit = unit.lower()
853
  else:
854
    lcunit = 'm'
855

    
856
  if lcunit in ('m', 'mb', 'mib'):
857
    # Value already in MiB
858
    pass
859

    
860
  elif lcunit in ('g', 'gb', 'gib'):
861
    value *= 1024
862

    
863
  elif lcunit in ('t', 'tb', 'tib'):
864
    value *= 1024 * 1024
865

    
866
  else:
867
    raise errors.UnitParseError("Unknown unit: %s" % unit)
868

    
869
  # Make sure we round up
870
  if int(value) < value:
871
    value += 1
872

    
873
  # Round up to the next multiple of 4
874
  value = int(value)
875
  if value % 4:
876
    value += 4 - value % 4
877

    
878
  return value
879

    
880

    
881
def AddAuthorizedKey(file_name, key):
882
  """Adds an SSH public key to an authorized_keys file.
883

884
  @type file_name: str
885
  @param file_name: path to authorized_keys file
886
  @type key: str
887
  @param key: string containing key
888

889
  """
890
  key_fields = key.split()
891

    
892
  f = open(file_name, 'a+')
893
  try:
894
    nl = True
895
    for line in f:
896
      # Ignore whitespace changes
897
      if line.split() == key_fields:
898
        break
899
      nl = line.endswith('\n')
900
    else:
901
      if not nl:
902
        f.write("\n")
903
      f.write(key.rstrip('\r\n'))
904
      f.write("\n")
905
      f.flush()
906
  finally:
907
    f.close()
908

    
909

    
910
def RemoveAuthorizedKey(file_name, key):
911
  """Removes an SSH public key from an authorized_keys file.
912

913
  @type file_name: str
914
  @param file_name: path to authorized_keys file
915
  @type key: str
916
  @param key: string containing key
917

918
  """
919
  key_fields = key.split()
920

    
921
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
922
  try:
923
    out = os.fdopen(fd, 'w')
924
    try:
925
      f = open(file_name, 'r')
926
      try:
927
        for line in f:
928
          # Ignore whitespace changes while comparing lines
929
          if line.split() != key_fields:
930
            out.write(line)
931

    
932
        out.flush()
933
        os.rename(tmpname, file_name)
934
      finally:
935
        f.close()
936
    finally:
937
      out.close()
938
  except:
939
    RemoveFile(tmpname)
940
    raise
941

    
942

    
943
def SetEtcHostsEntry(file_name, ip, hostname, aliases):
944
  """Sets the name of an IP address and hostname in /etc/hosts.
945

946
  @type file_name: str
947
  @param file_name: path to the file to modify (usually C{/etc/hosts})
948
  @type ip: str
949
  @param ip: the IP address
950
  @type hostname: str
951
  @param hostname: the hostname to be added
952
  @type aliases: list
953
  @param aliases: the list of aliases to add for the hostname
954

955
  """
956
  # FIXME: use WriteFile + fn rather than duplicating its efforts
957
  # Ensure aliases are unique
958
  aliases = UniqueSequence([hostname] + aliases)[1:]
959

    
960
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
961
  try:
962
    out = os.fdopen(fd, 'w')
963
    try:
964
      f = open(file_name, 'r')
965
      try:
966
        for line in f:
967
          fields = line.split()
968
          if fields and not fields[0].startswith('#') and ip == fields[0]:
969
            continue
970
          out.write(line)
971

    
972
        out.write("%s\t%s" % (ip, hostname))
973
        if aliases:
974
          out.write(" %s" % ' '.join(aliases))
975
        out.write('\n')
976

    
977
        out.flush()
978
        os.fsync(out)
979
        os.chmod(tmpname, 0644)
980
        os.rename(tmpname, file_name)
981
      finally:
982
        f.close()
983
    finally:
984
      out.close()
985
  except:
986
    RemoveFile(tmpname)
987
    raise
988

    
989

    
990
def AddHostToEtcHosts(hostname):
991
  """Wrapper around SetEtcHostsEntry.
992

993
  @type hostname: str
994
  @param hostname: a hostname that will be resolved and added to
995
      L{constants.ETC_HOSTS}
996

997
  """
998
  hi = HostInfo(name=hostname)
999
  SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
1000

    
1001

    
1002
def RemoveEtcHostsEntry(file_name, hostname):
1003
  """Removes a hostname from /etc/hosts.
1004

1005
  IP addresses without names are removed from the file.
1006

1007
  @type file_name: str
1008
  @param file_name: path to the file to modify (usually C{/etc/hosts})
1009
  @type hostname: str
1010
  @param hostname: the hostname to be removed
1011

1012
  """
1013
  # FIXME: use WriteFile + fn rather than duplicating its efforts
1014
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1015
  try:
1016
    out = os.fdopen(fd, 'w')
1017
    try:
1018
      f = open(file_name, 'r')
1019
      try:
1020
        for line in f:
1021
          fields = line.split()
1022
          if len(fields) > 1 and not fields[0].startswith('#'):
1023
            names = fields[1:]
1024
            if hostname in names:
1025
              while hostname in names:
1026
                names.remove(hostname)
1027
              if names:
1028
                out.write("%s %s\n" % (fields[0], ' '.join(names)))
1029
              continue
1030

    
1031
          out.write(line)
1032

    
1033
        out.flush()
1034
        os.fsync(out)
1035
        os.chmod(tmpname, 0644)
1036
        os.rename(tmpname, file_name)
1037
      finally:
1038
        f.close()
1039
    finally:
1040
      out.close()
1041
  except:
1042
    RemoveFile(tmpname)
1043
    raise
1044

    
1045

    
1046
def RemoveHostFromEtcHosts(hostname):
1047
  """Wrapper around RemoveEtcHostsEntry.
1048

1049
  @type hostname: str
1050
  @param hostname: hostname that will be resolved and its
1051
      full and shot name will be removed from
1052
      L{constants.ETC_HOSTS}
1053

1054
  """
1055
  hi = HostInfo(name=hostname)
1056
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
1057
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
1058

    
1059

    
1060
def CreateBackup(file_name):
1061
  """Creates a backup of a file.
1062

1063
  @type file_name: str
1064
  @param file_name: file to be backed up
1065
  @rtype: str
1066
  @return: the path to the newly created backup
1067
  @raise errors.ProgrammerError: for invalid file names
1068

1069
  """
1070
  if not os.path.isfile(file_name):
1071
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
1072
                                file_name)
1073

    
1074
  prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
1075
  dir_name = os.path.dirname(file_name)
1076

    
1077
  fsrc = open(file_name, 'rb')
1078
  try:
1079
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
1080
    fdst = os.fdopen(fd, 'wb')
1081
    try:
1082
      shutil.copyfileobj(fsrc, fdst)
1083
    finally:
1084
      fdst.close()
1085
  finally:
1086
    fsrc.close()
1087

    
1088
  return backup_name
1089

    
1090

    
1091
def ShellQuote(value):
1092
  """Quotes shell argument according to POSIX.
1093

1094
  @type value: str
1095
  @param value: the argument to be quoted
1096
  @rtype: str
1097
  @return: the quoted value
1098

1099
  """
1100
  if _re_shell_unquoted.match(value):
1101
    return value
1102
  else:
1103
    return "'%s'" % value.replace("'", "'\\''")
1104

    
1105

    
1106
def ShellQuoteArgs(args):
1107
  """Quotes a list of shell arguments.
1108

1109
  @type args: list
1110
  @param args: list of arguments to be quoted
1111
  @rtype: str
1112
  @return: the quoted arguments concatenated with spaces
1113

1114
  """
1115
  return ' '.join([ShellQuote(i) for i in args])
1116

    
1117

    
1118
def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
1119
  """Simple ping implementation using TCP connect(2).
1120

1121
  Check if the given IP is reachable by doing attempting a TCP connect
1122
  to it.
1123

1124
  @type target: str
1125
  @param target: the IP or hostname to ping
1126
  @type port: int
1127
  @param port: the port to connect to
1128
  @type timeout: int
1129
  @param timeout: the timeout on the connection attempt
1130
  @type live_port_needed: boolean
1131
  @param live_port_needed: whether a closed port will cause the
1132
      function to return failure, as if there was a timeout
1133
  @type source: str or None
1134
  @param source: if specified, will cause the connect to be made
1135
      from this specific source address; failures to bind other
1136
      than C{EADDRNOTAVAIL} will be ignored
1137

1138
  """
1139
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1140

    
1141
  success = False
1142

    
1143
  if source is not None:
1144
    try:
1145
      sock.bind((source, 0))
1146
    except socket.error, (errcode, _):
1147
      if errcode == errno.EADDRNOTAVAIL:
1148
        success = False
1149

    
1150
  sock.settimeout(timeout)
1151

    
1152
  try:
1153
    sock.connect((target, port))
1154
    sock.close()
1155
    success = True
1156
  except socket.timeout:
1157
    success = False
1158
  except socket.error, (errcode, _):
1159
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
1160

    
1161
  return success
1162

    
1163

    
1164
def OwnIpAddress(address):
1165
  """Check if the current host has the the given IP address.
1166

1167
  Currently this is done by TCP-pinging the address from the loopback
1168
  address.
1169

1170
  @type address: string
1171
  @param address: the address to check
1172
  @rtype: bool
1173
  @return: True if we own the address
1174

1175
  """
1176
  return TcpPing(address, constants.DEFAULT_NODED_PORT,
1177
                 source=constants.LOCALHOST_IP_ADDRESS)
1178

    
1179

    
1180
def ListVisibleFiles(path):
1181
  """Returns a list of visible files in a directory.
1182

1183
  @type path: str
1184
  @param path: the directory to enumerate
1185
  @rtype: list
1186
  @return: the list of all files not starting with a dot
1187

1188
  """
1189
  files = [i for i in os.listdir(path) if not i.startswith(".")]
1190
  files.sort()
1191
  return files
1192

    
1193

    
1194
def GetHomeDir(user, default=None):
1195
  """Try to get the homedir of the given user.
1196

1197
  The user can be passed either as a string (denoting the name) or as
1198
  an integer (denoting the user id). If the user is not found, the
1199
  'default' argument is returned, which defaults to None.
1200

1201
  """
1202
  try:
1203
    if isinstance(user, basestring):
1204
      result = pwd.getpwnam(user)
1205
    elif isinstance(user, (int, long)):
1206
      result = pwd.getpwuid(user)
1207
    else:
1208
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1209
                                   type(user))
1210
  except KeyError:
1211
    return default
1212
  return result.pw_dir
1213

    
1214

    
1215
def NewUUID():
1216
  """Returns a random UUID.
1217

1218
  @note: This is a Linux-specific method as it uses the /proc
1219
      filesystem.
1220
  @rtype: str
1221

1222
  """
1223
  return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
1224

    
1225

    
1226
def GenerateSecret(numbytes=20):
1227
  """Generates a random secret.
1228

1229
  This will generate a pseudo-random secret returning an hex string
1230
  (so that it can be used where an ASCII string is needed).
1231

1232
  @param numbytes: the number of bytes which will be represented by the returned
1233
      string (defaulting to 20, the length of a SHA1 hash)
1234
  @rtype: str
1235
  @return: an hex representation of the pseudo-random sequence
1236

1237
  """
1238
  return os.urandom(numbytes).encode('hex')
1239

    
1240

    
1241
def EnsureDirs(dirs):
1242
  """Make required directories, if they don't exist.
1243

1244
  @param dirs: list of tuples (dir_name, dir_mode)
1245
  @type dirs: list of (string, integer)
1246

1247
  """
1248
  for dir_name, dir_mode in dirs:
1249
    try:
1250
      os.mkdir(dir_name, dir_mode)
1251
    except EnvironmentError, err:
1252
      if err.errno != errno.EEXIST:
1253
        raise errors.GenericError("Cannot create needed directory"
1254
                                  " '%s': %s" % (dir_name, err))
1255
    if not os.path.isdir(dir_name):
1256
      raise errors.GenericError("%s is not a directory" % dir_name)
1257

    
1258

    
1259
def ReadFile(file_name, size=-1):
1260
  """Reads a file.
1261

1262
  @type size: int
1263
  @param size: Read at most size bytes (if negative, entire file)
1264
  @rtype: str
1265
  @return: the (possibly partial) content of the file
1266

1267
  """
1268
  f = open(file_name, "r")
1269
  try:
1270
    return f.read(size)
1271
  finally:
1272
    f.close()
1273

    
1274

    
1275
def WriteFile(file_name, fn=None, data=None,
1276
              mode=None, uid=-1, gid=-1,
1277
              atime=None, mtime=None, close=True,
1278
              dry_run=False, backup=False,
1279
              prewrite=None, postwrite=None):
1280
  """(Over)write a file atomically.
1281

1282
  The file_name and either fn (a function taking one argument, the
1283
  file descriptor, and which should write the data to it) or data (the
1284
  contents of the file) must be passed. The other arguments are
1285
  optional and allow setting the file mode, owner and group, and the
1286
  mtime/atime of the file.
1287

1288
  If the function doesn't raise an exception, it has succeeded and the
1289
  target file has the new contents. If the function has raised an
1290
  exception, an existing target file should be unmodified and the
1291
  temporary file should be removed.
1292

1293
  @type file_name: str
1294
  @param file_name: the target filename
1295
  @type fn: callable
1296
  @param fn: content writing function, called with
1297
      file descriptor as parameter
1298
  @type data: str
1299
  @param data: contents of the file
1300
  @type mode: int
1301
  @param mode: file mode
1302
  @type uid: int
1303
  @param uid: the owner of the file
1304
  @type gid: int
1305
  @param gid: the group of the file
1306
  @type atime: int
1307
  @param atime: a custom access time to be set on the file
1308
  @type mtime: int
1309
  @param mtime: a custom modification time to be set on the file
1310
  @type close: boolean
1311
  @param close: whether to close file after writing it
1312
  @type prewrite: callable
1313
  @param prewrite: function to be called before writing content
1314
  @type postwrite: callable
1315
  @param postwrite: function to be called after writing content
1316

1317
  @rtype: None or int
1318
  @return: None if the 'close' parameter evaluates to True,
1319
      otherwise the file descriptor
1320

1321
  @raise errors.ProgrammerError: if any of the arguments are not valid
1322

1323
  """
1324
  if not os.path.isabs(file_name):
1325
    raise errors.ProgrammerError("Path passed to WriteFile is not"
1326
                                 " absolute: '%s'" % file_name)
1327

    
1328
  if [fn, data].count(None) != 1:
1329
    raise errors.ProgrammerError("fn or data required")
1330

    
1331
  if [atime, mtime].count(None) == 1:
1332
    raise errors.ProgrammerError("Both atime and mtime must be either"
1333
                                 " set or None")
1334

    
1335
  if backup and not dry_run and os.path.isfile(file_name):
1336
    CreateBackup(file_name)
1337

    
1338
  dir_name, base_name = os.path.split(file_name)
1339
  fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
1340
  do_remove = True
1341
  # here we need to make sure we remove the temp file, if any error
1342
  # leaves it in place
1343
  try:
1344
    if uid != -1 or gid != -1:
1345
      os.chown(new_name, uid, gid)
1346
    if mode:
1347
      os.chmod(new_name, mode)
1348
    if callable(prewrite):
1349
      prewrite(fd)
1350
    if data is not None:
1351
      os.write(fd, data)
1352
    else:
1353
      fn(fd)
1354
    if callable(postwrite):
1355
      postwrite(fd)
1356
    os.fsync(fd)
1357
    if atime is not None and mtime is not None:
1358
      os.utime(new_name, (atime, mtime))
1359
    if not dry_run:
1360
      os.rename(new_name, file_name)
1361
      do_remove = False
1362
  finally:
1363
    if close:
1364
      os.close(fd)
1365
      result = None
1366
    else:
1367
      result = fd
1368
    if do_remove:
1369
      RemoveFile(new_name)
1370

    
1371
  return result
1372

    
1373

    
1374
def FirstFree(seq, base=0):
1375
  """Returns the first non-existing integer from seq.
1376

1377
  The seq argument should be a sorted list of positive integers. The
1378
  first time the index of an element is smaller than the element
1379
  value, the index will be returned.
1380

1381
  The base argument is used to start at a different offset,
1382
  i.e. C{[3, 4, 6]} with I{offset=3} will return 5.
1383

1384
  Example: C{[0, 1, 3]} will return I{2}.
1385

1386
  @type seq: sequence
1387
  @param seq: the sequence to be analyzed.
1388
  @type base: int
1389
  @param base: use this value as the base index of the sequence
1390
  @rtype: int
1391
  @return: the first non-used index in the sequence
1392

1393
  """
1394
  for idx, elem in enumerate(seq):
1395
    assert elem >= base, "Passed element is higher than base offset"
1396
    if elem > idx + base:
1397
      # idx is not used
1398
      return idx + base
1399
  return None
1400

    
1401

    
1402
def all(seq, pred=bool): # pylint: disable-msg=W0622
1403
  "Returns True if pred(x) is True for every element in the iterable"
1404
  for _ in itertools.ifilterfalse(pred, seq):
1405
    return False
1406
  return True
1407

    
1408

    
1409
def any(seq, pred=bool): # pylint: disable-msg=W0622
1410
  "Returns True if pred(x) is True for at least one element in the iterable"
1411
  for _ in itertools.ifilter(pred, seq):
1412
    return True
1413
  return False
1414

    
1415

    
1416
def UniqueSequence(seq):
1417
  """Returns a list with unique elements.
1418

1419
  Element order is preserved.
1420

1421
  @type seq: sequence
1422
  @param seq: the sequence with the source elements
1423
  @rtype: list
1424
  @return: list of unique elements from seq
1425

1426
  """
1427
  seen = set()
1428
  return [i for i in seq if i not in seen and not seen.add(i)]
1429

    
1430

    
1431
def NormalizeAndValidateMac(mac):
1432
  """Normalizes and check if a MAC address is valid.
1433

1434
  Checks whether the supplied MAC address is formally correct, only
1435
  accepts colon separated format. Normalize it to all lower.
1436

1437
  @type mac: str
1438
  @param mac: the MAC to be validated
1439
  @rtype: str
1440
  @return: returns the normalized and validated MAC.
1441

1442
  @raise errors.OpPrereqError: If the MAC isn't valid
1443

1444
  """
1445
  mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$", re.I)
1446
  if not mac_check.match(mac):
1447
    raise errors.OpPrereqError("Invalid MAC address specified: %s" %
1448
                               mac, errors.ECODE_INVAL)
1449

    
1450
  return mac.lower()
1451

    
1452

    
1453
def TestDelay(duration):
1454
  """Sleep for a fixed amount of time.
1455

1456
  @type duration: float
1457
  @param duration: the sleep duration
1458
  @rtype: boolean
1459
  @return: False for negative value, True otherwise
1460

1461
  """
1462
  if duration < 0:
1463
    return False, "Invalid sleep duration"
1464
  time.sleep(duration)
1465
  return True, None
1466

    
1467

    
1468
def _CloseFDNoErr(fd, retries=5):
1469
  """Close a file descriptor ignoring errors.
1470

1471
  @type fd: int
1472
  @param fd: the file descriptor
1473
  @type retries: int
1474
  @param retries: how many retries to make, in case we get any
1475
      other error than EBADF
1476

1477
  """
1478
  try:
1479
    os.close(fd)
1480
  except OSError, err:
1481
    if err.errno != errno.EBADF:
1482
      if retries > 0:
1483
        _CloseFDNoErr(fd, retries - 1)
1484
    # else either it's closed already or we're out of retries, so we
1485
    # ignore this and go on
1486

    
1487

    
1488
def CloseFDs(noclose_fds=None):
1489
  """Close file descriptors.
1490

1491
  This closes all file descriptors above 2 (i.e. except
1492
  stdin/out/err).
1493

1494
  @type noclose_fds: list or None
1495
  @param noclose_fds: if given, it denotes a list of file descriptor
1496
      that should not be closed
1497

1498
  """
1499
  # Default maximum for the number of available file descriptors.
1500
  if 'SC_OPEN_MAX' in os.sysconf_names:
1501
    try:
1502
      MAXFD = os.sysconf('SC_OPEN_MAX')
1503
      if MAXFD < 0:
1504
        MAXFD = 1024
1505
    except OSError:
1506
      MAXFD = 1024
1507
  else:
1508
    MAXFD = 1024
1509
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1510
  if (maxfd == resource.RLIM_INFINITY):
1511
    maxfd = MAXFD
1512

    
1513
  # Iterate through and close all file descriptors (except the standard ones)
1514
  for fd in range(3, maxfd):
1515
    if noclose_fds and fd in noclose_fds:
1516
      continue
1517
    _CloseFDNoErr(fd)
1518

    
1519

    
1520
def Daemonize(logfile):
1521
  """Daemonize the current process.
1522

1523
  This detaches the current process from the controlling terminal and
1524
  runs it in the background as a daemon.
1525

1526
  @type logfile: str
1527
  @param logfile: the logfile to which we should redirect stdout/stderr
1528
  @rtype: int
1529
  @return: the value zero
1530

1531
  """
1532
  # pylint: disable-msg=W0212
1533
  # yes, we really want os._exit
1534
  UMASK = 077
1535
  WORKDIR = "/"
1536

    
1537
  # this might fail
1538
  pid = os.fork()
1539
  if (pid == 0):  # The first child.
1540
    os.setsid()
1541
    # this might fail
1542
    pid = os.fork() # Fork a second child.
1543
    if (pid == 0):  # The second child.
1544
      os.chdir(WORKDIR)
1545
      os.umask(UMASK)
1546
    else:
1547
      # exit() or _exit()?  See below.
1548
      os._exit(0) # Exit parent (the first child) of the second child.
1549
  else:
1550
    os._exit(0) # Exit parent of the first child.
1551

    
1552
  for fd in range(3):
1553
    _CloseFDNoErr(fd)
1554
  i = os.open("/dev/null", os.O_RDONLY) # stdin
1555
  assert i == 0, "Can't close/reopen stdin"
1556
  i = os.open(logfile, os.O_WRONLY|os.O_CREAT|os.O_APPEND, 0600) # stdout
1557
  assert i == 1, "Can't close/reopen stdout"
1558
  # Duplicate standard output to standard error.
1559
  os.dup2(1, 2)
1560
  return 0
1561

    
1562

    
1563
def DaemonPidFileName(name):
1564
  """Compute a ganeti pid file absolute path
1565

1566
  @type name: str
1567
  @param name: the daemon name
1568
  @rtype: str
1569
  @return: the full path to the pidfile corresponding to the given
1570
      daemon name
1571

1572
  """
1573
  return os.path.join(constants.RUN_GANETI_DIR, "%s.pid" % name)
1574

    
1575

    
1576
def WritePidFile(name):
1577
  """Write the current process pidfile.
1578

1579
  The file will be written to L{constants.RUN_GANETI_DIR}I{/name.pid}
1580

1581
  @type name: str
1582
  @param name: the daemon name to use
1583
  @raise errors.GenericError: if the pid file already exists and
1584
      points to a live process
1585

1586
  """
1587
  pid = os.getpid()
1588
  pidfilename = DaemonPidFileName(name)
1589
  if IsProcessAlive(ReadPidFile(pidfilename)):
1590
    raise errors.GenericError("%s contains a live process" % pidfilename)
1591

    
1592
  WriteFile(pidfilename, data="%d\n" % pid)
1593

    
1594

    
1595
def RemovePidFile(name):
1596
  """Remove the current process pidfile.
1597

1598
  Any errors are ignored.
1599

1600
  @type name: str
1601
  @param name: the daemon name used to derive the pidfile name
1602

1603
  """
1604
  pidfilename = DaemonPidFileName(name)
1605
  # TODO: we could check here that the file contains our pid
1606
  try:
1607
    RemoveFile(pidfilename)
1608
  except: # pylint: disable-msg=W0702
1609
    pass
1610

    
1611

    
1612
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1613
                waitpid=False):
1614
  """Kill a process given by its pid.
1615

1616
  @type pid: int
1617
  @param pid: The PID to terminate.
1618
  @type signal_: int
1619
  @param signal_: The signal to send, by default SIGTERM
1620
  @type timeout: int
1621
  @param timeout: The timeout after which, if the process is still alive,
1622
                  a SIGKILL will be sent. If not positive, no such checking
1623
                  will be done
1624
  @type waitpid: boolean
1625
  @param waitpid: If true, we should waitpid on this process after
1626
      sending signals, since it's our own child and otherwise it
1627
      would remain as zombie
1628

1629
  """
1630
  def _helper(pid, signal_, wait):
1631
    """Simple helper to encapsulate the kill/waitpid sequence"""
1632
    os.kill(pid, signal_)
1633
    if wait:
1634
      try:
1635
        os.waitpid(pid, os.WNOHANG)
1636
      except OSError:
1637
        pass
1638

    
1639
  if pid <= 0:
1640
    # kill with pid=0 == suicide
1641
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1642

    
1643
  if not IsProcessAlive(pid):
1644
    return
1645

    
1646
  _helper(pid, signal_, waitpid)
1647

    
1648
  if timeout <= 0:
1649
    return
1650

    
1651
  def _CheckProcess():
1652
    if not IsProcessAlive(pid):
1653
      return
1654

    
1655
    try:
1656
      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
1657
    except OSError:
1658
      raise RetryAgain()
1659

    
1660
    if result_pid > 0:
1661
      return
1662

    
1663
    raise RetryAgain()
1664

    
1665
  try:
1666
    # Wait up to $timeout seconds
1667
    Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
1668
  except RetryTimeout:
1669
    pass
1670

    
1671
  if IsProcessAlive(pid):
1672
    # Kill process if it's still alive
1673
    _helper(pid, signal.SIGKILL, waitpid)
1674

    
1675

    
1676
def FindFile(name, search_path, test=os.path.exists):
1677
  """Look for a filesystem object in a given path.
1678

1679
  This is an abstract method to search for filesystem object (files,
1680
  dirs) under a given search path.
1681

1682
  @type name: str
1683
  @param name: the name to look for
1684
  @type search_path: str
1685
  @param search_path: location to start at
1686
  @type test: callable
1687
  @param test: a function taking one argument that should return True
1688
      if the a given object is valid; the default value is
1689
      os.path.exists, causing only existing files to be returned
1690
  @rtype: str or None
1691
  @return: full path to the object if found, None otherwise
1692

1693
  """
1694
  # validate the filename mask
1695
  if constants.EXT_PLUGIN_MASK.match(name) is None:
1696
    logging.critical("Invalid value passed for external script name: '%s'",
1697
                     name)
1698
    return None
1699

    
1700
  for dir_name in search_path:
1701
    item_name = os.path.sep.join([dir_name, name])
1702
    # check the user test and that we're indeed resolving to the given
1703
    # basename
1704
    if test(item_name) and os.path.basename(item_name) == name:
1705
      return item_name
1706
  return None
1707

    
1708

    
1709
def CheckVolumeGroupSize(vglist, vgname, minsize):
1710
  """Checks if the volume group list is valid.
1711

1712
  The function will check if a given volume group is in the list of
1713
  volume groups and has a minimum size.
1714

1715
  @type vglist: dict
1716
  @param vglist: dictionary of volume group names and their size
1717
  @type vgname: str
1718
  @param vgname: the volume group we should check
1719
  @type minsize: int
1720
  @param minsize: the minimum size we accept
1721
  @rtype: None or str
1722
  @return: None for success, otherwise the error message
1723

1724
  """
1725
  vgsize = vglist.get(vgname, None)
1726
  if vgsize is None:
1727
    return "volume group '%s' missing" % vgname
1728
  elif vgsize < minsize:
1729
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1730
            (vgname, minsize, vgsize))
1731
  return None
1732

    
1733

    
1734
def SplitTime(value):
1735
  """Splits time as floating point number into a tuple.
1736

1737
  @param value: Time in seconds
1738
  @type value: int or float
1739
  @return: Tuple containing (seconds, microseconds)
1740

1741
  """
1742
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1743

    
1744
  assert 0 <= seconds, \
1745
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1746
  assert 0 <= microseconds <= 999999, \
1747
    "Microseconds must be 0-999999, but are %s" % microseconds
1748

    
1749
  return (int(seconds), int(microseconds))
1750

    
1751

    
1752
def MergeTime(timetuple):
1753
  """Merges a tuple into time as a floating point number.
1754

1755
  @param timetuple: Time as tuple, (seconds, microseconds)
1756
  @type timetuple: tuple
1757
  @return: Time as a floating point number expressed in seconds
1758

1759
  """
1760
  (seconds, microseconds) = timetuple
1761

    
1762
  assert 0 <= seconds, \
1763
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1764
  assert 0 <= microseconds <= 999999, \
1765
    "Microseconds must be 0-999999, but are %s" % microseconds
1766

    
1767
  return float(seconds) + (float(microseconds) * 0.000001)
1768

    
1769

    
1770
def GetDaemonPort(daemon_name):
1771
  """Get the daemon port for this cluster.
1772

1773
  Note that this routine does not read a ganeti-specific file, but
1774
  instead uses C{socket.getservbyname} to allow pre-customization of
1775
  this parameter outside of Ganeti.
1776

1777
  @type daemon_name: string
1778
  @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
1779
  @rtype: int
1780

1781
  """
1782
  if daemon_name not in constants.DAEMONS_PORTS:
1783
    raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
1784

    
1785
  (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
1786
  try:
1787
    port = socket.getservbyname(daemon_name, proto)
1788
  except socket.error:
1789
    port = default_port
1790

    
1791
  return port
1792

    
1793

    
1794
def SetupLogging(logfile, debug=0, stderr_logging=False, program="",
1795
                 multithreaded=False, syslog=constants.SYSLOG_USAGE):
1796
  """Configures the logging module.
1797

1798
  @type logfile: str
1799
  @param logfile: the filename to which we should log
1800
  @type debug: integer
1801
  @param debug: if greater than zero, enable debug messages, otherwise
1802
      only those at C{INFO} and above level
1803
  @type stderr_logging: boolean
1804
  @param stderr_logging: whether we should also log to the standard error
1805
  @type program: str
1806
  @param program: the name under which we should log messages
1807
  @type multithreaded: boolean
1808
  @param multithreaded: if True, will add the thread name to the log file
1809
  @type syslog: string
1810
  @param syslog: one of 'no', 'yes', 'only':
1811
      - if no, syslog is not used
1812
      - if yes, syslog is used (in addition to file-logging)
1813
      - if only, only syslog is used
1814
  @raise EnvironmentError: if we can't open the log file and
1815
      syslog/stderr logging is disabled
1816

1817
  """
1818
  fmt = "%(asctime)s: " + program + " pid=%(process)d"
1819
  sft = program + "[%(process)d]:"
1820
  if multithreaded:
1821
    fmt += "/%(threadName)s"
1822
    sft += " (%(threadName)s)"
1823
  if debug:
1824
    fmt += " %(module)s:%(lineno)s"
1825
    # no debug info for syslog loggers
1826
  fmt += " %(levelname)s %(message)s"
1827
  # yes, we do want the textual level, as remote syslog will probably
1828
  # lose the error level, and it's easier to grep for it
1829
  sft += " %(levelname)s %(message)s"
1830
  formatter = logging.Formatter(fmt)
1831
  sys_fmt = logging.Formatter(sft)
1832

    
1833
  root_logger = logging.getLogger("")
1834
  root_logger.setLevel(logging.NOTSET)
1835

    
1836
  # Remove all previously setup handlers
1837
  for handler in root_logger.handlers:
1838
    handler.close()
1839
    root_logger.removeHandler(handler)
1840

    
1841
  if stderr_logging:
1842
    stderr_handler = logging.StreamHandler()
1843
    stderr_handler.setFormatter(formatter)
1844
    if debug:
1845
      stderr_handler.setLevel(logging.NOTSET)
1846
    else:
1847
      stderr_handler.setLevel(logging.CRITICAL)
1848
    root_logger.addHandler(stderr_handler)
1849

    
1850
  if syslog in (constants.SYSLOG_YES, constants.SYSLOG_ONLY):
1851
    facility = logging.handlers.SysLogHandler.LOG_DAEMON
1852
    syslog_handler = logging.handlers.SysLogHandler(constants.SYSLOG_SOCKET,
1853
                                                    facility)
1854
    syslog_handler.setFormatter(sys_fmt)
1855
    # Never enable debug over syslog
1856
    syslog_handler.setLevel(logging.INFO)
1857
    root_logger.addHandler(syslog_handler)
1858

    
1859
  if syslog != constants.SYSLOG_ONLY:
1860
    # this can fail, if the logging directories are not setup or we have
1861
    # a permisssion problem; in this case, it's best to log but ignore
1862
    # the error if stderr_logging is True, and if false we re-raise the
1863
    # exception since otherwise we could run but without any logs at all
1864
    try:
1865
      logfile_handler = logging.FileHandler(logfile)
1866
      logfile_handler.setFormatter(formatter)
1867
      if debug:
1868
        logfile_handler.setLevel(logging.DEBUG)
1869
      else:
1870
        logfile_handler.setLevel(logging.INFO)
1871
      root_logger.addHandler(logfile_handler)
1872
    except EnvironmentError:
1873
      if stderr_logging or syslog == constants.SYSLOG_YES:
1874
        logging.exception("Failed to enable logging to file '%s'", logfile)
1875
      else:
1876
        # we need to re-raise the exception
1877
        raise
1878

    
1879

    
1880
def IsNormAbsPath(path):
1881
  """Check whether a path is absolute and also normalized
1882

1883
  This avoids things like /dir/../../other/path to be valid.
1884

1885
  """
1886
  return os.path.normpath(path) == path and os.path.isabs(path)
1887

    
1888

    
1889
def TailFile(fname, lines=20):
1890
  """Return the last lines from a file.
1891

1892
  @note: this function will only read and parse the last 4KB of
1893
      the file; if the lines are very long, it could be that less
1894
      than the requested number of lines are returned
1895

1896
  @param fname: the file name
1897
  @type lines: int
1898
  @param lines: the (maximum) number of lines to return
1899

1900
  """
1901
  fd = open(fname, "r")
1902
  try:
1903
    fd.seek(0, 2)
1904
    pos = fd.tell()
1905
    pos = max(0, pos-4096)
1906
    fd.seek(pos, 0)
1907
    raw_data = fd.read()
1908
  finally:
1909
    fd.close()
1910

    
1911
  rows = raw_data.splitlines()
1912
  return rows[-lines:]
1913

    
1914

    
1915
def SafeEncode(text):
1916
  """Return a 'safe' version of a source string.
1917

1918
  This function mangles the input string and returns a version that
1919
  should be safe to display/encode as ASCII. To this end, we first
1920
  convert it to ASCII using the 'backslashreplace' encoding which
1921
  should get rid of any non-ASCII chars, and then we process it
1922
  through a loop copied from the string repr sources in the python; we
1923
  don't use string_escape anymore since that escape single quotes and
1924
  backslashes too, and that is too much; and that escaping is not
1925
  stable, i.e. string_escape(string_escape(x)) != string_escape(x).
1926

1927
  @type text: str or unicode
1928
  @param text: input data
1929
  @rtype: str
1930
  @return: a safe version of text
1931

1932
  """
1933
  if isinstance(text, unicode):
1934
    # only if unicode; if str already, we handle it below
1935
    text = text.encode('ascii', 'backslashreplace')
1936
  resu = ""
1937
  for char in text:
1938
    c = ord(char)
1939
    if char  == '\t':
1940
      resu += r'\t'
1941
    elif char == '\n':
1942
      resu += r'\n'
1943
    elif char == '\r':
1944
      resu += r'\'r'
1945
    elif c < 32 or c >= 127: # non-printable
1946
      resu += "\\x%02x" % (c & 0xff)
1947
    else:
1948
      resu += char
1949
  return resu
1950

    
1951

    
1952
def UnescapeAndSplit(text, sep=","):
1953
  """Split and unescape a string based on a given separator.
1954

1955
  This function splits a string based on a separator where the
1956
  separator itself can be escape in order to be an element of the
1957
  elements. The escaping rules are (assuming coma being the
1958
  separator):
1959
    - a plain , separates the elements
1960
    - a sequence \\\\, (double backslash plus comma) is handled as a
1961
      backslash plus a separator comma
1962
    - a sequence \, (backslash plus comma) is handled as a
1963
      non-separator comma
1964

1965
  @type text: string
1966
  @param text: the string to split
1967
  @type sep: string
1968
  @param text: the separator
1969
  @rtype: string
1970
  @return: a list of strings
1971

1972
  """
1973
  # we split the list by sep (with no escaping at this stage)
1974
  slist = text.split(sep)
1975
  # next, we revisit the elements and if any of them ended with an odd
1976
  # number of backslashes, then we join it with the next
1977
  rlist = []
1978
  while slist:
1979
    e1 = slist.pop(0)
1980
    if e1.endswith("\\"):
1981
      num_b = len(e1) - len(e1.rstrip("\\"))
1982
      if num_b % 2 == 1:
1983
        e2 = slist.pop(0)
1984
        # here the backslashes remain (all), and will be reduced in
1985
        # the next step
1986
        rlist.append(e1 + sep + e2)
1987
        continue
1988
    rlist.append(e1)
1989
  # finally, replace backslash-something with something
1990
  rlist = [re.sub(r"\\(.)", r"\1", v) for v in rlist]
1991
  return rlist
1992

    
1993

    
1994
def CommaJoin(names):
1995
  """Nicely join a set of identifiers.
1996

1997
  @param names: set, list or tuple
1998
  @return: a string with the formatted results
1999

2000
  """
2001
  return ", ".join([str(val) for val in names])
2002

    
2003

    
2004
def BytesToMebibyte(value):
2005
  """Converts bytes to mebibytes.
2006

2007
  @type value: int
2008
  @param value: Value in bytes
2009
  @rtype: int
2010
  @return: Value in mebibytes
2011

2012
  """
2013
  return int(round(value / (1024.0 * 1024.0), 0))
2014

    
2015

    
2016
def CalculateDirectorySize(path):
2017
  """Calculates the size of a directory recursively.
2018

2019
  @type path: string
2020
  @param path: Path to directory
2021
  @rtype: int
2022
  @return: Size in mebibytes
2023

2024
  """
2025
  size = 0
2026

    
2027
  for (curpath, _, files) in os.walk(path):
2028
    for filename in files:
2029
      st = os.lstat(os.path.join(curpath, filename))
2030
      size += st.st_size
2031

    
2032
  return BytesToMebibyte(size)
2033

    
2034

    
2035
def GetFilesystemStats(path):
2036
  """Returns the total and free space on a filesystem.
2037

2038
  @type path: string
2039
  @param path: Path on filesystem to be examined
2040
  @rtype: int
2041
  @return: tuple of (Total space, Free space) in mebibytes
2042

2043
  """
2044
  st = os.statvfs(path)
2045

    
2046
  fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
2047
  tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
2048
  return (tsize, fsize)
2049

    
2050

    
2051
def RunInSeparateProcess(fn):
2052
  """Runs a function in a separate process.
2053

2054
  Note: Only boolean return values are supported.
2055

2056
  @type fn: callable
2057
  @param fn: Function to be called
2058
  @rtype: tuple of (int/None, int/None)
2059
  @return: Exit code and signal number
2060

2061
  """
2062
  pid = os.fork()
2063
  if pid == 0:
2064
    # Child process
2065
    try:
2066
      # In case the function uses temporary files
2067
      ResetTempfileModule()
2068

    
2069
      # Call function
2070
      result = int(bool(fn()))
2071
      assert result in (0, 1)
2072
    except: # pylint: disable-msg=W0702
2073
      logging.exception("Error while calling function in separate process")
2074
      # 0 and 1 are reserved for the return value
2075
      result = 33
2076

    
2077
    os._exit(result) # pylint: disable-msg=W0212
2078

    
2079
  # Parent process
2080

    
2081
  # Avoid zombies and check exit code
2082
  (_, status) = os.waitpid(pid, 0)
2083

    
2084
  if os.WIFSIGNALED(status):
2085
    exitcode = None
2086
    signum = os.WTERMSIG(status)
2087
  else:
2088
    exitcode = os.WEXITSTATUS(status)
2089
    signum = None
2090

    
2091
  if not (exitcode in (0, 1) and signum is None):
2092
    raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
2093
                              (exitcode, signum))
2094

    
2095
  return bool(exitcode)
2096

    
2097

    
2098
def LockedMethod(fn):
2099
  """Synchronized object access decorator.
2100

2101
  This decorator is intended to protect access to an object using the
2102
  object's own lock which is hardcoded to '_lock'.
2103

2104
  """
2105
  def _LockDebug(*args, **kwargs):
2106
    if debug_locks:
2107
      logging.debug(*args, **kwargs)
2108

    
2109
  def wrapper(self, *args, **kwargs):
2110
    # pylint: disable-msg=W0212
2111
    assert hasattr(self, '_lock')
2112
    lock = self._lock
2113
    _LockDebug("Waiting for %s", lock)
2114
    lock.acquire()
2115
    try:
2116
      _LockDebug("Acquired %s", lock)
2117
      result = fn(self, *args, **kwargs)
2118
    finally:
2119
      _LockDebug("Releasing %s", lock)
2120
      lock.release()
2121
      _LockDebug("Released %s", lock)
2122
    return result
2123
  return wrapper
2124

    
2125

    
2126
def LockFile(fd):
2127
  """Locks a file using POSIX locks.
2128

2129
  @type fd: int
2130
  @param fd: the file descriptor we need to lock
2131

2132
  """
2133
  try:
2134
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
2135
  except IOError, err:
2136
    if err.errno == errno.EAGAIN:
2137
      raise errors.LockError("File already locked")
2138
    raise
2139

    
2140

    
2141
def FormatTime(val):
2142
  """Formats a time value.
2143

2144
  @type val: float or None
2145
  @param val: the timestamp as returned by time.time()
2146
  @return: a string value or N/A if we don't have a valid timestamp
2147

2148
  """
2149
  if val is None or not isinstance(val, (int, float)):
2150
    return "N/A"
2151
  # these two codes works on Linux, but they are not guaranteed on all
2152
  # platforms
2153
  return time.strftime("%F %T", time.localtime(val))
2154

    
2155

    
2156
def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
2157
  """Reads the watcher pause file.
2158

2159
  @type filename: string
2160
  @param filename: Path to watcher pause file
2161
  @type now: None, float or int
2162
  @param now: Current time as Unix timestamp
2163
  @type remove_after: int
2164
  @param remove_after: Remove watcher pause file after specified amount of
2165
    seconds past the pause end time
2166

2167
  """
2168
  if now is None:
2169
    now = time.time()
2170

    
2171
  try:
2172
    value = ReadFile(filename)
2173
  except IOError, err:
2174
    if err.errno != errno.ENOENT:
2175
      raise
2176
    value = None
2177

    
2178
  if value is not None:
2179
    try:
2180
      value = int(value)
2181
    except ValueError:
2182
      logging.warning(("Watcher pause file (%s) contains invalid value,"
2183
                       " removing it"), filename)
2184
      RemoveFile(filename)
2185
      value = None
2186

    
2187
    if value is not None:
2188
      # Remove file if it's outdated
2189
      if now > (value + remove_after):
2190
        RemoveFile(filename)
2191
        value = None
2192

    
2193
      elif now > value:
2194
        value = None
2195

    
2196
  return value
2197

    
2198

    
2199
class RetryTimeout(Exception):
2200
  """Retry loop timed out.
2201

2202
  """
2203

    
2204

    
2205
class RetryAgain(Exception):
2206
  """Retry again.
2207

2208
  """
2209

    
2210

    
2211
class _RetryDelayCalculator(object):
2212
  """Calculator for increasing delays.
2213

2214
  """
2215
  __slots__ = [
2216
    "_factor",
2217
    "_limit",
2218
    "_next",
2219
    "_start",
2220
    ]
2221

    
2222
  def __init__(self, start, factor, limit):
2223
    """Initializes this class.
2224

2225
    @type start: float
2226
    @param start: Initial delay
2227
    @type factor: float
2228
    @param factor: Factor for delay increase
2229
    @type limit: float or None
2230
    @param limit: Upper limit for delay or None for no limit
2231

2232
    """
2233
    assert start > 0.0
2234
    assert factor >= 1.0
2235
    assert limit is None or limit >= 0.0
2236

    
2237
    self._start = start
2238
    self._factor = factor
2239
    self._limit = limit
2240

    
2241
    self._next = start
2242

    
2243
  def __call__(self):
2244
    """Returns current delay and calculates the next one.
2245

2246
    """
2247
    current = self._next
2248

    
2249
    # Update for next run
2250
    if self._limit is None or self._next < self._limit:
2251
      self._next = min(self._limit, self._next * self._factor)
2252

    
2253
    return current
2254

    
2255

    
2256
#: Special delay to specify whole remaining timeout
2257
RETRY_REMAINING_TIME = object()
2258

    
2259

    
2260
def Retry(fn, delay, timeout, args=None, wait_fn=time.sleep,
2261
          _time_fn=time.time):
2262
  """Call a function repeatedly until it succeeds.
2263

2264
  The function C{fn} is called repeatedly until it doesn't throw L{RetryAgain}
2265
  anymore. Between calls a delay, specified by C{delay}, is inserted. After a
2266
  total of C{timeout} seconds, this function throws L{RetryTimeout}.
2267

2268
  C{delay} can be one of the following:
2269
    - callable returning the delay length as a float
2270
    - Tuple of (start, factor, limit)
2271
    - L{RETRY_REMAINING_TIME} to sleep until the timeout expires (this is
2272
      useful when overriding L{wait_fn} to wait for an external event)
2273
    - A static delay as a number (int or float)
2274

2275
  @type fn: callable
2276
  @param fn: Function to be called
2277
  @param delay: Either a callable (returning the delay), a tuple of (start,
2278
                factor, limit) (see L{_RetryDelayCalculator}),
2279
                L{RETRY_REMAINING_TIME} or a number (int or float)
2280
  @type timeout: float
2281
  @param timeout: Total timeout
2282
  @type wait_fn: callable
2283
  @param wait_fn: Waiting function
2284
  @return: Return value of function
2285

2286
  """
2287
  assert callable(fn)
2288
  assert callable(wait_fn)
2289
  assert callable(_time_fn)
2290

    
2291
  if args is None:
2292
    args = []
2293

    
2294
  end_time = _time_fn() + timeout
2295

    
2296
  if callable(delay):
2297
    # External function to calculate delay
2298
    calc_delay = delay
2299

    
2300
  elif isinstance(delay, (tuple, list)):
2301
    # Increasing delay with optional upper boundary
2302
    (start, factor, limit) = delay
2303
    calc_delay = _RetryDelayCalculator(start, factor, limit)
2304

    
2305
  elif delay is RETRY_REMAINING_TIME:
2306
    # Always use the remaining time
2307
    calc_delay = None
2308

    
2309
  else:
2310
    # Static delay
2311
    calc_delay = lambda: delay
2312

    
2313
  assert calc_delay is None or callable(calc_delay)
2314

    
2315
  while True:
2316
    try:
2317
      # pylint: disable-msg=W0142
2318
      return fn(*args)
2319
    except RetryAgain:
2320
      pass
2321

    
2322
    remaining_time = end_time - _time_fn()
2323

    
2324
    if remaining_time < 0.0:
2325
      raise RetryTimeout()
2326

    
2327
    assert remaining_time >= 0.0
2328

    
2329
    if calc_delay is None:
2330
      wait_fn(remaining_time)
2331
    else:
2332
      current_delay = calc_delay()
2333
      if current_delay > 0.0:
2334
        wait_fn(current_delay)
2335

    
2336

    
2337
class FileLock(object):
2338
  """Utility class for file locks.
2339

2340
  """
2341
  def __init__(self, filename):
2342
    """Constructor for FileLock.
2343

2344
    This will open the file denoted by the I{filename} argument.
2345

2346
    @type filename: str
2347
    @param filename: path to the file to be locked
2348

2349
    """
2350
    self.filename = filename
2351
    self.fd = open(self.filename, "w")
2352

    
2353
  def __del__(self):
2354
    self.Close()
2355

    
2356
  def Close(self):
2357
    """Close the file and release the lock.
2358

2359
    """
2360
    if hasattr(self, "fd") and self.fd:
2361
      self.fd.close()
2362
      self.fd = None
2363

    
2364
  def _flock(self, flag, blocking, timeout, errmsg):
2365
    """Wrapper for fcntl.flock.
2366

2367
    @type flag: int
2368
    @param flag: operation flag
2369
    @type blocking: bool
2370
    @param blocking: whether the operation should be done in blocking mode.
2371
    @type timeout: None or float
2372
    @param timeout: for how long the operation should be retried (implies
2373
                    non-blocking mode).
2374
    @type errmsg: string
2375
    @param errmsg: error message in case operation fails.
2376

2377
    """
2378
    assert self.fd, "Lock was closed"
2379
    assert timeout is None or timeout >= 0, \
2380
      "If specified, timeout must be positive"
2381

    
2382
    if timeout is not None:
2383
      flag |= fcntl.LOCK_NB
2384
      timeout_end = time.time() + timeout
2385

    
2386
    # Blocking doesn't have effect with timeout
2387
    elif not blocking:
2388
      flag |= fcntl.LOCK_NB
2389
      timeout_end = None
2390

    
2391
    # TODO: Convert to utils.Retry
2392

    
2393
    retry = True
2394
    while retry:
2395
      try:
2396
        fcntl.flock(self.fd, flag)
2397
        retry = False
2398
      except IOError, err:
2399
        if err.errno in (errno.EAGAIN, ):
2400
          if timeout_end is not None and time.time() < timeout_end:
2401
            # Wait before trying again
2402
            time.sleep(max(0.1, min(1.0, timeout)))
2403
          else:
2404
            raise errors.LockError(errmsg)
2405
        else:
2406
          logging.exception("fcntl.flock failed")
2407
          raise
2408

    
2409
  def Exclusive(self, blocking=False, timeout=None):
2410
    """Locks the file in exclusive mode.
2411

2412
    @type blocking: boolean
2413
    @param blocking: whether to block and wait until we
2414
        can lock the file or return immediately
2415
    @type timeout: int or None
2416
    @param timeout: if not None, the duration to wait for the lock
2417
        (in blocking mode)
2418

2419
    """
2420
    self._flock(fcntl.LOCK_EX, blocking, timeout,
2421
                "Failed to lock %s in exclusive mode" % self.filename)
2422

    
2423
  def Shared(self, blocking=False, timeout=None):
2424
    """Locks the file in shared mode.
2425

2426
    @type blocking: boolean
2427
    @param blocking: whether to block and wait until we
2428
        can lock the file or return immediately
2429
    @type timeout: int or None
2430
    @param timeout: if not None, the duration to wait for the lock
2431
        (in blocking mode)
2432

2433
    """
2434
    self._flock(fcntl.LOCK_SH, blocking, timeout,
2435
                "Failed to lock %s in shared mode" % self.filename)
2436

    
2437
  def Unlock(self, blocking=True, timeout=None):
2438
    """Unlocks the file.
2439

2440
    According to C{flock(2)}, unlocking can also be a nonblocking
2441
    operation::
2442

2443
      To make a non-blocking request, include LOCK_NB with any of the above
2444
      operations.
2445

2446
    @type blocking: boolean
2447
    @param blocking: whether to block and wait until we
2448
        can lock the file or return immediately
2449
    @type timeout: int or None
2450
    @param timeout: if not None, the duration to wait for the lock
2451
        (in blocking mode)
2452

2453
    """
2454
    self._flock(fcntl.LOCK_UN, blocking, timeout,
2455
                "Failed to unlock %s" % self.filename)
2456

    
2457

    
2458
def SignalHandled(signums):
2459
  """Signal Handled decoration.
2460

2461
  This special decorator installs a signal handler and then calls the target
2462
  function. The function must accept a 'signal_handlers' keyword argument,
2463
  which will contain a dict indexed by signal number, with SignalHandler
2464
  objects as values.
2465

2466
  The decorator can be safely stacked with iself, to handle multiple signals
2467
  with different handlers.
2468

2469
  @type signums: list
2470
  @param signums: signals to intercept
2471

2472
  """
2473
  def wrap(fn):
2474
    def sig_function(*args, **kwargs):
2475
      assert 'signal_handlers' not in kwargs or \
2476
             kwargs['signal_handlers'] is None or \
2477
             isinstance(kwargs['signal_handlers'], dict), \
2478
             "Wrong signal_handlers parameter in original function call"
2479
      if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
2480
        signal_handlers = kwargs['signal_handlers']
2481
      else:
2482
        signal_handlers = {}
2483
        kwargs['signal_handlers'] = signal_handlers
2484
      sighandler = SignalHandler(signums)
2485
      try:
2486
        for sig in signums:
2487
          signal_handlers[sig] = sighandler
2488
        return fn(*args, **kwargs)
2489
      finally:
2490
        sighandler.Reset()
2491
    return sig_function
2492
  return wrap
2493

    
2494

    
2495
class SignalHandler(object):
2496
  """Generic signal handler class.
2497

2498
  It automatically restores the original handler when deconstructed or
2499
  when L{Reset} is called. You can either pass your own handler
2500
  function in or query the L{called} attribute to detect whether the
2501
  signal was sent.
2502

2503
  @type signum: list
2504
  @ivar signum: the signals we handle
2505
  @type called: boolean
2506
  @ivar called: tracks whether any of the signals have been raised
2507

2508
  """
2509
  def __init__(self, signum):
2510
    """Constructs a new SignalHandler instance.
2511

2512
    @type signum: int or list of ints
2513
    @param signum: Single signal number or set of signal numbers
2514

2515
    """
2516
    self.signum = set(signum)
2517
    self.called = False
2518

    
2519
    self._previous = {}
2520
    try:
2521
      for signum in self.signum:
2522
        # Setup handler
2523
        prev_handler = signal.signal(signum, self._HandleSignal)
2524
        try:
2525
          self._previous[signum] = prev_handler
2526
        except:
2527
          # Restore previous handler
2528
          signal.signal(signum, prev_handler)
2529
          raise
2530
    except:
2531
      # Reset all handlers
2532
      self.Reset()
2533
      # Here we have a race condition: a handler may have already been called,
2534
      # but there's not much we can do about it at this point.
2535
      raise
2536

    
2537
  def __del__(self):
2538
    self.Reset()
2539

    
2540
  def Reset(self):
2541
    """Restore previous handler.
2542

2543
    This will reset all the signals to their previous handlers.
2544

2545
    """
2546
    for signum, prev_handler in self._previous.items():
2547
      signal.signal(signum, prev_handler)
2548
      # If successful, remove from dict
2549
      del self._previous[signum]
2550

    
2551
  def Clear(self):
2552
    """Unsets the L{called} flag.
2553

2554
    This function can be used in case a signal may arrive several times.
2555

2556
    """
2557
    self.called = False
2558

    
2559
  # we don't care about arguments, but we leave them named for the future
2560
  def _HandleSignal(self, signum, frame): # pylint: disable-msg=W0613
2561
    """Actual signal handling function.
2562

2563
    """
2564
    # This is not nice and not absolutely atomic, but it appears to be the only
2565
    # solution in Python -- there are no atomic types.
2566
    self.called = True
2567

    
2568

    
2569
class FieldSet(object):
2570
  """A simple field set.
2571

2572
  Among the features are:
2573
    - checking if a string is among a list of static string or regex objects
2574
    - checking if a whole list of string matches
2575
    - returning the matching groups from a regex match
2576

2577
  Internally, all fields are held as regular expression objects.
2578

2579
  """
2580
  def __init__(self, *items):
2581
    self.items = [re.compile("^%s$" % value) for value in items]
2582

    
2583
  def Extend(self, other_set):
2584
    """Extend the field set with the items from another one"""
2585
    self.items.extend(other_set.items)
2586

    
2587
  def Matches(self, field):
2588
    """Checks if a field matches the current set
2589

2590
    @type field: str
2591
    @param field: the string to match
2592
    @return: either None or a regular expression match object
2593

2594
    """
2595
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
2596
      return m
2597
    return None
2598

    
2599
  def NonMatching(self, items):
2600
    """Returns the list of fields not matching the current set
2601

2602
    @type items: list
2603
    @param items: the list of fields to check
2604
    @rtype: list
2605
    @return: list of non-matching fields
2606

2607
    """
2608
    return [val for val in items if not self.Matches(val)]