Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ bf988c29

History | View | Annotate | Download (50.3 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
    try:
207
      pollresult = poller.poll()
208
    except EnvironmentError, eerr:
209
      if eerr.errno == errno.EINTR:
210
        continue
211
      raise
212
    except select.error, serr:
213
      if serr[0] == errno.EINTR:
214
        continue
215
      raise
216

    
217
    for fd, event in pollresult:
218
      if event & select.POLLIN or event & select.POLLPRI:
219
        data = fdmap[fd][1].read()
220
        # no data from read signifies EOF (the same as POLLHUP)
221
        if not data:
222
          poller.unregister(fd)
223
          del fdmap[fd]
224
          continue
225
        fdmap[fd][0].write(data)
226
      if (event & select.POLLNVAL or event & select.POLLHUP or
227
          event & select.POLLERR):
228
        poller.unregister(fd)
229
        del fdmap[fd]
230

    
231
  out = out.getvalue()
232
  err = err.getvalue()
233

    
234
  status = child.wait()
235
  return out, err, status
236

    
237

    
238
def _RunCmdFile(cmd, env, via_shell, output, cwd):
239
  """Run a command and save its output to a file.
240

241
  @type  cmd: string or list
242
  @param cmd: Command to run
243
  @type env: dict
244
  @param env: The environment to use
245
  @type via_shell: bool
246
  @param via_shell: if we should run via the shell
247
  @type output: str
248
  @param output: the filename in which to save the output
249
  @type cwd: string
250
  @param cwd: the working directory for the program
251
  @rtype: int
252
  @return: the exit status
253

254
  """
255
  fh = open(output, "a")
256
  try:
257
    child = subprocess.Popen(cmd, shell=via_shell,
258
                             stderr=subprocess.STDOUT,
259
                             stdout=fh,
260
                             stdin=subprocess.PIPE,
261
                             close_fds=True, env=env,
262
                             cwd=cwd)
263

    
264
    child.stdin.close()
265
    status = child.wait()
266
  finally:
267
    fh.close()
268
  return status
269

    
270

    
271
def RemoveFile(filename):
272
  """Remove a file ignoring some errors.
273

274
  Remove a file, ignoring non-existing ones or directories. Other
275
  errors are passed.
276

277
  @type filename: str
278
  @param filename: the file to be removed
279

280
  """
281
  try:
282
    os.unlink(filename)
283
  except OSError, err:
284
    if err.errno not in (errno.ENOENT, errno.EISDIR):
285
      raise
286

    
287

    
288
def _FingerprintFile(filename):
289
  """Compute the fingerprint of a file.
290

291
  If the file does not exist, a None will be returned
292
  instead.
293

294
  @type filename: str
295
  @param filename: the filename to checksum
296
  @rtype: str
297
  @return: the hex digest of the sha checksum of the contents
298
      of the file
299

300
  """
301
  if not (os.path.exists(filename) and os.path.isfile(filename)):
302
    return None
303

    
304
  f = open(filename)
305

    
306
  fp = sha.sha()
307
  while True:
308
    data = f.read(4096)
309
    if not data:
310
      break
311

    
312
    fp.update(data)
313

    
314
  return fp.hexdigest()
315

    
316

    
317
def FingerprintFiles(files):
318
  """Compute fingerprints for a list of files.
319

320
  @type files: list
321
  @param files: the list of filename to fingerprint
322
  @rtype: dict
323
  @return: a dictionary filename: fingerprint, holding only
324
      existing files
325

326
  """
327
  ret = {}
328

    
329
  for filename in files:
330
    cksum = _FingerprintFile(filename)
331
    if cksum:
332
      ret[filename] = cksum
333

    
334
  return ret
335

    
336

    
337
def CheckDict(target, template, logname=None):
338
  """Ensure a dictionary has a required set of keys.
339

340
  For the given dictionaries I{target} and I{template}, ensure
341
  I{target} has all the keys from I{template}. Missing keys are added
342
  with values from template.
343

344
  @type target: dict
345
  @param target: the dictionary to update
346
  @type template: dict
347
  @param template: the dictionary holding the default values
348
  @type logname: str or None
349
  @param logname: if not None, causes the missing keys to be
350
      logged with this name
351

352
  """
353
  missing = []
354
  for k in template:
355
    if k not in target:
356
      missing.append(k)
357
      target[k] = template[k]
358

    
359
  if missing and logname:
360
    logging.warning('%s missing keys %s', logname, ', '.join(missing))
361

    
362

    
363
def IsProcessAlive(pid):
364
  """Check if a given pid exists on the system.
365

366
  @note: zombie status is not handled, so zombie processes
367
      will be returned as alive
368
  @type pid: int
369
  @param pid: the process ID to check
370
  @rtype: boolean
371
  @return: True if the process exists
372

373
  """
374
  if pid <= 0:
375
    return False
376

    
377
  try:
378
    os.stat("/proc/%d/status" % pid)
379
    return True
380
  except EnvironmentError, err:
381
    if err.errno in (errno.ENOENT, errno.ENOTDIR):
382
      return False
383
    raise
384

    
385

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

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

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

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

    
409
  return pid
410

    
411

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

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

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

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

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

    
438

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
491
    return result
492

    
493

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

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

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

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

    
517
    retval[name] = size
518

    
519
  return retval
520

    
521

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

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

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

    
533

    
534
def CheckBEParams(beparams):
535
  """Checks whether the user-supplied be-params are valid,
536
  and converts them from string format where appropriate.
537

538
  @type beparams: dict
539
  @param beparams: new params dict
540

541
  """
542
  if beparams:
543
    for item in beparams:
544
      if item not in constants.BES_PARAMETERS:
545
        raise errors.OpPrereqError("Unknown backend parameter %s" % item)
546
      if item in (constants.BE_MEMORY, constants.BE_VCPUS):
547
        val = beparams[item]
548
        if val != constants.VALUE_DEFAULT:
549
          try:
550
            val = int(val)
551
          except ValueError, err:
552
            raise errors.OpPrereqError("Invalid %s size: %s" % (item, str(err)))
553
          beparams[item] = val
554
      if item in (constants.BE_AUTO_BALANCE):
555
        val = beparams[item]
556
        if not isinstance(val, bool):
557
          if val == constants.VALUE_TRUE:
558
            beparams[item] = True
559
          elif val == constants.VALUE_FALSE:
560
            beparams[item] = False
561
          else:
562
            raise errors.OpPrereqError("Invalid %s value: %s" % (item, val))
563

    
564

    
565
def NiceSort(name_list):
566
  """Sort a list of strings based on digit and non-digit groupings.
567

568
  Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
569
  will sort the list in the logical order C{['a1', 'a2', 'a10',
570
  'a11']}.
571

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

576
  @type name_list: list
577
  @param name_list: the names to be sorted
578
  @rtype: list
579
  @return: a copy of the name list sorted with our algorithm
580

581
  """
582
  _SORTER_BASE = "(\D+|\d+)"
583
  _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
584
                                                  _SORTER_BASE, _SORTER_BASE,
585
                                                  _SORTER_BASE, _SORTER_BASE,
586
                                                  _SORTER_BASE, _SORTER_BASE)
