Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ 6e797216

History | View | Annotate | Download (51.3 kB)

1
#
2
#
3

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

    
21

    
22
"""Ganeti utility module.
23

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

27
"""
28

    
29

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

    
48
from cStringIO import StringIO
49

    
50
from ganeti import errors
51
from ganeti import constants
52

    
53

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

    
57
debug = False
58
debug_locks = False
59

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

    
63

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

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

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

    
86

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

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

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

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

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

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

    
114

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
237

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

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

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

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

    
270

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

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

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

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

    
287

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

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

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

    
313

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

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

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

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

    
330
  f = open(filename)
331

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

    
338
    fp.update(data)
339

    
340
  return fp.hexdigest()
341

    
342

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

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

352
  """
353
  ret = {}
354

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

    
360
  return ret
361

    
362

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

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

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

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

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

    
388

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

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

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

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

    
411

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

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

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

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

    
435
  return pid
436

    
437

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

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

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

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

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

    
464

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
517
    return result
518

    
519

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

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

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

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

    
543
    retval[name] = size
544

    
545
  return retval
546

    
547

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

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

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

    
559

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

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

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

    
590

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

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

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

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

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

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

    
627

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

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

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

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

    
649

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

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

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

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

    
667

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

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

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

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

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

    
686

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

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

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

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

    
708

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

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

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

    
727
  suffix = ''
728

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

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

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

    
744

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

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

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

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

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

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

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

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

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

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

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

    
787
  return value
788

    
789

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

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

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

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

    
818

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

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

827
  """
828
  key_fields = key.split()
829

    
830
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
831
  try:
832
    out = os.fdopen(fd, 'w')
833
    try:
834
      f = open(file_name, 'r')
835
      try:
836
        for line in f:
837
          # Ignore whitespace changes while comparing lines
838
          if line.split() != key_fields:
839
            out.write(line)
840

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

    
851

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

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

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

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

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

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

    
896

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

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

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

    
908

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

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

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

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

    
937
          out.write(line)
938

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

    
950

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

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

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

    
964

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

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

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

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

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

    
993
  return backup_name
994

    
995

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

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

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

    
1010

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

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

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

    
1022

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

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

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

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

    
1046
  success = False
1047

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

    
1055
  sock.settimeout(timeout)
1056

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

    
1066
  return success
1067

    
1068

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

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

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

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

    
1084

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

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

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

    
1098

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

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

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

    
1119

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

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

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

    
1134

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

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

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

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

    
1147

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

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

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

    
1166

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

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

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

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

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

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

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

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

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

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

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

    
1260
  return result
1261

    
1262

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

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

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

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

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

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

    
1290

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

    
1297

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

    
1304

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

1308
  Element order is preserved.
1309

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

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

    
1319

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

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

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

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

    
1335

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

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

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

    
1350

    
1351
def Daemonize(logfile, noclose_fds=None):
1352
  """Daemonize the current process.
1353

1354
  This detaches the current process from the controlling terminal and
1355
  runs it in the background as a daemon.
1356

1357
  @type logfile: str
1358
  @param logfile: the logfile to which we should redirect stdout/stderr
1359
  @type noclose_fds: list or None
1360
  @param noclose_fds: if given, it denotes a list of file descriptor
1361
      that should not be closed
1362
  @rtype: int
1363
  @returns: the value zero
1364

1365
  """
1366
  UMASK = 077
1367
  WORKDIR = "/"
1368
  # Default maximum for the number of available file descriptors.
1369
  if 'SC_OPEN_MAX' in os.sysconf_names:
1370
    try:
1371
      MAXFD = os.sysconf('SC_OPEN_MAX')
1372
      if MAXFD < 0:
1373
        MAXFD = 1024
1374
    except OSError:
1375
      MAXFD = 1024
1376
  else:
1377
    MAXFD = 1024
1378

    
1379
  # this might fail
1380
  pid = os.fork()
