Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ 90eb468c

History | View | Annotate | Download (50.1 kB)

1
#
2
#
3

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

    
21

    
22
"""Ganeti utility module.
23

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

27
"""
28

    
29

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

    
48
from cStringIO import StringIO
49

    
50
from ganeti import errors
51
from ganeti import constants
52

    
53

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

    
57
debug = False
58
debug_locks = False
59

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

    
63

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

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

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

    
86

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

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

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

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

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

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

    
114

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
226

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

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

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

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

    
259

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

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

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

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

    
276

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

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

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

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

    
293
  f = open(filename)
294

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

    
301
    fp.update(data)
302

    
303
  return fp.hexdigest()
304

    
305

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

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

315
  """
316
  ret = {}
317

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

    
323
  return ret
324

    
325

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

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

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

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

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

    
351

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

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

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

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

    
374

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

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

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

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

    
398
  return pid
399

    
400

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

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

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

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

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

    
427

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
480
    return result
481

    
482

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

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

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

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

    
506
    retval[name] = size
507

    
508
  return retval
509

    
510

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

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

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

    
522

    
523
def CheckBEParams(beparams):
524
  """Checks whether the user-supplied be-params are valid,
525
  and converts them from string format where appropriate.
526

527
  @type beparams: dict
528
  @param beparams: new params dict
529

530
  """
531
  if beparams:
532
    for item in beparams:
533
      if item not in constants.BES_PARAMETERS:
534
        raise errors.OpPrereqError("Unknown backend parameter %s" % item)
535
      if item in (constants.BE_MEMORY, constants.BE_VCPUS):
536
        val = beparams[item]
537
        if val != constants.VALUE_DEFAULT:
538
          try:
539
            val = int(val)
540
          except ValueError, err:
541
            raise errors.OpPrereqError("Invalid %s size: %s" % (item, str(err)))
542
          beparams[item] = val
543
      if item in (constants.BE_AUTO_BALANCE):
544
        val = beparams[item]
545
        if not isinstance(val, bool):
546
          if val == constants.VALUE_TRUE:
547
            beparams[item] = True
548
          elif val == constants.VALUE_FALSE:
549
            beparams[item] = False
550
          else:
551
            raise errors.OpPrereqError("Invalid %s value: %s" % (item, val))
552

    
553

    
554
def NiceSort(name_list):
555
  """Sort a list of strings based on digit and non-digit groupings.
556

557
  Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
558
  will sort the list in the logical order C{['a1', 'a2', 'a10',
559
  'a11']}.
560

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

565
  @type name_list: list
566
  @param name_list: the names to be sorted
567
  @rtype: list
568
  @return: a copy of the name list sorted with our algorithm
569

570
  """
571
  _SORTER_BASE = "(\D+|\d+)"
572
  _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
573
                                                  _SORTER_BASE, _SORTER_BASE,
574
                                                  _SORTER_BASE, _SORTER_BASE,
575
                                                  _SORTER_BASE, _SORTER_BASE)
576
  _SORTER_RE = re.compile(_SORTER_FULL)
577
  _SORTER_NODIGIT = re.compile("^\D*$")
578
  def _TryInt(val):
579
    """Attempts to convert a variable to integer."""
580
    if val is None or _SORTER_NODIGIT.match(val):
581
      return val
582
    rval = int(val)
583
    return rval
584

    
585
  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
586
             for name in name_list]
587
  to_sort.sort()
588
  return [tup[1] for tup in to_sort]
589

    
590

    
591
def TryConvert(fn, val):
592
  """Try to convert a value ignoring errors.
593

594
  This function tries to apply function I{fn} to I{val}. If no
595
  C{ValueError} or C{TypeError} exceptions are raised, it will return
596
  the result, else it will return the original value. Any other
597
  exceptions are propagated to the caller.
598

599
  @type fn: callable
600
  @param fn: function to apply to the value
601
  @param val: the value to be converted
602
  @return: The converted value if the conversion was successful,
603
      otherwise the original value.
604

605
  """
606
  try:
607
    nv = fn(val)
608
  except (ValueError, TypeError), err:
609
    nv = val
610
  return nv
611

    
612

    
613
def IsValidIP(ip):
614
  """Verifies the syntax of an IPv4 address.
615

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

619
  @type ip: str
620
  @param ip: the address to be checked
621
  @rtype: a regular expression match object
622
  @return: a regular epression match object, or None if the
623
      address is not valid
624

625
  """
626
  unit = "(0|[1-9]\d{0,2})"
627
  #TODO: convert and return only boolean
628
  return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
629

    
630

    
631
def IsValidShellParam(word):
632
  """Verifies is the given word is safe from the shell's p.o.v.
633

634
  This means that we can pass this to a command via the shell and be
635
  sure that it doesn't alter the command line and is passed as such to
636
  the actual command.
637

638
  Note that we are overly restrictive here, in order to be on the safe
639
  side.
640

641
  @type word: str
642
  @param word: the word to check
643
  @rtype: boolean
644
  @return: True if the word is 'safe'