587
  _SORTER_RE = re.compile(_SORTER_FULL)
588
  _SORTER_NODIGIT = re.compile("^\D*$")
589
  def _TryInt(val):
590
    """Attempts to convert a variable to integer."""
591
    if val is None or _SORTER_NODIGIT.match(val):
592
      return val
593
    rval = int(val)
594
    return rval
595

    
596
  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
597
             for name in name_list]
598
  to_sort.sort()
599
  return [tup[1] for tup in to_sort]
600

    
601

    
602
def TryConvert(fn, val):
603
  """Try to convert a value ignoring errors.
604

605
  This function tries to apply function I{fn} to I{val}. If no
606
  C{ValueError} or C{TypeError} exceptions are raised, it will return
607
  the result, else it will return the original value. Any other
608
  exceptions are propagated to the caller.
609

610
  @type fn: callable
611
  @param fn: function to apply to the value
612
  @param val: the value to be converted
613
  @return: The converted value if the conversion was successful,
614
      otherwise the original value.
615

616
  """
617
  try:
618
    nv = fn(val)
619
  except (ValueError, TypeError), err:
620
    nv = val
621
  return nv
622

    
623

    
624
def IsValidIP(ip):
625
  """Verifies the syntax of an IPv4 address.
626

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

630
  @type ip: str
631
  @param ip: the address to be checked
632
  @rtype: a regular expression match object
633
  @return: a regular epression match object, or None if the
634
      address is not valid
635

636
  """
637
  unit = "(0|[1-9]\d{0,2})"
638
  #TODO: convert and return only boolean
639
  return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
640

    
641

    
642
def IsValidShellParam(word):
643
  """Verifies is the given word is safe from the shell's p.o.v.
644

645
  This means that we can pass this to a command via the shell and be
646
  sure that it doesn't alter the command line and is passed as such to
647
  the actual command.
648

649
  Note that we are overly restrictive here, in order to be on the safe
650
  side.
651

652
  @type word: str
653
  @param word: the word to check
654
  @rtype: boolean
655
  @return: True if the word is 'safe'
656

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

    
660

    
661
def BuildShellCmd(template, *args):
662
  """Build a safe shell command line from the given arguments.
663

664
  This function will check all arguments in the args list so that they
665
  are valid shell parameters (i.e. they don't contain shell
666
  metacharaters). If everything is ok, it will return the result of
667
  template % args.
668

669
  @type template: str
670
  @param template: the string holding the template for the
671
      string formatting
672
  @rtype: str
673
  @return: the expanded command line
674

675
  """
676
  for word in args:
677
    if not IsValidShellParam(word):
678
      raise errors.ProgrammerError("Shell argument '%s' contains"
679
                                   " invalid characters" % word)
680
  return template % args
681

    
682

    
683
def FormatUnit(value, units):
684
  """Formats an incoming number of MiB with the appropriate unit.
685

686
  @type value: int
687
  @param value: integer representing the value in MiB (1048576)
688
  @type units: char
689
  @param units: the type of formatting we should do:
690
      - 'h' for automatic scaling
691
      - 'm' for MiBs
692
      - 'g' for GiBs
693
      - 't' for TiBs
694
  @rtype: str
695
  @return: the formatted value (with suffix)
696

697
  """
698
  if units not in ('m', 'g', 't', 'h'):
699
    raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
700

    
701
  suffix = ''
702

    
703
  if units == 'm' or (units == 'h' and value < 1024):
704
    if units == 'h':
705
      suffix = 'M'
706
    return "%d%s" % (round(value, 0), suffix)
707

    
708
  elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
709
    if units == 'h':
710
      suffix = 'G'
711
    return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
712

    
713
  else:
714
    if units == 'h':
715
      suffix = 'T'
716
    return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
717

    
718

    
719
def ParseUnit(input_string):
720
  """Tries to extract number and scale from the given string.
721

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

726
  """
727
  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', input_string)
728
  if not m:
729
    raise errors.UnitParseError("Invalid format")
730

    
731
  value = float(m.groups()[0])
732

    
733
  unit = m.groups()[1]
734
  if unit:
735
    lcunit = unit.lower()
736
  else:
737
    lcunit = 'm'
738

    
739
  if lcunit in ('m', 'mb', 'mib'):
740
    # Value already in MiB
741
    pass
742

    
743
  elif lcunit in ('g', 'gb', 'gib'):
744
    value *= 1024
745

    
746
  elif lcunit in ('t', 'tb', 'tib'):
747
    value *= 1024 * 1024
748

    
749
  else:
750
    raise errors.UnitParseError("Unknown unit: %s" % unit)
751

    
752
  # Make sure we round up
753
  if int(value) < value:
754
    value += 1
755

    
756
  # Round up to the next multiple of 4
757
  value = int(value)
758
  if value % 4:
759
    value += 4 - value % 4
760

    
761
  return value
762

    
763

    
764
def AddAuthorizedKey(file_name, key):
765
  """Adds an SSH public key to an authorized_keys file.
