Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ 8b46606c

History | View | Annotate | Download (55.8 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 time
33
import subprocess
34
import re
35
import socket
36
import tempfile
37
import shutil
38
import errno
39
import pwd
40
import itertools
41
import select
42
import fcntl
43
import resource
44
import logging
45
import signal
46

    
47
from cStringIO import StringIO
48

    
49
try:
50
  from hashlib import sha1
51
except ImportError:
52
  import sha
53
  sha1 = sha.new
54

    
55
from ganeti import errors
56
from ganeti import constants
57

    
58

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

    
62
debug = False
63
debug_locks = False
64

    
65
#: when set to True, L{RunCmd} is disabled
66
no_fork = False
67

    
68

    
69
class RunResult(object):
70
  """Holds the result of running external programs.
71

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

87
  """
88
  __slots__ = ["exit_code", "signal", "stdout", "stderr",
89
               "failed", "fail_reason", "cmd"]
90

    
91

    
92
  def __init__(self, exit_code, signal_, stdout, stderr, cmd):
93
    self.cmd = cmd
94
    self.exit_code = exit_code
95
    self.signal = signal_
96
    self.stdout = stdout
97
    self.stderr = stderr
98
    self.failed = (signal_ is not None or exit_code != 0)
99

    
100
    if self.signal is not None:
101
      self.fail_reason = "terminated by signal %s" % self.signal
102
    elif self.exit_code is not None:
103
      self.fail_reason = "exited with exit code %s" % self.exit_code
104
    else:
105
      self.fail_reason = "unable to determine termination reason"
106

    
107
    if self.failed:
108
      logging.debug("Command '%s' failed (%s); output: %s",
109
                    self.cmd, self.fail_reason, self.output)
110

    
111
  def _GetOutput(self):
112
    """Returns the combined stdout and stderr for easier usage.
113

114
    """
115
    return self.stdout + self.stderr
116

    
117
  output = property(_GetOutput, None, None, "Return full output")
118

    
119

    
120
def RunCmd(cmd, env=None, output=None, cwd='/'):
121
  """Execute a (shell) command.
122

123
  The command should not read from its standard input, as it will be
124
  closed.
125

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

141
  """
142
  if no_fork:
143
    raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
144

    
145
  if isinstance(cmd, list):
146
    cmd = [str(val) for val in cmd]
147
    strcmd = " ".join(cmd)
148
    shell = False
149
  else:
150
    strcmd = cmd
151
    shell = True
152
  logging.debug("RunCmd '%s'", strcmd)
153

    
154
  cmd_env = os.environ.copy()
155
  cmd_env["LC_ALL"] = "C"
156
  if env is not None:
157
    cmd_env.update(env)
158

    
159
  try:
160
    if output is None:
161
      out, err, status = _RunCmdPipe(cmd, cmd_env, shell, cwd)
162
    else:
163
      status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
164
      out = err = ""
165
  except OSError, err:
166
    if err.errno == errno.ENOENT:
167
      raise errors.OpExecError("Can't execute '%s': not found (%s)" %
168
                               (strcmd, err))
169
    else:
170
      raise
171

    
172
  if status >= 0:
173
    exitcode = status
174
    signal_ = None
175
  else:
176
    exitcode = None
177
    signal_ = -status
178

    
179
  return RunResult(exitcode, signal_, out, err, strcmd)
180

    
181

    
182
def _RunCmdPipe(cmd, env, via_shell, cwd):
183
  """Run a command and return its output.
184

185
  @type  cmd: string or list
186
  @param cmd: Command to run
187
  @type env: dict
188
  @param env: The environment to use
189
  @type via_shell: bool
190
  @param via_shell: if we should run via the shell
191
  @type cwd: string
192
  @param cwd: the working directory for the program
193
  @rtype: tuple
194
  @return: (out, err, status)
195

196
  """
197
  poller = select.poll()
198
  child = subprocess.Popen(cmd, shell=via_shell,
199
                           stderr=subprocess.PIPE,
200
                           stdout=subprocess.PIPE,
201
                           stdin=subprocess.PIPE,
202
                           close_fds=True, env=env,
203
                           cwd=cwd)
204

    
205
  child.stdin.close()
206
  poller.register(child.stdout, select.POLLIN)
207
  poller.register(child.stderr, select.POLLIN)
208
  out = StringIO()
209
  err = StringIO()
210
  fdmap = {
211
    child.stdout.fileno(): (out, child.stdout),
212
    child.stderr.fileno(): (err, child.stderr),
213
    }
214
  for fd in fdmap:
215
    status = fcntl.fcntl(fd, fcntl.F_GETFL)
216
    fcntl.fcntl(fd, fcntl.F_SETFL, status | os.O_NONBLOCK)
217

    
218
  while fdmap:
219
    try:
220
      pollresult = poller.poll()
221
    except EnvironmentError, eerr:
222
      if eerr.errno == errno.EINTR:
223
        continue
224
      raise
225
    except select.error, serr:
226
      if serr[0] == errno.EINTR:
227
        continue
228
      raise
229

    
230
    for fd, event in pollresult:
231
      if event & select.POLLIN or event & select.POLLPRI:
232
        data = fdmap[fd][1].read()
233
        # no data from read signifies EOF (the same as POLLHUP)
234
        if not data:
235
          poller.unregister(fd)
236
          del fdmap[fd]
237
          continue
238
        fdmap[fd][0].write(data)
239
      if (event & select.POLLNVAL or event & select.POLLHUP or
240
          event & select.POLLERR):
241
        poller.unregister(fd)
242
        del fdmap[fd]
243

    
244
  out = out.getvalue()
245
  err = err.getvalue()
246

    
247
  status = child.wait()
248
  return out, err, status
249

    
250

    
251
def _RunCmdFile(cmd, env, via_shell, output, cwd):
252
  """Run a command and save its output to a file.
253

254
  @type  cmd: string or list
255
  @param cmd: Command to run
256
  @type env: dict
257
  @param env: The environment to use
258
  @type via_shell: bool
259
  @param via_shell: if we should run via the shell
260
  @type output: str
261
  @param output: the filename in which to save the output
262
  @type cwd: string
263
  @param cwd: the working directory for the program
264
  @rtype: int
265
  @return: the exit status
266

267
  """
268
  fh = open(output, "a")
269
  try:
270
    child = subprocess.Popen(cmd, shell=via_shell,
271
                             stderr=subprocess.STDOUT,
272
                             stdout=fh,
273
                             stdin=subprocess.PIPE,
274
                             close_fds=True, env=env,
275
                             cwd=cwd)
276

    
277
    child.stdin.close()
278
    status = child.wait()
279
  finally:
280
    fh.close()
281
  return status
282

    
283

    
284
def RemoveFile(filename):
285
  """Remove a file ignoring some errors.
286

287
  Remove a file, ignoring non-existing ones or directories. Other
288
  errors are passed.
289

290
  @type filename: str
291
  @param filename: the file to be removed
292

293
  """
294
  try:
295
    os.unlink(filename)
296
  except OSError, err:
297
    if err.errno not in (errno.ENOENT, errno.EISDIR):
298
      raise
299

    
300

    
301
def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
302
  """Renames a file.
303

304
  @type old: string
305
  @param old: Original path
306
  @type new: string
307
  @param new: New path
308
  @type mkdir: bool
309
  @param mkdir: Whether to create target directory if it doesn't exist
310
  @type mkdir_mode: int
311
  @param mkdir_mode: Mode for newly created directories
312

313
  """
314
  try:
315
    return os.rename(old, new)
316
  except OSError, err:
317
    # In at least one use case of this function, the job queue, directory
318
    # creation is very rare. Checking for the directory before renaming is not
319
    # as efficient.
320
    if mkdir and err.errno == errno.ENOENT:
321
      # Create directory and try again
322
      os.makedirs(os.path.dirname(new), mkdir_mode)
323
      return os.rename(old, new)
324
    raise
325

    
326

    
327
def _FingerprintFile(filename):
328
  """Compute the fingerprint of a file.
329

330
  If the file does not exist, a None will be returned
331
  instead.
332

333
  @type filename: str
334
  @param filename: the filename to checksum
335
  @rtype: str
336
  @return: the hex digest of the sha checksum of the contents
337
      of the file
338

339
  """
340
  if not (os.path.exists(filename) and os.path.isfile(filename)):
341
    return None
342

    
343
  f = open(filename)
344

    
345
  fp = sha1()
346
  while True:
347
    data = f.read(4096)
348
    if not data:
349
      break
350

    
351
    fp.update(data)
352

    
353
  return fp.hexdigest()
354

    
355

    
356
def FingerprintFiles(files):
357
  """Compute fingerprints for a list of files.
358

359
  @type files: list
360
  @param files: the list of filename to fingerprint
361
  @rtype: dict
362
  @return: a dictionary filename: fingerprint, holding only
363
      existing files
364

365
  """
366
  ret = {}
367

    
368
  for filename in files:
369
    cksum = _FingerprintFile(filename)
370
    if cksum:
371
      ret[filename] = cksum
372

    
373
  return ret
374

    
375

    
376
def ForceDictType(target, key_types, allowed_values=None):
377
  """Force the values of a dict to have certain types.
378

379
  @type target: dict
380
  @param target: the dict to update
381
  @type key_types: dict
382
  @param key_types: dict mapping target dict keys to types
383
                    in constants.ENFORCEABLE_TYPES
384
  @type allowed_values: list
385
  @keyword allowed_values: list of specially allowed values
386

387
  """
388
  if allowed_values is None:
389
    allowed_values = []
390

    
391
  if not isinstance(target, dict):
392
    msg = "Expected dictionary, got '%s'" % target
393
    raise errors.TypeEnforcementError(msg)
394

    
395
  for key in target:
396
    if key not in key_types:
397
      msg = "Unknown key '%s'" % key
398
      raise errors.TypeEnforcementError(msg)
399

    
400
    if target[key] in allowed_values:
401
      continue
402

    
403
    ktype = key_types[key]
404
    if ktype not in constants.ENFORCEABLE_TYPES:
405
      msg = "'%s' has non-enforceable type %s" % (key, ktype)
406
      raise errors.ProgrammerError(msg)
407

    
408
    if ktype == constants.VTYPE_STRING:
409
      if not isinstance(target[key], basestring):
410
        if isinstance(target[key], bool) and not target[key]:
411
          target[key] = ''
412
        else:
413
          msg = "'%s' (value %s) is not a valid string" % (key, target[key])
414
          raise errors.TypeEnforcementError(msg)
415
    elif ktype == constants.VTYPE_BOOL:
416
      if isinstance(target[key], basestring) and target[key]:
417
        if target[key].lower() == constants.VALUE_FALSE:
418
          target[key] = False
419
        elif target[key].lower() == constants.VALUE_TRUE:
420
          target[key] = True
421
        else:
422
          msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
423
          raise errors.TypeEnforcementError(msg)
424
      elif target[key]:
425
        target[key] = True
426
      else:
427
        target[key] = False
428
    elif ktype == constants.VTYPE_SIZE:
429
      try:
430
        target[key] = ParseUnit(target[key])
431
      except errors.UnitParseError, err:
432
        msg = "'%s' (value %s) is not a valid size. error: %s" % \
433
              (key, target[key], err)
434
        raise errors.TypeEnforcementError(msg)
435
    elif ktype == constants.VTYPE_INT:
436
      try:
437
        target[key] = int(target[key])
438
      except (ValueError, TypeError):
439
        msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
440
        raise errors.TypeEnforcementError(msg)
441

    
442

    
443
def IsProcessAlive(pid):
444
  """Check if a given pid exists on the system.
445

446
  @note: zombie status is not handled, so zombie processes
447
      will be returned as alive
448
  @type pid: int
449
  @param pid: the process ID to check
450
  @rtype: boolean
451
  @return: True if the process exists
452

453
  """
454
  if pid <= 0:
455
    return False
456

    
457
  try:
458
    os.stat("/proc/%d/status" % pid)
459
    return True
460
  except EnvironmentError, err:
461
    if err.errno in (errno.ENOENT, errno.ENOTDIR):
462
      return False
463
    raise
464

    
465

    
466
def ReadPidFile(pidfile):
467
  """Read a pid from a file.
468

469
  @type  pidfile: string
470
  @param pidfile: path to the file containing the pid
471
  @rtype: int
472
  @return: The process id, if the file exists and contains a valid PID,
473
           otherwise 0
474

475
  """
476
  try:
477
    pf = open(pidfile, 'r')
478
  except EnvironmentError, err:
479
    if err.errno != errno.ENOENT:
480
      logging.exception("Can't read pid file?!")
481
    return 0
482

    
483
  try:
484
    pid = int(pf.read())
485
  except ValueError, err:
486
    logging.info("Can't parse pid file contents", exc_info=True)
487
    return 0
488

    
489
  return pid
490

    
491

    
492
def MatchNameComponent(key, name_list):
493
  """Try to match a name against a list.
494

495
  This function will try to match a name like test1 against a list
496
  like C{['test1.example.com', 'test2.example.com', ...]}. Against
497
  this list, I{'test1'} as well as I{'test1.example'} will match, but
498
  not I{'test1.ex'}. A multiple match will be considered as no match
499
  at all (e.g. I{'test1'} against C{['test1.example.com',
500
  'test1.example.org']}).
501

502
  @type key: str
503
  @param key: the name to be searched
504
  @type name_list: list
505
  @param name_list: the list of strings against which to search the key
506

507
  @rtype: None or str
508
  @return: None if there is no match I{or} if there are multiple matches,
509
      otherwise the element from the list which matches
510

511
  """
512
  mo = re.compile("^%s(\..*)?$" % re.escape(key))
513
  names_filtered = [name for name in name_list if mo.match(name) is not None]
514
  if len(names_filtered) != 1:
515
    return None
516
  return names_filtered[0]
517

    
518

    
519
class HostInfo:
520
  """Class implementing resolver and hostname functionality
521

522
  """
523
  def __init__(self, name=None):
524
    """Initialize the host name object.
525

526
    If the name argument is not passed, it will use this system's
527
    name.
528

529
    """
530
    if name is None:
531
      name = self.SysName()
532

    
533
    self.query = name
534
    self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
535
    self.ip = self.ipaddrs[0]
536

    
537
  def ShortName(self):
538
    """Returns the hostname without domain.
539

540
    """
541
    return self.name.split('.')[0]
542

    
543
  @staticmethod
544
  def SysName():
545
    """Return the current system's name.
546

547
    This is simply a wrapper over C{socket.gethostname()}.
548

549
    """
550
    return socket.gethostname()
551

    
552
  @staticmethod
553
  def LookupHostname(hostname):
554
    """Look up hostname
555

556
    @type hostname: str
557
    @param hostname: hostname to look up
558

559
    @rtype: tuple
560
    @return: a tuple (name, aliases, ipaddrs) as returned by
561
        C{socket.gethostbyname_ex}
562
    @raise errors.ResolverError: in case of errors in resolving
563

564
    """
565
    try:
566
      result = socket.gethostbyname_ex(hostname)
567
    except socket.gaierror, err:
568
      # hostname not found in DNS
569
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
570

    
571
    return result
572

    
573

    
574
def ListVolumeGroups():
575
  """List volume groups and their size
576

577
  @rtype: dict
578
  @return:
579
       Dictionary with keys volume name and values
580
       the size of the volume
581

582
  """
583
  command = "vgs --noheadings --units m --nosuffix -o name,size"
584
  result = RunCmd(command)
585
  retval = {}
586
  if result.failed:
587
    return retval
588

    
589
  for line in result.stdout.splitlines():
590
    try:
591
      name, size = line.split()
592
      size = int(float(size))
593
    except (IndexError, ValueError), err:
594
      logging.error("Invalid output from vgs (%s): %s", err, line)
595
      continue
596

    
597
    retval[name] = size
598

    
599
  return retval
600

    
601

    
602
def BridgeExists(bridge):
603
  """Check whether the given bridge exists in the system
604

605
  @type bridge: str
606
  @param bridge: the bridge name to check
607
  @rtype: boolean
608
  @return: True if it does
609

610
  """
611
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
612

    
613

    
614
def NiceSort(name_list):
615
  """Sort a list of strings based on digit and non-digit groupings.
616

617
  Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
618
  will sort the list in the logical order C{['a1', 'a2', 'a10',
619
  'a11']}.
620

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

625
  @type name_list: list
626
  @param name_list: the names to be sorted
627
  @rtype: list
628
  @return: a copy of the name list sorted with our algorithm
629

630
  """
631
  _SORTER_BASE = "(\D+|\d+)"
632
  _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
633
                                                  _SORTER_BASE, _SORTER_BASE,
634
                                                  _SORTER_BASE, _SORTER_BASE,
635
                                                  _SORTER_BASE, _SORTER_BASE)
