Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ 9fbfbb7b

History | View | Annotate | Download (49.1 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 sys
31
import os
32
import sha
33
import time
34
import subprocess
35
import re
36
import socket
37
import tempfile
38
import shutil
39
import errno
40
import pwd
41
import itertools
42
import select
43
import fcntl
44
import resource
45
import logging
46
import signal
47

    
48
from cStringIO import StringIO
49

    
50
from ganeti import errors
51
from ganeti import constants
52

    
53

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

    
57
debug = False
58
debug_locks = False
59

    
60
#: when set to True, L{RunCmd} is disabled
61
no_fork = False
62

    
63

    
64
class RunResult(object):
65
  """Holds the result of running external programs.
66

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

82
  """
83
  __slots__ = ["exit_code", "signal", "stdout", "stderr",
84
               "failed", "fail_reason", "cmd"]
85

    
86

    
87
  def __init__(self, exit_code, signal_, stdout, stderr, cmd):
88
    self.cmd = cmd
89
    self.exit_code = exit_code
90
    self.signal = signal_
91
    self.stdout = stdout
92
    self.stderr = stderr
93
    self.failed = (signal_ is not None or exit_code != 0)
94

    
95
    if self.signal is not None:
96
      self.fail_reason = "terminated by signal %s" % self.signal
97
    elif self.exit_code is not None:
98
      self.fail_reason = "exited with exit code %s" % self.exit_code
99
    else:
100
      self.fail_reason = "unable to determine termination reason"
101

    
102
    if self.failed:
103
      logging.debug("Command '%s' failed (%s); output: %s",
104
                    self.cmd, self.fail_reason, self.output)
105

    
106
  def _GetOutput(self):
107
    """Returns the combined stdout and stderr for easier usage.
108

109
    """
110
    return self.stdout + self.stderr
111

    
112
  output = property(_GetOutput, None, None, "Return full output")
113

    
114

    
115
def RunCmd(cmd, env=None, output=None, cwd='/'):
116
  """Execute a (shell) command.
117

118
  The command should not read from its standard input, as it will be
119
  closed.
120

121
  @type  cmd: string or list
122
  @param cmd: Command to run
123
  @type env: dict
124
  @param env: Additional environment
125
  @type output: str
126
  @param output: if desired, the output of the command can be
127
      saved in a file instead of the RunResult instance; this
128
      parameter denotes the file name (if not None)
129
  @type cwd: string
130
  @param cwd: if specified, will be used as the working
131
      directory for the command; the default will be /
132
  @rtype: L{RunResult}
133
  @return: RunResult instance
134
  @raise erors.ProgrammerError: if we call this when forks are disabled
135

136
  """
137
  if no_fork:
138
    raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
139

    
140
  if isinstance(cmd, list):
141
    cmd = [str(val) for val in cmd]
142
    strcmd = " ".join(cmd)
143
    shell = False
144
  else:
145
    strcmd = cmd
146
    shell = True
147
  logging.debug("RunCmd '%s'", strcmd)
148

    
149
  cmd_env = os.environ.copy()
150
  cmd_env["LC_ALL"] = "C"
151
  if env is not None:
152
    cmd_env.update(env)
153

    
154
  if output is None:
155
    out, err, status = _RunCmdPipe(cmd, cmd_env, shell, cwd)
156
  else:
157
    status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
158
    out = err = ""
159

    
160
  if status >= 0:
161
    exitcode = status
162
    signal_ = None
163
  else:
164
    exitcode = None
165
    signal_ = -status
166

    
167
  return RunResult(exitcode, signal_, out, err, strcmd)
168

    
169
def _RunCmdPipe(cmd, env, via_shell, cwd):
170
  """Run a command and return its output.
171

172
  @type  cmd: string or list
173
  @param cmd: Command to run
174
  @type env: dict
175
  @param env: The environment to use
176
  @type via_shell: bool
177
  @param via_shell: if we should run via the shell
178
  @type cwd: string
179
  @param cwd: the working directory for the program
180
  @rtype: tuple
181
  @return: (out, err, status)
182

183
  """
184
  poller = select.poll()
185
  child = subprocess.Popen(cmd, shell=via_shell,
186
                           stderr=subprocess.PIPE,
187
                           stdout=subprocess.PIPE,
188
                           stdin=subprocess.PIPE,
189
                           close_fds=True, env=env,
190
                           cwd=cwd)
191

    
192
  child.stdin.close()
193
  poller.register(child.stdout, select.POLLIN)
194
  poller.register(child.stderr, select.POLLIN)
195
  out = StringIO()
196
  err = StringIO()
197
  fdmap = {
198
    child.stdout.fileno(): (out, child.stdout),
199
    child.stderr.fileno(): (err, child.stderr),
200
    }
201
  for fd in fdmap:
202
    status = fcntl.fcntl(fd, fcntl.F_GETFL)
203
    fcntl.fcntl(fd, fcntl.F_SETFL, status | os.O_NONBLOCK)
204

    
205
  while fdmap:
206
    for fd, event in poller.poll():
207
      if event & select.POLLIN or event & select.POLLPRI:
208
        data = fdmap[fd][1].read()
209
        # no data from read signifies EOF (the same as POLLHUP)
210
        if not data:
211
          poller.unregister(fd)
212
          del fdmap[fd]
213
          continue
214
        fdmap[fd][0].write(data)
215
      if (event & select.POLLNVAL or event & select.POLLHUP or
216
          event & select.POLLERR):
217
        poller.unregister(fd)
218
        del fdmap[fd]
219

    
220
  out = out.getvalue()
221
  err = err.getvalue()
222

    
223
  status = child.wait()
224
  return out, err, status
225

    
226

    
227
def _RunCmdFile(cmd, env, via_shell, output, cwd):
228
  """Run a command and save its output to a file.
229

230
  @type  cmd: string or list
231
  @param cmd: Command to run
232
  @type env: dict
233
  @param env: The environment to use
234
  @type via_shell: bool
235
  @param via_shell: if we should run via the shell
236
  @type output: str
237
  @param output: the filename in which to save the output
238
  @type cwd: string
239
  @param cwd: the working directory for the program
240
  @rtype: int
241
  @return: the exit status
242

243
  """
244
  fh = open(output, "a")
245
  try:
246
    child = subprocess.Popen(cmd, shell=via_shell,
247
                             stderr=subprocess.STDOUT,
248
                             stdout=fh,
249
                             stdin=subprocess.PIPE,
250
                             close_fds=True, env=env,
251
                             cwd=cwd)
252

    
253
    child.stdin.close()
254
    status = child.wait()
255
  finally:
256
    fh.close()
257
  return status
258

    
259

    
260
def RemoveFile(filename):
261
  """Remove a file ignoring some errors.
262

263
  Remove a file, ignoring non-existing ones or directories. Other
264
  errors are passed.
265

266
  @type filename: str
267
  @param filename: the file to be removed
268

269
  """
270
  try:
271
    os.unlink(filename)
272
  except OSError, err:
273
    if err.errno not in (errno.ENOENT, errno.EISDIR):
274
      raise
275

    
276

    
277
def _FingerprintFile(filename):
278
  """Compute the fingerprint of a file.
279

280
  If the file does not exist, a None will be returned
281
  instead.
282

283
  @type filename: str
284
  @param filename: the filename to checksum
285
  @rtype: str
286
  @return: the hex digest of the sha checksum of the contents
287
      of the file
288

289
  """
290
  if not (os.path.exists(filename) and os.path.isfile(filename)):
291
    return None
292

    
293
  f = open(filename)
294

    
295
  fp = sha.sha()
296
  while True:
297
    data = f.read(4096)
298
    if not data:
299
      break
300

    
301
    fp.update(data)
302

    
303
  return fp.hexdigest()
304

    
305

    
306
def FingerprintFiles(files):
307
  """Compute fingerprints for a list of files.
308

309
  @type files: list
310
  @param files: the list of filename to fingerprint
311
  @rtype: dict
312
  @return: a dictionary filename: fingerprint, holding only
313
      existing files
314

315
  """
316
  ret = {}
317

    
318
  for filename in files:
319
    cksum = _FingerprintFile(filename)
320
    if cksum:
321
      ret[filename] = cksum
322

    
323
  return ret
324

    
325

    
326
def CheckDict(target, template, logname=None):
327
  """Ensure a dictionary has a required set of keys.
328

329
  For the given dictionaries I{target} and I{template}, ensure
330
  I{target} has all the keys from I{template}. Missing keys are added
331
  with values from template.
332

333
  @type target: dict
334
  @param target: the dictionary to update
335
  @type template: dict
336
  @param template: the dictionary holding the default values
337
  @type logname: str or None
338
  @param logname: if not None, causes the missing keys to be
339
      logged with this name
340

341
  """
342
  missing = []
343
  for k in template:
344
    if k not in target:
345
      missing.append(k)
346
      target[k] = template[k]
347

    
348
  if missing and logname:
349
    logging.warning('%s missing keys %s', logname, ', '.join(missing))
350

    
351

    
352
def IsProcessAlive(pid):
353
  """Check if a given pid exists on the system.
354

355
  @note: zombie status is not handled, so zombie processes
356
      will be returned as alive
357
  @type pid: int
358
  @param pid: the process ID to check
359
  @rtype: boolean
360
  @return: True if the process exists
361

362
  """
363
  if pid <= 0:
364
    return False
365

    
366
  try:
367
    os.stat("/proc/%d/status" % pid)
368
    return True
369
  except EnvironmentError, err:
370
    if err.errno in (errno.ENOENT, errno.ENOTDIR):
371
      return False
372
    raise
373

    
374

    
375
def ReadPidFile(pidfile):
376
  """Read a pid from a file.
377

378
  @type  pidfile: string
379
  @param pidfile: path to the file containing the pid
380
  @rtype: int
381
  @return: The process id, if the file exista and contains a valid PID,
382
           otherwise 0
383

384
  """
385
  try:
386
    pf = open(pidfile, 'r')
387
  except EnvironmentError, err:
388
    if err.errno != errno.ENOENT:
389
      logging.exception("Can't read pid file?!")
390
    return 0
391

    
392
  try:
393
    pid = int(pf.read())
394
  except ValueError, err:
395
    logging.info("Can't parse pid file contents", exc_info=True)
396
    return 0
397

    
398
  return pid
399

    
400

    
401
def MatchNameComponent(key, name_list):
402
  """Try to match a name against a list.
403

404
  This function will try to match a name like test1 against a list
405
  like C{['test1.example.com', 'test2.example.com', ...]}. Against
406
  this list, I{'test1'} as well as I{'test1.example'} will match, but
407
  not I{'test1.ex'}. A multiple match will be considered as no match
408
  at all (e.g. I{'test1'} against C{['test1.example.com',
409
  'test1.example.org']}).
410

411
  @type key: str
412
  @param key: the name to be searched
413
  @type name_list: list
414
  @param name_list: the list of strings against which to search the key
415

416
  @rtype: None or str
417
  @return: None if there is no match I{or} if there are multiple matches,
418
      otherwise the element from the list which matches
419

420
  """
421
  mo = re.compile("^%s(\..*)?$" % re.escape(key))
422
  names_filtered = [name for name in name_list if mo.match(name) is not None]
423
  if len(names_filtered) != 1:
424
    return None
425
  return names_filtered[0]
426

    
427

    
428
class HostInfo:
429
  """Class implementing resolver and hostname functionality
430

431
  """
432
  def __init__(self, name=None):
433
    """Initialize the host name object.
434

435
    If the name argument is not passed, it will use this system's
436
    name.
437

438
    """
439
    if name is None:
440
      name = self.SysName()
441

    
442
    self.query = name
443
    self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
444
    self.ip = self.ipaddrs[0]
445

    
446
  def ShortName(self):
447
    """Returns the hostname without domain.
448

449
    """
450
    return self.name.split('.')[0]
451

    
452
  @staticmethod
453
  def SysName():
454
    """Return the current system's name.
455

456
    This is simply a wrapper over C{socket.gethostname()}.
457

458
    """
459
    return socket.gethostname()
460

    
461
  @staticmethod
462
  def LookupHostname(hostname):
463
    """Look up hostname
464

465
    @type hostname: str
466
    @param hostname: hostname to look up
467

468
    @rtype: tuple
469
    @return: a tuple (name, aliases, ipaddrs) as returned by
470
        C{socket.gethostbyname_ex}
471
    @raise errors.ResolverError: in case of errors in resolving
472

473
    """
474
    try:
475
      result = socket.gethostbyname_ex(hostname)
476
    except socket.gaierror, err:
477
      # hostname not found in DNS
478
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
479

    
480
    return result
481

    
482

    
483
def ListVolumeGroups():
484
  """List volume groups and their size
485

486
  @rtype: dict
487
  @return:
488
       Dictionary with keys volume name and values
489
       the size of the volume
490

491
  """
492
  command = "vgs --noheadings --units m --nosuffix -o name,size"
493
  result = RunCmd(command)
494
  retval = {}
495
  if result.failed:
496
    return retval
497

    
498
  for line in result.stdout.splitlines():
499
    try:
500
      name, size = line.split()
501
      size = int(float(size))
502
    except (IndexError, ValueError), err:
503
      logging.error("Invalid output from vgs (%s): %s", err, line)
504
      continue
505

    
506
    retval[name] = size
507

    
508
  return retval
509

    
510

    
511
def BridgeExists(bridge):
512
  """Check whether the given bridge exists in the system
513

514
  @type bridge: str
515
  @param bridge: the bridge name to check
516
  @rtype: boolean
517
  @return: True if it does
518

519
  """
520
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
521

    
522

    
523
def NiceSort(name_list):
524
  """Sort a list of strings based on digit and non-digit groupings.
525

526
  Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
527
  will sort the list in the logical order C{['a1', 'a2', 'a10',
528
  'a11']}.
529

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

534
  @type name_list: list
535
  @param name_list: the names to be sorted
536
  @rtype: list
537
  @return: a copy of the name list sorted with our algorithm
538

539
  """
540
  _SORTER_BASE = "(\D+|\d+)"
541
  _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
542
                                                  _SORTER_BASE, _SORTER_BASE,
543
                                                  _SORTER_BASE, _SORTER_BASE,
544
                                                  _SORTER_BASE, _SORTER_BASE)