766

767
  @type file_name: str
768
  @param file_name: path to authorized_keys file
769
  @type key: str
770
  @param key: string containing key
771

772
  """
773
  key_fields = key.split()
774

    
775
  f = open(file_name, 'a+')
776
  try:
777
    nl = True
778
    for line in f:
779
      # Ignore whitespace changes
780
      if line.split() == key_fields:
781
        break
782
      nl = line.endswith('\n')
783
    else:
784
      if not nl:
785
        f.write("\n")
786
      f.write(key.rstrip('\r\n'))
787
      f.write("\n")
788
      f.flush()
789
  finally:
790
    f.close()
791

    
792

    
793
def RemoveAuthorizedKey(file_name, key):
794
  """Removes an SSH public key from an authorized_keys file.
795

796
  @type file_name: str
797
  @param file_name: path to authorized_keys file
798
  @type key: str
799
  @param key: string containing key
800

801
  """
802
  key_fields = key.split()
803

    
804
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
805
  try:
806
    out = os.fdopen(fd, 'w')
807
    try:
808
      f = open(file_name, 'r')
809
      try:
810
        for line in f:
811
          # Ignore whitespace changes while comparing lines
812
          if line.split() != key_fields:
813
            out.write(line)
814

    
815
        out.flush()
816
        os.rename(tmpname, file_name)
817
      finally:
818
        f.close()
819
    finally:
820
      out.close()
821
  except:
822
    RemoveFile(tmpname)
823
    raise
824

    
825

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

829
  @type file_name: str
830
  @param file_name: path to the file to modify (usually C{/etc/hosts})
831
  @type ip: str
832
  @param ip: the IP address
833
  @type hostname: str
834
  @param hostname: the hostname to be added
835
  @type aliases: list
836
  @param aliases: the list of aliases to add for the hostname
837

838
  """
839
  # Ensure aliases are unique
840
  aliases = UniqueSequence([hostname] + aliases)[1:]
841

    
842
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
843
  try:
844
    out = os.fdopen(fd, 'w')
845
    try:
846
      f = open(file_name, 'r')
847
      try:
848
        for line in f:
849
          fields = line.split()
850
          if fields and not fields[0].startswith('#') and ip == fields[0]:
851
            continue
852
          out.write(line)
853

    
854
        out.write("%s\t%s" % (ip, hostname))
855
        if aliases:
856
          out.write(" %s" % ' '.join(aliases))
857
        out.write('\n')
858

    
859
        out.flush()
860
        os.fsync(out)
861
        os.rename(tmpname, file_name)
862
      finally:
863
        f.close()
864
    finally:
865
      out.close()
866
  except:
867
    RemoveFile(tmpname)
868
    raise
869

    
870

    
871
def AddHostToEtcHosts(hostname):
872
  """Wrapper around SetEtcHostsEntry.
873

874
  @type hostname: str
875
  @param hostname: a hostname that will be resolved and added to
876
      L{constants.ETC_HOSTS}
877

878
  """
879
  hi = HostInfo(name=hostname)
880
  SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
881

    
882

    
883
def RemoveEtcHostsEntry(file_name, hostname):
884
  """Removes a hostname from /etc/hosts.
885

886
  IP addresses without names are removed from the file.
887

888
  @type file_name: str
889
  @param file_name: path to the file to modify (usually C{/etc/hosts})
890
  @type hostname: str
891
  @param hostname: the hostname to be removed
892

893
  """
894
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
895
  try:
896
    out = os.fdopen(fd, 'w')
897
    try:
898
      f = open(file_name, 'r')
899
      try:
900
        for line in f:
901
          fields = line.split()
902
          if len(fields) > 1 and not fields[0].startswith('#'):
903
            names = fields[1:]
904
            if hostname in names:
905
              while hostname in names:
906
                names.remove(hostname)
907
              if names:
908
                out.write("%s %s\n" % (fields[0], ' '.join(names)))
909
              continue
910

    
911
          out.write(line)
912

    
913
        out.flush()
914
        os.fsync(out)
915
        os.rename(tmpname, file_name)
916
      finally:
917
        f.close()
918
    finally:
919
      out.close()
920
  except:
921
    RemoveFile(tmpname)
922
    raise
923

    
924

    
925
def RemoveHostFromEtcHosts(hostname):
926
  """Wrapper around RemoveEtcHostsEntry.
927

928
  @type hostname: str
929
  @param hostname: hostname that will be resolved and its
930
      full and shot name will be removed from
931
      L{constants.ETC_HOSTS}
932

933
  """
934
  hi = HostInfo(name=hostname)
935
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
936
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
937

    
938

    
939
def CreateBackup(file_name):
940
  """Creates a backup of a file.
941

942
  @type file_name: str
943
  @param file_name: file to be backed up
944
  @rtype: str
945
  @return: the path to the newly created backup
946
  @raise errors.ProgrammerError: for invalid file names
947

948
  """
949
  if not os.path.isfile(file_name):
950
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
951
                                file_name)
952

    
953
  prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
954
  dir_name = os.path.dirname(file_name)
955

    
956
  fsrc = open(file_name, 'rb')
957
  try:
958
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
959
    fdst = os.fdopen(fd, 'wb')
960
    try:
961
      shutil.copyfileobj(fsrc, fdst)
962
    finally:
963
      fdst.close()
964
  finally:
965
    fsrc.close()
966

    
967
  return backup_name
968

    
969

    
970
def ShellQuote(value):
971
  """Quotes shell argument according to POSIX.
972

973
  @type value: str
974
  @param value: the argument to be quoted
975
  @rtype: str
976
  @return: the quoted value
977

978
  """
979
  if _re_shell_unquoted.match(value):
980
    return value
981
  else:
982
    return "'%s'" % value.replace("'", "'\\''")
983

    
984

    
985
def ShellQuoteArgs(args):
986
  """Quotes a list of shell arguments.
987

988
  @type args: list
989
  @param args: list of arguments to be quoted
990
  @rtype: str
991
  @return: the quoted arguments concatenaned with spaces
992

993
  """
