Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ 8797df43

History | View | Annotate | Download (47.2 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 processes treated as not alive, and giving a
356
      pid M{<= 0} causes the function to return False.
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
    f = open("/proc/%d/status" % pid)
368
  except IOError, err:
369
    if err.errno in (errno.ENOENT, errno.ENOTDIR):
370
      return False
371

    
372
  alive = True
373
  try:
374
    data = f.readlines()
375
    if len(data) > 1:
376
      state = data[1].split()
377
      if len(state) > 1 and state[1] == "Z":
378
        alive = False
379
  finally:
380
    f.close()
381

    
382
  return alive
383

    
384

    
385
def ReadPidFile(pidfile):
386
  """Read a pid from a file.
387

388
  @type  pidfile: string
389
  @param pidfile: path to the file containing the pid
390
  @rtype: int
391
  @return: The process id, if the file exista and contains a valid PID,
392
           otherwise 0
393

394
  """
395
  try:
396
    pf = open(pidfile, 'r')
397
  except EnvironmentError, err:
398
    if err.errno != errno.ENOENT:
399
      logging.exception("Can't read pid file?!")
400
    return 0
401

    
402
  try:
403
    pid = int(pf.read())
404
  except ValueError, err:
405
    logging.info("Can't parse pid file contents", exc_info=True)
406
    return 0
407

    
408
  return pid
409

    
410

    
411
def MatchNameComponent(key, name_list):
412
  """Try to match a name against a list.
413

414
  This function will try to match a name like test1 against a list
415
  like C{['test1.example.com', 'test2.example.com', ...]}. Against
416
  this list, I{'test1'} as well as I{'test1.example'} will match, but
417
  not I{'test1.ex'}. A multiple match will be considered as no match
418
  at all (e.g. I{'test1'} against C{['test1.example.com',
419
  'test1.example.org']}).
420

421
  @type key: str
422
  @param key: the name to be searched
423
  @type name_list: list
424
  @param name_list: the list of strings against which to search the key
425

426
  @rtype: None or str
427
  @return: None if there is no match I{or} if there are multiple matches,
428
      otherwise the element from the list which matches
429

430
  """
431
  mo = re.compile("^%s(\..*)?$" % re.escape(key))
432
  names_filtered = [name for name in name_list if mo.match(name) is not None]
433
  if len(names_filtered) != 1:
434
    return None
435
  return names_filtered[0]
436

    
437

    
438
class HostInfo:
439
  """Class implementing resolver and hostname functionality
440

441
  """
442
  def __init__(self, name=None):
443
    """Initialize the host name object.
444

445
    If the name argument is not passed, it will use this system's
446
    name.
447

448
    """
449
    if name is None:
450
      name = self.SysName()
451

    
452
    self.query = name
453
    self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
454
    self.ip = self.ipaddrs[0]
455

    
456
  def ShortName(self):
457
    """Returns the hostname without domain.
458

459
    """
460
    return self.name.split('.')[0]
461

    
462
  @staticmethod
463
  def SysName():
464
    """Return the current system's name.
465

466
    This is simply a wrapper over C{socket.gethostname()}.
467

468
    """
469
    return socket.gethostname()
470

    
471
  @staticmethod
472
  def LookupHostname(hostname):
473
    """Look up hostname
474

475
    @type hostname: str
476
    @param hostname: hostname to look up
477

478
    @rtype: tuple
479
    @return: a tuple (name, aliases, ipaddrs) as returned by
480
        C{socket.gethostbyname_ex}
481
    @raise errors.ResolverError: in case of errors in resolving
482

483
    """
484
    try:
485
      result = socket.gethostbyname_ex(hostname)
486
    except socket.gaierror, err:
487
      # hostname not found in DNS
488
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
489

    
490
    return result
491

    
492

    
493
def ListVolumeGroups():
494
  """List volume groups and their size
495

496
  @rtype: dict
497
  @return:
498
       Dictionary with keys volume name and values
499
       the size of the volume
500

501
  """
502
  command = "vgs --noheadings --units m --nosuffix -o name,size"
503
  result = RunCmd(command)
504
  retval = {}
505
  if result.failed:
506
    return retval
507

    
508
  for line in result.stdout.splitlines():
509
    try:
510
      name, size = line.split()
511
      size = int(float(size))
512
    except (IndexError, ValueError), err:
513
      logging.error("Invalid output from vgs (%s): %s", err, line)
514
      continue
515

    
516
    retval[name] = size
517

    
518
  return retval
519

    
520

    
521
def BridgeExists(bridge):
522
  """Check whether the given bridge exists in the system
523

524
  @type bridge: str
525
  @param bridge: the bridge name to check
526
  @rtype: boolean
527
  @return: True if it does
528

529
  """
530
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
531

    
532

    
533
def NiceSort(name_list):
534
  """Sort a list of strings based on digit and non-digit groupings.
535

536
  Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
537
  will sort the list in the logical order C{['a1', 'a2', 'a10',
538
  'a11']}.
539

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

544
  @type name_list: list
545
  @param name_list: the names to be sorted
546
  @rtype: list
547
  @return: a copy of the name list sorted with our algorithm
548

549
  """
550
  _SORTER_BASE = "(\D+|\d+)"
551
  _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
552
                                                  _SORTER_BASE, _SORTER_BASE,
553
                                                  _SORTER_BASE, _SORTER_BASE,
554
                                                  _SORTER_BASE, _SORTER_BASE)