645

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

    
649

    
650
def BuildShellCmd(template, *args):
651
  """Build a safe shell command line from the given arguments.
652

653
  This function will check all arguments in the args list so that they
654
  are valid shell parameters (i.e. they don't contain shell
655
  metacharaters). If everything is ok, it will return the result of
656
  template % args.
657

658
  @type template: str
659
  @param template: the string holding the template for the
660
      string formatting
661
  @rtype: str
662
  @return: the expanded command line
663

664
  """
665
  for word in args:
666
    if not IsValidShellParam(word):
667
      raise errors.ProgrammerError("Shell argument '%s' contains"
668
                                   " invalid characters" % word)
669
  return template % args
670

    
671

    
672
def FormatUnit(value, units):
673
  """Formats an incoming number of MiB with the appropriate unit.
674

675
  @type value: int
676
  @param value: integer representing the value in MiB (1048576)
677
  @type units: char
678
  @param units: the type of formatting we should do:
679
      - 'h' for automatic scaling
680
      - 'm' for MiBs
681
      - 'g' for GiBs
682
      - 't' for TiBs
683
  @rtype: str
684
  @return: the formatted value (with suffix)
685

686
  """
687
  if units not in ('m', 'g', 't', 'h'):
688
    raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
689

    
690
  suffix = ''
691

    
692
  if units == 'm' or (units == 'h' and value < 1024):
693
    if units == 'h':
694
      suffix = 'M'
695
    return "%d%s" % (round(value, 0), suffix)
696

    
697
  elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
698
    if units == 'h':
699
      suffix = 'G'
700
    return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
701

    
702
  else:
703
    if units == 'h':
704
      suffix = 'T'
705
    return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
706

    
707

    
708
def ParseUnit(input_string):
709
  """Tries to extract number and scale from the given string.
710

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

715
  """
716
  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', input_string)
717
  if not m:
718
    raise errors.UnitParseError("Invalid format")
719

    
720
  value = float(m.groups()[0])
721

    
722
  unit = m.groups()[1]
723
  if unit:
724
    lcunit = unit.lower()
725
  else:
726
    lcunit = 'm'
727

    
728
  if lcunit in ('m', 'mb', 'mib'):
729
    # Value already in MiB
730
    pass
731

    
732
  elif lcunit in ('g', 'gb', 'gib'):
733
    value *= 1024
734

    
735
  elif lcunit in ('t', 'tb', 'tib'):
736
    value *= 1024 * 1024
737

    
738
  else:
739
    raise errors.UnitParseError("Unknown unit: %s" % unit)
740

    
741
  # Make sure we round up
742
  if int(value) < value:
743
    value += 1
744

    
745
  # Round up to the next multiple of 4
746
  value = int(value)
747
  if value % 4:
748
    value += 4 - value % 4
749

    
750
  return value
751

    
752

    
753
def AddAuthorizedKey(file_name, key):
754
  """Adds an SSH public key to an authorized_keys file.
755

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

761
  """
762
  key_fields = key.split()
763

    
764
  f = open(file_name, 'a+')
765
  try:
766
    nl = True
767
    for line in f:
768
      # Ignore whitespace changes
769
      if line.split() == key_fields:
770
        break
771
      nl = line.endswith('\n')
772
    else:
773
      if not nl:
774
        f.write("\n")
775
      f.write(key.rstrip('\r\n'))
776
      f.write("\n")
777
      f.flush()
778
  finally:
779
    f.close()
780

    
781

    
782
def RemoveAuthorizedKey(file_name, key):
783
  """Removes an SSH public key from an authorized_keys file.
784

785
  @type file_name: str
786
  @param file_name: path to authorized_keys file
787
  @type key: str
788
  @param key: string containing key
789

790
  """
791
  key_fields = key.split()
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
        for line in f:
800
          # Ignore whitespace changes while comparing lines
801
          if line.split() != key_fields:
802
            out.write(line)
803

    
804
        out.flush()
805
        os.rename(tmpname, file_name)
806
      finally:
807
        f.close()
808
    finally:
809
      out.close()
810
  except:
811
    RemoveFile(tmpname)
812
    raise
813

    
814

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

818
  @type file_name: str
819
  @param file_name: path to the file to modify (usually C{/etc/hosts})
820
  @type ip: str
821
  @param ip: the IP address
822
  @type hostname: str
823
  @param hostname: the hostname to be added
824
  @type aliases: list
825
  @param aliases: the list of aliases to add for the hostname
826

827
  """
828
  # Ensure aliases are unique
829
  aliases = UniqueSequence([hostname] + aliases)[1:]
830

    
831
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
832
  try:
833
    out = os.fdopen(fd, 'w')
834
    try:
835
      f = open(file_name, 'r')
836
      try:
837
        written = False
838
        for line in f:
839
          fields = line.split()
840
          if fields and not fields[0].startswith('#') and ip == fields[0]:
841
            continue
842
          out.write(line)
843

    
844
        out.write("%s\t%s" % (ip, hostname))
845
        if aliases:
846
          out.write(" %s" % ' '.join(aliases))
847
        out.write('\n')
848

    
849
        out.flush()
850
        os.fsync(out)
851
        os.rename(tmpname, file_name)
852
      finally:
853
        f.close()
854
    finally:
855
      out.close()
856
  except:
857
    RemoveFile(tmpname)
858
    raise
859

    
860

    
861
def AddHostToEtcHosts(hostname):
862
  """Wrapper around SetEtcHostsEntry.
