Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ 81b7354c

History | View | Annotate | Download (55.2 kB)

1
#
2
#
3

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

    
21

    
22
"""Ganeti utility module.
23

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

27
"""
28

    
29

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

    
48
from cStringIO import StringIO
49

    
50
from ganeti import errors
51
from ganeti import constants
52

    
53

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

    
57
debug = False
58
debug_locks = False
59

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

    
63

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

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

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

    
86

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

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

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

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

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

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

    
114

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

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

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

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

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

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

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

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

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

    
169

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

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

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

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

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

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

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

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

    
238

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

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

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

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

    
271

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

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

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

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

    
288

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

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

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

    
314

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

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

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

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

    
331
  f = open(filename)
332

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

    
339
    fp.update(data)
340

    
341
  return fp.hexdigest()
342

    
343

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

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

353
  """
354
  ret = {}
355

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

    
361
  return ret
362

    
363

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

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

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

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

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

    
389

    
390
def ForceDictType(target, key_types, allowed_values=None):
391
  """Force the values of a dict to have certain types.
392

393
  @type target: dict
394
  @param target: the dict to update
395
  @type key_types: dict
396
  @param key_types: dict mapping target dict keys to types
397
                    in constants.ENFORCEABLE_TYPES
398
  @type allowed_values: list
399
  @keyword allowed_values: list of specially allowed values
400

401
  """
402
  if allowed_values is None:
403
    allowed_values = []
404

    
405
  for key in target:
406
    if key not in key_types:
407
      msg = "Unknown key '%s'" % key
408
      raise errors.TypeEnforcementError(msg)
409

    
410
    if target[key] in allowed_values:
411
      continue
412

    
413
    type = key_types[key]
414
    if type not in constants.ENFORCEABLE_TYPES:
415
      msg = "'%s' has non-enforceable type %s" % (key, type)
416
      raise errors.ProgrammerError(msg)
417

    
418
    if type == constants.VTYPE_STRING:
419
      if not isinstance(target[key], basestring):
420
        if isinstance(target[key], bool) and not target[key]:
421
          target[key] = ''
422
        else:
423
          msg = "'%s' (value %s) is not a valid string" % (key, target[key])
424
          raise errors.TypeEnforcementError(msg)
425
    elif type == constants.VTYPE_BOOL:
426
      if isinstance(target[key], basestring) and target[key]:
427
        if target[key].lower() == constants.VALUE_FALSE:
428
          target[key] = False
429
        elif target[key].lower() == constants.VALUE_TRUE:
430
          target[key] = True
431
        else:
432
          msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
433
          raise errors.TypeEnforcementError(msg)
434
      elif target[key]:
435
        target[key] = True
436
      else:
437
        target[key] = False
438
    elif type == constants.VTYPE_SIZE:
439
      try:
440
        target[key] = ParseUnit(target[key])
441
      except errors.UnitParseError, err:
442
        msg = "'%s' (value %s) is not a valid size. error: %s" % \
443
              (key, target[key], err)
444
        raise errors.TypeEnforcementError(msg)
445
    elif type == constants.VTYPE_INT:
446
      try:
447
        target[key] = int(target[key])
448
      except (ValueError, TypeError):
449
        msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
450
        raise errors.TypeEnforcementError(msg)
451

    
452

    
453
def IsProcessAlive(pid):
454
  """Check if a given pid exists on the system.
455

456
  @note: zombie status is not handled, so zombie processes
457
      will be returned as alive
458
  @type pid: int
459
  @param pid: the process ID to check
460
  @rtype: boolean
461
  @return: True if the process exists
462

463
  """
464
  if pid <= 0:
465
    return False
466

    
467
  try:
468
    os.stat("/proc/%d/status" % pid)
469
    return True
470
  except EnvironmentError, err:
471
    if err.errno in (errno.ENOENT, errno.ENOTDIR):
472
      return False
473
    raise
474

    
475

    
476
def ReadPidFile(pidfile):
477
  """Read a pid from a file.
478

479
  @type  pidfile: string
480
  @param pidfile: path to the file containing the pid
481
  @rtype: int
482
  @return: The process id, if the file exists and contains a valid PID,
483
           otherwise 0
484

485
  """
486
  try:
487
    pf = open(pidfile, 'r')
488
  except EnvironmentError, err:
489
    if err.errno != errno.ENOENT:
490
      logging.exception("Can't read pid file?!")
491
    return 0
492

    
493
  try:
494
    pid = int(pf.read())
495
  except ValueError, err:
496
    logging.info("Can't parse pid file contents", exc_info=True)
497
    return 0
498

    
499
  return pid
500

    
501

    
502
def MatchNameComponent(key, name_list):
503
  """Try to match a name against a list.
504

505
  This function will try to match a name like test1 against a list
506
  like C{['test1.example.com', 'test2.example.com', ...]}. Against
507
  this list, I{'test1'} as well as I{'test1.example'} will match, but
508
  not I{'test1.ex'}. A multiple match will be considered as no match
509
  at all (e.g. I{'test1'} against C{['test1.example.com',
510
  'test1.example.org']}).
511

512
  @type key: str
513
  @param key: the name to be searched
514
  @type name_list: list
515
  @param name_list: the list of strings against which to search the key
516

517
  @rtype: None or str
518
  @return: None if there is no match I{or} if there are multiple matches,
519
      otherwise the element from the list which matches
520

521
  """
522
  mo = re.compile("^%s(\..*)?$" % re.escape(key))
523
  names_filtered = [name for name in name_list if mo.match(name) is not None]
524
  if len(names_filtered) != 1:
525
    return None
526
  return names_filtered[0]
527

    
528

    
529
class HostInfo:
530
  """Class implementing resolver and hostname functionality
531

532
  """
533
  def __init__(self, name=None):
534
    """Initialize the host name object.
535

536
    If the name argument is not passed, it will use this system's
537
    name.
538

539
    """
540
    if name is None:
541
      name = self.SysName()
542

    
543
    self.query = name
544
    self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
545
    self.ip = self.ipaddrs[0]
546

    
547
  def ShortName(self):
548
    """Returns the hostname without domain.
549

550
    """
551
    return self.name.split('.')[0]
552

    
553
  @staticmethod
554
  def SysName():
555
    """Return the current system's name.
556

557
    This is simply a wrapper over C{socket.gethostname()}.
558

559
    """
560
    return socket.gethostname()
561

    
562
  @staticmethod
563
  def LookupHostname(hostname):
564
    """Look up hostname
565

566
    @type hostname: str
567
    @param hostname: hostname to look up
568

569
    @rtype: tuple