1381
  if (pid == 0):  # The first child.
1382
    os.setsid()
1383
    # this might fail
1384
    pid = os.fork() # Fork a second child.
1385
    if (pid == 0):  # The second child.
1386
      os.chdir(WORKDIR)
1387
      os.umask(UMASK)
1388
    else:
1389
      # exit() or _exit()?  See below.
1390
      os._exit(0) # Exit parent (the first child) of the second child.
1391
  else:
1392
    os._exit(0) # Exit parent of the first child.
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.
1398
  for fd in range(0, maxfd):
1399
    if noclose_fds and fd in noclose_fds:
1400
      continue
1401
    try:
1402
      os.close(fd)
1403
    except OSError: # ERROR, fd wasn't open to begin with (ignored)
1404
      pass
1405
  os.open(logfile, os.O_RDWR|os.O_CREAT|os.O_APPEND, 0600)
1406
  # Duplicate standard input to standard output and standard error.
1407
  os.dup2(0, 1)     # standard output (1)
1408
  os.dup2(0, 2)     # standard error (2)
1409
  return 0
1410

    
1411

    
1412
def DaemonPidFileName(name):
1413
  """Compute a ganeti pid file absolute path
1414

1415
  @type name: str
1416
  @param name: the daemon name
1417
  @rtype: str
1418
  @return: the full path to the pidfile corresponding to the given
1419
      daemon name
1420

1421
  """
1422
  return os.path.join(constants.RUN_GANETI_DIR, "%s.pid" % name)
1423

    
1424

    
1425
def WritePidFile(name):
1426
  """Write the current process pidfile.
1427

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

1430
  @type name: str
1431
  @param name: the daemon name to use
1432
  @raise errors.GenericError: if the pid file already exists and
1433
      points to a live process
1434

1435
  """
1436
  pid = os.getpid()
1437
  pidfilename = DaemonPidFileName(name)
1438
  if IsProcessAlive(ReadPidFile(pidfilename)):
1439
    raise errors.GenericError("%s contains a live process" % pidfilename)
1440

    
1441
  WriteFile(pidfilename, data="%d\n" % pid)
1442

    
1443

    
1444
def RemovePidFile(name):
1445
  """Remove the current process pidfile.
1446

1447
  Any errors are ignored.
1448

1449
  @type name: str
1450
  @param name: the daemon name used to derive the pidfile name
1451

1452
  """
1453
  pid = os.getpid()
1454
  pidfilename = DaemonPidFileName(name)
1455
  # TODO: we could check here that the file contains our pid
1456
  try:
1457
    RemoveFile(pidfilename)
1458
  except:
1459
    pass
1460

    
1461

    
1462
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1463
                waitpid=False):
1464
  """Kill a process given by its pid.
1465

1466
  @type pid: int
1467
  @param pid: The PID to terminate.
1468
  @type signal_: int
1469
  @param signal_: The signal to send, by default SIGTERM
1470
  @type timeout: int
1471
  @param timeout: The timeout after which, if the process is still alive,
1472
                  a SIGKILL will be sent. If not positive, no such checking
1473
                  will be done
1474
  @type waitpid: boolean
1475
  @param waitpid: If true, we should waitpid on this process after
1476
      sending signals, since it's our own child and otherwise it
1477
      would remain as zombie
1478

1479
  """
1480
  def _helper(pid, signal_, wait):
1481
    """Simple helper to encapsulate the kill/waitpid sequence"""
1482
    os.kill(pid, signal_)
1483
    if wait:
1484
      try:
1485
        os.waitpid(pid, os.WNOHANG)
1486
      except OSError:
1487
        pass
1488

    
1489
  if pid <= 0:
1490
    # kill with pid=0 == suicide
1491
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1492

    
1493
  if not IsProcessAlive(pid):
1494
    return
1495
  _helper(pid, signal_, waitpid)
1496
  if timeout <= 0:
1497
    return
1498

    
1499
  # Wait up to $timeout seconds