863

864
  @type hostname: str
865
  @param hostname: a hostname that will be resolved and added to
866
      L{constants.ETC_HOSTS}
867

868
  """
869
  hi = HostInfo(name=hostname)
870
  SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
871

    
872

    
873
def RemoveEtcHostsEntry(file_name, hostname):
874
  """Removes a hostname from /etc/hosts.
875

876
  IP addresses without names are removed from the file.
877

878
  @type file_name: str
879
  @param file_name: path to the file to modify (usually C{/etc/hosts})
880
  @type hostname: str
881
  @param hostname: the hostname to be removed
882

883
  """
884
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
885
  try:
886
    out = os.fdopen(fd, 'w')
887
    try:
888
      f = open(file_name, 'r')
889
      try:
890
        for line in f:
891
          fields = line.split()
892
          if len(fields) > 1 and not fields[0].startswith('#'):
893
            names = fields[1:]
894
            if hostname in names:
895
              while hostname in names:
896
                names.remove(hostname)
897
              if names:
898
                out.write("%s %s\n" % (fields[0], ' '.join(names)))
899
              continue
900

    
901
          out.write(line)
902

    
903
        out.flush()
904
        os.fsync(out)
905
        os.rename(tmpname, file_name)
906
      finally:
907
        f.close()
908
    finally:
909
      out.close()
910
  except:
911
    RemoveFile(tmpname)
912
    raise
913

    
914

    
915
def RemoveHostFromEtcHosts(hostname):
916
  """Wrapper around RemoveEtcHostsEntry.
917

918
  @type hostname: str
919
  @param hostname: hostname that will be resolved and its
920
      full and shot name will be removed from
921
      L{constants.ETC_HOSTS}
922

923
  """
924
  hi = HostInfo(name=hostname)
925
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
926
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
927

    
928

    
929
def CreateBackup(file_name):
930
  """Creates a backup of a file.
931

932
  @type file_name: str
933
  @param file_name: file to be backed up
934
  @rtype: str
935
  @return: the path to the newly created backup
936
  @raise errors.ProgrammerError: for invalid file names
937

938
  """
939
  if not os.path.isfile(file_name):
940
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
941
                                file_name)
942

    
943
  prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
944
  dir_name = os.path.dirname(file_name)
945

    
946
  fsrc = open(file_name, 'rb')
947
  try:
948
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
949
    fdst = os.fdopen(fd, 'wb')
950
    try:
951
      shutil.copyfileobj(fsrc, fdst)
952
    finally:
953
      fdst.close()
954
  finally:
955
    fsrc.close()
956

    
957
  return backup_name
958

    
959

    
960
def ShellQuote(value):
961
  """Quotes shell argument according to POSIX.
962

963
  @type value: str
964
  @param value: the argument to be quoted
965
  @rtype: str
966
  @return: the quoted value
967

968
  """
969
  if _re_shell_unquoted.match(value):
970
    return value
971
  else:
972
    return "'%s'" % value.replace("'", "'\\''")
973

    
974

    
975
def ShellQuoteArgs(args):
976
  """Quotes a list of shell arguments.
977

978
  @type args: list
979
  @param args: list of arguments to be quoted
980
  @rtype: str
981
  @return: the quoted arguments concatenaned with spaces
982

983
  """
984
  return ' '.join([ShellQuote(i) for i in args])
985

    
986

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

990
  Check if the given IP is reachable by doing attempting a TCP connect
991
  to it.
992

993
  @type target: str
994
  @param target: the IP or hostname to ping
995
  @type port: int
996
  @param port: the port to connect to
997
  @type timeout: int
998
  @param timeout: the timeout on the connection attemp
999
  @type live_port_needed: boolean
1000
  @param live_port_needed: whether a closed port will cause the
1001
      function to return failure, as if there was a timeout
1002
  @type source: str or None
1003
  @param source: if specified, will cause the connect to be made
1004
      from this specific source address; failures to bind other
1005
      than C{EADDRNOTAVAIL} will be ignored
1006

1007
  """
1008
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1009

    
1010
  sucess = False
1011

    
1012
  if source is not None:
1013
    try:
1014
      sock.bind((source, 0))
1015
    except socket.error, (errcode, errstring):
1016
      if errcode == errno.EADDRNOTAVAIL:
1017
        success = False
1018

    
1019
  sock.settimeout(timeout)
1020

    
1021
  try:
1022
    sock.connect((target, port))
1023
    sock.close()
1024
    success = True
1025
  except socket.timeout:
1026
    success = False
1027
  except socket.error, (errcode, errstring):
1028
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
1029

    
1030
  return success
1031

    
1032

    
1033
def OwnIpAddress(address):
1034
  """Check if the current host has the the given IP address.
1035

1036
  Currently this is done by TCP-pinging the address from the loopback
1037
  address.
1038

1039
  @type address: string
1040
  @param address: the addres to check
1041
  @rtype: bool
1042
  @return: True if we own the address
1043

1044
  """
1045
  return TcpPing(address, constants.DEFAULT_NODED_PORT,
1046
                 source=constants.LOCALHOST_IP_ADDRESS)
