Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ a133af22

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 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
  if output is None:
160
    out, err, status = _RunCmdPipe(cmd, cmd_env, shell, cwd)
161
  else:
162
    status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
163
    out = err = ""
164

    
165
  if status >= 0:
166
    exitcode = status
167
    signal_ = None
168
  else:
169
    exitcode = None
170
    signal_ = -status
171

    
172
  return RunResult(exitcode, signal_, out, err, strcmd)
173

    
174

    
175
def _RunCmdPipe(cmd, env, via_shell, cwd):
176
  """Run a command and return its output.
177

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

189
  """
190
  poller = select.poll()
191
  child = subprocess.Popen(cmd, shell=via_shell,
192
                           stderr=subprocess.PIPE,
193
                           stdout=subprocess.PIPE,
194
                           stdin=subprocess.PIPE,
195
                           close_fds=True, env=env,
196
                           cwd=cwd)
197

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

    
211
  while fdmap:
212
    try:
213
      pollresult = poller.poll()
214
    except EnvironmentError, eerr:
215
      if eerr.errno == errno.EINTR:
216
        continue
217
      raise
218
    except select.error, serr:
219
      if serr[0] == errno.EINTR:
220
        continue
221
      raise
222

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

    
237
  out = out.getvalue()
238
  err = err.getvalue()
239

    
240
  status = child.wait()
241
  return out, err, status
242

    
243

    
244
def _RunCmdFile(cmd, env, via_shell, output, cwd):
245
  """Run a command and save its output to a file.
246

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

260
  """
261
  fh = open(output, "a")
262
  try:
263
    child = subprocess.Popen(cmd, shell=via_shell,
264
                             stderr=subprocess.STDOUT,
265
                             stdout=fh,
266
                             stdin=subprocess.PIPE,
267
                             close_fds=True, env=env,
268
                             cwd=cwd)
269

    
270
    child.stdin.close()
271
    status = child.wait()
272
  finally:
273
    fh.close()
274
  return status
275

    
276

    
277
def RemoveFile(filename):
278
  """Remove a file ignoring some errors.
279

280
  Remove a file, ignoring non-existing ones or directories. Other
281
  errors are passed.
282

283
  @type filename: str
284
  @param filename: the file to be removed
285

286
  """
287
  try:
288
    os.unlink(filename)
289
  except OSError, err:
290
    if err.errno not in (errno.ENOENT, errno.EISDIR):
291
      raise
292

    
293

    
294
def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
295
  """Renames a file.
296

297
  @type old: string
298
  @param old: Original path
299
  @type new: string
300
  @param new: New path
301
  @type mkdir: bool
302
  @param mkdir: Whether to create target directory if it doesn't exist
303
  @type mkdir_mode: int
304
  @param mkdir_mode: Mode for newly created directories
305

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

    
319

    
320
def _FingerprintFile(filename):
321
  """Compute the fingerprint of a file.
322

323
  If the file does not exist, a None will be returned
324
  instead.
325

326
  @type filename: str
327
  @param filename: the filename to checksum
328
  @rtype: str
329
  @return: the hex digest of the sha checksum of the contents
330
      of the file
331

332
  """
333
  if not (os.path.exists(filename) and os.path.isfile(filename)):
334
    return None
335

    
336
  f = open(filename)
337

    
338
  fp = sha1()
339
  while True:
340
    data = f.read(4096)
341
    if not data:
342
      break
343

    
344
    fp.update(data)
345

    
346
  return fp.hexdigest()
347

    
348

    
349
def FingerprintFiles(files):
350
  """Compute fingerprints for a list of files.
351

352
  @type files: list
353
  @param files: the list of filename to fingerprint
354
  @rtype: dict
355
  @return: a dictionary filename: fingerprint, holding only
356
      existing files
357

358
  """
359
  ret = {}
360

    
361
  for filename in files:
362
    cksum = _FingerprintFile(filename)
363
    if cksum:
364
      ret[filename] = cksum
365

    
366
  return ret
367

    
368

    
369
def CheckDict(target, template, logname=None):
370
  """Ensure a dictionary has a required set of keys.