636
  _SORTER_RE = re.compile(_SORTER_FULL)
637
  _SORTER_NODIGIT = re.compile("^\D*$")
638
  def _TryInt(val):
639
    """Attempts to convert a variable to integer."""
640
    if val is None or _SORTER_NODIGIT.match(val):
641
      return val
642
    rval = int(val)
643
    return rval
644

    
645
  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
646
             for name in name_list]
647
  to_sort.sort()
648
  return [tup[1] for tup in to_sort]
649

    
650

    
651
def TryConvert(fn, val):
652
  """Try to convert a value ignoring errors.
653

654
  This function tries to apply function I{fn} to I{val}. If no
655
  C{ValueError} or C{TypeError} exceptions are raised, it will return
656
  the result, else it will return the original value. Any other
657
  exceptions are propagated to the caller.
658

659
  @type fn: callable
660
  @param fn: function to apply to the value
661
  @param val: the value to be converted
662
  @return: The converted value if the conversion was successful,
663
      otherwise the original value.
664

665
  """
666
  try:
667
    nv = fn(val)
668
  except (ValueError, TypeError), err:
669
    nv = val
670
  return nv
671

    
672

    
673
def IsValidIP(ip):
674
  """Verifies the syntax of an IPv4 address.
675

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

679
  @type ip: str
680
  @param ip: the address to be checked
681
  @rtype: a regular expression match object
682
  @return: a regular epression match object, or None if the
683
      address is not valid
684

685
  """