1047

    
1048

    
1049
def ListVisibleFiles(path):
1050
  """Returns a list of visible files in a directory.
1051

1052
  @type path: str
1053
  @param path: the directory to enumerate
1054
  @rtype: list
1055
  @return: the list of all files not starting with a dot
1056

1057
  """
1058
  files = [i for i in os.listdir(path) if not i.startswith(".")]
1059
  files.sort()
1060
  return files
1061

    
1062

    
1063
def GetHomeDir(user, default=None):
1064
  """Try to get the homedir of the given user.
1065

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

1070
  """
1071
  try:
1072
    if isinstance(user, basestring):
1073
      result = pwd.getpwnam(user)
1074
    elif isinstance(user, (int, long)):
1075
      result = pwd.getpwuid(user)
1076
    else:
1077
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1078
                                   type(user))
1079
  except KeyError:
1080
    return default
1081
  return result.pw_dir
1082

    
1083

    
1084
def NewUUID():
1085
  """Returns a random UUID.
1086

1087
  @note: This is a Linux-specific method as it uses the /proc
1088
      filesystem.
1089
  @rtype: str
1090

1091
  """
1092
  f = open("/proc/sys/kernel/random/uuid", "r")
1093
  try:
1094
    return f.read(128).rstrip("\n")
1095
  finally:
1096
    f.close()
1097

    
1098

    
1099
def GenerateSecret():
1100
  """Generates a random secret.
1101

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

1105
  @rtype: str
1106
  @return: a sha1 hexdigest of a block of 64 random bytes
1107

1108
  """
1109
  return sha.new(os.urandom(64)).hexdigest()
1110

    
1111

    
1112
def ReadFile(file_name, size=None):
1113
  """Reads a file.
1114

1115
  @type size: None or int
1116
  @param size: Read at most size bytes
1117
  @rtype: str
1118
  @return: the (possibly partial) conent of the file
1119

1120
  """
1121
  f = open(file_name, "r")
1122
  try:
1123
    if size is None:
1124
      return f.read()
1125
    else:
1126
      return f.read(size)
1127
  finally:
1128
    f.close()
1129

    
1130

    
1131
def WriteFile(file_name, fn=None, data=None,
1132
              mode=None, uid=-1, gid=-1,
1133
              atime=None, mtime=None, close=True,
1134
              dry_run=False, backup=False,
1135
              prewrite=None, postwrite=None):
1136
  """(Over)write a file atomically.
1137

1138
  The file_name and either fn (a function taking one argument, the
1139
  file descriptor, and which should write the data to it) or data (the
1140
  contents of the file) must be passed. The other arguments are
1141
  optional and allow setting the file mode, owner and group, and the
1142
  mtime/atime of the file.
1143

1144
  If the function doesn't raise an exception, it has succeeded and the
1145
  target file has the new contents. If the file has raised an
1146
  exception, an existing target file should be unmodified and the
1147
  temporary file should be removed.
1148

1149
  @type file_name: str
1150
  @param file_name: the target filename
1151
  @type fn: callable
1152
  @param fn: content writing function, called with
1153
      file descriptor as parameter
1154
  @type data: sr
1155
  @param data: contents of the file
1156
  @type mode: int
1157
  @param mode: file mode
1158
  @type uid: int
1159
  @param uid: the owner of the file
1160
  @type gid: int
1161
  @param gid: the group of the file
1162
  @type atime: int
1163
  @param atime: a custom access time to be set on the file
1164
  @type mtime: int
1165
  @param mtime: a custom modification time to be set on the file
1166
  @type close: boolean
1167
  @param close: whether to close file after writing it
1168
  @type prewrite: callable
1169
  @param prewrite: function to be called before writing content
1170
  @type postwrite: callable
1171
  @param postwrite: function to be called after writing content
1172

1173
  @rtype: None or int
1174
  @return: None if the 'close' parameter evaluates to True,
1175
      otherwise the file descriptor
1176

1177
  @raise errors.ProgrammerError: if an of the arguments are not valid
1178

1179
  """
1180
  if not os.path.isabs(file_name):
1181
    raise errors.ProgrammerError("Path passed to WriteFile is not"
1182
                                 " absolute: '%s'" % file_name)
1183

    
1184
  if [fn, data].count(None) != 1:
1185
    raise errors.ProgrammerError("fn or data required")
1186

    
1187
  if [atime, mtime].count(None) == 1:
1188
    raise errors.ProgrammerError("Both atime and mtime must be either"
1189
                                 " set or None")
1190

    
1191
  if backup and not dry_run and os.path.isfile(file_name):
1192
    CreateBackup(file_name)
1193

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

    
1224
  return result
1225

    
1226

    
1227
def FirstFree(seq, base=0):
1228
  """Returns the first non-existing integer from seq.
1229

1230
  The seq argument should be a sorted list of positive integers. The
1231
  first time the index of an element is smaller than the element
1232
  value, the index will be returned.
1233

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

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

1239
  @type seq: sequence
1240
  @param seq: the sequence to be analyzed.
1241
  @type base: int
1242
  @param base: use this value as the base index of the sequence
1243
  @rtype: int
1244
  @return: the first non-used index in the sequence
1245

1246
  """
1247
  for idx, elem in enumerate(seq):
1248
    assert elem >= base, "Passed element is higher than base offset"