371

372
  For the given dictionaries I{target} and I{template}, ensure
373
  I{target} has all the keys from I{template}. Missing keys are added
374
  with values from template.
375

376
  @type target: dict
377
  @param target: the dictionary to update
378
  @type template: dict
379
  @param template: the dictionary holding the default values
380
  @type logname: str or None
381
  @param logname: if not None, causes the missing keys to be
382
      logged with this name
383

384
  """
385
  missing = []
386
  for k in template:
387
    if k not in target:
388
      missing.append(k)
389
      target[k] = template[k]
390

    
391
  if missing and logname:
392
    logging.warning('%s missing keys %s', logname, ', '.join(missing))
393

    
394

    
395
def ForceDictType(target, key_types, allowed_values=None):
396
  """Force the values of a dict to have certain types.
397

398
  @type target: dict
399
  @param target: the dict to update
400
  @type key_types: dict
401
  @param key_types: dict mapping target dict keys to types
402
                    in constants.ENFORCEABLE_TYPES
403
  @type allowed_values: list
404
  @keyword allowed_values: list of specially allowed values
405

406
  """
407
  if allowed_values is None:
408
    allowed_values = []
409

    
410
  for key in target:
411
    if key not in key_types:
412
      msg = "Unknown key '%s'" % key
413
      raise errors.TypeEnforcementError(msg)
414

    
415
    if target[key] in allowed_values:
416
      continue
417

    
418
    type = key_types[key]
419
    if type not in constants.ENFORCEABLE_TYPES:
420
      msg = "'%s' has non-enforceable type %s" % (key, type)
421
      raise errors.ProgrammerError(msg)
422

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

    
457

    
458
def IsProcessAlive(pid):
459
  """Check if a given pid exists on the system.
460

461
  @note: zombie status is not handled, so zombie processes
462
      will be returned as alive
463
  @type pid: int
464
  @param pid: the process ID to check
465
  @rtype: boolean
466
  @return: True if the process exists
467

468
  """
469
  if pid <= 0:
470
    return False
471

    
472
  try:
473
    os.stat("/proc/%d/status" % pid)
474
    return True
475
  except EnvironmentError, err:
476
    if err.errno in (errno.ENOENT, errno.ENOTDIR):
477
      return False
478
    raise
479

    
480

    
481
def ReadPidFile(pidfile):
482
  """Read a pid from a file.
483

484
  @type  pidfile: string
485
  @param pidfile: path to the file containing the pid
486
  @rtype: int
487
  @return: The process id, if the file exists and contains a valid PID,
488
           otherwise 0
489

490
  """
491
  try:
492
    pf = open(pidfile, 'r')
493
  except EnvironmentError, err:
494
    if err.errno != errno.ENOENT:
495
      logging.exception("Can't read pid file?!")
496
    return 0
497

    
498
  try:
499
    pid = int(pf.read())
500
  except ValueError, err:
501
    logging.info("Can't parse pid file contents", exc_info=True)
502
    return 0
503

    
504
  return pid
505

    
506

    
507
def MatchNameComponent(key, name_list):
508
  """Try to match a name against a list.
509

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

517
  @type key: str
518
  @param key: the name to be searched
519
  @type name_list: list
520
  @param name_list: the list of strings against which to search the key
521

522
  @rtype: None or str
523
  @return: None if there is no match I{or} if there are multiple matches,
524
      otherwise the element from the list which matches
525

526
  """
527
  mo = re.compile("^%s(\..*)?$" % re.escape(key))
528
  names_filtered = [name for name in name_list if mo.match(name) is not None]
529
  if len(names_filtered) != 1:
530
    return None
531
  return names_filtered[0]
532

    
533

    
534
class HostInfo:
535
  """Class implementing resolver and hostname functionality
536

537
  """
538
  def __init__(self, name=None):
539
    """Initialize the host name object.
540

541
    If the name argument is not passed, it will use this system's
542
    name.
543

544
    """
545
    if name is None:
546
      name = self.SysName()
547

    
548
    self.query = name
549
    self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
550
    self.ip = self.ipaddrs[0]
551

    
552
  def ShortName(self):
553
    """Returns the hostname without domain.