570
    @return: a tuple (name, aliases, ipaddrs) as returned by
571
        C{socket.gethostbyname_ex}
572
    @raise errors.ResolverError: in case of errors in resolving
573

574
    """
575
    try:
576
      result = socket.gethostbyname_ex(hostname)
577
    except socket.gaierror, err:
578
      # hostname not found in DNS
579
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
580

    
581
    return result
582

    
583

    
584
def ListVolumeGroups():
585
  """List volume groups and their size
586

587
  @rtype: dict
588
  @return:
589
       Dictionary with keys volume name and values
590
       the size of the volume
591

592
  """
593
  command = "vgs --noheadings --units m --nosuffix -o name,size"
594
  result = RunCmd(command)
595
  retval = {}
596
  if result.failed:
597
    return retval
598

    
599
  for line in result.stdout.splitlines():
600
    try:
601
      name, size = line.split()
602
      size = int(float(size))
603
    except (IndexError, ValueError), err:
604
      logging.error("Invalid output from vgs (%s): %s", err, line)
605
      continue
606

    
607
    retval[name] = size
608

    
609
  return retval
610

    
611

    
612
def BridgeExists(bridge):
613
  """Check whether the given bridge exists in the system
614

615
  @type bridge: str
616
  @param bridge: the bridge name to check
617
  @rtype: boolean
618
  @return: True if it does
619

620
  """
621
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
622

    
623

    
624
def NiceSort(name_list):
625
  """Sort a list of strings based on digit and non-digit groupings.
626

627
  Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
628
  will sort the list in the logical order C{['a1', 'a2', 'a10',
629
  'a11']}.
630

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

635
  @type name_list: list
636
  @param name_list: the names to be sorted
637
  @rtype: list
638
  @return: a copy of the name list sorted with our algorithm
639

640
  """
641
  _SORTER_BASE = "(\D+|\d+)"
642
  _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
643
                                                  _SORTER_BASE, _SORTER_BASE,
644
                                                  _SORTER_BASE, _SORTER_BASE,
645
                                                  _SORTER_BASE, _SORTER_BASE)
646
  _SORTER_RE = re.compile(_SORTER_FULL)
647
  _SORTER_NODIGIT = re.compile("^\D*$")
648
  def _TryInt(val):
649
    """Attempts to convert a variable to integer."""
650
    if val is None or _SORTER_NODIGIT.match(val):
651
      return val
652
    rval = int(val)
653
    return rval
654

    
655
  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
656
             for name in name_list]
657
  to_sort.sort()
658
  return [tup[1] for tup in to_sort]
659

    
660

    
661
def TryConvert(fn, val):
662
  """Try to convert a value ignoring errors.
663

664
  This function tries to apply function I{fn} to I{val}. If no
665
  C{ValueError} or C{TypeError} exceptions are raised, it will return
666
  the result, else it will return the original value. Any other
667
  exceptions are propagated to the caller.
668

669
  @type fn: callable
670
  @param fn: function to apply to the value
671
  @param val: the value to be converted
672
  @return: The converted value if the conversion was successful,
673
      otherwise the original value.
674

675
  """
676
  try:
677
    nv = fn(val)
678
  except (ValueError, TypeError), err:
679
    nv = val
680
  return nv
681

    
682

    
683
def IsValidIP(ip):
684
  """Verifies the syntax of an IPv4 address.
685

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

689
  @type ip: str
690
  @param ip: the address to be checked
691
  @rtype: a regular expression match object
692
  @return: a regular epression match object, or None if the
693
      address is not valid
694

695
  """
696
  unit = "(0|[1-9]\d{0,2})"
697
  #TODO: convert and return only boolean
698
  return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
699

    
700

    
701
def IsValidShellParam(word):
702
  """Verifies is the given word is safe from the shell's p.o.v.
703

704
  This means that we can pass this to a command via the shell and be
705
  sure that it doesn't alter the command line and is passed as such to
706
  the actual command.
707

708
  Note that we are overly restrictive here, in order to be on the safe
709
  side.
710

711
  @type word: str
712
  @param word: the word to check
713
  @rtype: boolean
714
  @return: True if the word is 'safe'
715

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

    
719

    
720
def BuildShellCmd(template, *args):
721
  """Build a safe shell command line from the given arguments.
722

723
  This function will check all arguments in the args list so that they
724
  are valid shell parameters (i.e. they don't contain shell
725
  metacharaters). If everything is ok, it will return the result of
726
  template % args.
727

728
  @type template: str
729
  @param template: the string holding the template for the
730
      string formatting
731
  @rtype: str
732
  @return: the expanded command line
733

734
  """
735
  for word in args:
736
    if not IsValidShellParam(word):
737
      raise errors.ProgrammerError("Shell argument '%s' contains"
738
                                   " invalid characters" % word)
739
  return template % args
740

    
741

    
742
def FormatUnit(value, units):
743
  """Formats an incoming number of MiB with the appropriate unit.
744

745
  @type value: int
746
  @param value: integer representing the value in MiB (1048576)
747
  @type units: char
748
  @param units: the type of formatting we should do:
749
      - 'h' for automatic scaling
750
      - 'm' for MiBs
751
      - 'g' for GiBs
752
      - 't' for TiBs
753
  @rtype: str
754
  @return: the formatted value (with suffix)
755

756
  """
757
  if units not in ('m', 'g', 't', 'h'):
758
    raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
759

    
760
  suffix = ''
761

    
762
  if units == 'm' or (units == 'h' and value < 1024):
763
    if units == 'h':
764
      suffix = 'M'
765
    return "%d%s" % (round(value, 0), suffix)
766

    
767
  elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
768
    if units == 'h':
769
      suffix = 'G'
770
    return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
771

    
772
  else:
773
    if units == 'h':
774
      suffix = 'T'
775
    return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
776

    
777

    
778
def ParseUnit(input_string):
779
  """Tries to extract number and scale from the given string.
780

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

785
  """
786
  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', str(input_string))
787
  if not m:
788
    raise errors.UnitParseError("Invalid format")
789

    
790
  value = float(m.groups()[0])
791

    
792
  unit = m.groups()[1]
793
  if unit:
794
    lcunit = unit.lower()
795
  else:
796
    lcunit = 'm'
797

    
798
  if lcunit in ('m', 'mb', 'mib'):
799
    # Value already in MiB
800
    pass
801

    
802
  elif lcunit in ('g', 'gb', 'gib'):
803
    value *= 1024
804

    
805
  elif lcunit in ('t', 'tb', 'tib'):
806
    value *= 1024 * 1024
807

    
808
  else:
809
    raise errors.UnitParseError("Unknown unit: %s" % unit)
810

    
811
  # Make sure we round up
812
  if int(value) < value:
813
    value += 1
814

    
815
  # Round up to the next multiple of 4
816
  value = int(value)
817
  if value % 4:
818
    value += 4 - value % 4
819

    
820
  return value
821

    
822

    
823
def AddAuthorizedKey(file_name, key):
824
  """Adds an SSH public key to an authorized_keys file.
825

826
  @type file_name: str
827
  @param file_name: path to authorized_keys file
828
  @type key: str
829
  @param key: string containing key
830

831
  """
832
  key_fields = key.split()
833

    
834
  f = open(file_name, 'a+')
835
  try:
836
    nl = True
837
    for line in f:
838
      # Ignore whitespace changes
839
      if line.split() == key_fields:
840
        break
841
      nl = line.endswith('\n')
842
    else:
843
      if not nl:
844
        f.write("\n")
845
      f.write(key.rstrip('\r\n'))
846
      f.write("\n")
847
      f.flush()
848
  finally:
849
    f.close()
850

    
851

    
852
def RemoveAuthorizedKey(file_name, key):
853
  """Removes an SSH public key from an authorized_keys file.
854

855
  @type file_name: str
856
  @param file_name: path to authorized_keys file
857
  @type key: str
858
  @param key: string containing key
859

860
  """
861
  key_fields = key.split()
862

    
863
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
864
  try:
865
    out = os.fdopen(fd, 'w')
866
    try:
867
      f = open(file_name, 'r')
868
      try:
869
        for line in f:
870
          # Ignore whitespace changes while comparing lines
871
          if line.split() != key_fields:
872
            out.write(line)
873

    
874
        out.flush()
875
        os.rename(tmpname, file_name)
876
      finally:
877
        f.close()
878
    finally:
879
      out.close()
880
  except:
881
    RemoveFile(tmpname)
882
    raise
883

    
884

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

888
  @type file_name: str
889
  @param file_name: path to the file to modify (usually C{/etc/hosts})
890
  @type ip: str
891
  @param ip: the IP address
892
  @type hostname: str
893
  @param hostname: the hostname to be added
894
  @type aliases: list
895
  @param aliases: the list of aliases to add for the hostname
896

897
  """
898
  # FIXME: use WriteFile + fn rather than duplicating its efforts
899
  # Ensure aliases are unique
900
  aliases = UniqueSequence([hostname] + aliases)[1:]
901

    
902
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
903
  try:
904
    out = os.fdopen(fd, 'w')
905
    try:
906
      f = open(file_name, 'r')
907
      try:
908
        for line in f:
909
          fields = line.split()
910
          if fields and not fields[0].startswith('#') and ip == fields[0]:
911
            continue
912
          out.write(line)
913

    
914
        out.write("%s\t%s" % (ip, hostname))
915
        if aliases:
916
          out.write(" %s" % ' '.join(aliases))
917
        out.write('\n')
918

    
919
        out.flush()
920
        os.fsync(out)
921
        os.chmod(tmpname, 0644)
922
        os.rename(tmpname, file_name)
923
      finally:
924
        f.close()
925
    finally:
926
      out.close()
927
  except:
928
    RemoveFile(tmpname)
929
    raise
930

    
931

    
932
def AddHostToEtcHosts(hostname):
933
  """Wrapper around SetEtcHostsEntry.
934

935
  @type hostname: str
936
  @param hostname: a hostname that will be resolved and added to
937
      L{constants.ETC_HOSTS}
938

939
  """
940
  hi = HostInfo(name=hostname)
941
  SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
942

    
943

    
944
def RemoveEtcHostsEntry(file_name, hostname):
945
  """Removes a hostname from /etc/hosts.
946

947
  IP addresses without names are removed from the file.
948

949
  @type file_name: str
950
  @param file_name: path to the file to modify (usually C{/etc/hosts})
951
  @type hostname: str
952
  @param hostname: the hostname to be removed
953

954
  """
955
  # FIXME: use WriteFile + fn rather than duplicating its efforts
956
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
957
  try:
958
    out = os.fdopen(fd, 'w')
959
    try:
960
      f = open(file_name, 'r')
961
      try:
962
        for line in f:
963
          fields = line.split()
964
          if len(fields) > 1 and not fields[0].startswith('#'):
965
            names = fields[1:]
966
            if hostname in names:
967
              while hostname in names:
968
                names.remove(hostname)
969
              if names:
970
                out.write("%s %s\n" % (fields[0], ' '.join(names)))
971
              continue
972

    
973
          out.write(line)
974

    
975
        out.flush()
976
        os.fsync(out)
977
        os.chmod(tmpname, 0644)
978
        os.rename(tmpname, file_name)
979
      finally:
980
        f.close()
981
    finally:
982
      out.close()
983
  except:
984
    RemoveFile(tmpname)
985
    raise
986

    
987

    
988
def RemoveHostFromEtcHosts(hostname):
989
  """Wrapper around RemoveEtcHostsEntry.
990

991
  @type hostname: str
992
  @param hostname: hostname that will be resolved and its
993
      full and shot name will be removed from
994
      L{constants.ETC_HOSTS}
995

996
  """
997
  hi = HostInfo(name=hostname)
998
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
999
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
1000

    
1001

    
1002
def CreateBackup(file_name):
1003
  """Creates a backup of a file.
1004

1005
  @type file_name: str
1006
  @param file_name: file to be backed up
1007
  @rtype: str
1008
  @return: the path to the newly created backup
1009
  @raise errors.ProgrammerError: for invalid file names
1010

1011
  """
1012
  if not os.path.isfile(file_name):
1013
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
1014
                                file_name)
1015

    
1016
  prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
1017
  dir_name = os.path.dirname(file_name)
1018

    
1019
  fsrc = open(file_name, 'rb')
1020
  try:
1021
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
1022
    fdst = os.fdopen(fd, 'wb')
1023
    try:
1024
      shutil.copyfileobj(fsrc, fdst)
1025
    finally:
1026
      fdst.close()
1027
  finally:
1028
    fsrc.close()
1029

    
1030
  return backup_name
1031

    
1032

    
1033
def ShellQuote(value):
1034
  """Quotes shell argument according to POSIX.
1035

1036
  @type value: str
1037
  @param value: the argument to be quoted
1038
  @rtype: str
1039
  @return: the quoted value
1040

1041
  """
1042
  if _re_shell_unquoted.match(value):
1043
    return value
1044
  else:
1045
    return "'%s'" % value.replace("'", "'\\''")
1046

    
1047

    
1048
def ShellQuoteArgs(args):
1049
  """Quotes a list of shell arguments.
