Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ ae59efea

History | View | Annotate | Download (52.6 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

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

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

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

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

    
206
  while fdmap:
207
    try:
208
      pollresult = poller.poll()
209
    except EnvironmentError, eerr:
210
      if eerr.errno == errno.EINTR:
211
        continue
212
      raise
213
    except select.error, serr:
214
      if serr[0] == errno.EINTR:
215
        continue
216
      raise
217

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

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

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

    
238

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

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

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

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

    
271

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

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

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

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

    
288

    
289
def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
290
  """Renames a file.
291

292
  @type old: string
293
  @param old: Original path
294
  @type new: string
295
  @param new: New path
296
  @type mkdir: bool
297
  @param mkdir: Whether to create target directory if it doesn't exist
298
  @type mkdir_mode: int
299
  @param mkdir_mode: Mode for newly created directories
300

301
  """
302
  try:
303
    return os.rename(old, new)
304
  except OSError, err:
305
    # In at least one use case of this function, the job queue, directory
306
    # creation is very rare. Checking for the directory before renaming is not
307
    # as efficient.
308
    if mkdir and err.errno == errno.ENOENT:
309
      # Create directory and try again
310
      os.makedirs(os.path.dirname(new), mkdir_mode)
311
      return os.rename(old, new)
312
    raise
313

    
314

    
315
def _FingerprintFile(filename):
316
  """Compute the fingerprint of a file.
317

318
  If the file does not exist, a None will be returned
319
  instead.
320

321
  @type filename: str
322
  @param filename: the filename to checksum
323
  @rtype: str
324
  @return: the hex digest of the sha checksum of the contents
325
      of the file
326

327
  """
328
  if not (os.path.exists(filename) and os.path.isfile(filename)):
329
    return None
330

    
331
  f = open(filename)
332

    
333
  fp = sha.sha()
334
  while True:
335
    data = f.read(4096)
336
    if not data:
337
      break
338

    
339
    fp.update(data)
340

    
341
  return fp.hexdigest()
342

    
343

    
344
def FingerprintFiles(files):
345
  """Compute fingerprints for a list of files.
346

347
  @type files: list
348
  @param files: the list of filename to fingerprint
349
  @rtype: dict
350
  @return: a dictionary filename: fingerprint, holding only
351
      existing files
352

353
  """
354
  ret = {}
355

    
356
  for filename in files:
357
    cksum = _FingerprintFile(filename)
358
    if cksum:
359
      ret[filename] = cksum
360

    
361
  return ret
362

    
363

    
364
def CheckDict(target, template, logname=None):
365
  """Ensure a dictionary has a required set of keys.
366

367
  For the given dictionaries I{target} and I{template}, ensure
368
  I{target} has all the keys from I{template}. Missing keys are added
369
  with values from template.
370

371
  @type target: dict
372
  @param target: the dictionary to update
373
  @type template: dict
374
  @param template: the dictionary holding the default values
375
  @type logname: str or None
376
  @param logname: if not None, causes the missing keys to be
377
      logged with this name
378

379
  """
380
  missing = []
381
  for k in template:
382
    if k not in target:
383
      missing.append(k)
384
      target[k] = template[k]
385

    
386
  if missing and logname:
387
    logging.warning('%s missing keys %s', logname, ', '.join(missing))
388

    
389

    
390
def IsProcessAlive(pid):
391
  """Check if a given pid exists on the system.
392

393
  @note: zombie status is not handled, so zombie processes
394
      will be returned as alive
395
  @type pid: int
396
  @param pid: the process ID to check
397
  @rtype: boolean
398
  @return: True if the process exists
399

400
  """
401
  if pid <= 0:
402
    return False
403

    
404
  try:
405
    os.stat("/proc/%d/status" % pid)
406
    return True
407
  except EnvironmentError, err:
408
    if err.errno in (errno.ENOENT, errno.ENOTDIR):
409
      return False
410
    raise
411

    
412

    
413
def ReadPidFile(pidfile):
414
  """Read a pid from a file.
415

416
  @type  pidfile: string
417
  @param pidfile: path to the file containing the pid
418
  @rtype: int
419
  @return: The process id, if the file exista and contains a valid PID,
420
           otherwise 0
421

422
  """
423
  try:
424
    pf = open(pidfile, 'r')
425
  except EnvironmentError, err:
426
    if err.errno != errno.ENOENT:
427
      logging.exception("Can't read pid file?!")
428
    return 0
429

    
430
  try:
431
    pid = int(pf.read())
432
  except ValueError, err:
433
    logging.info("Can't parse pid file contents", exc_info=True)
434
    return 0
435

    
436
  return pid
437

    
438

    
439
def MatchNameComponent(key, name_list):
440
  """Try to match a name against a list.
441

442
  This function will try to match a name like test1 against a list
443
  like C{['test1.example.com', 'test2.example.com', ...]}. Against
444
  this list, I{'test1'} as well as I{'test1.example'} will match, but
445
  not I{'test1.ex'}. A multiple match will be considered as no match
446
  at all (e.g. I{'test1'} against C{['test1.example.com',
447
  'test1.example.org']}).
448

449
  @type key: str
450
  @param key: the name to be searched
451
  @type name_list: list
452
  @param name_list: the list of strings against which to search the key
453

454
  @rtype: None or str
455
  @return: None if there is no match I{or} if there are multiple matches,
456
      otherwise the element from the list which matches
457

458
  """
459
  mo = re.compile("^%s(\..*)?$" % re.escape(key))
460
  names_filtered = [name for name in name_list if mo.match(name) is not None]
461
  if len(names_filtered) != 1:
462
    return None
463
  return names_filtered[0]
464

    
465

    
466
class HostInfo:
467
  """Class implementing resolver and hostname functionality
468

469
  """
470
  def __init__(self, name=None):
471
    """Initialize the host name object.
472

473
    If the name argument is not passed, it will use this system's
474
    name.
475

476
    """
477
    if name is None:
478
      name = self.SysName()
479

    
480
    self.query = name
481
    self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
482
    self.ip = self.ipaddrs[0]
483

    
484
  def ShortName(self):
485
    """Returns the hostname without domain.
486

487
    """
488
    return self.name.split('.')[0]
489

    
490
  @staticmethod
491
  def SysName():
492
    """Return the current system's name.
493

494
    This is simply a wrapper over C{socket.gethostname()}.
495

496
    """
497
    return socket.gethostname()
498

    
499
  @staticmethod
500
  def LookupHostname(hostname):
501
    """Look up hostname
502

503
    @type hostname: str
504
    @param hostname: hostname to look up
505

506
    @rtype: tuple
507
    @return: a tuple (name, aliases, ipaddrs) as returned by
508
        C{socket.gethostbyname_ex}
509
    @raise errors.ResolverError: in case of errors in resolving
510

511
    """
512
    try:
513
      result = socket.gethostbyname_ex(hostname)
514
    except socket.gaierror, err:
515
      # hostname not found in DNS
516
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
517

    
518
    return result
519

    
520

    
521
def ListVolumeGroups():
522
  """List volume groups and their size
523

524
  @rtype: dict
525
  @return:
526
       Dictionary with keys volume name and values
527
       the size of the volume
528

529
  """
530
  command = "vgs --noheadings --units m --nosuffix -o name,size"
531
  result = RunCmd(command)
532
  retval = {}
533
  if result.failed:
534
    return retval
535

    
536
  for line in result.stdout.splitlines():
537
    try:
538
      name, size = line.split()
539
      size = int(float(size))
540
    except (IndexError, ValueError), err:
541
      logging.error("Invalid output from vgs (%s): %s", err, line)
542
      continue
543

    
544
    retval[name] = size
545

    
546
  return retval
547

    
548

    
549
def BridgeExists(bridge):
550
  """Check whether the given bridge exists in the system
551

552
  @type bridge: str
553
  @param bridge: the bridge name to check
554
  @rtype: boolean
555
  @return: True if it does
556

557
  """
558
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
559

    
560

    
561
def CheckBEParams(beparams):
562
  """Checks whether the user-supplied be-params are valid,
563
  and converts them from string format where appropriate.
564

565
  @type beparams: dict
566
  @param beparams: new params dict
567

568
  """
569
  if beparams:
570
    for item in beparams:
571
      if item not in constants.BES_PARAMETERS:
572
        raise errors.OpPrereqError("Unknown backend parameter %s" % item)
573
      if item in (constants.BE_MEMORY, constants.BE_VCPUS):
574
        val = beparams[item]
575
        if val != constants.VALUE_DEFAULT:
576
          try:
577
            val = int(val)
578
          except ValueError, err:
579
            raise errors.OpPrereqError("Invalid %s size: %s" % (item, str(err)))
580
          beparams[item] = val
581
      if item in (constants.BE_AUTO_BALANCE):
582
        val = beparams[item]
583
        if not isinstance(val, bool):
584
          if val == constants.VALUE_TRUE:
585
            beparams[item] = True
586
          elif val == constants.VALUE_FALSE:
587
            beparams[item] = False
588
          else:
589
            raise errors.OpPrereqError("Invalid %s value: %s" % (item, val))
590

    
591

    
592
def NiceSort(name_list):
593
  """Sort a list of strings based on digit and non-digit groupings.
594

595
  Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
596
  will sort the list in the logical order C{['a1', 'a2', 'a10',
597
  'a11']}.
598

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

603
  @type name_list: list
604
  @param name_list: the names to be sorted
605
  @rtype: list
606
  @return: a copy of the name list sorted with our algorithm
607

608
  """
609
  _SORTER_BASE = "(\D+|\d+)"
610
  _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
611
                                                  _SORTER_BASE, _SORTER_BASE,
612
                                                  _SORTER_BASE, _SORTER_BASE,
613
                                                  _SORTER_BASE, _SORTER_BASE)