545
  _SORTER_RE = re.compile(_SORTER_FULL)
546
  _SORTER_NODIGIT = re.compile("^\D*$")
547
  def _TryInt(val):
548
    """Attempts to convert a variable to integer."""
549
    if val is None or _SORTER_NODIGIT.match(val):
550
      return val
551
    rval = int(val)
552
    return rval
553

    
554
  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
555
             for name in name_list]
556
  to_sort.sort()
557
  return [tup[1] for tup in to_sort]
558

    
559

    
560
def TryConvert(fn, val):
561
  """Try to convert a value ignoring errors.
562

563
  This function tries to apply function I{fn} to I{val}. If no
564
  C{ValueError} or C{TypeError} exceptions are raised, it will return
565
  the result, else it will return the original value. Any other
566
  exceptions are propagated to the caller.
567

568
  @type fn: callable
569
  @param fn: function to apply to the value
570
  @param val: the value to be converted
571
  @return: The converted value if the conversion was successful,
572
      otherwise the original value.
573

574
  """
575
  try:
576
    nv = fn(val)
577
  except (ValueError, TypeError), err:
578
    nv = val
579
  return nv
580

    
581

    
582
def IsValidIP(ip):
583
  """Verifies the syntax of an IPv4 address.
584

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

588
  @type ip: str
589
  @param ip: the address to be checked
590
  @rtype: a regular expression match object
591
  @return: a regular epression match object, or None if the
592
      address is not valid
593

594
  """