1050

1051
  @type args: list
1052
  @param args: list of arguments to be quoted
1053
  @rtype: str
1054
  @return: the quoted arguments concatenaned with spaces
1055

1056
  """
1057
  return ' '.join([ShellQuote(i) for i in args])
1058

    
1059

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

1063
  Check if the given IP is reachable by doing attempting a TCP connect
1064
  to it.
1065

1066
  @type target: str
1067
  @param target: the IP or hostname to ping
1068
  @type port: int
1069
  @param port: the port to connect to
1070
  @type timeout: int
1071
  @param timeout: the timeout on the connection attemp
1072
  @type live_port_needed: boolean
1073
  @param live_port_needed: whether a closed port will cause the
1074
      function to return failure, as if there was a timeout
1075
  @type source: str or None
1076
  @param source: if specified, will cause the connect to be made
1077
      from this specific source address; failures to bind other
1078
      than C{EADDRNOTAVAIL} will be ignored
1079

1080
  """
1081
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1082

    
1083
  success = False
1084

    
1085
  if source is not None:
1086
    try:
1087
      sock.bind((source, 0))
1088
    except socket.error, (errcode, errstring):
1089
      if errcode == errno.EADDRNOTAVAIL:
1090
        success = False
1091

    
1092
  sock.settimeout(timeout)
1093

    
1094
  try:
1095
    sock.connect((target, port))
1096
    sock.close()
1097
    success = True
1098
  except socket.timeout:
1099
    success = False
1100
  except socket.error, (errcode, errstring):
1101
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
1102

    
1103
  return success
1104

    
1105

    
1106
def OwnIpAddress(address):
1107
  """Check if the current host has the the given IP address.
1108

1109
  Currently this is done by TCP-pinging the address from the loopback
1110
  address.
1111

1112
  @type address: string
1113
  @param address: the addres to check
1114
  @rtype: bool
1115
  @return: True if we own the address
1116

1117
  """
1118
  return TcpPing(address, constants.DEFAULT_NODED_PORT,
1119
                 source=constants.LOCALHOST_IP_ADDRESS)
1120

    
1121

    
1122
def ListVisibleFiles(path):
1123
  """Returns a list of visible files in a directory.
1124

1125
  @type path: str
1126
  @param path: the directory to enumerate
1127
  @rtype: list
1128
  @return: the list of all files not starting with a dot
1129

1130
  """
1131
  files = [i for i in os.listdir(path) if not i.startswith(".")]
1132
  files.sort()
1133
  return files
1134

    
1135

    
1136
def GetHomeDir(user, default=None):
1137
  """Try to get the homedir of the given user.
1138

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

1143
  """
1144
  try:
1145
    if isinstance(user, basestring):
1146
      result = pwd.getpwnam(user)
1147
    elif isinstance(user, (int, long)):
1148
      result = pwd.getpwuid(user)
1149
    else:
1150
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1151
                                   type(user))
1152
  except KeyError:
1153
    return default
1154
  return result.pw_dir
1155

    
1156

    
1157
def NewUUID():
1158
  """Returns a random UUID.
1159

1160
  @note: This is a Linux-specific method as it uses the /proc
1161
      filesystem.
1162
  @rtype: str
1163

1164
  """
1165
  f = open("/proc/sys/kernel/random/uuid", "r")
1166
  try:
1167
    return f.read(128).rstrip("\n")
1168
  finally:
1169
    f.close()
1170

    
1171

    
1172
def GenerateSecret():
1173
  """Generates a random secret.
1174

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

1178
  @rtype: str
1179
  @return: a sha1 hexdigest of a block of 64 random bytes
1180

1181
  """
1182
  return sha.new(os.urandom(64)).hexdigest()
1183

    
1184

    
1185
def EnsureDirs(dirs):
1186
  """Make required directories, if they don't exist.
1187

1188
  @param dirs: list of tuples (dir_name, dir_mode)
1189
  @type dirs: list of (string, integer)
1190

1191
  """
1192
  for dir_name, dir_mode in dirs:
1193
    try:
1194
      os.mkdir(dir_name, dir_mode)
1195
    except EnvironmentError, err:
1196
      if err.errno != errno.EEXIST:
1197
        raise errors.GenericError("Cannot create needed directory"
1198
                                  " '%s': %s" % (dir_name, err))
1199
    if not os.path.isdir(dir_name):
1200
      raise errors.GenericError("%s is not a directory" % dir_name)
1201

    
1202

    
1203
def ReadFile(file_name, size=None):
1204
  """Reads a file.
1205

1206
  @type size: None or int
1207
  @param size: Read at most size bytes
1208
  @rtype: str
1209
  @return: the (possibly partial) conent of the file
1210

1211
  """
1212
  f = open(file_name, "r")
1213
  try:
1214
    if size is None:
1215
      return f.read()
1216
    else:
1217
      return f.read(size)
1218
  finally:
1219
    f.close()
1220

    
1221

    
1222
def WriteFile(file_name, fn=None, data=None,
1223
              mode=None, uid=-1, gid=-1,
1224
              atime=None, mtime=None, close=True,
1225
              dry_run=False, backup=False,
1226
              prewrite=None, postwrite=None):
1227
  """(Over)write a file atomically.
1228

1229
  The file_name and either fn (a function taking one argument, the
1230
  file descriptor, and which should write the data to it) or data (the
1231
  contents of the file) must be passed. The other arguments are
1232
  optional and allow setting the file mode, owner and group, and the
1233
  mtime/atime of the file.
1234

1235
  If the function doesn't raise an exception, it has succeeded and the
1236
  target file has the new contents. If the function has raised an
1237
  exception, an existing target file should be unmodified and the
1238
  temporary file should be removed.
1239

1240
  @type file_name: str
1241
  @param file_name: the target filename
1242
  @type fn: callable
1243
  @param fn: content writing function, called with
1244
      file descriptor as parameter
1245
  @type data: str
1246
  @param data: contents of the file
1247
  @type mode: int
1248
  @param mode: file mode
1249
  @type uid: int
1250
  @param uid: the owner of the file
1251
  @type gid: int
1252
  @param gid: the group of the file
1253
  @type atime: int
1254
  @param atime: a custom access time to be set on the file
1255
  @type mtime: int
1256
  @param mtime: a custom modification time to be set on the file
1257
  @type close: boolean
1258
  @param close: whether to close file after writing it
1259
  @type prewrite: callable
1260
  @param prewrite: function to be called before writing content
1261
  @type postwrite: callable
1262
  @param postwrite: function to be called after writing content
1263

1264
  @rtype: None or int
1265
  @return: None if the 'close' parameter evaluates to True,
1266
      otherwise the file descriptor
1267

1268
  @raise errors.ProgrammerError: if any of the arguments are not valid
1269

1270
  """