555
  _SORTER_RE = re.compile(_SORTER_FULL)
556
  _SORTER_NODIGIT = re.compile("^\D*$")
557
  def _TryInt(val):
558
    """Attempts to convert a variable to integer."""
559
    if val is None or _SORTER_NODIGIT.match(val):
560
      return val
561
    rval = int(val)
562
    return rval
563

    
564
  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
565
             for name in name_list]
566
  to_sort.sort()
567
  return [tup[1] for tup in to_sort]
568

    
569

    
570
def TryConvert(fn, val):
571
  """Try to convert a value ignoring errors.
572

573
  This function tries to apply function I{fn} to I{val}. If no
574
  C{ValueError} or C{TypeError} exceptions are raised, it will return
575
  the result, else it will return the original value. Any other
576
  exceptions are propagated to the caller.
577

578
  @type fn: callable
579
  @param fn: function to apply to the value
580
  @param val: the value to be converted
581
  @return: The converted value if the conversion was successful,
582
      otherwise the original value.
583

584
  """
585
  try:
586
    nv = fn(val)
587
  except (ValueError, TypeError), err:
588
    nv = val
589
  return nv
590

    
591

    
592
def IsValidIP(ip):
593
  """Verifies the syntax of an IPv4 address.
594

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

598
  @type ip: str
599
  @param ip: the address to be checked
600
  @rtype: a regular expression match object
601
  @return: a regular epression match object, or None if the
602
      address is not valid
603

604
  """
605
  unit = "(0|[1-9]\d{0,2})"
606
  #TODO: convert and return only boolean
607
  return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
608

    
609

    
610
def IsValidShellParam(word):
611
  """Verifies is the given word is safe from the shell's p.o.v.
612

613
  This means that we can pass this to a command via the shell and be
614
  sure that it doesn't alter the command line and is passed as such to
615
  the actual command.
616

617
  Note that we are overly restrictive here, in order to be on the safe
618
  side.
619

620
  @type word: str
621
  @param word: the word to check
622
  @rtype: boolean
623
  @return: True if the word is 'safe'
624

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

    
628

    
629
def BuildShellCmd(template, *args):
630
  """Build a safe shell command line from the given arguments.
631

632
  This function will check all arguments in the args list so that they
633
  are valid shell parameters (i.e. they don't contain shell
634
  metacharaters). If everything is ok, it will return the result of
635
  template % args.
636

637
  @type template: str
638
  @param template: the string holding the template for the
639
      string formatting
640
  @rtype: str
641
  @return: the expanded command line
642

643
  """
644
  for word in args:
645
    if not IsValidShellParam(word):
646
      raise errors.ProgrammerError("Shell argument '%s' contains"
647
                                   " invalid characters" % word)
648
  return template % args
649

    
650

    
651
def FormatUnit(value):
652
  """Formats an incoming number of MiB with the appropriate unit.
653

654
  @type value: int
655
  @param value: integer representing the value in MiB (1048576)
656
  @rtype: str
657
  @return: the formatted value (with suffix)
658

659
  """
660
  if value < 1024:
661
    return "%dM" % round(value, 0)
662

    
663
  elif value < (1024 * 1024):
664
    return "%0.1fG" % round(float(value) / 1024, 1)
665

    
666
  else:
667
    return "%0.1fT" % round(float(value) / 1024 / 1024, 1)
668

    
669

    
670
def ParseUnit(input_string):
671
  """Tries to extract number and scale from the given string.
672

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

677
  """
678
  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', input_string)
679
  if not m:
680
    raise errors.UnitParseError("Invalid format")
681

    
682
  value = float(m.groups()[0])
683

    
684
  unit = m.groups()[1]
685
  if unit:
686
    lcunit = unit.lower()
687
  else:
688
    lcunit = 'm'
689

    
690
  if lcunit in ('m', 'mb', 'mib'):
691
    # Value already in MiB
692
    pass
693

    
694
  elif lcunit in ('g', 'gb', 'gib'):
695
    value *= 1024
696

    
697
  elif lcunit in ('t', 'tb', 'tib'):
698
    value *= 1024 * 1024
699

    
700
  else:
701
    raise errors.UnitParseError("Unknown unit: %s" % unit)
702

    
703
  # Make sure we round up
704
  if int(value) < value:
705
    value += 1
706

    
707
  # Round up to the next multiple of 4
708
  value = int(value)
709
  if value % 4:
710
    value += 4 - value % 4
711

    
712
  return value
713

    
714

    
715
def AddAuthorizedKey(file_name, key):
716
  """Adds an SSH public key to an authorized_keys file.