595
  unit = "(0|[1-9]\d{0,2})"
596
  #TODO: convert and return only boolean
597
  return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
598

    
599

    
600
def IsValidShellParam(word):
601
  """Verifies is the given word is safe from the shell's p.o.v.
602

603
  This means that we can pass this to a command via the shell and be
604
  sure that it doesn't alter the command line and is passed as such to
605
  the actual command.
606

607
  Note that we are overly restrictive here, in order to be on the safe
608
  side.
609

610
  @type word: str
611
  @param word: the word to check
612
  @rtype: boolean
613
  @return: True if the word is 'safe'
614

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

    
618

    
619
def BuildShellCmd(template, *args):
620
  """Build a safe shell command line from the given arguments.
621

622
  This function will check all arguments in the args list so that they
623
  are valid shell parameters (i.e. they don't contain shell
624
  metacharaters). If everything is ok, it will return the result of
625
  template % args.
626

627
  @type template: str
628
  @param template: the string holding the template for the
629
      string formatting
630
  @rtype: str
631
  @return: the expanded command line
632

633
  """
634
  for word in args:
635
    if not IsValidShellParam(word):
636
      raise errors.ProgrammerError("Shell argument '%s' contains"
637
                                   " invalid characters" % word)
638
  return template % args
639

    
640

    
641
def FormatUnit(value, units):
642
  """Formats an incoming number of MiB with the appropriate unit.
643

644
  @type value: int
645
  @param value: integer representing the value in MiB (1048576)
646
  @type units: char
647
  @param units: the type of formatting we should do:
648
      - 'h' for automatic scaling
649
      - 'm' for MiBs
650
      - 'g' for GiBs
651
      - 't' for TiBs
652
  @rtype: str
653
  @return: the formatted value (with suffix)
654

655
  """
656
  if units not in ('m', 'g', 't', 'h'):
657
    raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
658

    
659
  suffix = ''
660

    
661
  if units == 'm' or (units == 'h' and value < 1024):
662
    if units == 'h':
663
      suffix = 'M'
664
    return "%d%s" % (round(value, 0), suffix)
665

    
666
  elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
667
    if units == 'h':
668
      suffix = 'G'
669
    return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
670

    
671
  else:
672
    if units == 'h':
673
      suffix = 'T'
674
    return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
675

    
676

    
677
def ParseUnit(input_string):
678
  """Tries to extract number and scale from the given string.
679

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

684
  """
685
  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', input_string)
686
  if not m:
687
    raise errors.UnitParseError("Invalid format")
688

    
689
  value = float(m.groups()[0])
690

    
691
  unit = m.groups()[1]
692
  if unit:
693
    lcunit = unit.lower()
694
  else:
695
    lcunit = 'm'
696

    
697
  if lcunit in ('m', 'mb', 'mib'):
698
    # Value already in MiB
699
    pass
700

    
701
  elif lcunit in ('g', 'gb', 'gib'):
702
    value *= 1024
703

    
704
  elif lcunit in ('t', 'tb', 'tib'):
705
    value *= 1024 * 1024
706

    
707
  else:
708
    raise errors.UnitParseError("Unknown unit: %s" % unit)
709

    
710
  # Make sure we round up
711
  if int(value) < value:
712
    value += 1
713

    
714
  # Round up to the next multiple of 4
715
  value = int(value)
716
  if value % 4:
717
    value += 4 - value % 4
718

    
719
  return value
720

    
721

    
722
def AddAuthorizedKey(file_name, key):
723
  """Adds an SSH public key to an authorized_keys file.
724

725
  @type file_name: str
726
  @param file_name: path to authorized_keys file
727
  @type key: str
728
  @param key: string containing key
729

730
  """
731
  key_fields = key.split()
732

    
733
  f = open(file_name, 'a+')
734
  try:
735
    nl = True
736
    for line in f:
737
      # Ignore whitespace changes
738
      if line.split() == key_fields:
739
        break
740
      nl = line.endswith('\n')
741
    else:
742
      if not nl:
743
        f.write("\n")
744
      f.write(key.rstrip('\r\n'))
745
      f.write("\n")
746
      f.flush()
747
  finally:
748
    f.close()
749

    
750

    
751
def RemoveAuthorizedKey(file_name, key):
752
  """Removes an SSH public key from an authorized_keys file.
753

754
  @type file_name: str
755
  @param file_name: path to authorized_keys file
756
  @type key: str
757
  @param key: string containing key
758

759
  """
760
  key_fields = key.split()
761

    
762
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
763
  try:
764
    out = os.fdopen(fd, 'w')
765
    try:
766
      f = open(file_name, 'r')
767
      try:
768
        for line in f:
769
          # Ignore whitespace changes while comparing lines
770
          if line.split() != key_fields:
771
            out.write(line)
772

    
773
        out.flush()
774
        os.rename(tmpname, file_name)
775
      finally:
776
        f.close()
777
    finally:
778
      out.close()
779
  except:
780
    RemoveFile(tmpname)
781
    raise
782

    
783

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

787
  @type file_name: str
788
  @param file_name: path to the file to modify (usually C{/etc/hosts})
789
  @type ip: str
790
  @param ip: the IP address
791
  @type hostname: str
792
  @param hostname: the hostname to be added
793
  @type aliases: list
794
  @param aliases: the list of aliases to add for the hostname
795

796
  """