994
  return ' '.join([ShellQuote(i) for i in args])
995

    
996

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

1000
  Check if the given IP is reachable by doing attempting a TCP connect
1001
  to it.
1002

1003
  @type target: str
1004
  @param target: the IP or hostname to ping
1005
  @type port: int
1006
  @param port: the port to connect to
1007
  @type timeout: int
1008
  @param timeout: the timeout on the connection attemp
1009
  @type live_port_needed: boolean
1010
  @param live_port_needed: whether a closed port will cause the
1011
      function to return failure, as if there was a timeout
1012
  @type source: str or None
1013
  @param source: if specified, will cause the connect to be made
1014
      from this specific source address; failures to bind other
1015
      than C{EADDRNOTAVAIL} will be ignored
1016

1017
  """
1018
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1019

    
1020
  success = False
1021

    
1022
  if source is not None:
1023
    try:
1024
      sock.bind((source, 0))
1025
    except socket.error, (errcode, errstring):
1026
      if errcode == errno.EADDRNOTAVAIL:
1027
        success = False
1028

    
1029
  sock.settimeout(timeout)
1030

    
1031
  try:
1032
    sock.connect((target, port))
1033
    sock.close()
1034
    success = True
1035
  except socket.timeout:
1036
    success = False
1037
  except socket.error, (errcode, errstring):
1038
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
1039

    
1040
  return success
1041

    
1042

    
1043
def OwnIpAddress(address):
1044
  """Check if the current host has the the given IP address.
1045

1046
  Currently this is done by TCP-pinging the address from the loopback
1047
  address.
1048

1049
  @type address: string
1050
  @param address: the addres to check
1051
  @rtype: bool
1052
  @return: True if we own the address
1053

1054
  """
1055
  return TcpPing(address, constants.DEFAULT_NODED_PORT,
1056
                 source=constants.LOCALHOST_IP_ADDRESS)
1057

    
1058

    
1059
def ListVisibleFiles(path):
1060
  """Returns a list of visible files in a directory.
1061

1062
  @type path: str
1063
  @param path: the directory to enumerate
1064
  @rtype: list
1065
  @return: the list of all files not starting with a dot
1066

1067
  """
1068
  files = [i for i in os.listdir(path) if not i.startswith(".")]
1069
  files.sort()
1070
  return files
1071

    
1072

    
1073
def GetHomeDir(user, default=None):
1074
  """Try to get the homedir of the given user.
1075

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

1080
  """
1081
  try:
1082
    if isinstance(user, basestring):
1083
      result = pwd.getpwnam(user)
1084
    elif isinstance(user, (int, long)):
1085
      result = pwd.getpwuid(user)
1086
    else:
1087
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1088
                                   type(user))
1089
  except KeyError:
1090
    return default
1091
  return result.pw_dir
1092

    
1093

    
1094
def NewUUID():
1095
  """Returns a random UUID.
1096

1097
  @note: This is a Linux-specific method as it uses the /proc
1098
      filesystem.
1099
  @rtype: str
1100

1101
  """
1102
  f = open("/proc/sys/kernel/random/uuid", "r")
1103
  try:
1104
    return f.read(128).rstrip("\n")
1105
  finally:
1106
    f.close()
1107

    
1108

    
1109
def GenerateSecret():
1110
  """Generates a random secret.
1111

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

1115
  @rtype: str
1116
  @return: a sha1 hexdigest of a block of 64 random bytes
1117

1118
  """
1119
  return sha.new(os.urandom(64)).hexdigest()
1120

    
1121

    
1122
def ReadFile(file_name, size=None):
1123
  """Reads a file.
1124

1125
  @type size: None or int
1126
  @param size: Read at most size bytes
1127
  @rtype: str
1128
  @return: the (possibly partial) conent of the file
1129

1130
  """
1131
  f = open(file_name, "r")
1132
  try:
1133
    if size is None:
1134
      return f.read()
1135
    else:
1136
      return f.read(size)
1137
  finally:
1138
    f.close()
1139

    
1140

    
1141
def WriteFile(file_name, fn=None, data=None,
1142
              mode=None, uid=-1, gid=-1,
1143
              atime=None, mtime=None, close=True,
1144
              dry_run=False, backup=False,
1145
              prewrite=None, postwrite=None):
1146
  """(Over)write a file atomically.
1147

1148
  The file_name and either fn (a function taking one argument, the
1149
  file descriptor, and which should write the data to it) or data (the
1150
  contents of the file) must be passed. The other arguments are
1151
  optional and allow setting the file mode, owner and group, and the
1152
  mtime/atime of the file.
1153

1154
  If the function doesn't raise an exception, it has succeeded and the
1155
  target file has the new contents. If the file has raised an
1156
  exception, an existing target file should be unmodified and the
1157
  temporary file should be removed.
1158

1159
  @type file_name: str
1160
  @param file_name: the target filename
1161
  @type fn: callable
1162
  @param fn: content writing function, called with
1163
      file descriptor as parameter
1164
  @type data: sr
1165
  @param data: contents of the file
1166
  @type mode: int
1167
  @param mode: file mode
1168
  @type uid: int
1169
  @param uid: the owner of the file
1170
  @type gid: int
1171
  @param gid: the group of the file
1172
  @type atime: int
1173
  @param atime: a custom access time to be set on the file
1174
  @type mtime: int
1175
  @param mtime: a custom modification time to be set on the file
1176
  @type close: boolean
1177
  @param close: whether to close file after writing it
1178
  @type prewrite: callable
1179
  @param prewrite: function to be called before writing content
1180
  @type postwrite: callable
1181
  @param postwrite: function to be called after writing content
1182

1183
  @rtype: None or int
1184
  @return: None if the 'close' parameter evaluates to True,
1185
      otherwise the file descriptor
1186

1187
  @raise errors.ProgrammerError: if an of the arguments are not valid
1188