1249
    if elem > idx + base:
1250
      # idx is not used
1251
      return idx + base
1252
  return None
1253

    
1254

    
1255
def all(seq, pred=bool):
1256
  "Returns True if pred(x) is True for every element in the iterable"
1257
  for elem in itertools.ifilterfalse(pred, seq):
1258
    return False
1259
  return True
1260

    
1261

    
1262
def any(seq, pred=bool):
1263
  "Returns True if pred(x) is True for at least one element in the iterable"
1264
  for elem in itertools.ifilter(pred, seq):
1265
    return True
1266
  return False
1267

    
1268

    
1269
def UniqueSequence(seq):
1270
  """Returns a list with unique elements.
1271

1272
  Element order is preserved.
1273

1274
  @type seq: sequence
1275
  @param seq: the sequence with the source elementes
1276
  @rtype: list
1277
  @return: list of unique elements from seq
1278

1279
  """
1280
  seen = set()
1281
  return [i for i in seq if i not in seen and not seen.add(i)]
1282

    
1283

    
1284
def IsValidMac(mac):
1285
  """Predicate to check if a MAC address is valid.
1286

1287
  Checks wether the supplied MAC address is formally correct, only
1288
  accepts colon separated format.
1289

1290
  @type mac: str
1291
  @param mac: the MAC to be validated
1292
  @rtype: boolean
1293
  @return: True is the MAC seems valid
1294

1295
  """
1296
  mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$")
1297
  return mac_check.match(mac) is not None
1298

    
1299

    
1300
def TestDelay(duration):
1301
  """Sleep for a fixed amount of time.
1302

1303
  @type duration: float
1304
  @param duration: the sleep duration
1305
  @rtype: boolean
1306
  @return: False for negative value, True otherwise
1307

1308
  """
1309
  if duration < 0:
1310
    return False
1311
  time.sleep(duration)
1312
  return True
1313

    
1314

    
1315
def Daemonize(logfile, noclose_fds=None):
1316
  """Daemonize the current process.
1317

1318
  This detaches the current process from the controlling terminal and
1319
  runs it in the background as a daemon.
1320

1321
  @type logfile: str
1322
  @param logfile: the logfile to which we should redirect stdout/stderr
1323
  @type noclose_fds: list or None
1324
  @param noclose_fds: if given, it denotes a list of file descriptor
1325
      that should not be closed
1326
  @rtype: int
1327
  @returns: the value zero
1328

1329
  """
1330
  UMASK = 077
1331
  WORKDIR = "/"
1332
  # Default maximum for the number of available file descriptors.
1333
  if 'SC_OPEN_MAX' in os.sysconf_names:
1334
    try:
1335
      MAXFD = os.sysconf('SC_OPEN_MAX')
1336
      if MAXFD < 0:
1337
        MAXFD = 1024
1338
    except OSError:
1339
      MAXFD = 1024
1340
  else:
1341
    MAXFD = 1024
1342

    
1343
  # this might fail
1344
  pid = os.fork()
1345
  if (pid == 0):  # The first child.
1346
    os.setsid()
1347
    # this might fail
1348
    pid = os.fork() # Fork a second child.
1349
    if (pid == 0):  # The second child.
1350
      os.chdir(WORKDIR)
1351
      os.umask(UMASK)
1352
    else:
1353
      # exit() or _exit()?  See below.
1354
      os._exit(0) # Exit parent (the first child) of the second child.
1355
  else:
1356
    os._exit(0) # Exit parent of the first child.
1357
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1358
  if (maxfd == resource.RLIM_INFINITY):
1359
    maxfd = MAXFD
1360

    
1361
  # Iterate through and close all file descriptors.
1362
  for fd in range(0, maxfd):
1363
    if noclose_fds and fd in noclose_fds:
1364
      continue
1365
    try:
1366
      os.close(fd)
1367
    except OSError: # ERROR, fd wasn't open to begin with (ignored)
1368
      pass
1369
  os.open(logfile, os.O_RDWR|os.O_CREAT|os.O_APPEND, 0600)
1370
  # Duplicate standard input to standard output and standard error.
1371
  os.dup2(0, 1)     # standard output (1)
1372
  os.dup2(0, 2)     # standard error (2)
1373
  return 0
1374

    
1375

    
1376
def DaemonPidFileName(name):
1377
  """Compute a ganeti pid file absolute path
1378

1379
  @type name: str
1380
  @param name: the daemon name
1381
  @rtype: str
1382
  @return: the full path to the pidfile corresponding to the given
1383
      daemon name
1384

1385
  """
1386
  return os.path.join(constants.RUN_GANETI_DIR, "%s.pid" % name)
1387

    
1388

    
1389
def WritePidFile(name):
1390
  """Write the current process pidfile.
1391

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

1394
  @type name: str
1395
  @param name: the daemon name to use
1396
  @raise errors.GenericError: if the pid file already exists and
1397
      points to a live process
1398

1399
  """
1400
  pid = os.getpid()
1401
  pidfilename = DaemonPidFileName(name)
1402
  if IsProcessAlive(ReadPidFile(pidfilename)):
1403
    raise errors.GenericError("%s contains a live process" % pidfilename)
1404

    
1405
  WriteFile(pidfilename, data="%d\n" % pid)