686
  unit = "(0|[1-9]\d{0,2})"
687
  #TODO: convert and return only boolean
688
  return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
689

    
690

    
691
def IsValidShellParam(word):
692
  """Verifies is the given word is safe from the shell's p.o.v.
693

694
  This means that we can pass this to a command via the shell and be
695
  sure that it doesn't alter the command line and is passed as such to
696
  the actual command.
697

698
  Note that we are overly restrictive here, in order to be on the safe
699
  side.
700

701
  @type word: str
702
  @param word: the word to check
703
  @rtype: boolean
704
  @return: True if the word is 'safe'
705

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

    
709

    
710
def BuildShellCmd(template, *args):
711
  """Build a safe shell command line from the given arguments.
712

713
  This function will check all arguments in the args list so that they
714
  are valid shell parameters (i.e. they don't contain shell
715
  metacharaters). If everything is ok, it will return the result of
716
  template % args.
717

718
  @type template: str
719
  @param template: the string holding the template for the
720
      string formatting
721
  @rtype: str
722
  @return: the expanded command line
723

724
  """
725
  for word in args:
726
    if not IsValidShellParam(word):
727
      raise errors.ProgrammerError("Shell argument '%s' contains"
728
                                   " invalid characters" % word)
729
  return template % args
730

    
731

    
732
def FormatUnit(value, units):
733
  """Formats an incoming number of MiB with the appropriate unit.
734

735
  @type value: int
736
  @param value: integer representing the value in MiB (1048576)
737
  @type units: char
738
  @param units: the type of formatting we should do:
739
      - 'h' for automatic scaling
740
      - 'm' for MiBs
741
      - 'g' for GiBs
742
      - 't' for TiBs
743
  @rtype: str
744
  @return: the formatted value (with suffix)
745

746
  """
747
  if units not in ('m', 'g', 't', 'h'):
748
    raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
749

    
750
  suffix = ''
751

    
752
  if units == 'm' or (units == 'h' and value < 1024):
753
    if units == 'h':
754
      suffix = 'M'
755
    return "%d%s" % (round(value, 0), suffix)
756

    
757
  elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
758
    if units == 'h':
759
      suffix = 'G'
760
    return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
761

    
762
  else:
763
    if units == 'h':
764
      suffix = 'T'
765
    return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
766

    
767

    
768
def ParseUnit(input_string):
769
  """Tries to extract number and scale from the given string.
770

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

775
  """
776
  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', str(input_string))
777
  if not m:
778
    raise errors.UnitParseError("Invalid format")
779

    
780
  value = float(m.groups()[0])
781

    
782
  unit = m.groups()[1]
783
  if unit:
784
    lcunit = unit.lower()
785
  else:
786
    lcunit = 'm'
787

    
788
  if lcunit in ('m', 'mb', 'mib'):
789
    # Value already in MiB
790
    pass
791

    
792
  elif lcunit in ('g', 'gb', 'gib'):
793
    value *= 1024
794

    
795
  elif lcunit in ('t', 'tb', 'tib'):
796
    value *= 1024 * 1024
797

    
798
  else:
799
    raise errors.UnitParseError("Unknown unit: %s" % unit)
800

    
801
  # Make sure we round up
802
  if int(value) < value:
803
    value += 1
804

    
805
  # Round up to the next multiple of 4
806
  value = int(value)
807
  if value % 4:
808
    value += 4 - value % 4
809

    
810
  return value
811

    
812

    
813
def AddAuthorizedKey(file_name, key):
814
  """Adds an SSH public key to an authorized_keys file.
815

816
  @type file_name: str
817
  @param file_name: path to authorized_keys file
818
  @type key: str
819
  @param key: string containing key
820

821
  """
822
  key_fields = key.split()
823

    
824
  f = open(file_name, 'a+')
825
  try:
826
    nl = True
827
    for line in f:
828
      # Ignore whitespace changes
829
      if line.split() == key_fields:
830
        break
831
      nl = line.endswith('\n')
832
    else:
833
      if not nl:
834
        f.write("\n")
835
      f.write(key.rstrip('\r\n'))
836
      f.write("\n")
837
      f.flush()
838
  finally:
839
    f.close()
840

    
841

    
842
def RemoveAuthorizedKey(file_name, key):
843
  """Removes an SSH public key from an authorized_keys file.
844

845
  @type file_name: str
846
  @param file_name: path to authorized_keys file
847
  @type key: str
848
  @param key: string containing key
849

850
  """
851
  key_fields = key.split()
852

    
853
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
854
  try:
855
    out = os.fdopen(fd, 'w')
856
    try:
857
      f = open(file_name, 'r')
858
      try:
859
        for line in f:
860
          # Ignore whitespace changes while comparing lines
861
          if line.split() != key_fields:
862
            out.write(line)
863

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

    
874

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

878
  @type file_name: str
879
  @param file_name: path to the file to modify (usually C{/etc/hosts})
880
  @type ip: str
881
  @param ip: the IP address
882
  @type hostname: str
883
  @param hostname: the hostname to be added
884
  @type aliases: list
885
  @param aliases: the list of aliases to add for the hostname
886

887
  """
888
  # FIXME: use WriteFile + fn rather than duplicating its efforts
889
  # Ensure aliases are unique