1189
  """
1190
  if not os.path.isabs(file_name):
1191
    raise errors.ProgrammerError("Path passed to WriteFile is not"
1192
                                 " absolute: '%s'" % file_name)
1193

    
1194
  if [fn, data].count(None) != 1:
1195
    raise errors.ProgrammerError("fn or data required")
1196

    
1197
  if [atime, mtime].count(None) == 1:
1198
    raise errors.ProgrammerError("Both atime and mtime must be either"
1199
                                 " set or None")
1200

    
1201
  if backup and not dry_run and os.path.isfile(file_name):
1202
    CreateBackup(file_name)
1203

    
1204
  dir_name, base_name = os.path.split(file_name)
1205
  fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
1206
  # here we need to make sure we remove the temp file, if any error
1207
  # leaves it in place
1208
  try:
1209
    if uid != -1 or gid != -1:
1210
      os.chown(new_name, uid, gid)
1211
    if mode:
1212
      os.chmod(new_name, mode)
1213
    if callable(prewrite):
1214
      prewrite(fd)
1215
    if data is not None:
1216
      os.write(fd, data)
1217
    else:
1218
      fn(fd)
1219
    if callable(postwrite):
1220
      postwrite(fd)
1221
    os.fsync(fd)
1222
    if atime is not None and mtime is not None:
1223
      os.utime(new_name, (atime, mtime))
1224
    if not dry_run:
1225
      os.rename(new_name, file_name)
1226
  finally:
1227
    if close:
1228
      os.close(fd)
1229
      result = None
1230
    else:
1231
      result = fd
1232
    RemoveFile(new_name)
1233

    
1234
  return result
1235

    
1236

    
1237
def FirstFree(seq, base=0):
1238
  """Returns the first non-existing integer from seq.
1239

1240
  The seq argument should be a sorted list of positive integers. The
1241
  first time the index of an element is smaller than the element
1242
  value, the index will be returned.
1243

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

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

1249
  @type seq: sequence
1250
  @param seq: the sequence to be analyzed.
1251
  @type base: int
1252
  @param base: use this value as the base index of the sequence
1253
  @rtype: int
1254
  @return: the first non-used index in the sequence
1255

1256
  """
1257
  for idx, elem in enumerate(seq):
1258
    assert elem >= base, "Passed element is higher than base offset"
1259
    if elem > idx + base:
1260
      # idx is not used
1261
      return idx + base
1262
  return None
1263

    
1264

    
1265
def all(seq, pred=bool):
1266
  "Returns True if pred(x) is True for every element in the iterable"
1267
  for elem in itertools.ifilterfalse(pred, seq):
1268
    return False
1269
  return True
1270

    
1271

    
1272
def any(seq, pred=bool):
1273
  "Returns True if pred(x) is True for at least one element in the iterable"
1274
  for elem in itertools.ifilter(pred, seq):
1275
    return True
1276
  return False
1277

    
1278

    
1279
def UniqueSequence(seq):
1280
  """Returns a list with unique elements.
1281

1282
  Element order is preserved.
1283

1284
  @type seq: sequence
1285
  @param seq: the sequence with the source elementes
1286
  @rtype: list
1287
  @return: list of unique elements from seq
1288

1289
  """
1290
  seen = set()
1291
  return [i for i in seq if i not in seen and not seen.add(i)]
1292

    
1293

    
1294
def IsValidMac(mac):
1295
  """Predicate to check if a MAC address is valid.
1296

1297
  Checks wether the supplied MAC address is formally correct, only
1298
  accepts colon separated format.
1299

1300
  @type mac: str
1301
  @param mac: the MAC to be validated
1302
  @rtype: boolean
1303
  @return: True is the MAC seems valid
1304

1305
  """
1306
  mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$")
1307
  return mac_check.match(mac) is not None
1308

    
1309

    
1310
def TestDelay(duration):
1311
  """Sleep for a fixed amount of time.
1312

1313
  @type duration: float
1314
  @param duration: the sleep duration
1315
  @rtype: boolean
1316
  @return: False for negative value, True otherwise
1317

1318
  """
1319
  if duration < 0:
1320
    return False
1321
  time.sleep(duration)
1322
  return True
1323

    
1324

    
1325
def Daemonize(logfile, noclose_fds=None):
1326
  """Daemonize the current process.
1327

1328
  This detaches the current process from the controlling terminal and
1329
  runs it in the background as a daemon.
1330

1331
  @type logfile: str
1332
  @param logfile: the logfile to which we should redirect stdout/stderr
1333
  @type noclose_fds: list or None
1334
  @param noclose_fds: if given, it denotes a list of file descriptor
1335
      that should not be closed
1336
  @rtype: int
1337
  @returns: the value zero
1338

1339
  """
1340
  UMASK = 077
1341
  WORKDIR = "/"
1342
  # Default maximum for the number of available file descriptors.
1343
  if 'SC_OPEN_MAX' in os.sysconf_names:
1344
    try:
1345
      MAXFD = os.sysconf('SC_OPEN_MAX')
1346
      if MAXFD < 0:
1347
        MAXFD = 1024
1348
    except OSError:
1349
      MAXFD = 1024
1350
  else:
1351
    MAXFD = 1024
1352

    
1353
  # this might fail
1354
  pid = os.fork()
1355
  if (pid == 0):  # The first child.
1356
    os.setsid()
1357
    # this might fail
1358
    pid = os.fork() # Fork a second child.
1359
    if (pid == 0):  # The second child.
1360
      os.chdir(WORKDIR)
1361
      os.umask(UMASK)
1362
    else:
1363
      # exit() or _exit()?  See below.
1364
      os._exit(0) # Exit parent (the first child) of the second child.
1365
  else:
1366
    os._exit(0) # Exit parent of the first child.
1367
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1368
  if (maxfd == resource.RLIM_INFINITY):
1369
    maxfd = MAXFD
1370

    
1371
  # Iterate through and close all file descriptors.
1372
  for fd in range(0, maxfd):
1373
    if noclose_fds and fd in noclose_fds:
1374
      continue
1375
    try:
1376
      os.close(fd)
1377
    except OSError: # ERROR, fd wasn't open to begin with (ignored)
1378
      pass
1379
  os.open(logfile, os.O_RDWR|os.O_CREAT|os.O_APPEND, 0600)
1380
  # Duplicate standard input to standard output and standard error.
1381
  os.dup2(0, 1)     # standard output (1)
1382
  os.dup2(0, 2)     # standard error (2)
1383
  return 0
1384

    
1385

    
1386
def DaemonPidFileName(name):
1387
  """Compute a ganeti pid file absolute path