614
  _SORTER_RE = re.compile(_SORTER_FULL)
615
  _SORTER_NODIGIT = re.compile("^\D*$")
616
  def _TryInt(val):
617
    """Attempts to convert a variable to integer."""
618
    if val is None or _SORTER_NODIGIT.match(val):
619
      return val
620
    rval = int(val)
621
    return rval
622

    
623
  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
624
             for name in name_list]
625
  to_sort.sort()
626
  return [tup[1] for tup in to_sort]
627

    
628

    
629
def TryConvert(fn, val):
630
  """Try to convert a value ignoring errors.
631

632
  This function tries to apply function I{fn} to I{val}. If no
633
  C{ValueError} or C{TypeError} exceptions are raised, it will return
634
  the result, else it will return the original value. Any other
635
  exceptions are propagated to the caller.
636

637
  @type fn: callable
638
  @param fn: function to apply to the value
639
  @param val: the value to be converted
640
  @return: The converted value if the conversion was successful,
641
      otherwise the original value.
642

643
  """
644
  try:
645
    nv = fn(val)
646
  except (ValueError, TypeError), err:
647
    nv = val
648
  return nv
649

    
650

    
651
def IsValidIP(ip):
652
  """Verifies the syntax of an IPv4 address.
653

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

657
  @type ip: str
658
  @param ip: the address to be checked
659
  @rtype: a regular expression match object
660
  @return: a regular epression match object, or None if the
661
      address is not valid
662

663
  """
664
  unit = "(0|[1-9]\d{0,2})"
665
  #TODO: convert and return only boolean
666
  return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
667

    
668

    
669
def IsValidShellParam(word):
670
  """Verifies is the given word is safe from the shell's p.o.v.
671

672
  This means that we can pass this to a command via the shell and be
673
  sure that it doesn't alter the command line and is passed as such to
674
  the actual command.
675

676
  Note that we are overly restrictive here, in order to be on the safe
677
  side.
678

679
  @type word: str
680
  @param word: the word to check
681
  @rtype: boolean
682
  @return: True if the word is 'safe'
683

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

    
687

    
688
def BuildShellCmd(template, *args):
689
  """Build a safe shell command line from the given arguments.
690

691
  This function will check all arguments in the args list so that they
692
  are valid shell parameters (i.e. they don't contain shell
693
  metacharaters). If everything is ok, it will return the result of
694
  template % args.
695

696
  @type template: str
697
  @param template: the string holding the template for the
698
      string formatting
699
  @rtype: str
700
  @return: the expanded command line
701

702
  """
703
  for word in args:
704
    if not IsValidShellParam(word):
705
      raise errors.ProgrammerError("Shell argument '%s' contains"
706
                                   " invalid characters" % word)
707
  return template % args
708

    
709

    
710
def FormatUnit(value, units):
711
  """Formats an incoming number of MiB with the appropriate unit.
712

713
  @type value: int
714
  @param value: integer representing the value in MiB (1048576)
715
  @type units: char
716
  @param units: the type of formatting we should do:
717
      - 'h' for automatic scaling
718
      - 'm' for MiBs
719
      - 'g' for GiBs
720
      - 't' for TiBs
721
  @rtype: str
722
  @return: the formatted value (with suffix)
723