890
  aliases = UniqueSequence([hostname] + aliases)[1:]
891

    
892
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
893
  try:
894
    out = os.fdopen(fd, 'w')
895
    try:
896
      f = open(file_name, 'r')
897
      try:
898
        for line in f:
899
          fields = line.split()
900
          if fields and not fields[0].startswith('#') and ip == fields[0]:
901
            continue
902
          out.write(line)
903

    
904
        out.write("%s\t%s" % (ip, hostname))
905
        if aliases:
906
          out.write(" %s" % ' '.join(aliases))
907
        out.write('\n')
908

    
909
        out.flush()
910
        os.fsync(out)
911
        os.chmod(tmpname, 0644)
912
        os.rename(tmpname, file_name)
913
      finally:
914
        f.close()
915
    finally:
916
      out.close()
917
  except:
918
    RemoveFile(tmpname)
919
    raise
920

    
921

    
922
def AddHostToEtcHosts(hostname):
923
  """Wrapper around SetEtcHostsEntry.
924

925
  @type hostname: str
926
  @param hostname: a hostname that will be resolved and added to
927
      L{constants.ETC_HOSTS}
928

929
  """
930
  hi = HostInfo(name=hostname)
931
  SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
932

    
933

    
934
def RemoveEtcHostsEntry(file_name, hostname):
935
  """Removes a hostname from /etc/hosts.
936

937
  IP addresses without names are removed from the file.
938

939
  @type file_name: str
940
  @param file_name: path to the file to modify (usually C{/etc/hosts})
941
  @type hostname: str
942
  @param hostname: the hostname to be removed
943

944
  """
945
  # FIXME: use WriteFile + fn rather than duplicating its efforts
946
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
947
  try:
948
    out = os.fdopen(fd, 'w')
949
    try:
950
      f = open(file_name, 'r')
951
      try:
952
        for line in f:
953
          fields = line.split()
954
          if len(fields) > 1 and not fields[0].startswith('#'):
955
            names = fields[1:]
956
            if hostname in names:
957
              while hostname in names:
958
                names.remove(hostname)
959
              if names:
960
                out.write("%s %s\n" % (fields[0], ' '.join(names)))
961
              continue
962

    
963
          out.write(line)
964

    
965
        out.flush()
966
        os.fsync(out)
967
        os.chmod(tmpname, 0644)
968
        os.rename(tmpname, file_name)
969
      finally:
970
        f.close()
971
    finally:
972
      out.close()
973
  except:
974
    RemoveFile(tmpname)
975
    raise
976

    
977

    
978
def RemoveHostFromEtcHosts(hostname):
979
  """Wrapper around RemoveEtcHostsEntry.
980

981
  @type hostname: str
982
  @param hostname: hostname that will be resolved and its
983
      full and shot name will be removed from
984
      L{constants.ETC_HOSTS}
985

986
  """
987
  hi = HostInfo(name=hostname)
988
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
989
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
990

    
991

    
992
def CreateBackup(file_name):
993
  """Creates a backup of a file.
994

995
  @type file_name: str
996
  @param file_name: file to be backed up
997
  @rtype: str
998
  @return: the path to the newly created backup
999
  @raise errors.ProgrammerError: for invalid file names
1000

1001
  """
1002
  if not os.path.isfile(file_name):
1003
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
1004
                                file_name)
1005

    
1006
  prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
1007
  dir_name = os.path.dirname(file_name)
1008

    
1009
  fsrc = open(file_name, 'rb')
1010
  try:
1011
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
1012
    fdst = os.fdopen(fd, 'wb')
1013
    try:
1014
      shutil.copyfileobj(fsrc, fdst)
1015
    finally:
1016
      fdst.close()
1017
  finally:
1018
    fsrc.close()
1019

    
1020
  return backup_name
1021

    
1022

    
1023
def ShellQuote(value):
1024
  """Quotes shell argument according to POSIX.
1025

1026
  @type value: str
1027
  @param value: the argument to be quoted
1028
  @rtype: str
1029
  @return: the quoted value
1030

1031
  """
1032
  if _re_shell_unquoted.match(value):
1033
    return value
1034
  else:
1035
    return "'%s'" % value.replace("'", "'\\''")
1036

    
1037

    
1038
def ShellQuoteArgs(args):
1039
  """Quotes a list of shell arguments.
1040

1041
  @type args: list
1042
  @param args: list of arguments to be quoted
1043
  @rtype: str
1044
  @return: the quoted arguments concatenaned with spaces
1045

1046
  """
1047
  return ' '.join([ShellQuote(i) for i in args])
1048

    
1049

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

1053
  Check if the given IP is reachable by doing attempting a TCP connect
1054
  to it.
1055

1056
  @type target: str
1057
  @param target: the IP or hostname to ping
1058
  @type port: int
1059
  @param port: the port to connect to
1060
  @type timeout: int
1061
  @param timeout: the timeout on the connection attemp
1062
  @type live_port_needed: boolean
1063
  @param live_port_needed: whether a closed port will cause the
1064
      function to return failure, as if there was a timeout
1065
  @type source: str or None
1066
  @param source: if specified, will cause the connect to be made
1067
      from this specific source address; failures to bind other
1068
      than C{EADDRNOTAVAIL} will be ignored
1069

1070
  """
1071
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1072

    
1073
  success = False
1074

    
1075
  if source is not None:
1076
    try:
1077
      sock.bind((source, 0))
1078
    except socket.error, (errcode, errstring):
1079
      if errcode == errno.EADDRNOTAVAIL:
1080
        success = False
1081

    
1082
  sock.settimeout(timeout)
1083

    
1084
  try:
1085
    sock.connect((target, port))
1086
    sock.close()
1087
    success = True
1088
  except socket.timeout:
1089
    success = False
1090
  except socket.error, (errcode, errstring):
1091
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
1092

    
1093
  return success
1094

    
1095

    
1096
def OwnIpAddress(address):
1097
  """Check if the current host has the the given IP address.
1098

1099
  Currently this is done by TCP-pinging the address from the loopback
1100
  address.
1101

1102
  @type address: string
1103
  @param address: the addres to check
1104
  @rtype: bool
1105
  @return: True if we own the address
1106

1107
  """
1108
  return TcpPing(address, constants.DEFAULT_NODED_PORT,
1109
                 source=constants.LOCALHOST_IP_ADDRESS)
1110

    
1111

    
1112
def ListVisibleFiles(path):
1113
  """Returns a list of visible files in a directory.
1114

1115
  @type path: str
1116
  @param path: the directory to enumerate
1117
  @rtype: list
1118
  @return: the list of all files not starting with a dot
1119

1120
  """
1121
  files = [i for i in os.listdir(path) if not i.startswith(".")]
1122
  files.sort()
1123
  return files
1124

    
1125

    
1126
def GetHomeDir(user, default=None):
1127
  """Try to get the homedir of the given user.
1128

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

1133
  """
1134
  try:
1135
    if isinstance(user, basestring):
1136
      result = pwd.getpwnam(user)
1137
    elif isinstance(user, (int, long)):
1138
      result = pwd.getpwuid(user)
1139
    else:
1140
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1141
                                   type(user))
1142
  except KeyError:
1143
    return default
1144
  return result.pw_dir
1145

    
1146

    
1147
def NewUUID():
1148
  """Returns a random UUID.
1149

1150
  @note: This is a Linux-specific method as it uses the /proc
1151
      filesystem.
1152
  @rtype: str
1153

1154
  """
1155
  f = open("/proc/sys/kernel/random/uuid", "r")
1156
  try:
1157
    return f.read(128).rstrip("\n")
1158
  finally:
1159
    f.close()
1160

    
1161

    
1162
def GenerateSecret():
1163
  """Generates a random secret.
1164

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

1168
  @rtype: str
1169
  @return: a sha1 hexdigest of a block of 64 random bytes
1170