717

718
  @type file_name: str
719
  @param file_name: path to authorized_keys file
720
  @type key: str
721
  @param key: string containing key
722

723
  """
724
  key_fields = key.split()
725

    
726
  f = open(file_name, 'a+')
727
  try:
728
    nl = True
729
    for line in f:
730
      # Ignore whitespace changes
731
      if line.split() == key_fields:
732
        break
733
      nl = line.endswith('\n')
734
    else:
735
      if not nl:
736
        f.write("\n")
737
      f.write(key.rstrip('\r\n'))
738
      f.write("\n")
739
      f.flush()
740
  finally:
741
    f.close()
742

    
743

    
744
def RemoveAuthorizedKey(file_name, key):
745
  """Removes an SSH public key from an authorized_keys file.
746

747
  @type file_name: str
748
  @param file_name: path to authorized_keys file
749
  @type key: str
750
  @param key: string containing key
751

752
  """
753
  key_fields = key.split()
754

    
755
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
756
  try:
757
    out = os.fdopen(fd, 'w')
758
    try:
759
      f = open(file_name, 'r')
760
      try:
761
        for line in f:
762
          # Ignore whitespace changes while comparing lines
763
          if line.split() != key_fields:
764
            out.write(line)
765

    
766
        out.flush()
767
        os.rename(tmpname, file_name)
768
      finally:
769
        f.close()
770
    finally:
771
      out.close()
772
  except:
773
    RemoveFile(tmpname)
774
    raise
775

    
776

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

780
  @type file_name: str
781
  @param file_name: path to the file to modify (usually C{/etc/hosts})
782
  @type ip: str
783
  @param ip: the IP address
784
  @type hostname: str
785
  @param hostname: the hostname to be added
786
  @type aliases: list
787
  @param aliases: the list of aliases to add for the hostname
788

789
  """
790
  # Ensure aliases are unique
791
  aliases = UniqueSequence([hostname] + aliases)[1:]
792

    
793
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
794
  try:
795
    out = os.fdopen(fd, 'w')
796
    try:
797
      f = open(file_name, 'r')
798
      try:
799
        written = False
800
        for line in f:
801
          fields = line.split()
802
          if fields and not fields[0].startswith('#') and ip == fields[0]:
803
            continue
804
          out.write(line)
805

    
806
        out.write("%s\t%s" % (ip, hostname))
807
        if aliases:
808
          out.write(" %s" % ' '.join(aliases))
809
        out.write('\n')
810

    
811
        out.flush()
812
        os.fsync(out)
813
        os.rename(tmpname, file_name)
814
      finally:
815
        f.close()
816
    finally:
817
      out.close()
818
  except:
819
    RemoveFile(tmpname)
820
    raise
821

    
822

    
823
def AddHostToEtcHosts(hostname):
824
  """Wrapper around SetEtcHostsEntry.
825

826
  @type hostname: str
827
  @param hostname: a hostname that will be resolved and added to
828
      L{constants.ETC_HOSTS}
829

830
  """
831
  hi = HostInfo(name=hostname)
832
  SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
833

    
834

    
835
def RemoveEtcHostsEntry(file_name, hostname):
836
  """Removes a hostname from /etc/hosts.
837

838
  IP addresses without names are removed from the file.
839

840
  @type file_name: str
841
  @param file_name: path to the file to modify (usually C{/etc/hosts})
842
  @type hostname: str
843
  @param hostname: the hostname to be removed
844

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

    
863
          out.write(line)
864

    
865
        out.flush()
866
        os.fsync(out)
867
        os.rename(tmpname, file_name)
868
      finally:
869
        f.close()
870
    finally:
871
      out.close()
872
  except:
873
    RemoveFile(tmpname)
874
    raise
875

    
876

    
877
def RemoveHostFromEtcHosts(hostname):
878
  """Wrapper around RemoveEtcHostsEntry.
879

880
  @type hostname: str
881
  @param hostname: hostname that will be resolved and its
882
      full and shot name will be removed from
883
      L{constants.ETC_HOSTS}
884

885
  """
886
  hi = HostInfo(name=hostname)
887
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
888
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
889

    
890

    
891
def CreateBackup(file_name):
892
  """Creates a backup of a file.
893

894
  @type file_name: str
895
  @param file_name: file to be backed up
896
  @rtype: str
897
  @return: the path to the newly created backup
898
  @raise errors.ProgrammerError: for invalid file names
899

900
  """
901
  if not os.path.isfile(file_name):
902
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
903
                                file_name)
904

    
905
  prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
906
  dir_name = os.path.dirname(file_name)
907

    
908
  fsrc = open(file_name, 'rb')
909
  try:
910
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
911
    fdst = os.fdopen(fd, 'wb')
912
    try:
913
      shutil.copyfileobj(fsrc, fdst)
914
    finally:
915
      fdst.close()
916
  finally:
917
    fsrc.close()
918

    
919
  return backup_name
920

    
921

    
922
def ShellQuote(value):
923
  """Quotes shell argument according to POSIX.