554

555
    """
556
    return self.name.split('.')[0]
557

    
558
  @staticmethod
559
  def SysName():
560
    """Return the current system's name.
561

562
    This is simply a wrapper over C{socket.gethostname()}.
563

564
    """
565
    return socket.gethostname()
566

    
567
  @staticmethod
568
  def LookupHostname(hostname):
569
    """Look up hostname
570

571
    @type hostname: str
572
    @param hostname: hostname to look up
573

574
    @rtype: tuple
575
    @return: a tuple (name, aliases, ipaddrs) as returned by
576
        C{socket.gethostbyname_ex}
577
    @raise errors.ResolverError: in case of errors in resolving
578

579
    """
580
    try:
581
      result = socket.gethostbyname_ex(hostname)
582
    except socket.gaierror, err:
583
      # hostname not found in DNS
584
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
585

    
586
    return result
587

    
588

    
589
def ListVolumeGroups():
590
  """List volume groups and their size
591

592
  @rtype: dict
593
  @return:
594
       Dictionary with keys volume name and values
595
       the size of the volume
596

597
  """
598
  command = "vgs --noheadings --units m --nosuffix -o name,size"
599
  result = RunCmd(command)
600
  retval = {}
601
  if result.failed:
602
    return retval
603

    
604
  for line in result.stdout.splitlines():
605
    try:
606
      name, size = line.split()
607
      size = int(float(size))
608
    except (IndexError, ValueError), err:
609
      logging.error("Invalid output from vgs (%s): %s", err, line)
610
      continue
611

    
612
    retval[name] = size
613

    
614
  return retval
615

    
616

    
617
def BridgeExists(bridge):
618
  """Check whether the given bridge exists in the system
619

620
  @type bridge: str
621
  @param bridge: the bridge name to check
622
  @rtype: boolean
623
  @return: True if it does
624

625
  """
626
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
627

    
628

    
629
def NiceSort(name_list):
630
  """Sort a list of strings based on digit and non-digit groupings.
631

632
  Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
633
  will sort the list in the logical order C{['a1', 'a2', 'a10',
634
  'a11']}.
635

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

640
  @type name_list: list
641
  @param name_list: the names to be sorted
642
  @rtype: list
643
  @return: a copy of the name list sorted with our algorithm
644

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

    
660
  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
661
             for name in name_list]
662
  to_sort.sort()
663
  return [tup[1] for tup in to_sort]
664

    
665

    
666
def TryConvert(fn, val):
667
  """Try to convert a value ignoring errors.
668

669
  This function tries to apply function I{fn} to I{val}. If no
670
  C{ValueError} or C{TypeError} exceptions are raised, it will return
671
  the result, else it will return the original value. Any other
672
  exceptions are propagated to the caller.
673

674
  @type fn: callable
675
  @param fn: function to apply to the value
676
  @param val: the value to be converted
677
  @return: The converted value if the conversion was successful,
678
      otherwise the original value.
679

680
  """
681
  try:
682
    nv = fn(val)
683
  except (ValueError, TypeError), err:
684
    nv = val
685
  return nv
686

    
687

    
688
def IsValidIP(ip):
689
  """Verifies the syntax of an IPv4 address.
690

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

694
  @type ip: str
695
  @param ip: the address to be checked
696
  @rtype: a regular expression match object
697
  @return: a regular epression match object, or None if the
698
      address is not valid
699

700
  """
701
  unit = "(0|[1-9]\d{0,2})"
702
  #TODO: convert and return only boolean
703
  return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
704

    
705

    
706
def IsValidShellParam(word):
707
  """Verifies is the given word is safe from the shell's p.o.v.
708

709
  This means that we can pass this to a command via the shell and be
710
  sure that it doesn't alter the command line and is passed as such to
711
  the actual command.
712

713
  Note that we are overly restrictive here, in order to be on the safe
714
  side.
715

716
  @type word: str
717
  @param word: the word to check
718
  @rtype: boolean
719
  @return: True if the word is 'safe'
720

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

    
724

    
725
def BuildShellCmd(template, *args):
726
  """Build a safe shell command line from the given arguments.