1271
  if not os.path.isabs(file_name):
1272
    raise errors.ProgrammerError("Path passed to WriteFile is not"
1273
                                 " absolute: '%s'" % file_name)
1274

    
1275
  if [fn, data].count(None) != 1:
1276
    raise errors.ProgrammerError("fn or data required")
1277

    
1278
  if [atime, mtime].count(None) == 1:
1279
    raise errors.ProgrammerError("Both atime and mtime must be either"
1280
                                 " set or None")
1281

    
1282
  if backup and not dry_run and os.path.isfile(file_name):
1283
    CreateBackup(file_name)
1284

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

    
1318
  return result
1319

    
1320

    
1321
def FirstFree(seq, base=0):
1322
  """Returns the first non-existing integer from seq.
1323

1324
  The seq argument should be a sorted list of positive integers. The
1325
  first time the index of an element is smaller than the element
1326
  value, the index will be returned.
1327

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

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

1333
  @type seq: sequence
1334
  @param seq: the sequence to be analyzed.
1335
  @type base: int
1336
  @param base: use this value as the base index of the sequence
1337
  @rtype: int
1338
  @return: the first non-used index in the sequence
1339

1340
  """
1341
  for idx, elem in enumerate(seq):
1342
    assert elem >= base, "Passed element is higher than base offset"
1343
    if elem > idx + base:
1344
      # idx is not used
1345
      return idx + base
1346
  return None
1347

    
1348

    
1349
def all(seq, pred=bool):
1350
  "Returns True if pred(x) is True for every element in the iterable"
1351
  for elem in itertools.ifilterfalse(pred, seq):
1352
    return False
1353
  return True
1354

    
1355

    
1356
def any(seq, pred=bool):
1357
  "Returns True if pred(x) is True for at least one element in the iterable"
1358
  for elem in itertools.ifilter(pred, seq):
1359
    return True
1360
  return False
1361

    
1362

    
1363
def UniqueSequence(seq):
1364
  """Returns a list with unique elements.
1365

1366
  Element order is preserved.
1367

1368
  @type seq: sequence
1369
  @param seq: the sequence with the source elementes
1370
  @rtype: list
1371
  @return: list of unique elements from seq
1372

1373
  """
1374
  seen = set()
1375
  return [i for i in seq if i not in seen and not seen.add(i)]
1376

    
1377

    
1378
def IsValidMac(mac):
1379
  """Predicate to check if a MAC address is valid.
1380

1381
  Checks wether the supplied MAC address is formally correct, only
1382
  accepts colon separated format.
1383

1384
  @type mac: str
1385
  @param mac: the MAC to be validated
1386
  @rtype: boolean
1387
  @return: True is the MAC seems valid
1388

1389
  """
1390
  mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$")
1391
  return mac_check.match(mac) is not None
1392

    
1393

    
1394
def TestDelay(duration):
1395
  """Sleep for a fixed amount of time.
1396

1397
  @type duration: float
1398
  @param duration: the sleep duration
1399
  @rtype: boolean
1400
  @return: False for negative value, True otherwise
1401

1402
  """
1403
  if duration < 0:
1404
    return False
1405
  time.sleep(duration)
1406
  return True
1407

    
1408

    
1409
def _CloseFDNoErr(fd, retries=5):
1410
  """Close a file descriptor ignoring errors.
1411

1412
  @type fd: int
1413
  @param fd: the file descriptor
1414
  @type retries: int
1415
  @param retries: how many retries to make, in case we get any
1416
      other error than EBADF
1417

1418
  """
1419
  try:
1420
    os.close(fd)
1421
  except OSError, err:
1422
    if err.errno != errno.EBADF:
1423
      if retries > 0:
1424
        _CloseFDNoErr(fd, retries - 1)
1425
    # else either it's closed already or we're out of retries, so we
1426
    # ignore this and go on
1427

    
1428

    
1429
def CloseFDs(noclose_fds=None):
1430
  """Close file descriptors.
1431

1432
  This closes all file descriptors above 2 (i.e. except
1433
  stdin/out/err).
1434

1435
  @type noclose_fds: list or None
1436
  @param noclose_fds: if given, it denotes a list of file descriptor
1437
      that should not be closed
1438

1439
  """
1440
  # Default maximum for the number of available file descriptors.
1441
  if 'SC_OPEN_MAX' in os.sysconf_names:
1442
    try:
1443
      MAXFD = os.sysconf('SC_OPEN_MAX')
1444
      if MAXFD < 0:
1445
        MAXFD = 1024
1446
    except OSError:
1447
      MAXFD = 1024
1448
  else:
1449
    MAXFD = 1024
1450
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1451
  if (maxfd == resource.RLIM_INFINITY):
1452
    maxfd = MAXFD
1453

    
1454
  # Iterate through and close all file descriptors (except the standard ones)
1455
  for fd in range(3, maxfd):
1456
    if noclose_fds and fd in noclose_fds:
1457
      continue
1458
    _CloseFDNoErr(fd)
1459

    
1460

    
1461
def Daemonize(logfile):
1462
  """Daemonize the current process.
1463

1464
  This detaches the current process from the controlling terminal and
1465
  runs it in the background as a daemon.
1466

1467
  @type logfile: str
1468
  @param logfile: the logfile to which we should redirect stdout/stderr
1469
  @rtype: int
1470
  @return: the value zero
1471

1472
  """
1473
  UMASK = 077
1474
  WORKDIR = "/"
1475

    
1476
  # this might fail
1477
  pid = os.fork()
1478
  if (pid == 0):  # The first child.
1479
    os.setsid()
1480
    # this might fail
1481
    pid = os.fork() # Fork a second child.
1482
    if (pid == 0):  # The second child.
1483
      os.chdir(WORKDIR)
1484
      os.umask(UMASK)
1485
    else:
1486
      # exit() or _exit()?  See below.
1487
      os._exit(0) # Exit parent (the first child) of the second child.
1488
  else:
1489
    os._exit(0) # Exit parent of the first child.
1490

    
1491
  for fd in range(3):
1492
    _CloseFDNoErr(fd)
1493
  i = os.open("/dev/null", os.O_RDONLY) # stdin
1494
  assert i == 0, "Can't close/reopen stdin"