1500
  end = time.time() + timeout
1501
  wait = 0.01
1502
  while time.time() < end and IsProcessAlive(pid):
1503
    try:
1504
      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
1505
      if result_pid > 0:
1506
        break
1507
    except OSError:
1508
      pass
1509
    time.sleep(wait)
1510
    # Make wait time longer for next try
1511
    if wait < 0.1:
1512
      wait *= 1.5
1513

    
1514
  if IsProcessAlive(pid):
1515
    # Kill process if it's still alive
1516
    _helper(pid, signal.SIGKILL, waitpid)
1517

    
1518

    
1519
def FindFile(name, search_path, test=os.path.exists):
1520
  """Look for a filesystem object in a given path.
1521

1522
  This is an abstract method to search for filesystem object (files,
1523
  dirs) under a given search path.
1524

1525
  @type name: str
1526
  @param name: the name to look for
1527
  @type search_path: str
1528
  @param search_path: location to start at
1529
  @type test: callable
1530
  @param test: a function taking one argument that should return True
1531
      if the a given object is valid; the default value is
1532
      os.path.exists, causing only existing files to be returned
1533
  @rtype: str or None
1534
  @return: full path to the object if found, None otherwise
1535

1536
  """
1537
  for dir_name in search_path:
1538
    item_name = os.path.sep.join([dir_name, name])
1539
    if test(item_name):
1540
      return item_name
1541
  return None
1542

    
1543

    
1544
def CheckVolumeGroupSize(vglist, vgname, minsize):
1545
  """Checks if the volume group list is valid.
1546

1547
  The function will check if a given volume group is in the list of
1548
  volume groups and has a minimum size.
1549

1550
  @type vglist: dict
1551
  @param vglist: dictionary of volume group names and their size
1552
  @type vgname: str
1553
  @param vgname: the volume group we should check
1554
  @type minsize: int
1555
  @param minsize: the minimum size we accept
1556
  @rtype: None or str
1557
  @return: None for success, otherwise the error message
1558

1559
  """
1560
  vgsize = vglist.get(vgname, None)
1561
  if vgsize is None:
1562
    return "volume group '%s' missing" % vgname
1563
  elif vgsize < minsize:
1564
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1565
            (vgname, minsize, vgsize))
1566
  return None
1567

    
1568

    
1569
def SplitTime(value):
1570
  """Splits time as floating point number into a tuple.
1571

1572
  @param value: Time in seconds
1573
  @type value: int or float
1574
  @return: Tuple containing (seconds, microseconds)
1575

1576
  """
1577
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1578

    
1579
  assert 0 <= seconds, \
1580
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1581
  assert 0 <= microseconds <= 999999, \
1582
    "Microseconds must be 0-999999, but are %s" % microseconds
1583

    
1584
  return (int(seconds), int(microseconds))
1585

    
1586

    
1587
def MergeTime(timetuple):
1588
  """Merges a tuple into time as a floating point number.
1589

1590
  @param timetuple: Time as tuple, (seconds, microseconds)
1591
  @type timetuple: tuple
1592
  @return: Time as a floating point number expressed in seconds
1593

1594
  """
1595
  (seconds, microseconds) = timetuple
1596

    
1597
  assert 0 <= seconds, \
1598
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1599
  assert 0 <= microseconds <= 999999, \
1600
    "Microseconds must be 0-999999, but are %s" % microseconds
1601

    
1602
  return float(seconds) + (float(microseconds) * 0.000001)
1603

    
1604

    
1605
def GetNodeDaemonPort():
1606
  """Get the node daemon port for this cluster.
1607

1608
  Note that this routine does not read a ganeti-specific file, but
1609
  instead uses C{socket.getservbyname} to allow pre-customization of
1610
  this parameter outside of Ganeti.
1611

1612
  @rtype: int
1613

1614
  """
1615
  try:
1616
    port = socket.getservbyname("ganeti-noded", "tcp")