797
  # Ensure aliases are unique
798
  aliases = UniqueSequence([hostname] + aliases)[1:]
799

    
800
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
801
  try:
802
    out = os.fdopen(fd, 'w')
803
    try:
804
      f = open(file_name, 'r')
805
      try:
806
        written = False
807
        for line in f:
808
          fields = line.split()
809
          if fields and not fields[0].startswith('#') and ip == fields[0]:
810
            continue
811
          out.write(line)
812

    
813
        out.write("%s\t%s" % (ip, hostname))
814
        if aliases:
815
          out.write(" %s" % ' '.join(aliases))
816
        out.write('\n')
817

    
818
        out.flush()
819
        os.fsync(out)
820
        os.rename(tmpname, file_name)
821
      finally:
822
        f.close()
823
    finally:
824
      out.close()
825
  except:
826
    RemoveFile(tmpname)
827
    raise
828

    
829

    
830
def AddHostToEtcHosts(hostname):
831
  """Wrapper around SetEtcHostsEntry.
832

833
  @type hostname: str
834
  @param hostname: a hostname that will be resolved and added to
835
      L{constants.ETC_HOSTS}
836

837
  """
838
  hi = HostInfo(name=hostname)
839
  SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
840

    
841

    
842
def RemoveEtcHostsEntry(file_name, hostname):
843
  """Removes a hostname from /etc/hosts.
844

845
  IP addresses without names are removed from the file.
846

847
  @type file_name: str
848
  @param file_name: path to the file to modify (usually C{/etc/hosts})
849
  @type hostname: str
850
  @param hostname: the hostname to be removed
851

852
  """
853
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
854
  try:
855
    out = os.fdopen(fd, 'w')
856
    try:
857
      f = open(file_name, 'r')
858
      try:
859
        for line in f:
860
          fields = line.split()
861
          if len(fields) > 1 and not fields[0].startswith('#'):
862
            names = fields[1:]
863
            if hostname in names:
864
              while hostname in names:
865
                names.remove(hostname)
866
              if names:
867
                out.write("%s %s\n" % (fields[0], ' '.join(names)))
868
              continue
869

    
870
          out.write(line)
871

    
872
        out.flush()
873
        os.fsync(out)
874
        os.rename(tmpname, file_name)
875
      finally:
876
        f.close()
877
    finally:
878
      out.close()
879
  except:
880
    RemoveFile(tmpname)
881
    raise
882

    
883

    
884
def RemoveHostFromEtcHosts(hostname):
885
  """Wrapper around RemoveEtcHostsEntry.
886

887
  @type hostname: str
888
  @param hostname: hostname that will be resolved and its
889
      full and shot name will be removed from
890
      L{constants.ETC_HOSTS}
891

892
  """
893
  hi = HostInfo(name=hostname)
894
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
895
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
896

    
897

    
898
def CreateBackup(file_name):
899
  """Creates a backup of a file.
900

901
  @type file_name: str
902
  @param file_name: file to be backed up
903
  @rtype: str
904
  @return: the path to the newly created backup
905
  @raise errors.ProgrammerError: for invalid file names
906

907
  """
908
  if not os.path.isfile(file_name):
909
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
910
                                file_name)
911

    
912
  prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
913
  dir_name = os.path.dirname(file_name)
914

    
915
  fsrc = open(file_name, 'rb')
916
  try:
917
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
918
    fdst = os.fdopen(fd, 'wb')
919
    try:
920
      shutil.copyfileobj(fsrc, fdst)
921
    finally:
922
      fdst.close()
923
  finally:
924
    fsrc.close()
925

    
926
  return backup_name
927

    
928

    
929
def ShellQuote(value):
930
  """Quotes shell argument according to POSIX.
931

932
  @type value: str
933
  @param value: the argument to be quoted
934
  @rtype: str
935
  @return: the quoted value
936

937
  """
938
  if _re_shell_unquoted.match(value):
939
    return value
940
  else:
941
    return "'%s'" % value.replace("'", "'\\''")
942

    
943

    
944
def ShellQuoteArgs(args):
945
  """Quotes a list of shell arguments.
946

947
  @type args: list
948
  @param args: list of arguments to be quoted
949
  @rtype: str
950
  @return: the quoted arguments concatenaned with spaces
951

952
  """
953
  return ' '.join([ShellQuote(i) for i in args])
954

    
955

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

959
  Check if the given IP is reachable by doing attempting a TCP connect
960
  to it.
961

962
  @type target: str
963
  @param target: the IP or hostname to ping
964
  @type port: int
965
  @param port: the port to connect to
966
  @type timeout: int
967
  @param timeout: the timeout on the connection attemp
968
  @type live_port_needed: boolean
969
  @param live_port_needed: whether a closed port will cause the
970
      function to return failure, as if there was a timeout
971
  @type source: str or None
972
  @param source: if specified, will cause the connect to be made
973
      from this specific source address; failures to bind other
974
      than C{EADDRNOTAVAIL} will be ignored
975

976
  """
977
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
978

    
979
  sucess = False
980

    
981
  if source is not None:
982
    try:
983
      sock.bind((source, 0))
984
    except socket.error, (errcode, errstring):
985
      if errcode == errno.EADDRNOTAVAIL:
986
        success = False
987

    
988
  sock.settimeout(timeout)
989

    
990
  try:
991
    sock.connect((target, port))
992
    sock.close()
993
    success = True
994
  except socket.timeout:
995
    success = False
996
  except socket.error, (errcode, errstring):
997
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
998

    
999
  return success
1000

    
1001

    
1002
def OwnIpAddress(address):
1003
  """Check if the current host has the the given IP address.
1004

1005
  Currently this is done by TCP-pinging the address from the loopback
1006
  address.
1007

1008
  @type address: string
1009
  @param address: the addres to check
1010
  @rtype: bool
1011
  @return: True if we own the address
1012

1013
  """
1014
  return TcpPing(address, constants.DEFAULT_NODED_PORT,
1015
                 source=constants.LOCALHOST_IP_ADDRESS)
1016

    
1017

    
1018
def ListVisibleFiles(path):
1019
  """Returns a list of visible files in a directory.
1020

1021
  @type path: str
1022
  @param path: the directory to enumerate
1023
  @rtype: list
1024
  @return: the list of all files not starting with a dot
1025

1026
  """
1027
  files = [i for i in os.listdir(path) if not i.startswith(".")]
1028
  files.sort()
1029
  return files
1030

    
1031

    
1032
def GetHomeDir(user, default=None):
1033
  """Try to get the homedir of the given user.
1034

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