1171
  """
1172
  return sha1(os.urandom(64)).hexdigest()
1173

    
1174

    
1175
def EnsureDirs(dirs):
1176
  """Make required directories, if they don't exist.
1177

1178
  @param dirs: list of tuples (dir_name, dir_mode)
1179
  @type dirs: list of (string, integer)
1180

1181
  """
1182
  for dir_name, dir_mode in dirs:
1183
    try:
1184
      os.mkdir(dir_name, dir_mode)
1185
    except EnvironmentError, err:
1186
      if err.errno != errno.EEXIST:
1187
        raise errors.GenericError("Cannot create needed directory"
1188
                                  " '%s': %s" % (dir_name, err))
1189
    if not os.path.isdir(dir_name):
1190
      raise errors.GenericError("%s is not a directory" % dir_name)
1191

    
1192

    
1193
def ReadFile(file_name, size=None):
1194
  """Reads a file.
1195

1196
  @type size: None or int
1197
  @param size: Read at most size bytes
1198
  @rtype: str
1199
  @return: the (possibly partial) conent of the file
1200

1201
  """
1202
  f = open(file_name, "r")
1203
  try:
1204
    if size is None:
1205
      return f.read()
1206
    else:
1207
      return f.read(size)
1208
  finally:
1209
    f.close()
1210

    
1211

    
1212
def WriteFile(file_name, fn=None, data=None,
1213
              mode=None, uid=-1, gid=-1,
1214
              atime=None, mtime=None, close=True,
1215
              dry_run=False, backup=False,
1216
              prewrite=None, postwrite=None):
1217
  """(Over)write a file atomically.
1218

1219
  The file_name and either fn (a function taking one argument, the
1220
  file descriptor, and which should write the data to it) or data (the
1221
  contents of the file) must be passed. The other arguments are
1222
  optional and allow setting the file mode, owner and group, and the
1223
  mtime/atime of the file.
1224

1225
  If the function doesn't raise an exception, it has succeeded and the
1226
  target file has the new contents. If the function has raised an
1227
  exception, an existing target file should be unmodified and the
1228
  temporary file should be removed.
1229

1230
  @type file_name: str
1231
  @param file_name: the target filename
1232
  @type fn: callable
1233
  @param fn: content writing function, called with
1234
      file descriptor as parameter
1235
  @type data: str
1236
  @param data: contents of the file
1237
  @type mode: int
1238
  @param mode: file mode
1239
  @type uid: int
1240
  @param uid: the owner of the file
1241
  @type gid: int
1242
  @param gid: the group of the file
1243
  @type atime: int
1244
  @param atime: a custom access time to be set on the file
1245
  @type mtime: int
1246
  @param mtime: a custom modification time to be set on the file
1247
  @type close: boolean
1248
  @param close: whether to close file after writing it
1249
  @type prewrite: callable
1250
  @param prewrite: function to be called before writing content
1251
  @type postwrite: callable
1252
  @param postwrite: function to be called after writing content
1253

1254
  @rtype: None or int
1255
  @return: None if the 'close' parameter evaluates to True,
1256
      otherwise the file descriptor
1257

1258
  @raise errors.ProgrammerError: if any of the arguments are not valid
1259

1260
  """
1261
  if not os.path.isabs(file_name):
1262
    raise errors.ProgrammerError("Path passed to WriteFile is not"
1263
                                 " absolute: '%s'" % file_name)
1264

    
1265
  if [fn, data].count(None) != 1:
1266
    raise errors.ProgrammerError("fn or data required")
1267

    
1268
  if [atime, mtime].count(None) == 1:
1269
    raise errors.ProgrammerError("Both atime and mtime must be either"
1270
                                 " set or None")
1271

    
1272
  if backup and not dry_run and os.path.isfile(file_name):
1273
    CreateBackup(file_name)
1274

    
1275
  dir_name, base_name = os.path.split(file_name)
1276
  fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
1277
  do_remove = True
1278
  # here we need to make sure we remove the temp file, if any error
1279
  # leaves it in place
1280
  try:
1281
    if uid != -1 or gid != -1:
1282
      os.chown(new_name, uid, gid)
1283
    if mode:
1284
      os.chmod(new_name, mode)
1285
    if callable(prewrite):
1286
      prewrite(fd)
1287
    if data is not None:
1288
      os.write(fd, data)
1289
    else:
1290
      fn(fd)
1291
    if callable(postwrite):
1292
      postwrite(fd)
1293
    os.fsync(fd)
1294
    if atime is not None and mtime is not None:
1295
      os.utime(new_name, (atime, mtime))
1296
    if not dry_run:
1297
      os.rename(new_name, file_name)
1298
      do_remove = False
1299
  finally:
1300
    if close:
1301
      os.close(fd)
1302
      result = None
1303
    else:
1304
      result = fd
1305
    if do_remove:
1306
      RemoveFile(new_name)
1307

    
1308
  return result
1309

    
1310

    
1311
def FirstFree(seq, base=0):
1312
  """Returns the first non-existing integer from seq.
1313

1314
  The seq argument should be a sorted list of positive integers. The
1315
  first time the index of an element is smaller than the element
1316
  value, the index will be returned.
1317

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

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

1323
  @type seq: sequence
1324
  @param seq: the sequence to be analyzed.
1325
  @type base: int
1326
  @param base: use this value as the base index of the sequence
1327
  @rtype: int
1328
  @return: the first non-used index in the sequence
1329

1330
  """
1331
  for idx, elem in enumerate(seq):
1332
    assert elem >= base, "Passed element is higher than base offset"
1333
    if elem > idx + base:
1334
      # idx is not used
1335
      return idx + base
1336
  return None
1337

    
1338

    
1339
def all(seq, pred=bool):
1340
  "Returns True if pred(x) is True for every element in the iterable"
1341
  for elem in itertools.ifilterfalse(pred, seq):
1342
    return False
1343
  return True
1344

    
1345

    
1346
def any(seq, pred=bool):
1347
  "Returns True if pred(x) is True for at least one element in the iterable"
1348
  for elem in itertools.ifilter(pred, seq):
1349
    return True
1350
  return False
1351

    
1352

    
1353
def UniqueSequence(seq):
1354
  """Returns a list with unique elements.
1355

1356
  Element order is preserved.
1357

1358
  @type seq: sequence
1359
  @param seq: the sequence with the source elementes
1360
  @rtype: list
1361
  @return: list of unique elements from seq
1362

1363
  """
1364
  seen = set()
1365
  return [i for i in seq if i not in seen and not seen.add(i)]
1366

    
1367

    
1368
def IsValidMac(mac):
1369
  """Predicate to check if a MAC address is valid.
1370

1371
  Checks wether the supplied MAC address is formally correct, only
1372
  accepts colon separated format.
1373

1374
  @type mac: str
1375
  @param mac: the MAC to be validated
1376
  @rtype: boolean
1377
  @return: True is the MAC seems valid
1378

1379
  """
1380
  mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$")
1381
  return mac_check.match(mac) is not None
1382

    
1383

    
1384
def TestDelay(duration):
1385
  """Sleep for a fixed amount of time.
1386

1387
  @type duration: float
1388
  @param duration: the sleep duration
1389
  @rtype: boolean
1390
  @return: False for negative value, True otherwise
1391

1392
  """
1393
  if duration < 0:
1394
    return False, "Invalid sleep duration"
1395
  time.sleep(duration)
1396
  return True, None
1397

    
1398

    
1399
def _CloseFDNoErr(fd, retries=5):
1400
  """Close a file descriptor ignoring errors.
1401

1402
  @type fd: int
1403
  @param fd: the file descriptor
1404
  @type retries: int
1405
  @param retries: how many retries to make, in case we get any
1406
      other error than EBADF
1407

1408
  """
1409
  try:
1410
    os.close(fd)
1411
  except OSError, err:
1412
    if err.errno != errno.EBADF:
1413
      if retries > 0:
1414
        _CloseFDNoErr(fd, retries - 1)
1415
    # else either it's closed already or we're out of retries, so we
1416
    # ignore this and go on
1417

    
1418

    
1419
def CloseFDs(noclose_fds=None):
1420
  """Close file descriptors.