724
  """
725
  if units not in ('m', 'g', 't', 'h'):
726
    raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
727

    
728
  suffix = ''
729

    
730
  if units == 'm' or (units == 'h' and value < 1024):
731
    if units == 'h':
732
      suffix = 'M'
733
    return "%d%s" % (round(value, 0), suffix)
734

    
735
  elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
736
    if units == 'h':
737
      suffix = 'G'
738
    return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
739

    
740
  else:
741
    if units == 'h':
742
      suffix = 'T'
743
    return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
744

    
745

    
746
def ParseUnit(input_string):
747
  """Tries to extract number and scale from the given string.
748

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

753
  """
754
  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', input_string)
755
  if not m:
756
    raise errors.UnitParseError("Invalid format")
757

    
758
  value = float(m.groups()[0])
759

    
760
  unit = m.groups()[1]
761
  if unit:
762
    lcunit = unit.lower()
763
  else:
764
    lcunit = 'm'
765

    
766
  if lcunit in ('m', 'mb', 'mib'):
767
    # Value already in MiB
768
    pass
769

    
770
  elif lcunit in ('g', 'gb', 'gib'):
771
    value *= 1024
772

    
773
  elif lcunit in ('t', 'tb', 'tib'):
774
    value *= 1024 * 1024
775

    
776
  else:
777
    raise errors.UnitParseError("Unknown unit: %s" % unit)
778

    
779
  # Make sure we round up
780
  if int(value) < value:
781
    value += 1
782

    
783
  # Round up to the next multiple of 4
784
  value = int(value)
785
  if value % 4:
786
    value += 4 - value % 4
787

    
788
  return value
789

    
790

    
791
def AddAuthorizedKey(file_name, key):
792
  """Adds an SSH public key to an authorized_keys file.
793

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

799
  """
800
  key_fields = key.split()
801

    
802
  f = open(file_name, 'a+')
803
  try:
804
    nl = True
805
    for line in f:
806
      # Ignore whitespace changes
807
      if line.split() == key_fields:
808
        break
809
      nl = line.endswith('\n')
810
    else:
811
      if not nl:
812
        f.write("\n")
813
      f.write(key.rstrip('\r\n'))
814
      f.write("\n")
815
      f.flush()
816
  finally:
817
    f.close()
818

    
819

    
820
def RemoveAuthorizedKey(file_name, key):
821
  """Removes an SSH public key from an authorized_keys file.
822

823
  @type file_name: str
824
  @param file_name: path to authorized_keys file
825
  @type key: str
826
  @param key: string containing key
827

828
  """
829
  key_fields = key.split()
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
        for line in f:
838
          # Ignore whitespace changes while comparing lines
839
          if line.split() != key_fields:
840
            out.write(line)
841

    
842
        out.flush()
843
        os.rename(tmpname, file_name)
844
      finally:
845
        f.close()
846
    finally:
847
      out.close()
848
  except:
849
    RemoveFile(tmpname)
850
    raise
851

    
852

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

856
  @type file_name: str
857
  @param file_name: path to the file to modify (usually C{/etc/hosts})
858
  @type ip: str
859
  @param ip: the IP address
860
  @type hostname: str
861
  @param hostname: the hostname to be added
862
  @type aliases: list
863
  @param aliases: the list of aliases to add for the hostname
864

865
  """
866
  # Ensure aliases are unique
867
  aliases = UniqueSequence([hostname] + aliases)[1:]
868

    
869
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
870
  try:
871
    out = os.fdopen(fd, 'w')
872
    try:
873
      f = open(file_name, 'r')
874
      try:
875
        for line in f:
876
          fields = line.split()
877
          if fields and not fields[0].startswith('#') and ip == fields[0]:
878
            continue
879
          out.write(line)
880

    
881
        out.write("%s\t%s" % (ip, hostname))
882
        if aliases:
883
          out.write(" %s" % ' '.join(aliases))
884
        out.write('\n')
885

    
886
        out.flush()
887
        os.fsync(out)
888
        os.rename(tmpname, file_name)
889
      finally:
890
        f.close()
891
    finally:
892
      out.close()
893
  except:
894
    RemoveFile(tmpname)
895
    raise
896

    
897

    
898
def AddHostToEtcHosts(hostname):
899
  """Wrapper around SetEtcHostsEntry.
900

901
  @type hostname: str
902
  @param hostname: a hostname that will be resolved and added to
903
      L{constants.ETC_HOSTS}
904

905
  """
906
  hi = HostInfo(name=hostname)
907
  SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
908

    
909

    
910
def RemoveEtcHostsEntry(file_name, hostname):
911
  """Removes a hostname from /etc/hosts.
912

913
  IP addresses without names are removed from the file.
914

915
  @type file_name: str
916
  @param file_name: path to the file to modify (usually C{/etc/hosts})
917
  @type hostname: str
918
  @param hostname: the hostname to be removed
919

920
  """
921
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
922
  try:
923
    out = os.fdopen(fd, 'w')
924
    try:
925
      f = open(file_name, 'r')
926
      try:
927
        for line in f:
928
          fields = line.split()
929
          if len(fields) > 1 and not fields[0].startswith('#'):
930
            names = fields[1:]
931
            if hostname in names:
932
              while hostname in names:
933
                names.remove(hostname)
934
              if names:
935
                out.write("%s %s\n" % (fields[0], ' '.join(names)))
936
              continue
937

    
938
          out.write(line)
939

    
940
        out.flush()
941
        os.fsync(out)
942
        os.rename(tmpname, file_name)
943
      finally:
944
        f.close()
945
    finally:
946
      out.close()
947
  except:
948
    RemoveFile(tmpname)
949
    raise
950

    
951

    
952
def RemoveHostFromEtcHosts(hostname):
953
  """Wrapper around RemoveEtcHostsEntry.
954

955
  @type hostname: str
956
  @param hostname: hostname that will be resolved and its
957
      full and shot name will be removed from
958
      L{constants.ETC_HOSTS}
959

960
  """
961
  hi = HostInfo(name=hostname)
962
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
963
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
964

    
965

    
966
def CreateBackup(file_name):
967
  """Creates a backup of a file.
968

969
  @type file_name: str
970
  @param file_name: file to be backed up
971
  @rtype: str
972
  @return: the path to the newly created backup
973
  @raise errors.ProgrammerError: for invalid file names
974

975
  """
976
  if not os.path.isfile(file_name):
977
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
978
                                file_name)
979

    
980
  prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
981
  dir_name = os.path.dirname(file_name)
982

    
983
  fsrc = open(file_name, 'rb')
984
  try:
985
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
986
    fdst = os.fdopen(fd, 'wb')
987
    try:
988
      shutil.copyfileobj(fsrc, fdst)
989
    finally:
990
      fdst.close()
991
  finally:
992
    fsrc.close()