1495
  i = os.open(logfile, os.O_WRONLY|os.O_CREAT|os.O_APPEND, 0600) # stdout
1496
  assert i == 1, "Can't close/reopen stdout"
1497
  # Duplicate standard output to standard error.
1498
  os.dup2(1, 2)
1499
  return 0
1500

    
1501

    
1502
def DaemonPidFileName(name):
1503
  """Compute a ganeti pid file absolute path
1504

1505
  @type name: str
1506
  @param name: the daemon name
1507
  @rtype: str
1508
  @return: the full path to the pidfile corresponding to the given
1509
      daemon name
1510

1511
  """
1512
  return os.path.join(constants.RUN_GANETI_DIR, "%s.pid" % name)
1513

    
1514

    
1515
def WritePidFile(name):
1516
  """Write the current process pidfile.
1517

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

1520
  @type name: str
1521
  @param name: the daemon name to use
1522
  @raise errors.GenericError: if the pid file already exists and
1523
      points to a live process
1524

1525
  """
1526
  pid = os.getpid()
1527
  pidfilename = DaemonPidFileName(name)
1528
  if IsProcessAlive(ReadPidFile(pidfilename)):
1529
    raise errors.GenericError("%s contains a live process" % pidfilename)
1530

    
1531
  WriteFile(pidfilename, data="%d\n" % pid)
1532

    
1533

    
1534
def RemovePidFile(name):
1535
  """Remove the current process pidfile.
1536

1537
  Any errors are ignored.
1538

1539
  @type name: str
1540
  @param name: the daemon name used to derive the pidfile name
1541

1542
  """
1543
  pid = os.getpid()
1544
  pidfilename = DaemonPidFileName(name)
1545
  # TODO: we could check here that the file contains our pid
1546
  try:
1547
    RemoveFile(pidfilename)
1548
  except:
1549
    pass
1550

    
1551

    
1552
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1553
                waitpid=False):
1554
  """Kill a process given by its pid.
1555

1556
  @type pid: int
1557
  @param pid: The PID to terminate.
1558
  @type signal_: int
1559
  @param signal_: The signal to send, by default SIGTERM
1560
  @type timeout: int
1561
  @param timeout: The timeout after which, if the process is still alive,
1562
                  a SIGKILL will be sent. If not positive, no such checking
1563
                  will be done
1564
  @type waitpid: boolean
1565
  @param waitpid: If true, we should waitpid on this process after
1566
      sending signals, since it's our own child and otherwise it
1567
      would remain as zombie
1568

1569
  """
1570
  def _helper(pid, signal_, wait):
1571
    """Simple helper to encapsulate the kill/waitpid sequence"""
1572
    os.kill(pid, signal_)
1573
    if wait:
1574
      try:
1575
        os.waitpid(pid, os.WNOHANG)
1576
      except OSError:
1577
        pass
1578

    
1579
  if pid <= 0:
1580
    # kill with pid=0 == suicide
1581
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1582

    
1583
  if not IsProcessAlive(pid):
1584
    return
1585
  _helper(pid, signal_, waitpid)
1586
  if timeout <= 0:
1587
    return
1588

    
1589
  # Wait up to $timeout seconds
1590
  end = time.time() + timeout
1591
  wait = 0.01
1592
  while time.time() < end and IsProcessAlive(pid):
1593
    try:
1594
      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
1595
      if result_pid > 0:
1596
        break
1597
    except OSError:
1598
      pass
1599
    time.sleep(wait)
1600
    # Make wait time longer for next try
1601
    if wait < 0.1:
1602
      wait *= 1.5
1603

    
1604
  if IsProcessAlive(pid):
1605
    # Kill process if it's still alive
1606
    _helper(pid, signal.SIGKILL, waitpid)
1607

    
1608

    
1609
def FindFile(name, search_path, test=os.path.exists):
1610
  """Look for a filesystem object in a given path.
1611

1612
  This is an abstract method to search for filesystem object (files,
1613
  dirs) under a given search path.
1614

1615
  @type name: str
1616
  @param name: the name to look for
1617
  @type search_path: str
1618
  @param search_path: location to start at
1619
  @type test: callable
1620
  @param test: a function taking one argument that should return True
1621
      if the a given object is valid; the default value is
1622
      os.path.exists, causing only existing files to be returned
1623
  @rtype: str or None
1624
  @return: full path to the object if found, None otherwise
1625

1626
  """
1627
  for dir_name in search_path:
1628
    item_name = os.path.sep.join([dir_name, name])
1629
    if test(item_name):
1630
      return item_name
1631
  return None
1632

    
1633

    
1634
def CheckVolumeGroupSize(vglist, vgname, minsize):
1635
  """Checks if the volume group list is valid.
1636

1637
  The function will check if a given volume group is in the list of
1638
  volume groups and has a minimum size.
1639

1640
  @type vglist: dict
1641
  @param vglist: dictionary of volume group names and their size
1642
  @type vgname: str
1643
  @param vgname: the volume group we should check
1644
  @type minsize: int
1645
  @param minsize: the minimum size we accept
1646
  @rtype: None or str
1647
  @return: None for success, otherwise the error message
1648

1649
  """
1650
  vgsize = vglist.get(vgname, None)
1651
  if vgsize is None:
1652
    return "volume group '%s' missing" % vgname
1653
  elif vgsize < minsize:
1654
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1655
            (vgname, minsize, vgsize))
1656
  return None
1657

    
1658

    
1659
def SplitTime(value):
1660
  """Splits time as floating point number into a tuple.
1661

1662
  @param value: Time in seconds
1663
  @type value: int or float
1664
  @return: Tuple containing (seconds, microseconds)
1665

1666
  """
1667
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1668

    
1669
  assert 0 <= seconds, \
1670
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1671
  assert 0 <= microseconds <= 999999, \
1672
    "Microseconds must be 0-999999, but are %s" % microseconds
1673

    
1674
  return (int(seconds), int(microseconds))
1675

    
1676

    
1677
def MergeTime(timetuple):
1678
  """Merges a tuple into time as a floating point number.
1679

1680
  @param timetuple: Time as tuple, (seconds, microseconds)
1681
  @type timetuple: tuple
1682
  @return: Time as a floating point number expressed in seconds
1683

1684
  """
1685
  (seconds, microseconds) = timetuple
1686

    
1687
  assert 0 <= seconds, \
1688
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1689
  assert 0 <= microseconds <= 999999, \
1690
    "Microseconds must be 0-999999, but are %s" % microseconds
1691

    
1692
  return float(seconds) + (float(microseconds) * 0.000001)