1039
  """
1040
  try:
1041
    if isinstance(user, basestring):
1042
      result = pwd.getpwnam(user)
1043
    elif isinstance(user, (int, long)):
1044
      result = pwd.getpwuid(user)
1045
    else:
1046
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1047
                                   type(user))
1048
  except KeyError:
1049
    return default
1050
  return result.pw_dir
1051

    
1052

    
1053
def NewUUID():
1054
  """Returns a random UUID.
1055

1056
  @note: This is a Linux-specific method as it uses the /proc
1057
      filesystem.
1058
  @rtype: str
1059

1060
  """
1061
  f = open("/proc/sys/kernel/random/uuid", "r")
1062
  try:
1063
    return f.read(128).rstrip("\n")
1064
  finally:
1065
    f.close()
1066

    
1067

    
1068
def GenerateSecret():
1069
  """Generates a random secret.
1070

1071
  This will generate a pseudo-random secret, and return its sha digest
1072
  (so that it can be used where an ASCII string is needed).
1073

1074
  @rtype: str
1075
  @return: a sha1 hexdigest of a block of 64 random bytes
1076

1077
  """
1078
  return sha.new(os.urandom(64)).hexdigest()
1079

    
1080

    
1081
def ReadFile(file_name, size=None):
1082
  """Reads a file.
1083

1084
  @type size: None or int
1085
  @param size: Read at most size bytes
1086
  @rtype: str
1087
  @return: the (possibly partial) conent of the file
1088

1089
  """
1090
  f = open(file_name, "r")
1091
  try:
1092
    if size is None:
1093
      return f.read()
1094
    else:
1095
      return f.read(size)
1096
  finally:
1097
    f.close()
1098

    
1099

    
1100
def WriteFile(file_name, fn=None, data=None,
1101
              mode=None, uid=-1, gid=-1,
1102
              atime=None, mtime=None, close=True,
1103
              dry_run=False, backup=False,
1104
              prewrite=None, postwrite=None):
1105
  """(Over)write a file atomically.
1106

1107
  The file_name and either fn (a function taking one argument, the
1108
  file descriptor, and which should write the data to it) or data (the
1109
  contents of the file) must be passed. The other arguments are
1110
  optional and allow setting the file mode, owner and group, and the
1111
  mtime/atime of the file.
1112

1113
  If the function doesn't raise an exception, it has succeeded and the
1114
  target file has the new contents. If the file has raised an
1115
  exception, an existing target file should be unmodified and the
1116
  temporary file should be removed.
1117

1118
  @type file_name: str
1119
  @param file_name: the target filename
1120
  @type fn: callable
1121
  @param fn: content writing function, called with
1122
      file descriptor as parameter
1123
  @type data: sr
1124
  @param data: contents of the file
1125
  @type mode: int
1126
  @param mode: file mode
1127
  @type uid: int
1128
  @param uid: the owner of the file
1129
  @type gid: int
1130
  @param gid: the group of the file
1131
  @type atime: int
1132
  @param atime: a custom access time to be set on the file
1133
  @type mtime: int
1134
  @param mtime: a custom modification time to be set on the file
1135
  @type close: boolean
1136
  @param close: whether to close file after writing it
1137
  @type prewrite: callable
1138
  @param prewrite: function to be called before writing content
1139
  @type postwrite: callable
1140
  @param postwrite: function to be called after writing content
1141

1142
  @rtype: None or int
1143
  @return: None if the 'close' parameter evaluates to True,
1144
      otherwise the file descriptor
1145

1146
  @raise errors.ProgrammerError: if an of the arguments are not valid
1147

1148
  """
1149
  if not os.path.isabs(file_name):
1150
    raise errors.ProgrammerError("Path passed to WriteFile is not"
1151
                                 " absolute: '%s'" % file_name)
1152

    
1153
  if [fn, data].count(None) != 1:
1154
    raise errors.ProgrammerError("fn or data required")
1155

    
1156
  if [atime, mtime].count(None) == 1:
1157
    raise errors.ProgrammerError("Both atime and mtime must be either"
1158
                                 " set or None")
1159

    
1160
  if backup and not dry_run and os.path.isfile(file_name):
1161
    CreateBackup(file_name)
1162

    
1163
  dir_name, base_name = os.path.split(file_name)
1164
  fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
1165
  # here we need to make sure we remove the temp file, if any error
1166
  # leaves it in place
1167
  try:
1168
    if uid != -1 or gid != -1:
1169
      os.chown(new_name, uid, gid)
1170
    if mode:
1171
      os.chmod(new_name, mode)
1172
    if callable(prewrite):
1173
      prewrite(fd)
1174
    if data is not None:
1175
      os.write(fd, data)
1176
    else:
1177
      fn(fd)
1178
    if callable(postwrite):
1179
      postwrite(fd)
1180
    os.fsync(fd)
1181
    if atime is not None and mtime is not None:
1182
      os.utime(new_name, (atime, mtime))
1183
    if not dry_run:
1184
      os.rename(new_name, file_name)
1185
  finally:
1186
    if close:
1187
      os.close(fd)
1188
      result = None
1189
    else:
1190
      result = fd
1191
    RemoveFile(new_name)
1192

    
1193
  return result
1194

    
1195

    
1196
def FirstFree(seq, base=0):
1197
  """Returns the first non-existing integer from seq.
1198

1199
  The seq argument should be a sorted list of positive integers. The
1200
  first time the index of an element is smaller than the element
1201
  value, the index will be returned.
1202

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

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

1208
  @type seq: sequence
1209
  @param seq: the sequence to be analyzed.
1210
  @type base: int
1211
  @param base: use this value as the base index of the sequence
1212
  @rtype: int
1213
  @return: the first non-used index in the sequence
1214

1215
  """
1216
  for idx, elem in enumerate(seq):
1217
    assert elem >= base, "Passed element is higher than base offset"
1218
    if elem > idx + base:
1219
      # idx is not used
1220
      return idx + base
1221
  return None
1222

    
1223

    
1224
def all(seq, pred=bool):
1225
  "Returns True if pred(x) is True for every element in the iterable"
1226
  for elem in itertools.ifilterfalse(pred, seq):
1227
    return False
1228
  return True
1229

    
1230

    
1231
def any(seq, pred=bool):
1232
  "Returns True if pred(x) is True for at least one element in the iterable"
1233
  for elem in itertools.ifilter(pred, seq):
1234
    return True
1235
  return False
1236

    
1237

    
1238
def UniqueSequence(seq):
1239
  """Returns a list with unique elements.
1240

1241
  Element order is preserved.
1242

1243
  @type seq: sequence
1244
  @param seq: the sequence with the source elementes
1245
  @rtype: list
1246
  @return: list of unique elements from seq
1247

1248
  """
1249
  seen = set()
1250
  return [i for i in seq if i not in seen and not seen.add(i)]
1251

    
1252

    
1253
def IsValidMac(mac):
1254
  """Predicate to check if a MAC address is valid.
1255

1256
  Checks wether the supplied MAC address is formally correct, only
1257
  accepts colon separated format.