1406

    
1407

    
1408
def RemovePidFile(name):
1409
  """Remove the current process pidfile.
1410

1411
  Any errors are ignored.
1412

1413
  @type name: str
1414
  @param name: the daemon name used to derive the pidfile name
1415

1416
  """
1417
  pid = os.getpid()
1418
  pidfilename = DaemonPidFileName(name)
1419
  # TODO: we could check here that the file contains our pid
1420
  try:
1421
    RemoveFile(pidfilename)
1422
  except:
1423
    pass
1424

    
1425

    
1426
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1427
                waitpid=False):
1428
  """Kill a process given by its pid.
1429

1430
  @type pid: int
1431
  @param pid: The PID to terminate.
1432
  @type signal_: int
1433
  @param signal_: The signal to send, by default SIGTERM
1434
  @type timeout: int
1435
  @param timeout: The timeout after which, if the process is still alive,
1436
                  a SIGKILL will be sent. If not positive, no such checking
1437
                  will be done
1438
  @type waitpid: boolean
1439
  @param waitpid: If true, we should waitpid on this process after
1440
      sending signals, since it's our own child and otherwise it
1441
      would remain as zombie
1442

1443
  """
1444
  def _helper(pid, signal_, wait):
1445
    """Simple helper to encapsulate the kill/waitpid sequence"""
1446
    os.kill(pid, signal_)
1447
    if wait:
1448
      try:
1449
        os.waitpid(pid, os.WNOHANG)
1450
      except OSError:
1451
        pass
1452

    
1453
  if pid <= 0:
1454
    # kill with pid=0 == suicide
1455
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1456

    
1457
  if not IsProcessAlive(pid):
1458
    return
1459
  _helper(pid, signal_, waitpid)
1460
  if timeout <= 0:
1461
    return
1462
  end = time.time() + timeout
1463
  while time.time() < end and IsProcessAlive(pid):
1464
    time.sleep(0.1)
1465
  if IsProcessAlive(pid):
1466
    _helper(pid, signal.SIGKILL, waitpid)
1467

    
1468

    
1469
def FindFile(name, search_path, test=os.path.exists):
1470
  """Look for a filesystem object in a given path.
1471

1472
  This is an abstract method to search for filesystem object (files,
1473
  dirs) under a given search path.
1474

1475
  @type name: str
1476
  @param name: the name to look for
1477
  @type search_path: str
1478
  @param search_path: location to start at
1479
  @type test: callable
1480
  @param test: a function taking one argument that should return True
1481
      if the a given object is valid; the default value is
1482
      os.path.exists, causing only existing files to be returned
1483
  @rtype: str or None
1484
  @return: full path to the object if found, None otherwise
1485

1486
  """
1487
  for dir_name in search_path:
1488
    item_name = os.path.sep.join([dir_name, name])
1489
    if test(item_name):
1490
      return item_name
1491
  return None
1492

    
1493

    
1494
def CheckVolumeGroupSize(vglist, vgname, minsize):
1495
  """Checks if the volume group list is valid.
1496

1497
  The function will check if a given volume group is in the list of
1498
  volume groups and has a minimum size.
1499

1500
  @type vglist: dict
1501
  @param vglist: dictionary of volume group names and their size
1502
  @type vgname: str
1503
  @param vgname: the volume group we should check
1504
  @type minsize: int
1505
  @param minsize: the minimum size we accept
1506
  @rtype: None or str
1507
  @return: None for success, otherwise the error message
1508

1509
  """
1510
  vgsize = vglist.get(vgname, None)
1511
  if vgsize is None:
1512
    return "volume group '%s' missing" % vgname
1513
  elif vgsize < minsize:
1514
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1515
            (vgname, minsize, vgsize))
1516
  return None
1517

    
1518

    
1519
def SplitTime(value):
1520
  """Splits time as floating point number into a tuple.
1521

1522
  @param value: Time in seconds
1523
  @type value: int or float
1524
  @return: Tuple containing (seconds, microseconds)
1525

1526
  """
1527
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1528

    
1529
  assert 0 <= seconds, \
1530
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1531
  assert 0 <= microseconds <= 999999, \
1532
    "Microseconds must be 0-999999, but are %s" % microseconds
1533

    
1534
  return (int(seconds), int(microseconds))
1535

    
1536

    
1537
def MergeTime(timetuple):
1538
  """Merges a tuple into time as a floating point number.
1539

1540
  @param timetuple: Time as tuple, (seconds, microseconds)
1541
  @type timetuple: tuple
1542
  @return: Time as a floating point number expressed in seconds
1543

1544
  """
1545
  (seconds, microseconds) = timetuple
1546

    
1547
  assert 0 <= seconds, \
1548
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1549
  assert 0 <= microseconds <= 999999, \
1550
    "Microseconds must be 0-999999, but are %s" % microseconds
1551

    
1552
  return float(seconds) + (float(microseconds) * 0.000001)
1553

    
1554

    
1555
def GetNodeDaemonPort():
1556
  """Get the node daemon port for this cluster.
1557

1558
  Note that this routine does not read a ganeti-specific file, but
1559
  instead uses C{socket.getservbyname} to allow pre-customization of
1560
  this parameter outside of Ganeti.
1561

1562
  @rtype: int
1563

1564
  """