1421

1422
  This closes all file descriptors above 2 (i.e. except
1423
  stdin/out/err).
1424

1425
  @type noclose_fds: list or None
1426
  @param noclose_fds: if given, it denotes a list of file descriptor
1427
      that should not be closed
1428

1429
  """
1430
  # Default maximum for the number of available file descriptors.
1431
  if 'SC_OPEN_MAX' in os.sysconf_names:
1432
    try:
1433
      MAXFD = os.sysconf('SC_OPEN_MAX')
1434
      if MAXFD < 0:
1435
        MAXFD = 1024
1436
    except OSError:
1437
      MAXFD = 1024
1438
  else:
1439
    MAXFD = 1024
1440
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1441
  if (maxfd == resource.RLIM_INFINITY):
1442
    maxfd = MAXFD
1443

    
1444
  # Iterate through and close all file descriptors (except the standard ones)
1445
  for fd in range(3, maxfd):
1446
    if noclose_fds and fd in noclose_fds:
1447
      continue
1448
    _CloseFDNoErr(fd)
1449

    
1450

    
1451
def Daemonize(logfile):
1452
  """Daemonize the current process.
1453

1454
  This detaches the current process from the controlling terminal and
1455
  runs it in the background as a daemon.
1456

1457
  @type logfile: str
1458
  @param logfile: the logfile to which we should redirect stdout/stderr
1459
  @rtype: int
1460
  @return: the value zero
1461

1462
  """
1463
  UMASK = 077
1464
  WORKDIR = "/"
1465

    
1466
  # this might fail
1467
  pid = os.fork()
1468
  if (pid == 0):  # The first child.
1469
    os.setsid()
1470
    # this might fail
1471
    pid = os.fork() # Fork a second child.
1472
    if (pid == 0):  # The second child.
1473
      os.chdir(WORKDIR)
1474
      os.umask(UMASK)
1475
    else:
1476
      # exit() or _exit()?  See below.
1477
      os._exit(0) # Exit parent (the first child) of the second child.
1478
  else:
1479
    os._exit(0) # Exit parent of the first child.
1480

    
1481
  for fd in range(3):
1482
    _CloseFDNoErr(fd)
1483
  i = os.open("/dev/null", os.O_RDONLY) # stdin
1484
  assert i == 0, "Can't close/reopen stdin"
1485
  i = os.open(logfile, os.O_WRONLY|os.O_CREAT|os.O_APPEND, 0600) # stdout
1486
  assert i == 1, "Can't close/reopen stdout"
1487
  # Duplicate standard output to standard error.
1488
  os.dup2(1, 2)
1489
  return 0
1490

    
1491

    
1492
def DaemonPidFileName(name):
1493
  """Compute a ganeti pid file absolute path
1494

1495
  @type name: str
1496
  @param name: the daemon name
1497
  @rtype: str
1498
  @return: the full path to the pidfile corresponding to the given
1499
      daemon name
1500

1501
  """
1502
  return os.path.join(constants.RUN_GANETI_DIR, "%s.pid" % name)
1503

    
1504

    
1505
def WritePidFile(name):
1506
  """Write the current process pidfile.
1507

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

1510
  @type name: str
1511
  @param name: the daemon name to use
1512
  @raise errors.GenericError: if the pid file already exists and
1513
      points to a live process
1514

1515
  """
1516
  pid = os.getpid()
1517
  pidfilename = DaemonPidFileName(name)
1518
  if IsProcessAlive(ReadPidFile(pidfilename)):
1519
    raise errors.GenericError("%s contains a live process" % pidfilename)
1520

    
1521
  WriteFile(pidfilename, data="%d\n" % pid)
1522

    
1523

    
1524
def RemovePidFile(name):
1525
  """Remove the current process pidfile.
1526

1527
  Any errors are ignored.
1528

1529
  @type name: str
1530
  @param name: the daemon name used to derive the pidfile name
1531

1532
  """
1533
  pidfilename = DaemonPidFileName(name)
1534
  # TODO: we could check here that the file contains our pid
1535
  try:
1536
    RemoveFile(pidfilename)
1537
  except:
1538
    pass
1539

    
1540

    
1541
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1542
                waitpid=False):
1543
  """Kill a process given by its pid.
1544

1545
  @type pid: int
1546
  @param pid: The PID to terminate.
1547
  @type signal_: int
1548
  @param signal_: The signal to send, by default SIGTERM
1549
  @type timeout: int
1550
  @param timeout: The timeout after which, if the process is still alive,
1551
                  a SIGKILL will be sent. If not positive, no such checking
1552
                  will be done
1553
  @type waitpid: boolean
1554
  @param waitpid: If true, we should waitpid on this process after
1555
      sending signals, since it's our own child and otherwise it
1556
      would remain as zombie
1557

1558
  """
1559
  def _helper(pid, signal_, wait):
1560
    """Simple helper to encapsulate the kill/waitpid sequence"""
1561
    os.kill(pid, signal_)
1562
    if wait:
1563
      try:
1564
        os.waitpid(pid, os.WNOHANG)
1565
      except OSError:
1566
        pass
1567

    
1568
  if pid <= 0:
1569
    # kill with pid=0 == suicide
1570
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1571

    
1572
  if not IsProcessAlive(pid):
1573
    return
1574
  _helper(pid, signal_, waitpid)
1575
  if timeout <= 0:
1576
    return
1577

    
1578
  # Wait up to $timeout seconds
1579
  end = time.time() + timeout
1580
  wait = 0.01
1581
  while time.time() < end and IsProcessAlive(pid):
1582
    try:
1583
      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
1584
      if result_pid > 0:
1585
        break
1586
    except OSError:
1587
      pass
1588
    time.sleep(wait)
1589
    # Make wait time longer for next try
1590
    if wait < 0.1:
1591
      wait *= 1.5
1592

    
1593
  if IsProcessAlive(pid):
1594
    # Kill process if it's still alive
1595
    _helper(pid, signal.SIGKILL, waitpid)
1596

    
1597

    
1598
def FindFile(name, search_path, test=os.path.exists):
1599
  """Look for a filesystem object in a given path.
1600

1601
  This is an abstract method to search for filesystem object (files,
1602
  dirs) under a given search path.
1603

1604
  @type name: str
1605
  @param name: the name to look for
1606
  @type search_path: str
1607
  @param search_path: location to start at
1608
  @type test: callable
1609
  @param test: a function taking one argument that should return True
1610
      if the a given object is valid; the default value is
1611
      os.path.exists, causing only existing files to be returned
1612
  @rtype: str or None
1613
  @return: full path to the object if found, None otherwise
1614

1615
  """
1616
  for dir_name in search_path:
1617
    item_name = os.path.sep.join([dir_name, name])
1618
    if test(item_name):
1619
      return item_name
1620
  return None
1621

    
1622

    
1623
def CheckVolumeGroupSize(vglist, vgname, minsize):
1624
  """Checks if the volume group list is valid.
1625

1626
  The function will check if a given volume group is in the list of
1627
  volume groups and has a minimum size.
1628

1629
  @type vglist: dict
1630
  @param vglist: dictionary of volume group names and their size
1631
  @type vgname: str
1632
  @param vgname: the volume group we should check
1633
  @type minsize: int
1634
  @param minsize: the minimum size we accept
1635
  @rtype: None or str
1636
  @return: None for success, otherwise the error message
1637

1638
  """
1639
  vgsize = vglist.get(vgname, None)
1640
  if vgsize is None:
1641
    return "volume group '%s' missing" % vgname
1642
  elif vgsize < minsize:
1643
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1644
            (vgname, minsize, vgsize))
1645
  return None
1646

    
1647

    
1648
def SplitTime(value):
1649
  """Splits time as floating point number into a tuple.