993

    
994
  return backup_name
995

    
996

    
997
def ShellQuote(value):
998
  """Quotes shell argument according to POSIX.
999

1000
  @type value: str
1001
  @param value: the argument to be quoted
1002
  @rtype: str
1003
  @return: the quoted value
1004

1005
  """
1006
  if _re_shell_unquoted.match(value):
1007
    return value
1008
  else:
1009
    return "'%s'" % value.replace("'", "'\\''")
1010

    
1011

    
1012
def ShellQuoteArgs(args):
1013
  """Quotes a list of shell arguments.
1014

1015
  @type args: list
1016
  @param args: list of arguments to be quoted
1017
  @rtype: str
1018
  @return: the quoted arguments concatenaned with spaces
1019

1020
  """
1021
  return ' '.join([ShellQuote(i) for i in args])
1022

    
1023

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

1027
  Check if the given IP is reachable by doing attempting a TCP connect
1028
  to it.
1029

1030
  @type target: str
1031
  @param target: the IP or hostname to ping
1032
  @type port: int
1033
  @param port: the port to connect to
1034
  @type timeout: int
1035
  @param timeout: the timeout on the connection attemp
1036
  @type live_port_needed: boolean
1037
  @param live_port_needed: whether a closed port will cause the
1038
      function to return failure, as if there was a timeout
1039
  @type source: str or None
1040
  @param source: if specified, will cause the connect to be made
1041
      from this specific source address; failures to bind other
1042
      than C{EADDRNOTAVAIL} will be ignored
1043

1044
  """
1045
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1046

    
1047
  success = False
1048

    
1049
  if source is not None:
1050
    try:
1051
      sock.bind((source, 0))
1052
    except socket.error, (errcode, errstring):
1053
      if errcode == errno.EADDRNOTAVAIL:
1054
        success = False
1055

    
1056
  sock.settimeout(timeout)
1057

    
1058
  try:
1059
    sock.connect((target, port))
1060
    sock.close()
1061
    success = True
1062
  except socket.timeout:
1063
    success = False
1064
  except socket.error, (errcode, errstring):
1065
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
1066

    
1067
  return success
1068

    
1069

    
1070
def OwnIpAddress(address):
1071
  """Check if the current host has the the given IP address.
1072

1073
  Currently this is done by TCP-pinging the address from the loopback
1074
  address.
1075

1076
  @type address: string
1077
  @param address: the addres to check
1078
  @rtype: bool
1079
  @return: True if we own the address
1080

1081
  """
1082
  return TcpPing(address, constants.DEFAULT_NODED_PORT,
1083
                 source=constants.LOCALHOST_IP_ADDRESS)
1084

    
1085

    
1086
def ListVisibleFiles(path):
1087
  """Returns a list of visible files in a directory.
1088

1089
  @type path: str
1090
  @param path: the directory to enumerate
1091
  @rtype: list
1092
  @return: the list of all files not starting with a dot
1093

1094
  """
1095
  files = [i for i in os.listdir(path) if not i.startswith(".")]
1096
  files.sort()
1097
  return files
1098

    
1099

    
1100
def GetHomeDir(user, default=None):
1101
  """Try to get the homedir of the given user.
1102

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

1107
  """
1108
  try:
1109
    if isinstance(user, basestring):
1110
      result = pwd.getpwnam(user)
1111
    elif isinstance(user, (int, long)):
1112
      result = pwd.getpwuid(user)
1113
    else:
1114
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1115
                                   type(user))
1116
  except KeyError:
1117
    return default
1118
  return result.pw_dir
1119

    
1120

    
1121
def NewUUID():
1122
  """Returns a random UUID.
1123

1124
  @note: This is a Linux-specific method as it uses the /proc
1125
      filesystem.
1126
  @rtype: str
1127

1128
  """
1129
  f = open("/proc/sys/kernel/random/uuid", "r")
1130
  try:
1131
    return f.read(128).rstrip("\n")
1132
  finally:
1133
    f.close()
1134

    
1135

    
1136
def GenerateSecret():
1137
  """Generates a random secret.
1138

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

1142
  @rtype: str
1143
  @return: a sha1 hexdigest of a block of 64 random bytes
1144

1145
  """
1146
  return sha.new(os.urandom(64)).hexdigest()
1147

    
1148

    
1149
def ReadFile(file_name, size=None):
1150
  """Reads a file.
1151

1152
  @type size: None or int
1153
  @param size: Read at most size bytes
1154
  @rtype: str
1155
  @return: the (possibly partial) conent of the file
1156

1157
  """
1158
  f = open(file_name, "r")
1159
  try:
1160
    if size is None:
1161
      return f.read()
1162
    else:
1163
      return f.read(size)
1164
  finally:
1165
    f.close()
1166

    
1167

    
1168
def WriteFile(file_name, fn=None, data=None,
1169
              mode=None, uid=-1, gid=-1,
1170
              atime=None, mtime=None, close=True,
1171
              dry_run=False, backup=False,
1172
              prewrite=None, postwrite=None):
1173
  """(Over)write a file atomically.
1174

1175
  The file_name and either fn (a function taking one argument, the
1176
  file descriptor, and which should write the data to it) or data (the
1177
  contents of the file) must be passed. The other arguments are
1178
  optional and allow setting the file mode, owner and group, and the
1179
  mtime/atime of the file.
1180

1181
  If the function doesn't raise an exception, it has succeeded and the
1182
  target file has the new contents. If the file has raised an
1183
  exception, an existing target file should be unmodified and the
1184
  temporary file should be removed.
1185

1186
  @type file_name: str
1187
  @param file_name: the target filename
1188
  @type fn: callable
1189
  @param fn: content writing function, called with
1190
      file descriptor as parameter
1191
  @type data: sr
1192
  @param data: contents of the file
1193
  @type mode: int
1194
  @param mode: file mode
1195
  @type uid: int
1196
  @param uid: the owner of the file
1197
  @type gid: int
1198
  @param gid: the group of the file
1199
  @type atime: int
1200
  @param atime: a custom access time to be set on the file
1201
  @type mtime: int
1202
  @param mtime: a custom modification time to be set on the file
1203
  @type close: boolean
1204
  @param close: whether to close file after writing it
1205
  @type prewrite: callable
1206
  @param prewrite: function to be called before writing content
1207
  @type postwrite: callable
1208
  @param postwrite: function to be called after writing content
1209

1210
  @rtype: None or int
1211
  @return: None if the 'close' parameter evaluates to True,
1212
      otherwise the file descriptor
1213

1214
  @raise errors.ProgrammerError: if an of the arguments are not valid
1215