924

925
  @type value: str
926
  @param value: the argument to be quoted
927
  @rtype: str
928
  @return: the quoted value
929

930
  """
931
  if _re_shell_unquoted.match(value):
932
    return value
933
  else:
934
    return "'%s'" % value.replace("'", "'\\''")
935

    
936

    
937
def ShellQuoteArgs(args):
938
  """Quotes a list of shell arguments.
939

940
  @type args: list
941
  @param args: list of arguments to be quoted
942
  @rtype: str
943
  @return: the quoted arguments concatenaned with spaces
944

945
  """
946
  return ' '.join([ShellQuote(i) for i in args])
947

    
948

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

952
  Check if the given IP is reachable by doing attempting a TCP connect
953
  to it.
954

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

969
  """
970
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
971

    
972
  sucess = False
973

    
974
  if source is not None:
975
    try:
976
      sock.bind((source, 0))
977
    except socket.error, (errcode, errstring):
978
      if errcode == errno.EADDRNOTAVAIL:
979
        success = False
980

    
981
  sock.settimeout(timeout)
982

    
983
  try:
984
    sock.connect((target, port))
985
    sock.close()
986
    success = True
987
  except socket.timeout:
988
    success = False
989
  except socket.error, (errcode, errstring):
990
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
991

    
992
  return success
993

    
994

    
995
def OwnIpAddress(address):
996
  """Check if the current host has the the given IP address.
997

998
  Currently this is done by TCP-pinging the address from the loopback
999
  address.
1000

1001
  @type address: string
1002
  @param address: the addres to check
1003
  @rtype: bool
1004
  @return: True if we own the address
1005

1006
  """
1007
  return TcpPing(address, constants.DEFAULT_NODED_PORT,
1008
                 source=constants.LOCALHOST_IP_ADDRESS)
1009

    
1010

    
1011
def ListVisibleFiles(path):
1012
  """Returns a list of visible files in a directory.
1013

1014
  @type path: str
1015
  @param path: the directory to enumerate
1016
  @rtype: list
1017
  @return: the list of all files not starting with a dot
1018

1019
  """
1020
  files = [i for i in os.listdir(path) if not i.startswith(".")]
1021
  files.sort()
1022
  return files
1023

    
1024

    
1025
def GetHomeDir(user, default=None):
1026
  """Try to get the homedir of the given user.
1027

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

1032
  """
1033
  try:
1034
    if isinstance(user, basestring):
1035
      result = pwd.getpwnam(user)
1036
    elif isinstance(user, (int, long)):
1037
      result = pwd.getpwuid(user)
1038
    else:
1039
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1040
                                   type(user))
1041
  except KeyError:
1042
    return default
1043
  return result.pw_dir
1044

    
1045

    
1046
def NewUUID():
1047
  """Returns a random UUID.
1048

1049
  @note: This is a Linux-specific method as it uses the /proc
1050
      filesystem.
1051
  @rtype: str
1052

1053
  """
1054
  f = open("/proc/sys/kernel/random/uuid", "r")
1055
  try:
1056
    return f.read(128).rstrip("\n")
1057
  finally:
1058
    f.close()
1059

    
1060

    
1061
def GenerateSecret():
1062
  """Generates a random secret.
1063

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

1067
  @rtype: str
1068
  @return: a sha1 hexdigest of a block of 64 random bytes
1069

1070
  """
1071
  return sha.new(os.urandom(64)).hexdigest()
1072

    
1073

    
1074
def ReadFile(file_name, size=None):
1075
  """Reads a file.
1076

1077
  @type size: None or int
1078
  @param size: Read at most size bytes
1079
  @rtype: str
1080
  @return: the (possibly partial) conent of the file
1081

1082
  """
1083
  f = open(file_name, "r")
1084
  try:
1085
    if size is None:
1086
      return f.read()
1087
    else:
1088
      return f.read(size)
1089
  finally:
1090
    f.close()
1091

    
1092

    
1093
def WriteFile(file_name, fn=None, data=None,
1094
              mode=None, uid=-1, gid=-1,
1095
              atime=None, mtime=None, close=True,
1096
              dry_run=False, backup=False,
1097
              prewrite=None, postwrite=None):
1098
  """(Over)write a file atomically.
1099

1100
  The file_name and either fn (a function taking one argument, the
1101
  file descriptor, and which should write the data to it) or data (the
1102
  contents of the file) must be passed. The other arguments are
1103
  optional and allow setting the file mode, owner and group, and the
1104
  mtime/atime of the file.
1105

1106
  If the function doesn't raise an exception, it has succeeded and the
1107
  target file has the new contents. If the file has raised an
1108
  exception, an existing target file should be unmodified and the
1109
  temporary file should be removed.
1110

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

1135
  @rtype: None or int
1136
  @return: None if the 'close' parameter evaluates to True,
1137
      otherwise the file descriptor
1138

1139
  @raise errors.ProgrammerError: if an of the arguments are not valid
1140

1141
  """