727

728
  This function will check all arguments in the args list so that they
729
  are valid shell parameters (i.e. they don't contain shell
730
  metacharaters). If everything is ok, it will return the result of
731
  template % args.
732

733
  @type template: str
734
  @param template: the string holding the template for the
735
      string formatting
736
  @rtype: str
737
  @return: the expanded command line
738

739
  """
740
  for word in args:
741
    if not IsValidShellParam(word):
742
      raise errors.ProgrammerError("Shell argument '%s' contains"
743
                                   " invalid characters" % word)
744
  return template % args
745

    
746

    
747
def FormatUnit(value, units):
748
  """Formats an incoming number of MiB with the appropriate unit.
749

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

761
  """
762
  if units not in ('m', 'g', 't', 'h'):
763
    raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
764

    
765
  suffix = ''
766

    
767
  if units == 'm' or (units == 'h' and value < 1024):
768
    if units == 'h':
769
      suffix = 'M'
770
    return "%d%s" % (round(value, 0), suffix)
771

    
772
  elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
773
    if units == 'h':
774
      suffix = 'G'
775
    return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
776

    
777
  else:
778
    if units == 'h':
779
      suffix = 'T'
780
    return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
781

    
782

    
783
def ParseUnit(input_string):
784
  """Tries to extract number and scale from the given string.
785

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

790
  """
791
  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', str(input_string))
792
  if not m:
793
    raise errors.UnitParseError("Invalid format")
794

    
795
  value = float(m.groups()[0])
796

    
797
  unit = m.groups()[1]
798
  if unit:
799
    lcunit = unit.lower()
800
  else:
801
    lcunit = 'm'
802

    
803
  if lcunit in ('m', 'mb', 'mib'):
804
    # Value already in MiB
805
    pass
806

    
807
  elif lcunit in ('g', 'gb', 'gib'):
808
    value *= 1024
809

    
810
  elif lcunit in ('t', 'tb', 'tib'):
811
    value *= 1024 * 1024
812

    
813
  else:
814
    raise errors.UnitParseError("Unknown unit: %s" % unit)
815

    
816
  # Make sure we round up
817
  if int(value) < value:
818
    value += 1
819

    
820
  # Round up to the next multiple of 4
821
  value = int(value)
822
  if value % 4:
823
    value += 4 - value % 4
824

    
825
  return value
826

    
827

    
828
def AddAuthorizedKey(file_name, key):
829
  """Adds an SSH public key to an authorized_keys file.
830

831
  @type file_name: str
832
  @param file_name: path to authorized_keys file
833
  @type key: str
834
  @param key: string containing key
835

836
  """
837
  key_fields = key.split()
838

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

    
856

    
857
def RemoveAuthorizedKey(file_name, key):
858
  """Removes an SSH public key from an authorized_keys file.
859

860
  @type file_name: str
861
  @param file_name: path to authorized_keys file
862
  @type key: str
863
  @param key: string containing key
864

865
  """
866
  key_fields = key.split()
867

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

    
879
        out.flush()
880
        os.rename(tmpname, file_name)
881
      finally:
882
        f.close()
883
    finally:
884
      out.close()
885
  except:
886
    RemoveFile(tmpname)
887
    raise
888

    
889

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

893
  @type file_name: str
894
  @param file_name: path to the file to modify (usually C{/etc/hosts})
895
  @type ip: str
896
  @param ip: the IP address
897
  @type hostname: str
898
  @param hostname: the hostname to be added
899
  @type aliases: list
900
  @param aliases: the list of aliases to add for the hostname
901

902
  """
903
  # FIXME: use WriteFile + fn rather than duplicating its efforts
904
  # Ensure aliases are unique
905
  aliases = UniqueSequence([hostname] + aliases)[1:]
906

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

    
919
        out.write("%s\t%s" % (ip, hostname))
920
        if aliases:
921
          out.write(" %s" % ' '.join(aliases))
922
        out.write('\n')