1216
  """
1217
  if not os.path.isabs(file_name):
1218
    raise errors.ProgrammerError("Path passed to WriteFile is not"
1219
                                 " absolute: '%s'" % file_name)
1220

    
1221
  if [fn, data].count(None) != 1:
1222
    raise errors.ProgrammerError("fn or data required")
1223

    
1224
  if [atime, mtime].count(None) == 1:
1225
    raise errors.ProgrammerError("Both atime and mtime must be either"
1226
                                 " set or None")
1227

    
1228
  if backup and not dry_run and os.path.isfile(file_name):
1229
    CreateBackup(file_name)
1230

    
1231
  dir_name, base_name = os.path.split(file_name)
1232
  fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
1233
  # here we need to make sure we remove the temp file, if any error
1234
  # leaves it in place
1235
  try:
1236
    if uid != -1 or gid != -1:
1237
      os.chown(new_name, uid, gid)
1238
    if mode:
1239
      os.chmod(new_name, mode)
1240
    if callable(prewrite):
1241
      prewrite(fd)
1242
    if data is not None:
1243
      os.write(fd, data)
1244
    else:
1245
      fn(fd)
1246
    if callable(postwrite):
1247
      postwrite(fd)
1248
    os.fsync(fd)
1249
    if atime is not None and mtime is not None:
1250
      os.utime(new_name, (atime, mtime))
1251
    if not dry_run:
1252
      os.rename(new_name, file_name)
1253
  finally:
1254
    if close:
1255
      os.close(fd)
1256
      result = None
1257
    else:
1258
      result = fd
1259
    RemoveFile(new_name)
1260

    
1261
  return result
1262

    
1263

    
1264
def FirstFree(seq, base=0):
1265
  """Returns the first non-existing integer from seq.
1266

1267
  The seq argument should be a sorted list of positive integers. The
1268
  first time the index of an element is smaller than the element
1269
  value, the index will be returned.
1270

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

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

1276
  @type seq: sequence
1277
  @param seq: the sequence to be analyzed.
1278
  @type base: int
1279
  @param base: use this value as the base index of the sequence
1280
  @rtype: int
1281
  @return: the first non-used index in the sequence
1282

1283
  """
1284
  for idx, elem in enumerate(seq):
1285
    assert elem >= base, "Passed element is higher than base offset"
1286
    if elem > idx + base:
1287
      # idx is not used
1288
      return idx + base
1289
  return None
1290

    
1291

    
1292
def all(seq, pred=bool):
1293
  "Returns True if pred(x) is True for every element in the iterable"
1294
  for elem in itertools.ifilterfalse(pred, seq):
1295
    return False
1296
  return True
1297

    
1298

    
1299
def any(seq, pred=bool):
1300
  "Returns True if pred(x) is True for at least one element in the iterable"
1301
  for elem in itertools.ifilter(pred, seq):
1302
    return True
1303
  return False
1304

    
1305

    
1306
def UniqueSequence(seq):
1307
  """Returns a list with unique elements.
1308

1309
  Element order is preserved.
1310

1311
  @type seq: sequence
1312
  @param seq: the sequence with the source elementes
1313
  @rtype: list
1314
  @return: list of unique elements from seq
1315

1316
  """
1317
  seen = set()
1318
  return [i for i in seq if i not in seen and not seen.add(i)]
1319

    
1320

    
1321
def IsValidMac(mac):
1322
  """Predicate to check if a MAC address is valid.
1323

1324
  Checks wether the supplied MAC address is formally correct, only
1325
  accepts colon separated format.
1326

1327
  @type mac: str
1328
  @param mac: the MAC to be validated
1329
  @rtype: boolean
1330
  @return: True is the MAC seems valid
1331

1332
  """
1333
  mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$")
1334
  return mac_check.match(mac) is not None
1335

    
1336

    
1337
def TestDelay(duration):
1338
  """Sleep for a fixed amount of time.
1339

1340
  @type duration: float
1341
  @param duration: the sleep duration
1342
  @rtype: boolean
1343
  @return: False for negative value, True otherwise
1344

1345
  """
1346
  if duration < 0:
1347
    return False
1348
  time.sleep(duration)
1349
  return True
1350

    
1351

    
1352
def _CloseFDNoErr(fd, retries=5):
1353
  """Close a file descriptor ignoring errors.
1354

1355
  @type fd: int
1356
  @param fd: the file descriptor
1357
  @type retries: int
1358
  @param retries: how many retries to make, in case we get any
1359
      other error than EBADF
1360

1361
  """
1362
  try:
1363
    os.close(fd)
1364
  except OSError, err:
1365
    if err.errno != errno.EBADF:
1366
      if retries > 0:
1367
        _CloseFDNoErr(fd, retries - 1)
1368
    # else either it's closed already or we're out of retries, so we
1369
    # ignore this and go on
1370

    
1371

    
1372
def CloseFDs(noclose_fds=None):
1373
  """Close file descriptors.
1374

1375
  This closes all file descriptors above 2 (i.e. except
1376
  stdin/out/err).
1377

1378
  @type noclose_fds: list or None
1379
  @param noclose_fds: if given, it denotes a list of file descriptor
1380
      that should not be closed
1381

1382
  """
1383
  # Default maximum for the number of available file descriptors.
1384
  if 'SC_OPEN_MAX' in os.sysconf_names:
1385
    try:
1386
      MAXFD = os.sysconf('SC_OPEN_MAX')
1387
      if MAXFD < 0:
1388
        MAXFD = 1024
1389
    except OSError:
1390
      MAXFD = 1024
1391
  else:
1392
    MAXFD = 1024
1393
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1394
  if (maxfd == resource.RLIM_INFINITY):
1395
    maxfd = MAXFD
1396

    
1397
  # Iterate through and close all file descriptors (except the standard ones)
1398
  for fd in range(3, maxfd):
1399
    if noclose_fds and fd in noclose_fds:
1400
      continue
1401
    _CloseFDNoErr(fd)
1402

    
1403

    
1404
def Daemonize(logfile):
1405
  """Daemonize the current process.
1406

1407
  This detaches the current process from the controlling terminal and
1408
  runs it in the background as a daemon.
1409

1410
  @type logfile: str
1411
  @param logfile: the logfile to which we should redirect stdout/stderr
1412
  @rtype: int
1413
  @returns: the value zero
1414