1617
  except socket.error:
1618
    port = constants.DEFAULT_NODED_PORT
1619

    
1620
  return port
1621

    
1622

    
1623
def SetupLogging(logfile, debug=False, stderr_logging=False, program=""):
1624
  """Configures the logging module.
1625

1626
  @type logfile: str
1627
  @param logfile: the filename to which we should log
1628
  @type debug: boolean
1629
  @param debug: whether to enable debug messages too or
1630
      only those at C{INFO} and above level
1631
  @type stderr_logging: boolean
1632
  @param stderr_logging: whether we should also log to the standard error
1633
  @type program: str
1634
  @param program: the name under which we should log messages
1635
  @raise EnvironmentError: if we can't open the log file and
1636
      stderr logging is disabled
1637

1638
  """
1639
  fmt = "%(asctime)s: " + program + " "
1640
  if debug:
1641
    fmt += ("pid=%(process)d/%(threadName)s %(levelname)s"
1642
           " %(module)s:%(lineno)s %(message)s")
1643
  else:
1644
    fmt += "pid=%(process)d %(levelname)s %(message)s"
1645
  formatter = logging.Formatter(fmt)
1646

    
1647
  root_logger = logging.getLogger("")
1648
  root_logger.setLevel(logging.NOTSET)
1649

    
1650
  # Remove all previously setup handlers
1651
  for handler in root_logger.handlers:
1652
    root_logger.removeHandler(handler)
1653

    
1654
  if stderr_logging:
1655
    stderr_handler = logging.StreamHandler()
1656
    stderr_handler.setFormatter(formatter)
1657
    if debug:
1658
      stderr_handler.setLevel(logging.NOTSET)
1659
    else:
1660
      stderr_handler.setLevel(logging.CRITICAL)
1661
    root_logger.addHandler(stderr_handler)
1662

    
1663
  # this can fail, if the logging directories are not setup or we have
1664
  # a permisssion problem; in this case, it's best to log but ignore
1665
  # the error if stderr_logging is True, and if false we re-raise the
1666
  # exception since otherwise we could run but without any logs at all
1667
  try:
1668
    logfile_handler = logging.FileHandler(logfile)
1669
    logfile_handler.setFormatter(formatter)
1670
    if debug:
1671
      logfile_handler.setLevel(logging.DEBUG)
1672
    else:
1673
      logfile_handler.setLevel(logging.INFO)
1674
    root_logger.addHandler(logfile_handler)
1675
  except EnvironmentError, err:
1676
    if stderr_logging:
1677
      logging.exception("Failed to enable logging to file '%s'", logfile)
1678
    else:
1679
      # we need to re-raise the exception
1680
      raise
1681

    
1682

    
1683
def LockedMethod(fn):
1684
  """Synchronized object access decorator.
1685

1686
  This decorator is intended to protect access to an object using the
1687
  object's own lock which is hardcoded to '_lock'.
1688

1689
  """
1690
  def _LockDebug(*args, **kwargs):
1691
    if debug_locks:
1692
      logging.debug(*args, **kwargs)
1693

    
1694
  def wrapper(self, *args, **kwargs):
1695
    assert hasattr(self, '_lock')
1696
    lock = self._lock
1697
    _LockDebug("Waiting for %s", lock)
1698
    lock.acquire()
1699
    try:
1700
      _LockDebug("Acquired %s", lock)
1701
      result = fn(self, *args, **kwargs)
1702
    finally:
1703
      _LockDebug("Releasing %s", lock)
1704
      lock.release()
1705
      _LockDebug("Released %s", lock)
1706
    return result
1707
  return wrapper
1708

    
1709

    
1710
def LockFile(fd):
1711
  """Locks a file using POSIX locks.
1712

1713
  @type fd: int
1714
  @param fd: the file descriptor we need to lock
1715

1716
  """
1717
  try:
1718
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
1719
  except IOError, err:
1720
    if err.errno == errno.EAGAIN:
1721
      raise errors.LockError("File already locked")
1722
    raise
1723

    
1724

    
1725
class FileLock(object):
1726
  """Utility class for file locks.
1727

1728
  """
1729
  def __init__(self, filename):
1730
    """Constructor for FileLock.
1731

1732
    This will open the file denoted by the I{filename} argument.
1733

1734
    @type filename: str
1735
    @param filename: path to the file to be locked
1736

1737
    """
1738
    self.filename = filename
1739
    self.fd = open(self.filename, "w")
1740

    
1741
  def __del__(self):
1742
    self.Close()
1743

    
1744
  def Close(self):
1745
    """Close the file and release the lock.
1746

1747
    """
1748
    if self.fd:
1749
      self.fd.close()
1750
      self.fd = None
1751

    
1752
  def _flock(self, flag, blocking, timeout, errmsg):
1753
    """Wrapper for fcntl.flock.
1754

1755
    @type flag: int
1756
    @param flag: operation flag
1757
    @type blocking: bool
1758
    @param blocking: whether the operation should be done in blocking mode.
1759
    @type timeout: None or float
1760
    @param timeout: for how long the operation should be retried (implies
1761
                    non-blocking mode).
1762
    @type errmsg: string
1763
    @param errmsg: error message in case operation fails.
1764

1765
    """
1766
    assert self.fd, "Lock was closed"
1767
    assert timeout is None or timeout >= 0, \
1768
      "If specified, timeout must be positive"
1769

    
1770
    if timeout is not None:
1771
      flag |= fcntl.LOCK_NB
1772
      timeout_end = time.time() + timeout
1773

    
1774
    # Blocking doesn't have effect with timeout
1775
    elif not blocking:
1776
      flag |= fcntl.LOCK_NB
1777
      timeout_end = None
1778

    
1779
    retry = True
1780
    while retry:
1781
      try:
1782
        fcntl.flock(self.fd, flag)
1783
        retry = False
1784
      except IOError, err:
1785
        if err.errno in (errno.EAGAIN, ):
1786
          if timeout_end is not None and time.time() < timeout_end:
1787
            # Wait before trying again
1788
            time.sleep(max(0.1, min(1.0, timeout)))
1789
          else:
1790
            raise errors.LockError(errmsg)
1791
        else:
1792
          logging.exception("fcntl.flock failed")
1793
          raise
1794

    
1795
  def Exclusive(self, blocking=False, timeout=None):
1796
    """Locks the file in exclusive mode.
1797

1798
    @type blocking: boolean
1799
    @param blocking: whether to block and wait until we
1800
        can lock the file or return immediately
1801
    @type timeout: int or None
1802
    @param timeout: if not None, the duration to wait for the lock
1803
        (in blocking mode)
1804

1805
    """
1806
    self._flock(fcntl.LOCK_EX, blocking, timeout,
1807
                "Failed to lock %s in exclusive mode" % self.filename)
1808

    
1809
  def Shared(self, blocking=False, timeout=None):
1810
    """Locks the file in shared mode.
1811

1812
    @type blocking: boolean
1813
    @param blocking: whether to block and wait until we
1814
        can lock the file or return immediately
1815
    @type timeout: int or None
1816
    @param timeout: if not None, the duration to wait for the lock
1817
        (in blocking mode)
1818

1819
    """
1820
    self._flock(fcntl.LOCK_SH, blocking, timeout,
1821
                "Failed to lock %s in shared mode" % self.filename)
1822

    
1823
  def Unlock(self, blocking=True, timeout=None):
1824
    """Unlocks the file.
1825

1826
    According to C{flock(2)}, unlocking can also be a nonblocking
1827
    operation::
1828

1829
      To make a non-blocking request, include LOCK_NB with any of the above
1830
      operations.
1831

1832
    @type blocking: boolean
1833
    @param blocking: whether to block and wait until we
1834
        can lock the file or return immediately
1835
    @type timeout: int or None
1836
    @param timeout: if not None, the duration to wait for the lock
1837
        (in blocking mode)
1838

1839
    """