1693

    
1694

    
1695
def GetNodeDaemonPort():
1696
  """Get the node daemon port for this cluster.
1697

1698
  Note that this routine does not read a ganeti-specific file, but
1699
  instead uses C{socket.getservbyname} to allow pre-customization of
1700
  this parameter outside of Ganeti.
1701

1702
  @rtype: int
1703

1704
  """
1705
  try:
1706
    port = socket.getservbyname("ganeti-noded", "tcp")
1707
  except socket.error:
1708
    port = constants.DEFAULT_NODED_PORT
1709

    
1710
  return port
1711

    
1712

    
1713
def SetupLogging(logfile, debug=False, stderr_logging=False, program="",
1714
                 multithreaded=False):
1715
  """Configures the logging module.
1716

1717
  @type logfile: str
1718
  @param logfile: the filename to which we should log
1719
  @type debug: boolean
1720
  @param debug: whether to enable debug messages too or
1721
      only those at C{INFO} and above level
1722
  @type stderr_logging: boolean
1723
  @param stderr_logging: whether we should also log to the standard error
1724
  @type program: str
1725
  @param program: the name under which we should log messages
1726
  @type multithreaded: boolean
1727
  @param multithreaded: if True, will add the thread name to the log file
1728
  @raise EnvironmentError: if we can't open the log file and
1729
      stderr logging is disabled
1730

1731
  """
1732
  fmt = "%(asctime)s: " + program + " pid=%(process)d"
1733
  if multithreaded:
1734
    fmt += "/%(threadName)s"
1735
  if debug:
1736
    fmt += " %(module)s:%(lineno)s"
1737
  fmt += " %(levelname)s %(message)s"
1738
  formatter = logging.Formatter(fmt)
1739

    
1740
  root_logger = logging.getLogger("")
1741
  root_logger.setLevel(logging.NOTSET)
1742

    
1743
  # Remove all previously setup handlers
1744
  for handler in root_logger.handlers:
1745
    handler.close()
1746
    root_logger.removeHandler(handler)
1747

    
1748
  if stderr_logging:
1749
    stderr_handler = logging.StreamHandler()
1750
    stderr_handler.setFormatter(formatter)
1751
    if debug:
1752
      stderr_handler.setLevel(logging.NOTSET)
1753
    else:
1754
      stderr_handler.setLevel(logging.CRITICAL)
1755
    root_logger.addHandler(stderr_handler)
1756

    
1757
  # this can fail, if the logging directories are not setup or we have
1758
  # a permisssion problem; in this case, it's best to log but ignore
1759
  # the error if stderr_logging is True, and if false we re-raise the
1760
  # exception since otherwise we could run but without any logs at all
1761
  try:
1762
    logfile_handler = logging.FileHandler(logfile)
1763
    logfile_handler.setFormatter(formatter)
1764
    if debug:
1765
      logfile_handler.setLevel(logging.DEBUG)
1766
    else:
1767
      logfile_handler.setLevel(logging.INFO)
1768
    root_logger.addHandler(logfile_handler)
1769
  except EnvironmentError:
1770
    if stderr_logging:
1771
      logging.exception("Failed to enable logging to file '%s'", logfile)
1772
    else:
1773
      # we need to re-raise the exception
1774
      raise
1775

    
1776

    
1777
def TailFile(fname, lines=20):
1778
  """Return the last lines from a file.
1779

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

1784
  @param fname: the file name
1785
  @type lines: int
1786
  @param lines: the (maximum) number of lines to return
1787

1788
  """
1789
  fd = open(fname, "r")
1790
  try:
1791
    fd.seek(0, 2)
1792
    pos = fd.tell()
1793
    pos = max(0, pos-4096)
1794
    fd.seek(pos, 0)
1795
    raw_data = fd.read()
1796
  finally:
1797
    fd.close()
1798

    
1799
  rows = raw_data.splitlines()
1800
  return rows[-lines:]
1801

    
1802

    
1803
def SafeEncode(text):
1804
  """Return a 'safe' version of a source string.
1805

1806
  This function mangles the input string and returns a version that
1807
  should be safe to disply/encode as ASCII. To this end, we first
1808
  convert it to ASCII using the 'backslashreplace' encoding which
1809
  should get rid of any non-ASCII chars, and then we again encode it
1810
  via 'string_escape' which converts '\n' into '\\n' so that log
1811
  messages remain one-line.
1812

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

1818
  """
1819
  text = text.encode('ascii', 'backslashreplace')
1820
  text = text.encode('string_escape')
1821
  return text
1822

    
1823

    
1824
def LockedMethod(fn):
1825
  """Synchronized object access decorator.
1826

1827
  This decorator is intended to protect access to an object using the
1828
  object's own lock which is hardcoded to '_lock'.
1829

1830
  """
1831
  def _LockDebug(*args, **kwargs):
1832
    if debug_locks:
1833
      logging.debug(*args, **kwargs)
1834

    
1835
  def wrapper(self, *args, **kwargs):
1836
    assert hasattr(self, '_lock')
1837
    lock = self._lock
1838
    _LockDebug("Waiting for %s", lock)
1839
    lock.acquire()
1840
    try:
1841
      _LockDebug("Acquired %s", lock)
1842
      result = fn(self, *args, **kwargs)
1843
    finally:
1844
      _LockDebug("Releasing %s", lock)
1845
      lock.release()
1846
      _LockDebug("Released %s", lock)
1847
    return result
1848
  return wrapper
1849

    
1850

    
1851
def LockFile(fd):
1852
  """Locks a file using POSIX locks.
1853

1854
  @type fd: int
1855
  @param fd: the file descriptor we need to lock
1856

1857
  """
1858
  try:
1859
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
1860
  except IOError, err:
1861
    if err.errno == errno.EAGAIN:
1862
      raise errors.LockError("File already locked")
1863
    raise
1864

    
1865

    
1866
class FileLock(object):
1867
  """Utility class for file locks.
1868

1869
  """
1870
  def __init__(self, filename):
1871
    """Constructor for FileLock.
1872

1873
    This will open the file denoted by the I{filename} argument.
1874

1875
    @type filename: str
1876
    @param filename: path to the file to be locked
1877

1878
    """
1879
    self.filename = filename
1880
    self.fd = open(self.filename, "w")
1881

    
1882
  def __del__(self):
1883
    self.Close()
1884

    
1885
  def Close(self):
1886
    """Close the file and release the lock.
1887

1888
    """
1889
    if self.fd:
1890
      self.fd.close()
1891
      self.fd = None
1892

    
1893
  def _flock(self, flag, blocking, timeout, errmsg):