1415
  """
1416
  UMASK = 077
1417
  WORKDIR = "/"
1418

    
1419
  # this might fail
1420
  pid = os.fork()
1421
  if (pid == 0):  # The first child.
1422
    os.setsid()
1423
    # this might fail
1424
    pid = os.fork() # Fork a second child.
1425
    if (pid == 0):  # The second child.
1426
      os.chdir(WORKDIR)
1427
      os.umask(UMASK)
1428
    else:
1429
      # exit() or _exit()?  See below.
1430
      os._exit(0) # Exit parent (the first child) of the second child.
1431
  else:
1432
    os._exit(0) # Exit parent of the first child.
1433

    
1434
  for fd in range(3):
1435
    _CloseFDNoErr(fd)
1436
  i = os.open("/dev/null", os.O_RDONLY) # stdin
1437
  assert i == 0, "Can't close/reopen stdin"
1438
  i = os.open(logfile, os.O_WRONLY|os.O_CREAT|os.O_APPEND, 0600) # stdout
1439
  assert i == 1, "Can't close/reopen stdout"
1440
  # Duplicate standard output to standard error.
1441
  os.dup2(1, 2)
1442
  return 0
1443

    
1444

    
1445
def DaemonPidFileName(name):
1446
  """Compute a ganeti pid file absolute path
1447

1448
  @type name: str
1449
  @param name: the daemon name
1450
  @rtype: str
1451
  @return: the full path to the pidfile corresponding to the given
1452
      daemon name
1453

1454
  """
1455
  return os.path.join(constants.RUN_GANETI_DIR, "%s.pid" % name)
1456

    
1457

    
1458
def WritePidFile(name):
1459
  """Write the current process pidfile.
1460

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

1463
  @type name: str
1464
  @param name: the daemon name to use
1465
  @raise errors.GenericError: if the pid file already exists and
1466
      points to a live process
1467

1468
  """
1469
  pid = os.getpid()
1470
  pidfilename = DaemonPidFileName(name)
1471
  if IsProcessAlive(ReadPidFile(pidfilename)):
1472
    raise errors.GenericError("%s contains a live process" % pidfilename)
1473

    
1474
  WriteFile(pidfilename, data="%d\n" % pid)
1475

    
1476

    
1477
def RemovePidFile(name):
1478
  """Remove the current process pidfile.
1479

1480
  Any errors are ignored.
1481

1482
  @type name: str
1483
  @param name: the daemon name used to derive the pidfile name
1484

1485
  """
1486
  pid = os.getpid()
1487
  pidfilename = DaemonPidFileName(name)
1488
  # TODO: we could check here that the file contains our pid
1489
  try:
1490
    RemoveFile(pidfilename)
1491
  except:
1492
    pass
1493

    
1494

    
1495
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1496
                waitpid=False):
1497
  """Kill a process given by its pid.
1498

1499
  @type pid: int
1500
  @param pid: The PID to terminate.
1501
  @type signal_: int
1502
  @param signal_: The signal to send, by default SIGTERM
1503
  @type timeout: int
1504
  @param timeout: The timeout after which, if the process is still alive,
1505
                  a SIGKILL will be sent. If not positive, no such checking
1506
                  will be done
1507
  @type waitpid: boolean
1508
  @param waitpid: If true, we should waitpid on this process after
1509
      sending signals, since it's our own child and otherwise it
1510
      would remain as zombie
1511

1512
  """
1513
  def _helper(pid, signal_, wait):
1514
    """Simple helper to encapsulate the kill/waitpid sequence"""
1515
    os.kill(pid, signal_)
1516
    if wait:
1517
      try:
1518
        os.waitpid(pid, os.WNOHANG)
1519
      except OSError:
1520
        pass
1521

    
1522
  if pid <= 0:
1523
    # kill with pid=0 == suicide
1524
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1525

    
1526
  if not IsProcessAlive(pid):
1527
    return
1528
  _helper(pid, signal_, waitpid)
1529
  if timeout <= 0:
1530
    return
1531

    
1532
  # Wait up to $timeout seconds
1533
  end = time.time() + timeout
1534
  wait = 0.01
1535
  while time.time() < end and IsProcessAlive(pid):
1536
    try:
1537
      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
1538
      if result_pid > 0:
1539
        break
1540
    except OSError:
1541
      pass
1542
    time.sleep(wait)
1543
    # Make wait time longer for next try
1544
    if wait < 0.1:
1545
      wait *= 1.5
1546

    
1547
  if IsProcessAlive(pid):
1548
    # Kill process if it's still alive
1549
    _helper(pid, signal.SIGKILL, waitpid)
1550

    
1551

    
1552
def FindFile(name, search_path, test=os.path.exists):
1553
  """Look for a filesystem object in a given path.
1554

1555
  This is an abstract method to search for filesystem object (files,
1556
  dirs) under a given search path.
1557

1558
  @type name: str
1559
  @param name: the name to look for
1560
  @type search_path: str
1561
  @param search_path: location to start at
1562
  @type test: callable
1563
  @param test: a function taking one argument that should return True
1564
      if the a given object is valid; the default value is
1565
      os.path.exists, causing only existing files to be returned
1566
  @rtype: str or None
1567
  @return: full path to the object if found, None otherwise
1568

1569
  """
1570
  for dir_name in search_path:
1571
    item_name = os.path.sep.join([dir_name, name])
1572
    if test(item_name):
1573
      return item_name
1574
  return None
1575

    
1576

    
1577
def CheckVolumeGroupSize(vglist, vgname, minsize):
1578
  """Checks if the volume group list is valid.
1579

1580
  The function will check if a given volume group is in the list of
1581
  volume groups and has a minimum size.
1582

1583
  @type vglist: dict
1584
  @param vglist: dictionary of volume group names and their size
1585
  @type vgname: str
1586
  @param vgname: the volume group we should check
1587
  @type minsize: int
1588
  @param minsize: the minimum size we accept
1589
  @rtype: None or str
1590
  @return: None for success, otherwise the error message
1591

1592
  """
1593
  vgsize = vglist.get(vgname, None)
1594
  if vgsize is None:
1595
    return "volume group '%s' missing" % vgname
1596
  elif vgsize < minsize:
1597
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1598
            (vgname, minsize, vgsize))
1599
  return None
1600

    
1601

    
1602
def SplitTime(value):
1603
  """Splits time as floating point number into a tuple.
1604

1605
  @param value: Time in seconds
1606
  @type value: int or float
1607
  @return: Tuple containing (seconds, microseconds)
1608

1609
  """
1610
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1611

    
1612
  assert 0 <= seconds, \
1613
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1614
  assert 0 <= microseconds <= 999999, \
1615
    "Microseconds must be 0-999999, but are %s" % microseconds
1616

    
1617
  return (int(seconds), int(microseconds))
1618

    
1619

    
1620
def MergeTime(timetuple):
1621
  """Merges a tuple into time as a floating point number.
1622