1565
  try:
1566
    port = socket.getservbyname("ganeti-noded", "tcp")
1567
  except socket.error:
1568
    port = constants.DEFAULT_NODED_PORT
1569

    
1570
  return port
1571

    
1572

    
1573
def SetupLogging(logfile, debug=False, stderr_logging=False, program=""):
1574
  """Configures the logging module.
1575

1576
  @type logfile: str
1577
  @param logfile: the filename to which we should log
1578
  @type debug: boolean
1579
  @param debug: whether to enable debug messages too or
1580
      only those at C{INFO} and above level
1581
  @type stderr_logging: boolean
1582
  @param stderr_logging: whether we should also log to the standard error
1583
  @type program: str
1584
  @param program: the name under which we should log messages
1585
  @raise EnvironmentError: if we can't open the log file and
1586
      stderr logging is disabled
1587

1588
  """
1589
  fmt = "%(asctime)s: " + program + " "
1590
  if debug:
1591
    fmt += ("pid=%(process)d/%(threadName)s %(levelname)s"
1592
           " %(module)s:%(lineno)s %(message)s")
1593
  else:
1594
    fmt += "pid=%(process)d %(levelname)s %(message)s"
1595
  formatter = logging.Formatter(fmt)
1596

    
1597
  root_logger = logging.getLogger("")
1598
  root_logger.setLevel(logging.NOTSET)
1599

    
1600
  # Remove all previously setup handlers
1601
  for handler in root_logger.handlers:
1602
    root_logger.removeHandler(handler)
1603

    
1604
  if stderr_logging:
1605
    stderr_handler = logging.StreamHandler()
1606
    stderr_handler.setFormatter(formatter)
1607
    if debug:
1608
      stderr_handler.setLevel(logging.NOTSET)
1609
    else:
1610
      stderr_handler.setLevel(logging.CRITICAL)
1611
    root_logger.addHandler(stderr_handler)
1612

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

    
1632

    
1633
def LockedMethod(fn):
1634
  """Synchronized object access decorator.
1635

1636
  This decorator is intended to protect access to an object using the
1637
  object's own lock which is hardcoded to '_lock'.
1638

1639
  """
1640
  def _LockDebug(*args, **kwargs):
1641
    if debug_locks:
1642
      logging.debug(*args, **kwargs)
1643

    
1644
  def wrapper(self, *args, **kwargs):
1645
    assert hasattr(self, '_lock')
1646
    lock = self._lock
1647
    _LockDebug("Waiting for %s", lock)
1648
    lock.acquire()
1649
    try:
1650
      _LockDebug("Acquired %s", lock)
1651
      result = fn(self, *args, **kwargs)
1652
    finally:
1653
      _LockDebug("Releasing %s", lock)
1654
      lock.release()
1655
      _LockDebug("Released %s", lock)
1656
    return result
1657
  return wrapper
1658

    
1659

    
1660
def LockFile(fd):
1661
  """Locks a file using POSIX locks.
1662

1663
  @type fd: int
1664
  @param fd: the file descriptor we need to lock
1665

1666
  """
1667
  try:
1668
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
1669
  except IOError, err:
1670
    if err.errno == errno.EAGAIN:
1671
      raise errors.LockError("File already locked")
1672
    raise
1673

    
1674

    
1675
class FileLock(object):
1676
  """Utility class for file locks.
1677

1678
  """
1679
  def __init__(self, filename):
1680
    """Constructor for FileLock.
1681

1682
    This will open the file denoted by the I{filename} argument.
1683

1684
    @type filename: str
1685
    @param filename: path to the file to be locked
1686

1687
    """
1688
    self.filename = filename
1689
    self.fd = open(self.filename, "w")
1690

    
1691
  def __del__(self):
1692
    self.Close()
1693

    
1694
  def Close(self):
1695
    """Close the file and release the lock.
1696

1697
    """
1698
    if self.fd:
1699
      self.fd.close()
1700
      self.fd = None
1701

    
1702
  def _flock(self, flag, blocking, timeout, errmsg):
1703
    """Wrapper for fcntl.flock.
1704

1705
    @type flag: int
1706
    @param flag: operation flag
1707
    @type blocking: bool
1708
    @param blocking: whether the operation should be done in blocking mode.
1709
    @type timeout: None or float
1710
    @param timeout: for how long the operation should be retried (implies
1711
                    non-blocking mode).
1712
    @type errmsg: string
1713
    @param errmsg: error message in case operation fails.
1714

1715
    """
1716
    assert self.fd, "Lock was closed"
1717
    assert timeout is None or timeout >= 0, \
1718
      "If specified, timeout must be positive"
1719

    
1720
    if timeout is not None:
1721
      flag |= fcntl.LOCK_NB
1722
      timeout_end = time.time() + timeout
1723

    
1724
    # Blocking doesn't have effect with timeout
1725
    elif not blocking:
1726
      flag |= fcntl.LOCK_NB
1727
      timeout_end = None
1728

    
1729
    retry = True
1730
    while retry:
1731
      try:
1732
        fcntl.flock(self.fd, flag)
1733
        retry = False
1734
      except IOError, err:
1735
        if err.errno in (errno.EAGAIN, ):