1142
  if not os.path.isabs(file_name):
1143
    raise errors.ProgrammerError("Path passed to WriteFile is not"
1144
                                 " absolute: '%s'" % file_name)
1145

    
1146
  if [fn, data].count(None) != 1:
1147
    raise errors.ProgrammerError("fn or data required")
1148

    
1149
  if [atime, mtime].count(None) == 1:
1150
    raise errors.ProgrammerError("Both atime and mtime must be either"
1151
                                 " set or None")
1152

    
1153
  if backup and not dry_run and os.path.isfile(file_name):
1154
    CreateBackup(file_name)
1155

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

    
1186
  return result
1187

    
1188

    
1189
def FirstFree(seq, base=0):
1190
  """Returns the first non-existing integer from seq.
1191

1192
  The seq argument should be a sorted list of positive integers. The
1193
  first time the index of an element is smaller than the element
1194
  value, the index will be returned.
1195

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

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

1201
  @type seq: sequence
1202
  @param seq: the sequence to be analyzed.
1203
  @type base: int
1204
  @param base: use this value as the base index of the sequence
1205
  @rtype: int
1206
  @return: the first non-used index in the sequence
1207

1208
  """
1209
  for idx, elem in enumerate(seq):
1210
    assert elem >= base, "Passed element is higher than base offset"
1211
    if elem > idx + base:
1212
      # idx is not used
1213
      return idx + base
1214
  return None
1215

    
1216

    
1217
def all(seq, pred=bool):
1218
  "Returns True if pred(x) is True for every element in the iterable"
1219
  for elem in itertools.ifilterfalse(pred, seq):
1220
    return False
1221
  return True
1222

    
1223

    
1224
def any(seq, pred=bool):
1225
  "Returns True if pred(x) is True for at least one element in the iterable"
1226
  for elem in itertools.ifilter(pred, seq):
1227
    return True
1228
  return False
1229

    
1230

    
1231
def UniqueSequence(seq):
1232
  """Returns a list with unique elements.
1233

1234
  Element order is preserved.
1235

1236
  @type seq: sequence
1237
  @param seq: the sequence with the source elementes
1238
  @rtype: list
1239
  @return: list of unique elements from seq
1240

1241
  """
1242
  seen = set()
1243
  return [i for i in seq if i not in seen and not seen.add(i)]
1244

    
1245

    
1246
def IsValidMac(mac):
1247
  """Predicate to check if a MAC address is valid.
1248

1249
  Checks wether the supplied MAC address is formally correct, only
1250
  accepts colon separated format.
1251

1252
  @type mac: str
1253
  @param mac: the MAC to be validated
1254
  @rtype: boolean
1255
  @return: True is the MAC seems valid
1256

1257
  """
1258
  mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$")
1259
  return mac_check.match(mac) is not None
1260

    
1261

    
1262
def TestDelay(duration):
1263
  """Sleep for a fixed amount of time.
1264

1265
  @type duration: float
1266
  @param duration: the sleep duration
1267
  @rtype: boolean
1268
  @return: False for negative value, True otherwise
1269

1270
  """
1271
  if duration < 0:
1272
    return False
1273
  time.sleep(duration)
1274
  return True
1275

    
1276

    
1277
def Daemonize(logfile, noclose_fds=None):
1278
  """Daemonize the current process.
1279

1280
  This detaches the current process from the controlling terminal and
1281
  runs it in the background as a daemon.
1282

1283
  @type logfile: str
1284
  @param logfile: the logfile to which we should redirect stdout/stderr
1285
  @type noclose_fds: list or None
1286
  @param noclose_fds: if given, it denotes a list of file descriptor
1287
      that should not be closed
1288
  @rtype: int
1289
  @returns: the value zero
1290

1291
  """
1292
  UMASK = 077
1293
  WORKDIR = "/"
1294
  # Default maximum for the number of available file descriptors.
1295
  if 'SC_OPEN_MAX' in os.sysconf_names:
1296
    try:
1297
      MAXFD = os.sysconf('SC_OPEN_MAX')
1298
      if MAXFD < 0:
1299
        MAXFD = 1024
1300
    except OSError:
1301
      MAXFD = 1024
1302
  else:
1303
    MAXFD = 1024
1304

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

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

    
1337

    
1338
def DaemonPidFileName(name):
1339
  """Compute a ganeti pid file absolute path
1340

1341
  @type name: str
1342
  @param name: the daemon name
1343
  @rtype: str
1344
  @return: the full path to the pidfile corresponding to the given
1345
      daemon name
1346

1347
  """
1348
  return os.path.join(constants.RUN_GANETI_DIR, "%s.pid" % name)
1349

    
1350

    
1351
def WritePidFile(name):
1352
  """Write the current process pidfile.
1353

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

1356
  @type name: str
1357
  @param name: the daemon name to use
1358
  @raise errors.GenericError: if the pid file already exists and
1359
      points to a live process
1360

1361
  """
1362
  pid = os.getpid()