923

    
924
        out.flush()
925
        os.fsync(out)
926
        os.chmod(tmpname, 0644)
927
        os.rename(tmpname, file_name)
928
      finally:
929
        f.close()
930
    finally:
931
      out.close()
932
  except:
933
    RemoveFile(tmpname)
934
    raise
935

    
936

    
937
def AddHostToEtcHosts(hostname):
938
  """Wrapper around SetEtcHostsEntry.
939

940
  @type hostname: str
941
  @param hostname: a hostname that will be resolved and added to
942
      L{constants.ETC_HOSTS}
943

944
  """
945
  hi = HostInfo(name=hostname)
946
  SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
947

    
948

    
949
def RemoveEtcHostsEntry(file_name, hostname):
950
  """Removes a hostname from /etc/hosts.
951

952
  IP addresses without names are removed from the file.
953

954
  @type file_name: str
955
  @param file_name: path to the file to modify (usually C{/etc/hosts})
956
  @type hostname: str
957
  @param hostname: the hostname to be removed
958

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

    
978
          out.write(line)
979

    
980
        out.flush()
981
        os.fsync(out)
982
        os.chmod(tmpname, 0644)
983
        os.rename(tmpname, file_name)
984
      finally:
985
        f.close()
986
    finally:
987
      out.close()
988
  except:
989
    RemoveFile(tmpname)
990
    raise
991

    
992

    
993
def RemoveHostFromEtcHosts(hostname):
994
  """Wrapper around RemoveEtcHostsEntry.
995

996
  @type hostname: str
997
  @param hostname: hostname that will be resolved and its
998
      full and shot name will be removed from
999
      L{constants.ETC_HOSTS}
1000

1001
  """
1002
  hi = HostInfo(name=hostname)
1003
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
1004
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
1005

    
1006

    
1007
def CreateBackup(file_name):
1008
  """Creates a backup of a file.
1009

1010
  @type file_name: str
1011
  @param file_name: file to be backed up
1012
  @rtype: str
1013
  @return: the path to the newly created backup
1014
  @raise errors.ProgrammerError: for invalid file names
1015

1016
  """
1017
  if not os.path.isfile(file_name):
1018
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
1019
                                file_name)
1020

    
1021
  prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
1022
  dir_name = os.path.dirname(file_name)
1023

    
1024
  fsrc = open(file_name, 'rb')
1025
  try:
1026
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
1027
    fdst = os.fdopen(fd, 'wb')
1028
    try:
1029
      shutil.copyfileobj(fsrc, fdst)
1030
    finally:
1031
      fdst.close()
1032
  finally:
1033
    fsrc.close()
1034

    
1035
  return backup_name
1036

    
1037

    
1038
def ShellQuote(value):
1039
  """Quotes shell argument according to POSIX.
1040

1041
  @type value: str
1042
  @param value: the argument to be quoted
1043
  @rtype: str
1044
  @return: the quoted value
1045

1046
  """
1047
  if _re_shell_unquoted.match(value):
1048
    return value
1049
  else:
1050
    return "'%s'" % value.replace("'", "'\\''")
1051

    
1052

    
1053
def ShellQuoteArgs(args):
1054
  """Quotes a list of shell arguments.
1055

1056
  @type args: list
1057
  @param args: list of arguments to be quoted
1058
  @rtype: str
1059
  @return: the quoted arguments concatenaned with spaces
1060

1061
  """
1062
  return ' '.join([ShellQuote(i) for i in args])
1063

    
1064

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

1068
  Check if the given IP is reachable by doing attempting a TCP connect
1069
  to it.
1070

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

1085
  """
1086
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1087

    
1088
  success = False
1089

    
1090
  if source is not None:
1091
    try:
1092
      sock.bind((source, 0))
1093
    except socket.error, (errcode, errstring):
1094
      if errcode == errno.EADDRNOTAVAIL:
1095
        success = False
1096

    
1097
  sock.settimeout(timeout)
1098

    
1099
  try:
1100
    sock.connect((target, port))
1101
    sock.close()
1102
    success = True