1388

1389
  @type name: str
1390
  @param name: the daemon name
1391
  @rtype: str
1392
  @return: the full path to the pidfile corresponding to the given
1393
      daemon name
1394

1395
  """
1396
  return os.path.join(constants.RUN_GANETI_DIR, "%s.pid" % name)
1397

    
1398

    
1399
def WritePidFile(name):
1400
  """Write the current process pidfile.
1401

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

1404
  @type name: str
1405
  @param name: the daemon name to use
1406
  @raise errors.GenericError: if the pid file already exists and
1407
      points to a live process
1408

1409
  """
1410
  pid = os.getpid()
1411
  pidfilename = DaemonPidFileName(name)
1412
  if IsProcessAlive(ReadPidFile(pidfilename)):
1413
    raise errors.GenericError("%s contains a live process" % pidfilename)
1414

    
1415
  WriteFile(pidfilename, data="%d\n" % pid)
1416

    
1417

    
1418
def RemovePidFile(name):
1419
  """Remove the current process pidfile.
1420

1421
  Any errors are ignored.
1422

1423
  @type name: str
1424
  @param name: the daemon name used to derive the pidfile name
1425

1426
  """
1427
  pid = os.getpid()
1428
  pidfilename = DaemonPidFileName(name)
1429
  # TODO: we could check here that the file contains our pid
1430
  try:
1431
    RemoveFile(pidfilename)
1432
  except:
1433
    pass
1434

    
1435

    
1436
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1437
                waitpid=False):
1438
  """Kill a process given by its pid.
1439

1440
  @type pid: int
1441
  @param pid: The PID to terminate.
1442
  @type signal_: int
1443
  @param signal_: The signal to send, by default SIGTERM
1444
  @type timeout: int
1445
  @param timeout: The timeout after which, if the process is still alive,
1446
                  a SIGKILL will be sent. If not positive, no such checking
1447
                  will be done
1448
  @type waitpid: boolean
1449
  @param waitpid: If true, we should waitpid on this process after
1450
      sending signals, since it's our own child and otherwise it
1451
      would remain as zombie
1452

1453
  """
1454
  def _helper(pid, signal_, wait):
1455
    """Simple helper to encapsulate the kill/waitpid sequence"""
1456
    os.kill(pid, signal_)
1457
    if wait:
1458
      try:
1459
        os.waitpid(pid, os.WNOHANG)
1460
      except OSError:
1461
        pass
1462

    
1463
  if pid <= 0:
1464
    # kill with pid=0 == suicide
1465
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1466

    
1467
  if not IsProcessAlive(pid):
1468
    return
1469
  _helper(pid, signal_, waitpid)
1470
  if timeout <= 0:
1471
    return
1472
  end = time.time() + timeout
1473
  while time.time() < end and IsProcessAlive(pid):
1474
    time.sleep(0.1)
1475
  if IsProcessAlive(pid):
1476
    _helper(pid, signal.SIGKILL, waitpid)
1477

    
1478

    
1479
def FindFile(name, search_path, test=os.path.exists):
1480
  """Look for a filesystem object in a given path.
1481

1482
  This is an abstract method to search for filesystem object (files,
1483
  dirs) under a given search path.
1484

1485
  @type name: str
1486
  @param name: the name to look for
1487
  @type search_path: str
1488
  @param search_path: location to start at
1489
  @type test: callable
1490
  @param test: a function taking one argument that should return True
1491
      if the a given object is valid; the default value is
1492
      os.path.exists, causing only existing files to be returned
1493
  @rtype: str or None
1494
  @return: full path to the object if found, None otherwise
1495

1496
  """
1497
  for dir_name in search_path:
1498
    item_name = os.path.sep.join([dir_name, name])
1499
    if test(item_name):
1500
      return item_name
1501
  return None
1502

    
1503

    
1504
def CheckVolumeGroupSize(vglist, vgname, minsize):
1505
  """Checks if the volume group list is valid.
1506

1507
  The function will check if a given volume group is in the list of
1508
  volume groups and has a minimum size.
1509

1510
  @type vglist: dict
1511
  @param vglist: dictionary of volume group names and their size
1512
  @type vgname: str
1513
  @param vgname: the volume group we should check
1514
  @type minsize: int
1515
  @param minsize: the minimum size we accept
1516
  @rtype: None or str
1517
  @return: None for success, otherwise the error message
1518

1519
  """
1520
  vgsize = vglist.get(vgname, None)
1521
  if vgsize is None:
1522
    return "volume group '%s' missing" % vgname
1523
  elif vgsize < minsize:
1524
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1525
            (vgname, minsize, vgsize))
1526
  return None
1527

    
1528

    
1529
def SplitTime(value):
1530
  """Splits time as floating point number into a tuple.
1531

1532
  @param value: Time in seconds
1533
  @type value: int or float
1534
  @return: Tuple containing (seconds, microseconds)
1535

1536
  """
1537
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1538

    
1539
  assert 0 <= seconds, \
1540
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1541
  assert 0 <= microseconds <= 999999, \
1542
    "Microseconds must be 0-999999, but are %s" % microseconds
1543

    
1544
  return (int(seconds), int(microseconds))
1545

    
1546

    
1547
def MergeTime(timetuple):
1548
  """Merges a tuple into time as a floating point number.
1549

1550
  @param timetuple: Time as tuple, (seconds, microseconds)
1551
  @type timetuple: tuple
1552
  @return: Time as a floating point number expressed in seconds
1553

1554
  """
1555
  (seconds, microseconds) = timetuple
1556

    
1557
  assert 0 <= seconds, \
1558
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1559
  assert 0 <= microseconds <= 999999, \
1560
    "Microseconds must be 0-999999, but are %s" % microseconds
1561

    
1562
  return float(seconds) + (float(microseconds) * 0.000001)
1563

    
1564

    
1565
def GetNodeDaemonPort():
1566
  """Get the node daemon port for this cluster.