1363
  pidfilename = DaemonPidFileName(name)
1364
  if IsProcessAlive(ReadPidFile(pidfilename)):
1365
    raise errors.GenericError("%s contains a live process" % pidfilename)
1366

    
1367
  WriteFile(pidfilename, data="%d\n" % pid)
1368

    
1369

    
1370
def RemovePidFile(name):
1371
  """Remove the current process pidfile.
1372

1373
  Any errors are ignored.
1374

1375
  @type name: str
1376
  @param name: the daemon name used to derive the pidfile name
1377

1378
  """
1379
  pid = os.getpid()
1380
  pidfilename = DaemonPidFileName(name)
1381
  # TODO: we could check here that the file contains our pid
1382
  try:
1383
    RemoveFile(pidfilename)
1384
  except:
1385
    pass
1386

    
1387

    
1388
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30):
1389
  """Kill a process given by its pid.
1390

1391
  @type pid: int
1392
  @param pid: The PID to terminate.
1393
  @type signal_: int
1394
  @param signal_: The signal to send, by default SIGTERM
1395
  @type timeout: int
1396
  @param timeout: The timeout after which, if the process is still alive,
1397
                  a SIGKILL will be sent. If not positive, no such checking
1398
                  will be done
1399

1400
  """
1401
  if pid <= 0:
1402
    # kill with pid=0 == suicide
1403
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1404

    
1405
  if not IsProcessAlive(pid):
1406
    return
1407
  os.kill(pid, signal_)
1408
  if timeout <= 0:
1409
    return
1410
  end = time.time() + timeout
1411
  while time.time() < end and IsProcessAlive(pid):
1412
    time.sleep(0.1)
1413
  if IsProcessAlive(pid):
1414
    os.kill(pid, signal.SIGKILL)
1415

    
1416

    
1417
def FindFile(name, search_path, test=os.path.exists):
1418
  """Look for a filesystem object in a given path.
1419

1420
  This is an abstract method to search for filesystem object (files,
1421
  dirs) under a given search path.
1422

1423
  @type name: str
1424
  @param name: the name to look for
1425
  @type search_path: str
1426
  @param search_path: location to start at
1427
  @type test: callable
1428
  @param test: a function taking one argument that should return True
1429
      if the a given object is valid; the default value is
1430
      os.path.exists, causing only existing files to be returned
1431
  @rtype: str or None
1432
  @return: full path to the object if found, None otherwise
1433

1434
  """
1435
  for dir_name in search_path:
1436
    item_name = os.path.sep.join([dir_name, name])
1437
    if test(item_name):
1438
      return item_name
1439
  return None
1440

    
1441

    
1442
def CheckVolumeGroupSize(vglist, vgname, minsize):
1443
  """Checks if the volume group list is valid.
1444

1445
  The function will check if a given volume group is in the list of
1446
  volume groups and has a minimum size.
1447

1448
  @type vglist: dict
1449
  @param vglist: dictionary of volume group names and their size
1450
  @type vgname: str
1451
  @param vgname: the volume group we should check
1452
  @type minsize: int
1453
  @param minsize: the minimum size we accept
1454
  @rtype: None or str
1455
  @return: None for success, otherwise the error message
1456

1457
  """
1458
  vgsize = vglist.get(vgname, None)
1459
  if vgsize is None:
1460
    return "volume group '%s' missing" % vgname
1461
  elif vgsize < minsize:
1462
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1463
            (vgname, minsize, vgsize))
1464
  return None
1465

    
1466

    
1467
def SplitTime(value):
1468
  """Splits time as floating point number into a tuple.
1469

1470
  @param value: Time in seconds
1471
  @type value: int or float
1472
  @return: Tuple containing (seconds, microseconds)
1473

1474
  """
1475
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1476

    
1477
  assert 0 <= seconds, \
1478
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1479
  assert 0 <= microseconds <= 999999, \
1480
    "Microseconds must be 0-999999, but are %s" % microseconds
1481

    
1482
  return (int(seconds), int(microseconds))
1483

    
1484

    
1485
def MergeTime(timetuple):
1486
  """Merges a tuple into time as a floating point number.
1487

1488
  @param timetuple: Time as tuple, (seconds, microseconds)
1489
  @type timetuple: tuple
1490
  @return: Time as a floating point number expressed in seconds
1491

1492
  """
1493
  (seconds, microseconds) = timetuple
1494

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

    
1500
  return float(seconds) + (float(microseconds) * 0.000001)
1501

    
1502

    
1503
def GetNodeDaemonPort():
1504
  """Get the node daemon port for this cluster.
1505

1506
  Note that this routine does not read a ganeti-specific file, but
1507
  instead uses C{socket.getservbyname} to allow pre-customization of
1508
  this parameter outside of Ganeti.
1509

1510
  @rtype: int
1511

1512
  """
1513
  try:
1514
    port = socket.getservbyname("ganeti-noded", "tcp")
1515
  except socket.error:
1516
    port = constants.DEFAULT_NODED_PORT