1258

1259
  @type mac: str
1260
  @param mac: the MAC to be validated
1261
  @rtype: boolean
1262
  @return: True is the MAC seems valid
1263

1264
  """
1265
  mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$")
1266
  return mac_check.match(mac) is not None
1267

    
1268

    
1269
def TestDelay(duration):
1270
  """Sleep for a fixed amount of time.
1271

1272
  @type duration: float
1273
  @param duration: the sleep duration
1274
  @rtype: boolean
1275
  @return: False for negative value, True otherwise
1276

1277
  """
1278
  if duration < 0:
1279
    return False
1280
  time.sleep(duration)
1281
  return True
1282

    
1283

    
1284
def Daemonize(logfile, noclose_fds=None):
1285
  """Daemonize the current process.
1286

1287
  This detaches the current process from the controlling terminal and
1288
  runs it in the background as a daemon.
1289

1290
  @type logfile: str
1291
  @param logfile: the logfile to which we should redirect stdout/stderr
1292
  @type noclose_fds: list or None
1293
  @param noclose_fds: if given, it denotes a list of file descriptor
1294
      that should not be closed
1295
  @rtype: int
1296
  @returns: the value zero
1297

1298
  """
1299
  UMASK = 077
1300
  WORKDIR = "/"
1301
  # Default maximum for the number of available file descriptors.
1302
  if 'SC_OPEN_MAX' in os.sysconf_names:
1303
    try:
1304
      MAXFD = os.sysconf('SC_OPEN_MAX')
1305
      if MAXFD < 0:
1306
        MAXFD = 1024
1307
    except OSError:
1308
      MAXFD = 1024
1309
  else:
1310
    MAXFD = 1024
1311

    
1312
  # this might fail
1313
  pid = os.fork()
1314
  if (pid == 0):  # The first child.
1315
    os.setsid()
1316
    # this might fail
1317
    pid = os.fork() # Fork a second child.
1318
    if (pid == 0):  # The second child.
1319
      os.chdir(WORKDIR)
1320
      os.umask(UMASK)
1321
    else:
1322
      # exit() or _exit()?  See below.
1323
      os._exit(0) # Exit parent (the first child) of the second child.
1324
  else:
1325
    os._exit(0) # Exit parent of the first child.
1326
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1327
  if (maxfd == resource.RLIM_INFINITY):
1328
    maxfd = MAXFD
1329

    
1330
  # Iterate through and close all file descriptors.
1331
  for fd in range(0, maxfd):
1332
    if noclose_fds and fd in noclose_fds:
1333
      continue
1334
    try:
1335
      os.close(fd)
1336
    except OSError: # ERROR, fd wasn't open to begin with (ignored)
1337
      pass
1338
  os.open(logfile, os.O_RDWR|os.O_CREAT|os.O_APPEND, 0600)
1339
  # Duplicate standard input to standard output and standard error.
1340
  os.dup2(0, 1)     # standard output (1)
1341
  os.dup2(0, 2)     # standard error (2)
1342
  return 0
1343

    
1344

    
1345
def DaemonPidFileName(name):
1346
  """Compute a ganeti pid file absolute path
1347

1348
  @type name: str
1349
  @param name: the daemon name
1350
  @rtype: str
1351
  @return: the full path to the pidfile corresponding to the given
1352
      daemon name
1353

1354
  """
1355
  return os.path.join(constants.RUN_GANETI_DIR, "%s.pid" % name)
1356

    
1357

    
1358
def WritePidFile(name):
1359
  """Write the current process pidfile.
1360

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

1363
  @type name: str
1364
  @param name: the daemon name to use
1365
  @raise errors.GenericError: if the pid file already exists and
1366
      points to a live process
1367

1368
  """
1369
  pid = os.getpid()
1370
  pidfilename = DaemonPidFileName(name)
1371
  if IsProcessAlive(ReadPidFile(pidfilename)):
1372
    raise errors.GenericError("%s contains a live process" % pidfilename)
1373

    
1374
  WriteFile(pidfilename, data="%d\n" % pid)
1375

    
1376

    
1377
def RemovePidFile(name):
1378
  """Remove the current process pidfile.
1379

1380
  Any errors are ignored.
1381

1382
  @type name: str
1383
  @param name: the daemon name used to derive the pidfile name
1384

1385
  """
1386
  pid = os.getpid()
1387
  pidfilename = DaemonPidFileName(name)
1388
  # TODO: we could check here that the file contains our pid
1389
  try:
1390
    RemoveFile(pidfilename)
1391
  except:
1392
    pass
1393

    
1394

    
1395
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1396
                waitpid=False):
1397
  """Kill a process given by its pid.
1398

1399
  @type pid: int
1400
  @param pid: The PID to terminate.
1401
  @type signal_: int
1402
  @param signal_: The signal to send, by default SIGTERM
1403
  @type timeout: int
1404
  @param timeout: The timeout after which, if the process is still alive,
1405
                  a SIGKILL will be sent. If not positive, no such checking
1406
                  will be done
1407
  @type waitpid: boolean
1408
  @param waitpid: If true, we should waitpid on this process after
1409
      sending signals, since it's our own child and otherwise it
1410
      would remain as zombie
1411

1412
  """
1413
  def _helper(pid, signal_, wait):
1414
    """Simple helper to encapsulate the kill/waitpid sequence"""
1415
    os.kill(pid, signal_)
1416
    if wait:
1417
      try:
1418
        os.waitpid(pid, os.WNOHANG)
1419
      except OSError:
1420
        pass
1421

    
1422
  if pid <= 0:
1423
    # kill with pid=0 == suicide
1424
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1425

    
1426
  if not IsProcessAlive(pid):
1427
    return
1428
  _helper(pid, signal_, waitpid)
1429
  if timeout <= 0:
1430
    return
1431
  end = time.time() + timeout
1432
  while time.time() < end and IsProcessAlive(pid):
1433
    time.sleep(0.1)
1434
  if IsProcessAlive(pid):
1435
    _helper(pid, signal.SIGKILL, waitpid)
1436

    
1437

    
1438
def FindFile(name, search_path, test=os.path.exists):
1439
  """Look for a filesystem object in a given path.
1440

1441
  This is an abstract method to search for filesystem object (files,
1442
  dirs) under a given search path.
1443

1444
  @type name: str
1445
  @param name: the name to look for
1446
  @type search_path: str
1447
  @param search_path: location to start at
1448
  @type test: callable
1449
  @param test: a function taking one argument that should return True
1450
      if the a given object is valid; the default value is
1451
      os.path.exists, causing only existing files to be returned
1452
  @rtype: str or None
1453
  @return: full path to the object if found, None otherwise
1454

1455
  """
1456
  for dir_name in search_path:
1457
    item_name = os.path.sep.join([dir_name, name])
1458
    if test(item_name):
1459
      return item_name
1460
  return None
1461

    
1462

    
1463
def CheckVolumeGroupSize(vglist, vgname, minsize):
1464
  """Checks if the volume group list is valid.