1623
  @param timetuple: Time as tuple, (seconds, microseconds)
1624
  @type timetuple: tuple
1625
  @return: Time as a floating point number expressed in seconds
1626

1627
  """
1628
  (seconds, microseconds) = timetuple
1629

    
1630
  assert 0 <= seconds, \
1631
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1632
  assert 0 <= microseconds <= 999999, \
1633
    "Microseconds must be 0-999999, but are %s" % microseconds
1634

    
1635
  return float(seconds) + (float(microseconds) * 0.000001)
1636

    
1637

    
1638
def GetNodeDaemonPort():
1639
  """Get the node daemon port for this cluster.
1640

1641
  Note that this routine does not read a ganeti-specific file, but
1642
  instead uses C{socket.getservbyname} to allow pre-customization of
1643
  this parameter outside of Ganeti.
1644

1645
  @rtype: int
1646

1647
  """
1648
  try:
1649
    port = socket.getservbyname("ganeti-noded", "tcp")
1650
  except socket.error:
1651
    port = constants.DEFAULT_NODED_PORT
1652

    
1653
  return port
1654

    
1655

    
1656
def SetupLogging(logfile, debug=False, stderr_logging=False, program=""):
1657
  """Configures the logging module.
1658

1659
  @type logfile: str
1660
  @param logfile: the filename to which we should log
1661
  @type debug: boolean
1662
  @param debug: whether to enable debug messages too or
1663
      only those at C{INFO} and above level
1664
  @type stderr_logging: boolean
1665
  @param stderr_logging: whether we should also log to the standard error
1666
  @type program: str
1667
  @param program: the name under which we should log messages
1668
  @raise EnvironmentError: if we can't open the log file and
1669
      stderr logging is disabled
1670

1671
  """
1672
  fmt = "%(asctime)s: " + program + " "
1673
  if debug:
1674
    fmt += ("pid=%(process)d/%(threadName)s %(levelname)s"
1675
           " %(module)s:%(lineno)s %(message)s")
1676
  else:
1677
    fmt += "pid=%(process)d %(levelname)s %(message)s"
1678
  formatter = logging.Formatter(fmt)
1679

    
1680
  root_logger = logging.getLogger("")
1681
  root_logger.setLevel(logging.NOTSET)
1682

    
1683
  # Remove all previously setup handlers
1684
  for handler in root_logger.handlers:
1685
    handler.close()
1686
    root_logger.removeHandler(handler)
1687

    
1688
  if stderr_logging:
1689
    stderr_handler = logging.StreamHandler()
1690
    stderr_handler.setFormatter(formatter)
1691
    if debug:
1692
      stderr_handler.setLevel(logging.NOTSET)
1693
    else:
1694
      stderr_handler.setLevel(logging.CRITICAL)
1695
    root_logger.addHandler(stderr_handler)
1696

    
1697
  # this can fail, if the logging directories are not setup or we have
1698
  # a permisssion problem; in this case, it's best to log but ignore
1699
  # the error if stderr_logging is True, and if false we re-raise the
1700
  # exception since otherwise we could run but without any logs at all
1701
  try:
1702
    logfile_handler = logging.FileHandler(logfile)
1703
    logfile_handler.setFormatter(formatter)
1704
    if debug:
1705
      logfile_handler.setLevel(logging.DEBUG)
1706
    else:
1707
      logfile_handler.setLevel(logging.INFO)
1708
    root_logger.addHandler(logfile_handler)
1709
  except EnvironmentError, err:
1710
    if stderr_logging:
1711
      logging.exception("Failed to enable logging to file '%s'", logfile)
1712
    else:
1713
      # we need to re-raise the exception
1714
      raise
1715

    
1716

    
1717
def TailFile(fname, lines=20):
1718
  """Return the last lines from a file.
1719

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

1724
  @param fname: the file name
1725
  @type lines: int
1726
  @param lines: the (maximum) number of lines to return
1727

1728
  """
1729
  fd = open(fname, "r")
1730
  try:
1731
    fd.seek(0, 2)
1732
    pos = fd.tell()
1733
    pos = max(0, pos-4096)
1734
    fd.seek(pos, 0)
1735
    raw_data = fd.read()
1736
  finally:
1737
    fd.close()
1738

    
1739
  rows = raw_data.splitlines()
1740
  return rows[-lines:]
1741

    
1742

    
1743
def LockedMethod(fn):
1744
  """Synchronized object access decorator.
1745

1746
  This decorator is intended to protect access to an object using the
1747
  object's own lock which is hardcoded to '_lock'.
1748

1749
  """
1750
  def _LockDebug(*args, **kwargs):
1751
    if debug_locks:
1752
      logging.debug(*args, **kwargs)
1753

    
1754
  def wrapper(self, *args, **kwargs):
1755
    assert hasattr(self, '_lock')
1756
    lock = self._lock
1757
    _LockDebug("Waiting for %s", lock)
1758
    lock.acquire()
1759
    try:
1760
      _LockDebug("Acquired %s", lock)
1761
      result = fn(self, *args, **kwargs)
1762
    finally:
1763
      _LockDebug("Releasing %s", lock)
1764
      lock.release()
1765
      _LockDebug("Released %s", lock)
1766
    return result
1767
  return wrapper
1768

    
1769

    
1770
def LockFile(fd):
1771
  """Locks a file using POSIX locks.
1772

1773
  @type fd: int
1774
  @param fd: the file descriptor we need to lock
1775

1776
  """
1777
  try:
1778
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
1779
  except IOError, err:
1780
    if err.errno == errno.EAGAIN:
1781
      raise errors.LockError("File already locked")
1782
    raise
1783

    
1784

    
1785
class FileLock(object):
1786
  """Utility class for file locks.
1787

1788
  """
1789
  def __init__(self, filename):
1790
    """Constructor for FileLock.
1791

1792
    This will open the file denoted by the I{filename} argument.
1793

1794
    @type filename: str
1795
    @param filename: path to the file to be locked
1796

1797
    """
1798
    self.filename = filename
1799
    self.fd = open(self.filename, "w")
1800

    
1801
  def __del__(self):
1802
    self.Close()
1803

    
1804
  def Close(self):
1805
    """Close the file and release the lock.
1806

1807
    """
1808
    if self.fd:
1809
      self.fd.close()
1810
      self.fd = None
1811

    
1812
  def _flock(self, flag, blocking, timeout, errmsg):
1813
    """Wrapper for fcntl.flock.
1814

1815
    @type flag: int
1816
    @param flag: operation flag
1817
    @type blocking: bool
1818
    @param blocking: whether the operation should be done in blocking mode.