1650

1651
  @param value: Time in seconds
1652
  @type value: int or float
1653
  @return: Tuple containing (seconds, microseconds)
1654

1655
  """
1656
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1657

    
1658
  assert 0 <= seconds, \
1659
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1660
  assert 0 <= microseconds <= 999999, \
1661
    "Microseconds must be 0-999999, but are %s" % microseconds
1662

    
1663
  return (int(seconds), int(microseconds))
1664

    
1665

    
1666
def MergeTime(timetuple):
1667
  """Merges a tuple into time as a floating point number.
1668

1669
  @param timetuple: Time as tuple, (seconds, microseconds)
1670
  @type timetuple: tuple
1671
  @return: Time as a floating point number expressed in seconds
1672

1673
  """
1674
  (seconds, microseconds) = timetuple
1675

    
1676
  assert 0 <= seconds, \
1677
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1678
  assert 0 <= microseconds <= 999999, \
1679
    "Microseconds must be 0-999999, but are %s" % microseconds
1680

    
1681
  return float(seconds) + (float(microseconds) * 0.000001)
1682

    
1683

    
1684
def GetNodeDaemonPort():
1685
  """Get the node daemon port for this cluster.
1686

1687
  Note that this routine does not read a ganeti-specific file, but
1688
  instead uses C{socket.getservbyname} to allow pre-customization of
1689
  this parameter outside of Ganeti.
1690

1691
  @rtype: int
1692

1693
  """
1694
  try:
1695
    port = socket.getservbyname("ganeti-noded", "tcp")
1696
  except socket.error:
1697
    port = constants.DEFAULT_NODED_PORT
1698

    
1699
  return port
1700

    
1701

    
1702
def SetupLogging(logfile, debug=False, stderr_logging=False, program="",
1703
                 multithreaded=False):
1704
  """Configures the logging module.
1705

1706
  @type logfile: str
1707
  @param logfile: the filename to which we should log
1708
  @type debug: boolean
1709
  @param debug: whether to enable debug messages too or
1710
      only those at C{INFO} and above level
1711
  @type stderr_logging: boolean
1712
  @param stderr_logging: whether we should also log to the standard error
1713
  @type program: str
1714
  @param program: the name under which we should log messages
1715
  @type multithreaded: boolean
1716
  @param multithreaded: if True, will add the thread name to the log file
1717
  @raise EnvironmentError: if we can't open the log file and
1718
      stderr logging is disabled
1719

1720
  """
1721
  fmt = "%(asctime)s: " + program + " pid=%(process)d"
1722
  if multithreaded:
1723
    fmt += "/%(threadName)s"
1724
  if debug:
1725
    fmt += " %(module)s:%(lineno)s"
1726
  fmt += " %(levelname)s %(message)s"
1727
  formatter = logging.Formatter(fmt)
1728

    
1729
  root_logger = logging.getLogger("")
1730
  root_logger.setLevel(logging.NOTSET)
1731

    
1732
  # Remove all previously setup handlers
1733
  for handler in root_logger.handlers:
1734
    handler.close()
1735
    root_logger.removeHandler(handler)
1736

    
1737
  if stderr_logging:
1738
    stderr_handler = logging.StreamHandler()
1739
    stderr_handler.setFormatter(formatter)
1740
    if debug:
1741
      stderr_handler.setLevel(logging.NOTSET)
1742
    else:
1743
      stderr_handler.setLevel(logging.CRITICAL)
1744
    root_logger.addHandler(stderr_handler)
1745

    
1746
  # this can fail, if the logging directories are not setup or we have
1747
  # a permisssion problem; in this case, it's best to log but ignore
1748
  # the error if stderr_logging is True, and if false we re-raise the
1749
  # exception since otherwise we could run but without any logs at all
1750
  try:
1751
    logfile_handler = logging.FileHandler(logfile)
1752
    logfile_handler.setFormatter(formatter)
1753
    if debug:
1754
      logfile_handler.setLevel(logging.DEBUG)
1755
    else:
1756
      logfile_handler.setLevel(logging.INFO)
1757
    root_logger.addHandler(logfile_handler)
1758
  except EnvironmentError:
1759
    if stderr_logging:
1760
      logging.exception("Failed to enable logging to file '%s'", logfile)
1761
    else:
1762
      # we need to re-raise the exception
1763
      raise
1764

    
1765
def IsNormAbsPath(path):
1766
  """Check whether a path is absolute and also normalized
1767

1768
  This avoids things like /dir/../../other/path to be valid.
1769

1770
  """
1771
  return os.path.normpath(path) == path and os.path.isabs(path)
1772

    
1773
def TailFile(fname, lines=20):
1774
  """Return the last lines from a file.
1775

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

1780
  @param fname: the file name
1781
  @type lines: int
1782
  @param lines: the (maximum) number of lines to return
1783

1784
  """
1785
  fd = open(fname, "r")
1786
  try:
1787
    fd.seek(0, 2)
1788
    pos = fd.tell()
1789
    pos = max(0, pos-4096)
1790
    fd.seek(pos, 0)
1791
    raw_data = fd.read()
1792
  finally:
1793
    fd.close()
1794

    
1795
  rows = raw_data.splitlines()
1796
  return rows[-lines:]
1797

    
1798

    
1799
def SafeEncode(text):
1800
  """Return a 'safe' version of a source string.
1801

1802
  This function mangles the input string and returns a version that
1803
  should be safe to display/encode as ASCII. To this end, we first
1804
  convert it to ASCII using the 'backslashreplace' encoding which
1805
  should get rid of any non-ASCII chars, and then we process it
1806
  through a loop copied from the string repr sources in the python; we
1807
  don't use string_escape anymore since that escape single quotes and
1808
  backslashes too, and that is too much; and that escaping is not
1809
  stable, i.e. string_escape(string_escape(x)) != string_escape(x).
1810

1811
  @type text: str or unicode
1812
  @param text: input data
1813
  @rtype: str
1814
  @return: a safe version of text
1815

1816
  """
1817
  if isinstance(text, unicode):
1818
    # onli if unicode; if str already, we handle it below
1819
    text = text.encode('ascii', 'backslashreplace')
1820
  resu = ""
1821
  for char in text:
1822
    c = ord(char)
1823
    if char  == '\t':
1824
      resu += r'\t'
1825
    elif char == '\n':
1826
      resu += r'\n'
1827
    elif char == '\r':
1828
      resu += r'\'r'
1829
    elif c < 32 or c >= 127: # non-printable
1830
      resu += "\\x%02x" % (c & 0xff)
1831
    else:
1832
      resu += char
1833
  return resu
1834

    
1835

    
1836
def CommaJoin(names):
1837
  """Nicely join a set of identifiers.
1838

1839
  @param names: set, list or tuple
1840
  @return: a string with the formatted results
1841

1842
  """
1843
  return ", ".join(["'%s'" % val for val in names])
1844

    
1845

    
1846
def LockedMethod(fn):
1847
  """Synchronized object access decorator.
1848

1849
  This decorator is intended to protect access to an object using the
1850
  object's own lock which is hardcoded to '_lock'.
1851

1852
  """
1853
  def _LockDebug(*args, **kwargs):
1854
    if debug_locks:
1855
      logging.debug(*args, **kwargs)
1856

    
1857
  def wrapper(self, *args, **kwargs):
1858
    assert hasattr(self, '_lock')
1859
    lock = self._lock
1860
    _LockDebug("Waiting for %s", lock)
1861
    lock.acquire()
1862
    try:
1863
      _LockDebug("Acquired %s", lock)
1864
      result = fn(self, *args, **kwargs)
1865
    finally:
1866
      _LockDebug("Releasing %s", lock)
1867
      lock.release()
1868
      _LockDebug("Released %s", lock)
1869
    return result
1870
  return wrapper
1871

    
1872

    
1873
def LockFile(fd):
1874
  """Locks a file using POSIX locks.