1103
  except socket.timeout:
1104
    success = False
1105
  except socket.error, (errcode, errstring):
1106
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
1107

    
1108
  return success
1109

    
1110

    
1111
def OwnIpAddress(address):
1112
  """Check if the current host has the the given IP address.
1113

1114
  Currently this is done by TCP-pinging the address from the loopback
1115
  address.
1116

1117
  @type address: string
1118
  @param address: the addres to check
1119
  @rtype: bool
1120
  @return: True if we own the address
1121

1122
  """
1123
  return TcpPing(address, constants.DEFAULT_NODED_PORT,
1124
                 source=constants.LOCALHOST_IP_ADDRESS)
1125

    
1126

    
1127
def ListVisibleFiles(path):
1128
  """Returns a list of visible files in a directory.
1129

1130
  @type path: str
1131
  @param path: the directory to enumerate
1132
  @rtype: list
1133
  @return: the list of all files not starting with a dot
1134

1135
  """
1136
  files = [i for i in os.listdir(path) if not i.startswith(".")]
1137
  files.sort()
1138
  return files
1139

    
1140

    
1141
def GetHomeDir(user, default=None):
1142
  """Try to get the homedir of the given user.
1143

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

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

    
1161

    
1162
def NewUUID():
1163
  """Returns a random UUID.
1164

1165
  @note: This is a Linux-specific method as it uses the /proc
1166
      filesystem.
1167
  @rtype: str
1168

1169
  """
1170
  f = open("/proc/sys/kernel/random/uuid", "r")
1171
  try:
1172
    return f.read(128).rstrip("\n")
1173
  finally:
1174
    f.close()
1175

    
1176

    
1177
def GenerateSecret():
1178
  """Generates a random secret.
1179

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

1183
  @rtype: str
1184
  @return: a sha1 hexdigest of a block of 64 random bytes
1185

1186
  """
1187
  return sha1(os.urandom(64)).hexdigest()
1188

    
1189

    
1190
def EnsureDirs(dirs):
1191
  """Make required directories, if they don't exist.
1192

1193
  @param dirs: list of tuples (dir_name, dir_mode)
1194
  @type dirs: list of (string, integer)
1195

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

    
1207

    
1208
def ReadFile(file_name, size=None):
1209
  """Reads a file.
1210

1211
  @type size: None or int
1212
  @param size: Read at most size bytes
1213
  @rtype: str
1214
  @return: the (possibly partial) conent of the file
1215

1216
  """
1217
  f = open(file_name, "r")
1218
  try:
1219
    if size is None:
1220
      return f.read()
1221
    else:
1222
      return f.read(size)
1223
  finally:
1224
    f.close()
1225

    
1226

    
1227
def WriteFile(file_name, fn=None, data=None,
1228
              mode=None, uid=-1, gid=-1,
1229
              atime=None, mtime=None, close=True,
1230
              dry_run=False, backup=False,
1231
              prewrite=None, postwrite=None):
1232
  """(Over)write a file atomically.
1233

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

1240
  If the function doesn't raise an exception, it has succeeded and the
1241
  target file has the new contents. If the function has raised an
1242
  exception, an existing target file should be unmodified and the
1243
  temporary file should be removed.
1244

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

1269
  @rtype: None or int
1270
  @return: None if the 'close' parameter evaluates to True,
1271
      otherwise the file descriptor
1272

1273
  @raise errors.ProgrammerError: if any of the arguments are not valid
1274

1275
  """
1276
  if not os.path.isabs(file_name):
1277
    raise errors.ProgrammerError("Path passed to WriteFile is not"
1278
                                 " absolute: '%s'" % file_name)
1279

    
1280
  if [fn, data].count(None) != 1:
1281
    raise errors.ProgrammerError("fn or data required")
1282

    
1283
  if [atime, mtime].count(None) == 1:
1284
    raise errors.ProgrammerError("Both atime and mtime must be either"
1285
                                 " set or None")
1286

    
1287
  if backup and not dry_run and os.path.isfile(file_name):
1288
    CreateBackup(file_name)
1289

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

    
1320
  return result