1894
    """Wrapper for fcntl.flock.
1895

1896
    @type flag: int
1897
    @param flag: operation flag
1898
    @type blocking: bool
1899
    @param blocking: whether the operation should be done in blocking mode.
1900
    @type timeout: None or float
1901
    @param timeout: for how long the operation should be retried (implies
1902
                    non-blocking mode).
1903
    @type errmsg: string
1904
    @param errmsg: error message in case operation fails.
1905

1906
    """
1907
    assert self.fd, "Lock was closed"
1908
    assert timeout is None or timeout >= 0, \
1909
      "If specified, timeout must be positive"
1910

    
1911
    if timeout is not None:
1912
      flag |= fcntl.LOCK_NB
1913
      timeout_end = time.time() + timeout
1914

    
1915
    # Blocking doesn't have effect with timeout
1916
    elif not blocking:
1917
      flag |= fcntl.LOCK_NB
1918
      timeout_end = None
1919

    
1920
    retry = True
1921
    while retry:
1922
      try:
1923
        fcntl.flock(self.fd, flag)
1924
        retry = False
1925
      except IOError, err:
1926
        if err.errno in (errno.EAGAIN, ):
1927
          if timeout_end is not None and time.time() < timeout_end:
1928
            # Wait before trying again
1929
            time.sleep(max(0.1, min(1.0, timeout)))
1930
          else:
1931
            raise errors.LockError(errmsg)
1932
        else:
1933
          logging.exception("fcntl.flock failed")
1934
          raise
1935

    
1936
  def Exclusive(self, blocking=False, timeout=None):
1937
    """Locks the file in exclusive mode.
1938

1939
    @type blocking: boolean
1940
    @param blocking: whether to block and wait until we
1941
        can lock the file or return immediately
1942
    @type timeout: int or None
1943
    @param timeout: if not None, the duration to wait for the lock
1944
        (in blocking mode)
1945

1946
    """
1947
    self._flock(fcntl.LOCK_EX, blocking, timeout,
1948
                "Failed to lock %s in exclusive mode" % self.filename)
1949

    
1950
  def Shared(self, blocking=False, timeout=None):
1951
    """Locks the file in shared mode.
1952

1953
    @type blocking: boolean
1954
    @param blocking: whether to block and wait until we
1955
        can lock the file or return immediately
1956
    @type timeout: int or None
1957
    @param timeout: if not None, the duration to wait for the lock
1958
        (in blocking mode)
1959

1960
    """
1961
    self._flock(fcntl.LOCK_SH, blocking, timeout,
1962
                "Failed to lock %s in shared mode" % self.filename)
1963

    
1964
  def Unlock(self, blocking=True, timeout=None):
1965
    """Unlocks the file.
1966

1967
    According to C{flock(2)}, unlocking can also be a nonblocking
1968
    operation::
1969

1970
      To make a non-blocking request, include LOCK_NB with any of the above
1971
      operations.
1972

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

1980
    """
1981
    self._flock(fcntl.LOCK_UN, blocking, timeout,
1982
                "Failed to unlock %s" % self.filename)
1983

    
1984

    
1985
class SignalHandler(object):
1986
  """Generic signal handler class.
1987

1988
  It automatically restores the original handler when deconstructed or
1989
  when L{Reset} is called. You can either pass your own handler
1990
  function in or query the L{called} attribute to detect whether the
1991
  signal was sent.
1992

1993
  @type signum: list
1994
  @ivar signum: the signals we handle
1995
  @type called: boolean
1996
  @ivar called: tracks whether any of the signals have been raised
1997

1998
  """
1999
  def __init__(self, signum):
2000
    """Constructs a new SignalHandler instance.
2001

2002
    @type signum: int or list of ints
2003
    @param signum: Single signal number or set of signal numbers
2004

2005
    """
2006
    if isinstance(signum, (int, long)):
2007
      self.signum = set([signum])
2008
    else:
2009
      self.signum = set(signum)
2010

    
2011
    self.called = False
2012

    
2013
    self._previous = {}
2014
    try:
2015
      for signum in self.signum:
2016
        # Setup handler
2017
        prev_handler = signal.signal(signum, self._HandleSignal)
2018
        try:
2019
          self._previous[signum] = prev_handler
2020
        except:
2021
          # Restore previous handler
2022
          signal.signal(signum, prev_handler)
2023
          raise
2024
    except:
2025
      # Reset all handlers
2026
      self.Reset()
2027
      # Here we have a race condition: a handler may have already been called,
2028
      # but there's not much we can do about it at this point.
2029
      raise
2030

    
2031
  def __del__(self):
2032
    self.Reset()
2033

    
2034
  def Reset(self):
2035
    """Restore previous handler.
2036

2037
    This will reset all the signals to their previous handlers.
2038

2039
    """
2040
    for signum, prev_handler in self._previous.items():
2041
      signal.signal(signum, prev_handler)
2042
      # If successful, remove from dict
2043
      del self._previous[signum]
2044

    
2045
  def Clear(self):
2046
    """Unsets the L{called} flag.
2047

2048
    This function can be used in case a signal may arrive several times.
2049

2050
    """
2051
    self.called = False
2052

    
2053
  def _HandleSignal(self, signum, frame):
2054
    """Actual signal handling function.
2055

2056
    """
2057
    # This is not nice and not absolutely atomic, but it appears to be the only
2058
    # solution in Python -- there are no atomic types.
2059
    self.called = True
2060

    
2061

    
2062
class FieldSet(object):
2063
  """A simple field set.
2064

2065
  Among the features are:
2066
    - checking if a string is among a list of static string or regex objects
2067
    - checking if a whole list of string matches
2068
    - returning the matching groups from a regex match
2069

2070
  Internally, all fields are held as regular expression objects.
2071

2072
  """
2073
  def __init__(self, *items):
2074
    self.items = [re.compile("^%s$" % value) for value in items]
2075

    
2076
  def Extend(self, other_set):
2077
    """Extend the field set with the items from another one"""
2078
    self.items.extend(other_set.items)
2079

    
2080
  def Matches(self, field):
2081
    """Checks if a field matches the current set
2082

2083
    @type field: str
2084
    @param field: the string to match
2085
    @return: either False or a regular expression match object
2086

2087
    """
2088
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
2089
      return m
2090
    return False
2091

    
2092
  def NonMatching(self, items):
2093
    """Returns the list of fields not matching the current set
2094

2095
    @type items: list
2096
    @param items: the list of fields to check
2097
    @rtype: list
2098
    @return: list of non-matching fields
2099

2100
    """
2101
    return [val for val in items if not self.Matches(val)]