1875

1876
  @type fd: int
1877
  @param fd: the file descriptor we need to lock
1878

1879
  """
1880
  try:
1881
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
1882
  except IOError, err:
1883
    if err.errno == errno.EAGAIN:
1884
      raise errors.LockError("File already locked")
1885
    raise
1886

    
1887

    
1888
class FileLock(object):
1889
  """Utility class for file locks.
1890

1891
  """
1892
  def __init__(self, filename):
1893
    """Constructor for FileLock.
1894

1895
    This will open the file denoted by the I{filename} argument.
1896

1897
    @type filename: str
1898
    @param filename: path to the file to be locked
1899

1900
    """
1901
    self.filename = filename
1902
    self.fd = open(self.filename, "w")
1903

    
1904
  def __del__(self):
1905
    self.Close()
1906

    
1907
  def Close(self):
1908
    """Close the file and release the lock.
1909

1910
    """
1911
    if self.fd:
1912
      self.fd.close()
1913
      self.fd = None
1914

    
1915
  def _flock(self, flag, blocking, timeout, errmsg):
1916
    """Wrapper for fcntl.flock.
1917

1918
    @type flag: int
1919
    @param flag: operation flag
1920
    @type blocking: bool
1921
    @param blocking: whether the operation should be done in blocking mode.
1922
    @type timeout: None or float
1923
    @param timeout: for how long the operation should be retried (implies
1924
                    non-blocking mode).
1925
    @type errmsg: string
1926
    @param errmsg: error message in case operation fails.
1927

1928
    """
1929
    assert self.fd, "Lock was closed"
1930
    assert timeout is None or timeout >= 0, \
1931
      "If specified, timeout must be positive"
1932

    
1933
    if timeout is not None:
1934
      flag |= fcntl.LOCK_NB
1935
      timeout_end = time.time() + timeout
1936

    
1937
    # Blocking doesn't have effect with timeout
1938
    elif not blocking:
1939
      flag |= fcntl.LOCK_NB
1940
      timeout_end = None
1941

    
1942
    retry = True
1943
    while retry:
1944
      try:
1945
        fcntl.flock(self.fd, flag)
1946
        retry = False
1947
      except IOError, err:
1948
        if err.errno in (errno.EAGAIN, ):
1949
          if timeout_end is not None and time.time() < timeout_end:
1950
            # Wait before trying again
1951
            time.sleep(max(0.1, min(1.0, timeout)))
1952
          else:
1953
            raise errors.LockError(errmsg)
1954
        else:
1955
          logging.exception("fcntl.flock failed")
1956
          raise
1957

    
1958
  def Exclusive(self, blocking=False, timeout=None):
1959
    """Locks the file in exclusive mode.
1960

1961
    @type blocking: boolean
1962
    @param blocking: whether to block and wait until we
1963
        can lock the file or return immediately
1964
    @type timeout: int or None
1965
    @param timeout: if not None, the duration to wait for the lock
1966
        (in blocking mode)
1967

1968
    """
1969
    self._flock(fcntl.LOCK_EX, blocking, timeout,
1970
                "Failed to lock %s in exclusive mode" % self.filename)
1971

    
1972
  def Shared(self, blocking=False, timeout=None):
1973
    """Locks the file in shared mode.
1974

1975
    @type blocking: boolean
1976
    @param blocking: whether to block and wait until we
1977
        can lock the file or return immediately
1978
    @type timeout: int or None
1979
    @param timeout: if not None, the duration to wait for the lock
1980
        (in blocking mode)
1981

1982
    """
1983
    self._flock(fcntl.LOCK_SH, blocking, timeout,
1984
                "Failed to lock %s in shared mode" % self.filename)
1985

    
1986
  def Unlock(self, blocking=True, timeout=None):
1987
    """Unlocks the file.
1988

1989
    According to C{flock(2)}, unlocking can also be a nonblocking
1990
    operation::
1991

1992
      To make a non-blocking request, include LOCK_NB with any of the above
1993
      operations.
1994

1995
    @type blocking: boolean
1996
    @param blocking: whether to block and wait until we
1997
        can lock the file or return immediately
1998
    @type timeout: int or None
1999
    @param timeout: if not None, the duration to wait for the lock
2000
        (in blocking mode)
2001

2002
    """
2003
    self._flock(fcntl.LOCK_UN, blocking, timeout,
2004
                "Failed to unlock %s" % self.filename)
2005

    
2006

    
2007
class SignalHandler(object):
2008
  """Generic signal handler class.
2009

2010
  It automatically restores the original handler when deconstructed or
2011
  when L{Reset} is called. You can either pass your own handler
2012
  function in or query the L{called} attribute to detect whether the
2013
  signal was sent.
2014

2015
  @type signum: list
2016
  @ivar signum: the signals we handle
2017
  @type called: boolean
2018
  @ivar called: tracks whether any of the signals have been raised
2019

2020
  """
2021
  def __init__(self, signum):
2022
    """Constructs a new SignalHandler instance.
2023

2024
    @type signum: int or list of ints
2025
    @param signum: Single signal number or set of signal numbers
2026

2027
    """
2028
    if isinstance(signum, (int, long)):
2029
      self.signum = set([signum])
2030
    else:
2031
      self.signum = set(signum)
2032

    
2033
    self.called = False
2034

    
2035
    self._previous = {}
2036
    try:
2037
      for signum in self.signum:
2038
        # Setup handler
2039
        prev_handler = signal.signal(signum, self._HandleSignal)
2040
        try:
2041
          self._previous[signum] = prev_handler
2042
        except:
2043
          # Restore previous handler
2044
          signal.signal(signum, prev_handler)
2045
          raise
2046
    except:
2047
      # Reset all handlers
2048
      self.Reset()
2049
      # Here we have a race condition: a handler may have already been called,
2050
      # but there's not much we can do about it at this point.
2051
      raise
2052

    
2053
  def __del__(self):
2054
    self.Reset()
2055

    
2056
  def Reset(self):
2057
    """Restore previous handler.
2058

2059
    This will reset all the signals to their previous handlers.
2060

2061
    """
2062
    for signum, prev_handler in self._previous.items():
2063
      signal.signal(signum, prev_handler)
2064
      # If successful, remove from dict
2065
      del self._previous[signum]
2066

    
2067
  def Clear(self):
2068
    """Unsets the L{called} flag.
2069

2070
    This function can be used in case a signal may arrive several times.
2071

2072
    """
2073
    self.called = False
2074

    
2075
  def _HandleSignal(self, signum, frame):
2076
    """Actual signal handling function.
2077

2078
    """
2079
    # This is not nice and not absolutely atomic, but it appears to be the only
2080
    # solution in Python -- there are no atomic types.
2081
    self.called = True
2082

    
2083

    
2084
class FieldSet(object):
2085
  """A simple field set.
2086

2087
  Among the features are:
2088
    - checking if a string is among a list of static string or regex objects
2089
    - checking if a whole list of string matches
2090
    - returning the matching groups from a regex match
2091

2092
  Internally, all fields are held as regular expression objects.
2093

2094
  """
2095
  def __init__(self, *items):
2096
    self.items = [re.compile("^%s$" % value) for value in items]
2097

    
2098
  def Extend(self, other_set):
2099
    """Extend the field set with the items from another one"""
2100
    self.items.extend(other_set.items)
2101

    
2102
  def Matches(self, field):
2103
    """Checks if a field matches the current set
2104

2105
    @type field: str
2106
    @param field: the string to match
2107
    @return: either False or a regular expression match object
2108

2109
    """
2110
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
2111
      return m
2112
    return False
2113

    
2114
  def NonMatching(self, items):
2115
    """Returns the list of fields not matching the current set
2116

2117
    @type items: list
2118
    @param items: the list of fields to check
2119
    @rtype: list
2120
    @return: list of non-matching fields
2121

2122
    """
2123
    return [val for val in items if not self.Matches(val)]