1321

    
1322

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

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

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

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

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

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

    
1350

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

    
1357

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

    
1364

    
1365
def UniqueSequence(seq):
1366
  """Returns a list with unique elements.
1367

1368
  Element order is preserved.
1369

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

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

    
1379

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

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

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

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

    
1395

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

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

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

    
1410

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

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

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

    
1430

    
1431
def CloseFDs(noclose_fds=None):
1432
  """Close file descriptors.
1433

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

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

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

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

    
1462

    
1463
def Daemonize(logfile):
1464
  """Daemonize the current process.
1465

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

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

1474
  """
1475
  UMASK = 077
1476
  WORKDIR = "/"
1477

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

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

    
1503

    
1504
def DaemonPidFileName(name):
1505
  """Compute a ganeti pid file absolute path
1506

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

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

    
1516

    
1517
def WritePidFile(name):
1518
  """Write the current process pidfile.
1519

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

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

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

    
1533
  WriteFile(pidfilename, data="%d\n" % pid)
1534

    
1535

    
1536
def RemovePidFile(name):
1537
  """Remove the current process pidfile.
1538

1539
  Any errors are ignored.
1540

1541
  @type name: str
1542
  @param name: the daemon name used to derive the pidfile name
1543

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

    
1553

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

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

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

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

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

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

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

    
1610

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

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

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

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

    
1635

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

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

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

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

    
1660

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

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

1668
  """
1669
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1670

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

    
1676
  return (int(seconds), int(microseconds))
1677

    
1678

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

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

1686
  """
1687
  (seconds, microseconds) = timetuple
1688

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

    
1694
  return float(seconds) + (float(microseconds) * 0.000001)
1695

    
1696

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

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

1704
  @rtype: int
1705

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

    
1712
  return port
1713

    
1714

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

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

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

    
1742
  root_logger = logging.getLogger("")
1743
  root_logger.setLevel(logging.NOTSET)
1744

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

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

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

    
1778

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

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

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

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

    
1801
  rows = raw_data.splitlines()
1802
  return rows[-lines:]
1803

    
1804

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

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

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

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

    
1825

    
1826
def LockedMethod(fn):
1827
  """Synchronized object access decorator.
1828

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

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

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

    
1852

    
1853
def LockFile(fd):
1854
  """Locks a file using POSIX locks.
1855

1856
  @type fd: int
1857
  @param fd: the file descriptor we need to lock
1858

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

    
1867

    
1868
class FileLock(object):
1869
  """Utility class for file locks.
1870

1871
  """
1872
  def __init__(self, filename):
1873
    """Constructor for FileLock.
1874

1875
    This will open the file denoted by the I{filename} argument.
1876

1877
    @type filename: str
1878
    @param filename: path to the file to be locked
1879

1880
    """
1881
    self.filename = filename
1882
    self.fd = open(self.filename, "w")
1883

    
1884
  def __del__(self):
1885
    self.Close()
1886

    
1887
  def Close(self):
1888
    """Close the file and release the lock.
1889

1890
    """
1891
    if self.fd:
1892
      self.fd.close()
1893
      self.fd = None
1894

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1972
      To make a non-blocking request, include LOCK_NB with any of the above
1973
      operations.
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_UN, blocking, timeout,
1984
                "Failed to unlock %s" % self.filename)
1985

    
1986

    
1987
class SignalHandler(object):
1988
  """Generic signal handler class.
1989

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

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

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

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

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

    
2013
    self.called = False
2014

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

    
2033
  def __del__(self):
2034
    self.Reset()
2035

    
2036
  def Reset(self):
2037
    """Restore previous handler.
2038

2039
    This will reset all the signals to their previous handlers.
2040

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

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

2050
    This function can be used in case a signal may arrive several times.
2051

2052
    """
2053
    self.called = False
2054

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

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

    
2063

    
2064
class FieldSet(object):
2065
  """A simple field set.
2066

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

2072
  Internally, all fields are held as regular expression objects.
2073

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

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

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

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

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

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

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

2102
    """
2103
    return [val for val in items if not self.Matches(val)]