1840
    self._flock(fcntl.LOCK_UN, blocking, timeout,
1841
                "Failed to unlock %s" % self.filename)
1842

    
1843

    
1844
class SignalHandler(object):
1845
  """Generic signal handler class.
1846

1847
  It automatically restores the original handler when deconstructed or
1848
  when L{Reset} is called. You can either pass your own handler
1849
  function in or query the L{called} attribute to detect whether the
1850
  signal was sent.
1851

1852
  @type signum: list
1853
  @ivar signum: the signals we handle
1854
  @type called: boolean
1855
  @ivar called: tracks whether any of the signals have been raised
1856

1857
  """
1858
  def __init__(self, signum):
1859
    """Constructs a new SignalHandler instance.
1860

1861
    @type signum: int or list of ints
1862
    @param signum: Single signal number or set of signal numbers
1863

1864
    """
1865
    if isinstance(signum, (int, long)):
1866
      self.signum = set([signum])
1867
    else:
1868
      self.signum = set(signum)
1869

    
1870
    self.called = False
1871

    
1872
    self._previous = {}
1873
    try:
1874
      for signum in self.signum:
1875
        # Setup handler
1876
        prev_handler = signal.signal(signum, self._HandleSignal)
1877
        try:
1878
          self._previous[signum] = prev_handler
1879
        except:
1880
          # Restore previous handler
1881
          signal.signal(signum, prev_handler)
1882
          raise
1883
    except:
1884
      # Reset all handlers
1885
      self.Reset()
1886
      # Here we have a race condition: a handler may have already been called,
1887
      # but there's not much we can do about it at this point.
1888
      raise
1889

    
1890
  def __del__(self):
1891
    self.Reset()
1892

    
1893
  def Reset(self):
1894
    """Restore previous handler.
1895

1896
    This will reset all the signals to their previous handlers.
1897

1898
    """
1899
    for signum, prev_handler in self._previous.items():
1900
      signal.signal(signum, prev_handler)
1901
      # If successful, remove from dict
1902
      del self._previous[signum]
1903

    
1904
  def Clear(self):
1905
    """Unsets the L{called} flag.
1906

1907
    This function can be used in case a signal may arrive several times.
1908

1909
    """
1910
    self.called = False
1911

    
1912
  def _HandleSignal(self, signum, frame):
1913
    """Actual signal handling function.
1914

1915
    """
1916
    # This is not nice and not absolutely atomic, but it appears to be the only
1917
    # solution in Python -- there are no atomic types.
1918
    self.called = True
1919

    
1920

    
1921
class FieldSet(object):
1922
  """A simple field set.
1923

1924
  Among the features are:
1925
    - checking if a string is among a list of static string or regex objects
1926
    - checking if a whole list of string matches
1927
    - returning the matching groups from a regex match
1928

1929
  Internally, all fields are held as regular expression objects.
1930

1931
  """
1932
  def __init__(self, *items):
1933
    self.items = [re.compile("^%s$" % value) for value in items]
1934

    
1935
  def Extend(self, other_set):
1936
    """Extend the field set with the items from another one"""
1937
    self.items.extend(other_set.items)
1938

    
1939
  def Matches(self, field):
1940
    """Checks if a field matches the current set
1941

1942
    @type field: str
1943
    @param field: the string to match
1944
    @return: either False or a regular expression match object
1945

1946
    """
1947
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
1948
      return m
1949
    return False
1950

    
1951
  def NonMatching(self, items):
1952
    """Returns the list of fields not matching the current set
1953

1954
    @type items: list
1955
    @param items: the list of fields to check
1956
    @rtype: list
1957
    @return: list of non-matching fields
1958

1959
    """
1960
    return [val for val in items if not self.Matches(val)]