1517

    
1518
  return port
1519

    
1520

    
1521
def GetNodeDaemonPassword():
1522
  """Get the node password for the cluster.
1523

1524
  @rtype: str
1525

1526
  """
1527
  return ReadFile(constants.CLUSTER_PASSWORD_FILE)
1528

    
1529

    
1530
def SetupLogging(logfile, debug=False, stderr_logging=False, program=""):
1531
  """Configures the logging module.
1532

1533
  @type logfile: str
1534
  @param logfile: the filename to which we should log
1535
  @type debug: boolean
1536
  @param debug: whether to enable debug messages too or
1537
      only those at C{INFO} and above level
1538
  @type stderr_logging: boolean
1539
  @param stderr_logging: whether we should also log to the standard error
1540
  @type program: str
1541
  @param program: the name under which we should log messages
1542
  @raise EnvironmentError: if we can't open the log file and
1543
      stderr logging is disabled
1544

1545
  """
1546
  fmt = "%(asctime)s: " + program + " "
1547
  if debug:
1548
    fmt += ("pid=%(process)d/%(threadName)s %(levelname)s"
1549
           " %(module)s:%(lineno)s %(message)s")
1550
  else:
1551
    fmt += "pid=%(process)d %(levelname)s %(message)s"
1552
  formatter = logging.Formatter(fmt)
1553

    
1554
  root_logger = logging.getLogger("")
1555
  root_logger.setLevel(logging.NOTSET)
1556

    
1557
  if stderr_logging:
1558
    stderr_handler = logging.StreamHandler()
1559
    stderr_handler.setFormatter(formatter)
1560
    if debug:
1561
      stderr_handler.setLevel(logging.NOTSET)
1562
    else:
1563
      stderr_handler.setLevel(logging.CRITICAL)
1564
    root_logger.addHandler(stderr_handler)
1565

    
1566
  # this can fail, if the logging directories are not setup or we have
1567
  # a permisssion problem; in this case, it's best to log but ignore
1568
  # the error if stderr_logging is True, and if false we re-raise the
1569
  # exception since otherwise we could run but without any logs at all
1570
  try:
1571
    logfile_handler = logging.FileHandler(logfile)
1572
    logfile_handler.setFormatter(formatter)
1573
    if debug:
1574
      logfile_handler.setLevel(logging.DEBUG)
1575
    else:
1576
      logfile_handler.setLevel(logging.INFO)
1577
    root_logger.addHandler(logfile_handler)
1578
  except EnvironmentError, err:
1579
    if stderr_logging:
1580
      logging.exception("Failed to enable logging to file '%s'", logfile)
1581
    else:
1582
      # we need to re-raise the exception
1583
      raise
1584

    
1585

    
1586
def LockedMethod(fn):
1587
  """Synchronized object access decorator.
1588

1589
  This decorator is intended to protect access to an object using the
1590
  object's own lock which is hardcoded to '_lock'.
1591

1592
  """
1593
  def _LockDebug(*args, **kwargs):
1594
    if debug_locks:
1595
      logging.debug(*args, **kwargs)
1596

    
1597
  def wrapper(self, *args, **kwargs):
1598
    assert hasattr(self, '_lock')
1599
    lock = self._lock
1600
    _LockDebug("Waiting for %s", lock)
1601
    lock.acquire()
1602
    try:
1603
      _LockDebug("Acquired %s", lock)
1604
      result = fn(self, *args, **kwargs)
1605
    finally:
1606
      _LockDebug("Releasing %s", lock)
1607
      lock.release()
1608
      _LockDebug("Released %s", lock)
1609
    return result
1610
  return wrapper
1611

    
1612

    
1613
def LockFile(fd):
1614
  """Locks a file using POSIX locks.
1615

1616
  @type fd: int
1617
  @param fd: the file descriptor we need to lock
1618

1619
  """
1620
  try:
1621
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
1622
  except IOError, err:
1623
    if err.errno == errno.EAGAIN:
1624
      raise errors.LockError("File already locked")
1625
    raise
1626

    
1627

    
1628
class FileLock(object):
1629
  """Utility class for file locks.
1630

1631
  """
1632
  def __init__(self, filename):
1633
    """Constructor for FileLock.
1634

1635
    This will open the file denoted by the I{filename} argument.
1636

1637
    @type filename: str
1638
    @param filename: path to the file to be locked
1639

1640
    """
1641
    self.filename = filename
1642
    self.fd = open(self.filename, "w")
1643

    
1644
  def __del__(self):
1645
    self.Close()
1646

    
1647
  def Close(self):
1648
    """Close the file and release the lock.
1649

1650
    """
1651
    if self.fd:
1652
      self.fd.close()
1653
      self.fd = None
1654

    
1655
  def _flock(self, flag, blocking, timeout, errmsg):