1567

1568
  Note that this routine does not read a ganeti-specific file, but
1569
  instead uses C{socket.getservbyname} to allow pre-customization of
1570
  this parameter outside of Ganeti.
1571

1572
  @rtype: int
1573

1574
  """
1575
  try:
1576
    port = socket.getservbyname("ganeti-noded", "tcp")
1577
  except socket.error:
1578
    port = constants.DEFAULT_NODED_PORT
1579

    
1580
  return port
1581

    
1582

    
1583
def SetupLogging(logfile, debug=False, stderr_logging=False, program=""):
1584
  """Configures the logging module.
1585

1586
  @type logfile: str
1587
  @param logfile: the filename to which we should log
1588
  @type debug: boolean
1589
  @param debug: whether to enable debug messages too or
1590
      only those at C{INFO} and above level
1591
  @type stderr_logging: boolean
1592
  @param stderr_logging: whether we should also log to the standard error
1593
  @type program: str
1594
  @param program: the name under which we should log messages
1595
  @raise EnvironmentError: if we can't open the log file and
1596
      stderr logging is disabled
1597

1598
  """
1599
  fmt = "%(asctime)s: " + program + " "
1600
  if debug:
1601
    fmt += ("pid=%(process)d/%(threadName)s %(levelname)s"
1602
           " %(module)s:%(lineno)s %(message)s")
1603
  else:
1604
    fmt += "pid=%(process)d %(levelname)s %(message)s"
1605
  formatter = logging.Formatter(fmt)
1606

    
1607
  root_logger = logging.getLogger("")
1608
  root_logger.setLevel(logging.NOTSET)
1609

    
1610
  # Remove all previously setup handlers
1611
  for handler in root_logger.handlers:
1612
    root_logger.removeHandler(handler)
1613

    
1614
  if stderr_logging:
1615
    stderr_handler = logging.StreamHandler()
1616
    stderr_handler.setFormatter(formatter)
1617
    if debug:
1618
      stderr_handler.setLevel(logging.NOTSET)
1619
    else:
1620
      stderr_handler.setLevel(logging.CRITICAL)
1621
    root_logger.addHandler(stderr_handler)
1622

    
1623
  # this can fail, if the logging directories are not setup or we have
1624
  # a permisssion problem; in this case, it's best to log but ignore
1625
  # the error if stderr_logging is True, and if false we re-raise the
1626
  # exception since otherwise we could run but without any logs at all
1627
  try:
1628
    logfile_handler = logging.FileHandler(logfile)
1629
    logfile_handler.setFormatter(formatter)
1630
    if debug:
1631
      logfile_handler.setLevel(logging.DEBUG)
1632
    else:
1633
      logfile_handler.setLevel(logging.INFO)
1634
    root_logger.addHandler(logfile_handler)
1635
  except EnvironmentError, err:
1636
    if stderr_logging:
1637
      logging.exception("Failed to enable logging to file '%s'", logfile)
1638
    else:
1639
      # we need to re-raise the exception
1640
      raise
1641

    
1642

    
1643
def LockedMethod(fn):
1644
  """Synchronized object access decorator.
1645

1646
  This decorator is intended to protect access to an object using the
1647
  object's own lock which is hardcoded to '_lock'.
1648

1649
  """
1650
  def _LockDebug(*args, **kwargs):
1651
    if debug_locks:
1652
      logging.debug(*args, **kwargs)
1653

    
1654
  def wrapper(self, *args, **kwargs):
1655
    assert hasattr(self, '_lock')
1656
    lock = self._lock
1657
    _LockDebug("Waiting for %s", lock)
1658
    lock.acquire()
1659
    try:
1660
      _LockDebug("Acquired %s", lock)
1661
      result = fn(self, *args, **kwargs)
1662
    finally:
1663
      _LockDebug("Releasing %s", lock)
1664
      lock.release()
1665
      _LockDebug("Released %s", lock)
1666
    return result
1667
  return wrapper
1668

    
1669

    
1670
def LockFile(fd):
1671
  """Locks a file using POSIX locks.
1672

1673
  @type fd: int
1674
  @param fd: the file descriptor we need to lock
1675

1676
  """
1677
  try:
1678
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
1679
  except IOError, err:
1680
    if err.errno == errno.EAGAIN:
1681
      raise errors.LockError("File already locked")
1682
    raise
1683

    
1684

    
1685
class FileLock(object):
1686
  """Utility class for file locks.
1687

1688
  """
1689
  def __init__(self, filename):
1690
    """Constructor for FileLock.
1691

1692
    This will open the file denoted by the I{filename} argument.
1693

1694
    @type filename: str
1695
    @param filename: path to the file to be locked
1696

1697
    """
1698
    self.filename = filename
1699
    self.fd = open(self.filename, "w")
1700

    
1701
  def __del__(self):
1702
    self.Close()
1703

    
1704
  def Close(self):
1705
    """Close the file and release the lock.
1706

1707
    """
1708
    if self.fd:
1709
      self.fd.close()
1710
      self.fd = None
1711

    
1712
  def _flock(self, flag, blocking, timeout, errmsg):
1713
    """Wrapper for fcntl.flock.
1714

1715
    @type flag: int
1716
    @param flag: operation flag
1717
    @type blocking: bool
1718
    @param blocking: whether the operation should be done in blocking mode.
1719
    @type timeout: None or float
1720
    @param timeout: for how long the operation should be retried (implies
1721
                    non-blocking mode).
1722
    @type errmsg: string
1723
    @param errmsg: error message in case operation fails.
1724