1465

1466
  The function will check if a given volume group is in the list of
1467
  volume groups and has a minimum size.
1468

1469
  @type vglist: dict
1470
  @param vglist: dictionary of volume group names and their size
1471
  @type vgname: str
1472
  @param vgname: the volume group we should check
1473
  @type minsize: int
1474
  @param minsize: the minimum size we accept
1475
  @rtype: None or str
1476
  @return: None for success, otherwise the error message
1477

1478
  """
1479
  vgsize = vglist.get(vgname, None)
1480
  if vgsize is None:
1481
    return "volume group '%s' missing" % vgname
1482
  elif vgsize < minsize:
1483
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1484
            (vgname, minsize, vgsize))
1485
  return None
1486

    
1487

    
1488
def SplitTime(value):
1489
  """Splits time as floating point number into a tuple.
1490

1491
  @param value: Time in seconds
1492
  @type value: int or float
1493
  @return: Tuple containing (seconds, microseconds)
1494

1495
  """
1496
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1497

    
1498
  assert 0 <= seconds, \
1499
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1500
  assert 0 <= microseconds <= 999999, \
1501
    "Microseconds must be 0-999999, but are %s" % microseconds
1502

    
1503
  return (int(seconds), int(microseconds))
1504

    
1505

    
1506
def MergeTime(timetuple):
1507
  """Merges a tuple into time as a floating point number.
1508

1509
  @param timetuple: Time as tuple, (seconds, microseconds)
1510
  @type timetuple: tuple
1511
  @return: Time as a floating point number expressed in seconds
1512

1513
  """
1514
  (seconds, microseconds) = timetuple
1515

    
1516
  assert 0 <= seconds, \
1517
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1518
  assert 0 <= microseconds <= 999999, \
1519
    "Microseconds must be 0-999999, but are %s" % microseconds
1520

    
1521
  return float(seconds) + (float(microseconds) * 0.000001)
1522

    
1523

    
1524
def GetNodeDaemonPort():
1525
  """Get the node daemon port for this cluster.
1526

1527
  Note that this routine does not read a ganeti-specific file, but
1528
  instead uses C{socket.getservbyname} to allow pre-customization of
1529
  this parameter outside of Ganeti.
1530

1531
  @rtype: int
1532

1533
  """
1534
  try:
1535
    port = socket.getservbyname("ganeti-noded", "tcp")
1536
  except socket.error:
1537
    port = constants.DEFAULT_NODED_PORT
1538

    
1539
  return port
1540

    
1541

    
1542
def SetupLogging(logfile, debug=False, stderr_logging=False, program=""):
1543
  """Configures the logging module.
1544

1545
  @type logfile: str
1546
  @param logfile: the filename to which we should log
1547
  @type debug: boolean
1548
  @param debug: whether to enable debug messages too or
1549
      only those at C{INFO} and above level
1550
  @type stderr_logging: boolean
1551
  @param stderr_logging: whether we should also log to the standard error
1552
  @type program: str
1553
  @param program: the name under which we should log messages
1554
  @raise EnvironmentError: if we can't open the log file and
1555
      stderr logging is disabled
1556

1557
  """
1558
  fmt = "%(asctime)s: " + program + " "
1559
  if debug:
1560
    fmt += ("pid=%(process)d/%(threadName)s %(levelname)s"
1561
           " %(module)s:%(lineno)s %(message)s")
1562
  else:
1563
    fmt += "pid=%(process)d %(levelname)s %(message)s"
1564
  formatter = logging.Formatter(fmt)
1565

    
1566
  root_logger = logging.getLogger("")
1567
  root_logger.setLevel(logging.NOTSET)
1568

    
1569
  # Remove all previously setup handlers
1570
  for handler in root_logger.handlers:
1571
    root_logger.removeHandler(handler)
1572

    
1573
  if stderr_logging:
1574
    stderr_handler = logging.StreamHandler()
1575
    stderr_handler.setFormatter(formatter)
1576
    if debug:
1577
      stderr_handler.setLevel(logging.NOTSET)
1578
    else:
1579
      stderr_handler.setLevel(logging.CRITICAL)
1580
    root_logger.addHandler(stderr_handler)
1581

    
1582
  # this can fail, if the logging directories are not setup or we have
1583
  # a permisssion problem; in this case, it's best to log but ignore
1584
  # the error if stderr_logging is True, and if false we re-raise the
1585
  # exception since otherwise we could run but without any logs at all
1586
  try:
1587
    logfile_handler = logging.FileHandler(logfile)
1588
    logfile_handler.setFormatter(formatter)
1589
    if debug:
1590
      logfile_handler.setLevel(logging.DEBUG)
1591
    else:
1592
      logfile_handler.setLevel(logging.INFO)
1593
    root_logger.addHandler(logfile_handler)
1594
  except EnvironmentError, err:
1595
    if stderr_logging:
1596
      logging.exception("Failed to enable logging to file '%s'", logfile)
1597
    else:
1598
      # we need to re-raise the exception
1599
      raise
1600

    
1601

    
1602
def LockedMethod(fn):
1603
  """Synchronized object access decorator.
1604

1605
  This decorator is intended to protect access to an object using the
1606
  object's own lock which is hardcoded to '_lock'.
1607

1608
  """
1609
  def _LockDebug(*args, **kwargs):
1610
    if debug_locks:
1611
      logging.debug(*args, **kwargs)
1612

    
1613
  def wrapper(self, *args, **kwargs):
1614
    assert hasattr(self, '_lock')
1615
    lock = self._lock
1616
    _LockDebug("Waiting for %s", lock)
1617
    lock.acquire()
1618
    try:
1619
      _LockDebug("Acquired %s", lock)
1620
      result = fn(self, *args, **kwargs)
1621
    finally:
1622
      _LockDebug("Releasing %s", lock)
1623
      lock.release()
1624
      _LockDebug("Released %s", lock)
1625
    return result
1626
  return wrapper
1627

    
1628

    
1629
def LockFile(fd):
1630
  """Locks a file using POSIX locks.
1631

1632
  @type fd: int
1633
  @param fd: the file descriptor we need to lock
1634

1635
  """
1636
  try:
1637
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
1638
  except IOError, err:
1639
    if err.errno == errno.EAGAIN:
1640
      raise errors.LockError("File already locked")
1641
    raise
1642

    
1643

    
1644
class FileLock(object):
1645
  """Utility class for file locks.
1646

1647
  """
1648
  def __init__(self, filename):
1649
    """Constructor for FileLock.
1650

1651
    This will open the file denoted by the I{filename} argument.
1652

1653
    @type filename: str
1654
    @param filename: path to the file to be locked
1655

1656
    """
1657
    self.filename = filename
1658
    self.fd = open(self.filename, "w")
1659

    
1660
  def __del__(self):
1661
    self.Close()
1662

    
1663
  def Close(self):
1664
    """Close the file and release the lock.
1665