1656
    """Wrapper for fcntl.flock.
1657

1658
    @type flag: int
1659
    @param flag: operation flag
1660
    @type blocking: bool
1661
    @param blocking: whether the operation should be done in blocking mode.
1662
    @type timeout: None or float
1663
    @param timeout: for how long the operation should be retried (implies
1664
                    non-blocking mode).
1665
    @type errmsg: string
1666
    @param errmsg: error message in case operation fails.
1667

1668
    """
1669
    assert self.fd, "Lock was closed"
1670
    assert timeout is None or timeout >= 0, \
1671
      "If specified, timeout must be positive"
1672

    
1673
    if timeout is not None:
1674
      flag |= fcntl.LOCK_NB
1675
      timeout_end = time.time() + timeout
1676

    
1677
    # Blocking doesn't have effect with timeout
1678
    elif not blocking:
1679
      flag |= fcntl.LOCK_NB
1680
      timeout_end = None
1681

    
1682
    retry = True
1683
    while retry:
1684
      try:
1685
        fcntl.flock(self.fd, flag)
1686
        retry = False
1687
      except IOError, err:
1688
        if err.errno in (errno.EAGAIN, ):
1689
          if timeout_end is not None and time.time() < timeout_end:
1690
            # Wait before trying again
1691
            time.sleep(max(0.1, min(1.0, timeout)))
1692
          else:
1693
            raise errors.LockError(errmsg)
1694
        else:
1695
          logging.exception("fcntl.flock failed")
1696
          raise
1697

    
1698
  def Exclusive(self, blocking=False, timeout=None):
1699
    """Locks the file in exclusive mode.
1700

1701
    @type blocking: boolean
1702
    @param blocking: whether to block and wait until we
1703
        can lock the file or return immediately
1704
    @type timeout: int or None
1705
    @param timeout: if not None, the duration to wait for the lock
1706
        (in blocking mode)
1707

1708
    """
1709
    self._flock(fcntl.LOCK_EX, blocking, timeout,
1710
                "Failed to lock %s in exclusive mode" % self.filename)
1711

    
1712
  def Shared(self, blocking=False, timeout=None):
1713
    """Locks the file in shared mode.
1714

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

1722
    """
1723
    self._flock(fcntl.LOCK_SH, blocking, timeout,
1724
                "Failed to lock %s in shared mode" % self.filename)
1725

    
1726
  def Unlock(self, blocking=True, timeout=None):
1727
    """Unlocks the file.
1728

1729
    According to C{flock(2)}, unlocking can also be a nonblocking
1730
    operation::
1731

1732
      To make a non-blocking request, include LOCK_NB with any of the above
1733
      operations.
1734

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

1742
    """
1743
    self._flock(fcntl.LOCK_UN, blocking, timeout,
1744
                "Failed to unlock %s" % self.filename)
1745

    
1746

    
1747
class SignalHandler(object):
1748
  """Generic signal handler class.
1749

1750
  It automatically restores the original handler when deconstructed or
1751
  when L{Reset} is called. You can either pass your own handler
1752
  function in or query the L{called} attribute to detect whether the
1753
  signal was sent.
1754

1755
  @type signum: list
1756
  @ivar signum: the signals we handle
1757
  @type called: boolean
1758
  @ivar called: tracks whether any of the signals have been raised
1759

1760
  """
1761
  def __init__(self, signum):
1762
    """Constructs a new SignalHandler instance.
1763

1764
    @type signum: int or list of ints
1765
    @param signum: Single signal number or set of signal numbers
1766

1767
    """
1768
    if isinstance(signum, (int, long)):
1769
      self.signum = set([signum])
1770
    else:
1771
      self.signum = set(signum)
1772

    
1773
    self.called = False
1774

    
1775
    self._previous = {}
1776
    try:
1777
      for signum in self.signum:
1778
        # Setup handler
1779
        prev_handler = signal.signal(signum, self._HandleSignal)
1780
        try:
1781
          self._previous[signum] = prev_handler
1782
        except:
1783
          # Restore previous handler
1784
          signal.signal(signum, prev_handler)
1785
          raise
1786
    except:
1787
      # Reset all handlers
1788
      self.Reset()
1789
      # Here we have a race condition: a handler may have already been called,
1790
      # but there's not much we can do about it at this point.
1791
      raise
1792

    
1793
  def __del__(self):
1794
    self.Reset()
1795

    
1796
  def Reset(self):
1797
    """Restore previous handler.
1798

1799
    This will reset all the signals to their previous handlers.
1800

1801
    """
1802
    for signum, prev_handler in self._previous.items():
1803
      signal.signal(signum, prev_handler)
1804
      # If successful, remove from dict
1805
      del self._previous[signum]
1806

    
1807
  def Clear(self):
1808
    """Unsets the L{called} flag.
1809

1810
    This function can be used in case a signal may arrive several times.
1811

1812
    """
1813
    self.called = False
1814

    
1815
  def _HandleSignal(self, signum, frame):
1816
    """Actual signal handling function.
1817

1818
    """
1819
    # This is not nice and not absolutely atomic, but it appears to be the only
1820
    # solution in Python -- there are no atomic types.
1821
    self.called = True