1736
          if timeout_end is not None and time.time() < timeout_end:
1737
            # Wait before trying again
1738
            time.sleep(max(0.1, min(1.0, timeout)))
1739
          else:
1740
            raise errors.LockError(errmsg)
1741
        else:
1742
          logging.exception("fcntl.flock failed")
1743
          raise
1744

    
1745
  def Exclusive(self, blocking=False, timeout=None):
1746
    """Locks the file in exclusive mode.
1747

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

1755
    """
1756
    self._flock(fcntl.LOCK_EX, blocking, timeout,
1757
                "Failed to lock %s in exclusive mode" % self.filename)
1758

    
1759
  def Shared(self, blocking=False, timeout=None):
1760
    """Locks the file in shared mode.
1761

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

1769
    """
1770
    self._flock(fcntl.LOCK_SH, blocking, timeout,
1771
                "Failed to lock %s in shared mode" % self.filename)
1772

    
1773
  def Unlock(self, blocking=True, timeout=None):
1774
    """Unlocks the file.
1775

1776
    According to C{flock(2)}, unlocking can also be a nonblocking
1777
    operation::
1778

1779
      To make a non-blocking request, include LOCK_NB with any of the above
1780
      operations.
1781

1782
    @type blocking: boolean
1783
    @param blocking: whether to block and wait until we
1784
        can lock the file or return immediately
1785
    @type timeout: int or None
1786
    @param timeout: if not None, the duration to wait for the lock
1787
        (in blocking mode)
1788

1789
    """
1790
    self._flock(fcntl.LOCK_UN, blocking, timeout,
1791
                "Failed to unlock %s" % self.filename)
1792

    
1793

    
1794
class SignalHandler(object):
1795
  """Generic signal handler class.
1796

1797
  It automatically restores the original handler when deconstructed or
1798
  when L{Reset} is called. You can either pass your own handler
1799
  function in or query the L{called} attribute to detect whether the
1800
  signal was sent.
1801

1802
  @type signum: list
1803
  @ivar signum: the signals we handle
1804
  @type called: boolean
1805
  @ivar called: tracks whether any of the signals have been raised
1806

1807
  """
1808
  def __init__(self, signum):
1809
    """Constructs a new SignalHandler instance.
1810

1811
    @type signum: int or list of ints
1812
    @param signum: Single signal number or set of signal numbers
1813

1814
    """
1815
    if isinstance(signum, (int, long)):
1816
      self.signum = set([signum])
1817
    else:
1818
      self.signum = set(signum)
1819

    
1820
    self.called = False
1821

    
1822
    self._previous = {}
1823
    try:
1824
      for signum in self.signum:
1825
        # Setup handler
1826
        prev_handler = signal.signal(signum, self._HandleSignal)
1827
        try:
1828
          self._previous[signum] = prev_handler
1829
        except:
1830
          # Restore previous handler
1831
          signal.signal(signum, prev_handler)
1832
          raise
1833
    except:
1834
      # Reset all handlers
1835
      self.Reset()
1836
      # Here we have a race condition: a handler may have already been called,
1837
      # but there's not much we can do about it at this point.
1838
      raise
1839

    
1840
  def __del__(self):
1841
    self.Reset()
1842

    
1843
  def Reset(self):
1844
    """Restore previous handler.
1845

1846
    This will reset all the signals to their previous handlers.
1847

1848
    """
1849
    for signum, prev_handler in self._previous.items():
1850
      signal.signal(signum, prev_handler)
1851
      # If successful, remove from dict
1852
      del self._previous[signum]
1853

    
1854
  def Clear(self):
1855
    """Unsets the L{called} flag.
1856

1857
    This function can be used in case a signal may arrive several times.
1858

1859
    """
1860
    self.called = False
1861

    
1862
  def _HandleSignal(self, signum, frame):
1863
    """Actual signal handling function.
1864

1865
    """
1866
    # This is not nice and not absolutely atomic, but it appears to be the only
1867
    # solution in Python -- there are no atomic types.
1868
    self.called = True
1869

    
1870

    
1871
class FieldSet(object):
1872
  """A simple field set.
1873

1874
  Among the features are:
1875
    - checking if a string is among a list of static string or regex objects
1876
    - checking if a whole list of string matches
1877
    - returning the matching groups from a regex match
1878

1879
  Internally, all fields are held as regular expression objects.
1880

1881
  """
1882
  def __init__(self, *items):
1883
    self.items = [re.compile("^%s$" % value) for value in items]
1884

    
1885
  def Extend(self, other_set):
1886
    """Extend the field set with the items from another one"""
1887
    self.items.extend(other_set.items)
1888

    
1889
  def Matches(self, field):
1890
    """Checks if a field matches the current set
1891

1892
    @type field: str
1893
    @param field: the string to match
1894
    @return: either False or a regular expression match object
1895

1896
    """
1897
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
1898
      return m
1899
    return False
1900

    
1901
  def NonMatching(self, items):
1902
    """Returns the list of fields not matching the current set
1903

1904
    @type items: list
1905
    @param items: the list of fields to check
1906
    @rtype: list
1907
    @return: list of non-matching fields
1908

1909
    """
1910
    return [val for val in items if not self.Matches(val)]