1666
    """
1667
    if self.fd:
1668
      self.fd.close()
1669
      self.fd = None
1670

    
1671
  def _flock(self, flag, blocking, timeout, errmsg):
1672
    """Wrapper for fcntl.flock.
1673

1674
    @type flag: int
1675
    @param flag: operation flag
1676
    @type blocking: bool
1677
    @param blocking: whether the operation should be done in blocking mode.
1678
    @type timeout: None or float
1679
    @param timeout: for how long the operation should be retried (implies
1680
                    non-blocking mode).
1681
    @type errmsg: string
1682
    @param errmsg: error message in case operation fails.
1683

1684
    """
1685
    assert self.fd, "Lock was closed"
1686
    assert timeout is None or timeout >= 0, \
1687
      "If specified, timeout must be positive"
1688

    
1689
    if timeout is not None:
1690
      flag |= fcntl.LOCK_NB
1691
      timeout_end = time.time() + timeout
1692

    
1693
    # Blocking doesn't have effect with timeout
1694
    elif not blocking:
1695
      flag |= fcntl.LOCK_NB
1696
      timeout_end = None
1697

    
1698
    retry = True
1699
    while retry:
1700
      try:
1701
        fcntl.flock(self.fd, flag)
1702
        retry = False
1703
      except IOError, err:
1704
        if err.errno in (errno.EAGAIN, ):
1705
          if timeout_end is not None and time.time() < timeout_end:
1706
            # Wait before trying again
1707
            time.sleep(max(0.1, min(1.0, timeout)))
1708
          else:
1709
            raise errors.LockError(errmsg)
1710
        else:
1711
          logging.exception("fcntl.flock failed")
1712
          raise
1713

    
1714
  def Exclusive(self, blocking=False, timeout=None):
1715
    """Locks the file in exclusive mode.
1716

1717
    @type blocking: boolean
1718
    @param blocking: whether to block and wait until we
1719
        can lock the file or return immediately
1720
    @type timeout: int or None
1721
    @param timeout: if not None, the duration to wait for the lock
1722
        (in blocking mode)
1723

1724
    """
1725
    self._flock(fcntl.LOCK_EX, blocking, timeout,
1726
                "Failed to lock %s in exclusive mode" % self.filename)
1727

    
1728
  def Shared(self, blocking=False, timeout=None):
1729
    """Locks the file in shared mode.
1730

1731
    @type blocking: boolean
1732
    @param blocking: whether to block and wait until we
1733
        can lock the file or return immediately
1734
    @type timeout: int or None
1735
    @param timeout: if not None, the duration to wait for the lock
1736
        (in blocking mode)
1737

1738
    """
1739
    self._flock(fcntl.LOCK_SH, blocking, timeout,
1740
                "Failed to lock %s in shared mode" % self.filename)
1741

    
1742
  def Unlock(self, blocking=True, timeout=None):
1743
    """Unlocks the file.
1744

1745
    According to C{flock(2)}, unlocking can also be a nonblocking
1746
    operation::
1747

1748
      To make a non-blocking request, include LOCK_NB with any of the above
1749
      operations.
1750

1751
    @type blocking: boolean
1752
    @param blocking: whether to block and wait until we
1753
        can lock the file or return immediately
1754
    @type timeout: int or None
1755
    @param timeout: if not None, the duration to wait for the lock
1756
        (in blocking mode)
1757

1758
    """
1759
    self._flock(fcntl.LOCK_UN, blocking, timeout,
1760
                "Failed to unlock %s" % self.filename)
1761

    
1762

    
1763
class SignalHandler(object):
1764
  """Generic signal handler class.
1765

1766
  It automatically restores the original handler when deconstructed or
1767
  when L{Reset} is called. You can either pass your own handler
1768
  function in or query the L{called} attribute to detect whether the
1769
  signal was sent.
1770

1771
  @type signum: list
1772
  @ivar signum: the signals we handle
1773
  @type called: boolean
1774
  @ivar called: tracks whether any of the signals have been raised
1775

1776
  """
1777
  def __init__(self, signum):
1778
    """Constructs a new SignalHandler instance.
1779

1780
    @type signum: int or list of ints
1781
    @param signum: Single signal number or set of signal numbers
1782

1783
    """
1784
    if isinstance(signum, (int, long)):
1785
      self.signum = set([signum])
1786
    else:
1787
      self.signum = set(signum)
1788

    
1789
    self.called = False
1790

    
1791
    self._previous = {}
1792
    try:
1793
      for signum in self.signum:
1794
        # Setup handler
1795
        prev_handler = signal.signal(signum, self._HandleSignal)
1796
        try:
1797
          self._previous[signum] = prev_handler
1798
        except:
1799
          # Restore previous handler
1800
          signal.signal(signum, prev_handler)
1801
          raise
1802
    except:
1803
      # Reset all handlers
1804
      self.Reset()
1805
      # Here we have a race condition: a handler may have already been called,
1806
      # but there's not much we can do about it at this point.
1807
      raise
1808

    
1809
  def __del__(self):
1810
    self.Reset()
1811

    
1812
  def Reset(self):
1813
    """Restore previous handler.
1814

1815
    This will reset all the signals to their previous handlers.
1816

1817
    """
1818
    for signum, prev_handler in self._previous.items():
1819
      signal.signal(signum, prev_handler)
1820
      # If successful, remove from dict
1821
      del self._previous[signum]
1822

    
1823
  def Clear(self):
1824
    """Unsets the L{called} flag.
1825

1826
    This function can be used in case a signal may arrive several times.
1827

1828
    """
1829
    self.called = False
1830

    
1831
  def _HandleSignal(self, signum, frame):
1832
    """Actual signal handling function.
1833

1834
    """
1835
    # This is not nice and not absolutely atomic, but it appears to be the only
1836
    # solution in Python -- there are no atomic types.
1837
    self.called = True
1838

    
1839

    
1840
class FieldSet(object):
1841
  """A simple field set.
1842

1843
  Among the features are:
1844
    - checking if a string is among a list of static string or regex objects
1845
    - checking if a whole list of string matches
1846
    - returning the matching groups from a regex match
1847

1848
  Internally, all fields are held as regular expression objects.
1849

1850
  """
1851
  def __init__(self, *items):
1852
    self.items = [re.compile("^%s$" % value) for value in items]
1853

    
1854
  def Extend(self, other_set):
1855
    """Extend the field set with the items from another one"""
1856
    self.items.extend(other_set.items)
1857

    
1858
  def Matches(self, field):
1859
    """Checks if a field matches the current set
1860

1861
    @type field: str
1862
    @param field: the string to match
1863
    @return: either False or a regular expression match object
1864

1865
    """
1866
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
1867
      return m
1868
    return False
1869

    
1870
  def NonMatching(self, items):
1871
    """Returns the list of fields not matching the current set
1872

1873
    @type items: list
1874
    @param items: the list of fields to check
1875
    @rtype: list
1876
    @return: list of non-matching fields
1877

1878
    """
1879
    return [val for val in items if not self.Matches(val)]