1819
    @type timeout: None or float
1820
    @param timeout: for how long the operation should be retried (implies
1821
                    non-blocking mode).
1822
    @type errmsg: string
1823
    @param errmsg: error message in case operation fails.
1824

1825
    """
1826
    assert self.fd, "Lock was closed"
1827
    assert timeout is None or timeout >= 0, \
1828
      "If specified, timeout must be positive"
1829

    
1830
    if timeout is not None:
1831
      flag |= fcntl.LOCK_NB
1832
      timeout_end = time.time() + timeout
1833

    
1834
    # Blocking doesn't have effect with timeout
1835
    elif not blocking:
1836
      flag |= fcntl.LOCK_NB
1837
      timeout_end = None
1838

    
1839
    retry = True
1840
    while retry:
1841
      try:
1842
        fcntl.flock(self.fd, flag)
1843
        retry = False
1844
      except IOError, err:
1845
        if err.errno in (errno.EAGAIN, ):
1846
          if timeout_end is not None and time.time() < timeout_end:
1847
            # Wait before trying again
1848
            time.sleep(max(0.1, min(1.0, timeout)))
1849
          else:
1850
            raise errors.LockError(errmsg)
1851
        else:
1852
          logging.exception("fcntl.flock failed")
1853
          raise
1854

    
1855
  def Exclusive(self, blocking=False, timeout=None):
1856
    """Locks the file in exclusive mode.
1857

1858
    @type blocking: boolean
1859
    @param blocking: whether to block and wait until we
1860
        can lock the file or return immediately
1861
    @type timeout: int or None
1862
    @param timeout: if not None, the duration to wait for the lock
1863
        (in blocking mode)
1864

1865
    """
1866
    self._flock(fcntl.LOCK_EX, blocking, timeout,
1867
                "Failed to lock %s in exclusive mode" % self.filename)
1868

    
1869
  def Shared(self, blocking=False, timeout=None):
1870
    """Locks the file in shared mode.
1871

1872
    @type blocking: boolean
1873
    @param blocking: whether to block and wait until we
1874
        can lock the file or return immediately
1875
    @type timeout: int or None
1876
    @param timeout: if not None, the duration to wait for the lock
1877
        (in blocking mode)
1878

1879
    """
1880
    self._flock(fcntl.LOCK_SH, blocking, timeout,
1881
                "Failed to lock %s in shared mode" % self.filename)
1882

    
1883
  def Unlock(self, blocking=True, timeout=None):
1884
    """Unlocks the file.
1885

1886
    According to C{flock(2)}, unlocking can also be a nonblocking
1887
    operation::
1888

1889
      To make a non-blocking request, include LOCK_NB with any of the above
1890
      operations.
1891

1892
    @type blocking: boolean
1893
    @param blocking: whether to block and wait until we
1894
        can lock the file or return immediately
1895
    @type timeout: int or None
1896
    @param timeout: if not None, the duration to wait for the lock
1897
        (in blocking mode)
1898

1899
    """
1900
    self._flock(fcntl.LOCK_UN, blocking, timeout,
1901
                "Failed to unlock %s" % self.filename)
1902

    
1903

    
1904
class SignalHandler(object):
1905
  """Generic signal handler class.
1906

1907
  It automatically restores the original handler when deconstructed or
1908
  when L{Reset} is called. You can either pass your own handler
1909
  function in or query the L{called} attribute to detect whether the
1910
  signal was sent.
1911

1912
  @type signum: list
1913
  @ivar signum: the signals we handle
1914
  @type called: boolean
1915
  @ivar called: tracks whether any of the signals have been raised
1916

1917
  """
1918
  def __init__(self, signum):
1919
    """Constructs a new SignalHandler instance.
1920

1921
    @type signum: int or list of ints
1922
    @param signum: Single signal number or set of signal numbers
1923

1924
    """
1925
    if isinstance(signum, (int, long)):
1926
      self.signum = set([signum])
1927
    else:
1928
      self.signum = set(signum)
1929

    
1930
    self.called = False
1931

    
1932
    self._previous = {}
1933
    try:
1934
      for signum in self.signum:
1935
        # Setup handler
1936
        prev_handler = signal.signal(signum, self._HandleSignal)
1937
        try:
1938
          self._previous[signum] = prev_handler
1939
        except:
1940
          # Restore previous handler
1941
          signal.signal(signum, prev_handler)
1942
          raise
1943
    except:
1944
      # Reset all handlers
1945
      self.Reset()
1946
      # Here we have a race condition: a handler may have already been called,
1947
      # but there's not much we can do about it at this point.
1948
      raise
1949

    
1950
  def __del__(self):
1951
    self.Reset()
1952

    
1953
  def Reset(self):
1954
    """Restore previous handler.
1955

1956
    This will reset all the signals to their previous handlers.
1957

1958
    """
1959
    for signum, prev_handler in self._previous.items():
1960
      signal.signal(signum, prev_handler)
1961
      # If successful, remove from dict
1962
      del self._previous[signum]
1963

    
1964
  def Clear(self):
1965
    """Unsets the L{called} flag.
1966

1967
    This function can be used in case a signal may arrive several times.
1968

1969
    """
1970
    self.called = False
1971

    
1972
  def _HandleSignal(self, signum, frame):
1973
    """Actual signal handling function.
1974

1975
    """
1976
    # This is not nice and not absolutely atomic, but it appears to be the only
1977
    # solution in Python -- there are no atomic types.
1978
    self.called = True
1979

    
1980

    
1981
class FieldSet(object):
1982
  """A simple field set.
1983

1984
  Among the features are:
1985
    - checking if a string is among a list of static string or regex objects
1986
    - checking if a whole list of string matches
1987
    - returning the matching groups from a regex match
1988

1989
  Internally, all fields are held as regular expression objects.
1990

1991
  """
1992
  def __init__(self, *items):
1993
    self.items = [re.compile("^%s$" % value) for value in items]
1994

    
1995
  def Extend(self, other_set):
1996
    """Extend the field set with the items from another one"""
1997
    self.items.extend(other_set.items)
1998

    
1999
  def Matches(self, field):
2000
    """Checks if a field matches the current set
2001

2002
    @type field: str
2003
    @param field: the string to match
2004
    @return: either False or a regular expression match object
2005

2006
    """
2007
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
2008
      return m
2009
    return False
2010

    
2011
  def NonMatching(self, items):
2012
    """Returns the list of fields not matching the current set
2013

2014
    @type items: list
2015
    @param items: the list of fields to check
2016
    @rtype: list
2017
    @return: list of non-matching fields
2018

2019
    """
2020
    return [val for val in items if not self.Matches(val)]