1725
    """
1726
    assert self.fd, "Lock was closed"
1727
    assert timeout is None or timeout >= 0, \
1728
      "If specified, timeout must be positive"
1729

    
1730
    if timeout is not None:
1731
      flag |= fcntl.LOCK_NB
1732
      timeout_end = time.time() + timeout
1733

    
1734
    # Blocking doesn't have effect with timeout
1735
    elif not blocking:
1736
      flag |= fcntl.LOCK_NB
1737
      timeout_end = None
1738

    
1739
    retry = True
1740
    while retry:
1741
      try:
1742
        fcntl.flock(self.fd, flag)
1743
        retry = False
1744
      except IOError, err:
1745
        if err.errno in (errno.EAGAIN, ):
1746
          if timeout_end is not None and time.time() < timeout_end:
1747
            # Wait before trying again
1748
            time.sleep(max(0.1, min(1.0, timeout)))
1749
          else:
1750
            raise errors.LockError(errmsg)
1751
        else:
1752
          logging.exception("fcntl.flock failed")
1753
          raise
1754

    
1755
  def Exclusive(self, blocking=False, timeout=None):
1756
    """Locks the file in exclusive mode.
1757

1758
    @type blocking: boolean
1759
    @param blocking: whether to block and wait until we
1760
        can lock the file or return immediately
1761
    @type timeout: int or None
1762
    @param timeout: if not None, the duration to wait for the lock
1763
        (in blocking mode)
1764

1765
    """
1766
    self._flock(fcntl.LOCK_EX, blocking, timeout,
1767
                "Failed to lock %s in exclusive mode" % self.filename)
1768

    
1769
  def Shared(self, blocking=False, timeout=None):
1770
    """Locks the file in shared mode.
1771

1772
    @type blocking: boolean
1773
    @param blocking: whether to block and wait until we
1774
        can lock the file or return immediately
1775
    @type timeout: int or None
1776
    @param timeout: if not None, the duration to wait for the lock
1777
        (in blocking mode)
1778

1779
    """
1780
    self._flock(fcntl.LOCK_SH, blocking, timeout,
1781
                "Failed to lock %s in shared mode" % self.filename)
1782

    
1783
  def Unlock(self, blocking=True, timeout=None):
1784
    """Unlocks the file.
1785

1786
    According to C{flock(2)}, unlocking can also be a nonblocking
1787
    operation::
1788

1789
      To make a non-blocking request, include LOCK_NB with any of the above
1790
      operations.
1791

1792
    @type blocking: boolean
1793
    @param blocking: whether to block and wait until we
1794
        can lock the file or return immediately
1795
    @type timeout: int or None
1796
    @param timeout: if not None, the duration to wait for the lock
1797
        (in blocking mode)
1798

1799
    """
1800
    self._flock(fcntl.LOCK_UN, blocking, timeout,
1801
                "Failed to unlock %s" % self.filename)
1802

    
1803

    
1804
class SignalHandler(object):
1805
  """Generic signal handler class.
1806

1807
  It automatically restores the original handler when deconstructed or
1808
  when L{Reset} is called. You can either pass your own handler
1809
  function in or query the L{called} attribute to detect whether the
1810
  signal was sent.
1811

1812
  @type signum: list
1813
  @ivar signum: the signals we handle
1814
  @type called: boolean
1815
  @ivar called: tracks whether any of the signals have been raised
1816

1817
  """
1818
  def __init__(self, signum):
1819
    """Constructs a new SignalHandler instance.
1820

1821
    @type signum: int or list of ints
1822
    @param signum: Single signal number or set of signal numbers
1823

1824
    """
1825
    if isinstance(signum, (int, long)):
1826
      self.signum = set([signum])
1827
    else:
1828
      self.signum = set(signum)
1829

    
1830
    self.called = False
1831

    
1832
    self._previous = {}
1833
    try:
1834
      for signum in self.signum:
1835
        # Setup handler
1836
        prev_handler = signal.signal(signum, self._HandleSignal)
1837
        try:
1838
          self._previous[signum] = prev_handler
1839
        except:
1840
          # Restore previous handler
1841
          signal.signal(signum, prev_handler)
1842
          raise
1843
    except:
1844
      # Reset all handlers
1845
      self.Reset()
1846
      # Here we have a race condition: a handler may have already been called,
1847
      # but there's not much we can do about it at this point.
1848
      raise
1849

    
1850
  def __del__(self):
1851
    self.Reset()
1852

    
1853
  def Reset(self):
1854
    """Restore previous handler.
1855

1856
    This will reset all the signals to their previous handlers.
1857

1858
    """
1859
    for signum, prev_handler in self._previous.items():
1860
      signal.signal(signum, prev_handler)
1861
      # If successful, remove from dict
1862
      del self._previous[signum]
1863

    
1864
  def Clear(self):
1865
    """Unsets the L{called} flag.
1866

1867
    This function can be used in case a signal may arrive several times.
1868

1869
    """
1870
    self.called = False
1871

    
1872
  def _HandleSignal(self, signum, frame):
1873
    """Actual signal handling function.
1874

1875
    """
1876
    # This is not nice and not absolutely atomic, but it appears to be the only
1877
    # solution in Python -- there are no atomic types.
1878
    self.called = True
1879

    
1880

    
1881
class FieldSet(object):
1882
  """A simple field set.
1883

1884
  Among the features are:
1885
    - checking if a string is among a list of static string or regex objects
1886
    - checking if a whole list of string matches
1887
    - returning the matching groups from a regex match
1888

1889
  Internally, all fields are held as regular expression objects.
1890

1891
  """
1892
  def __init__(self, *items):
1893
    self.items = [re.compile("^%s$" % value) for value in items]
1894

    
1895
  def Extend(self, other_set):
1896
    """Extend the field set with the items from another one"""
1897
    self.items.extend(other_set.items)
1898

    
1899
  def Matches(self, field):
1900
    """Checks if a field matches the current set
1901

1902
    @type field: str
1903
    @param field: the string to match
1904
    @return: either False or a regular expression match object
1905

1906
    """
1907
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
1908
      return m
1909
    return False
1910

    
1911
  def NonMatching(self, items):
1912
    """Returns the list of fields not matching the current set
1913

1914
    @type items: list
1915
    @param items: the list of fields to check
1916
    @rtype: list
1917
    @return: list of non-matching fields
1918

1919
    """
1920
    return [val for val in items if not self.Matches(val)]