Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ e948770c

History | View | Annotate | Download (59.6 kB)

1
#
2
#
3

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

    
21

    
22
"""Ganeti utility module.
23

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

27
"""
28

    
29

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

    
46
from cStringIO import StringIO
47

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

    
54
from ganeti import errors
55
from ganeti import constants
56

    
57

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

    
61
debug_locks = False
62

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

    
66
_RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
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 errors.ProgrammerError: if we call this when forks are disabled
140

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

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

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

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

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

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

    
181

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

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

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

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

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

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

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

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

    
250

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

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

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

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

    
283

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

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

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

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

    
300

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

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

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

    
326

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

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

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

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

    
343
  f = open(filename)
344

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

    
351
    fp.update(data)
352

    
353
  return fp.hexdigest()
354

    
355

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

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

365
  """
366
  ret = {}
367

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

    
373
  return ret
374

    
375

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

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

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

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

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

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

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

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

    
442

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

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

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

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

    
465

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

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

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

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

    
489
  return pid
490

    
491

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

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

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

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

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

    
518

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
571
    return result
572

    
573

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

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

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

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

    
597
    retval[name] = size
598

    
599
  return retval
600

    
601

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

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

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

    
613

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

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

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

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

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

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

    
650

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

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

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

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

    
672

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

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

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

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

    
690

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

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

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

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

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

    
709

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

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

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

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

    
731

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

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

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

    
750
  suffix = ''
751

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

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

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

    
767

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

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

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

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

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

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

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

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

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

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

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

    
810
  return value
811

    
812

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

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

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

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

    
841

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

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

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

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

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

    
874

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

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

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

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

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

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

    
921

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

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

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

    
933

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

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

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

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

    
963
          out.write(line)
964

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

    
977

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

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

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

    
991

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

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

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

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

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

    
1020
  return backup_name
1021

    
1022

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

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

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

    
1037

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

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

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

    
1049

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

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

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

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

    
1073
  success = False
1074

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

    
1082
  sock.settimeout(timeout)
1083

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

    
1093
  return success
1094

    
1095

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

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

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

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

    
1111

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

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

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

    
1125

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

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

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

    
1146

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

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

1154
  """
1155
  return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
1156

    
1157

    
1158
def GenerateSecret(numbytes=20):
1159
  """Generates a random secret.
1160

1161
  This will generate a pseudo-random secret returning an hex string
1162
  (so that it can be used where an ASCII string is needed).
1163

1164
  @param numbytes: the number of bytes which will be represented by the returned
1165
      string (defaulting to 20, the length of a SHA1 hash)
1166
  @rtype: str
1167
  @return: an hex representation of the pseudo-random sequence
1168

1169
  """
1170
  return os.urandom(numbytes).encode('hex')
1171

    
1172

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

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

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

    
1190

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

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

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

    
1209

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

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

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

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

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

1256
  @raise errors.ProgrammerError: if any of the arguments are not valid
1257

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

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

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

    
1270
  if backup and not dry_run and os.path.isfile(file_name):
1271
    CreateBackup(file_name)
1272

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

    
1306
  return result
1307

    
1308

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

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

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

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

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

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

    
1336

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

    
1343

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

    
1350

    
1351
def UniqueSequence(seq):
1352
  """Returns a list with unique elements.
1353

1354
  Element order is preserved.
1355

1356
  @type seq: sequence
1357
  @param seq: the sequence with the source elements
1358
  @rtype: list
1359
  @return: list of unique elements from seq
1360

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

    
1365

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

1369
  Checks whether the supplied MAC address is formally correct, only
1370
  accepts colon separated format.
1371

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

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

    
1381

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

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

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

    
1396

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

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

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

    
1416

    
1417
def CloseFDs(noclose_fds=None):
1418
  """Close file descriptors.
1419

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

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

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

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

    
1448

    
1449
def Daemonize(logfile):
1450
  """Daemonize the current process.
1451

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

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

1460
  """
1461
  UMASK = 077
1462
  WORKDIR = "/"
1463

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

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

    
1489

    
1490
def DaemonPidFileName(name):
1491
  """Compute a ganeti pid file absolute path
1492

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

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

    
1502

    
1503
def WritePidFile(name):
1504
  """Write the current process pidfile.
1505

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

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

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

    
1519
  WriteFile(pidfilename, data="%d\n" % pid)
1520

    
1521

    
1522
def RemovePidFile(name):
1523
  """Remove the current process pidfile.
1524

1525
  Any errors are ignored.
1526

1527
  @type name: str
1528
  @param name: the daemon name used to derive the pidfile name
1529

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

    
1538

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

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

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

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

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

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

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

    
1595

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

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

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

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

    
1620

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

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

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

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

    
1645

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

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

1653
  """
1654
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1655

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

    
1661
  return (int(seconds), int(microseconds))
1662

    
1663

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

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

1671
  """
1672
  (seconds, microseconds) = timetuple
1673

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

    
1679
  return float(seconds) + (float(microseconds) * 0.000001)
1680

    
1681

    
1682
def GetDaemonPort(daemon_name):
1683
  """Get the daemon port for this cluster.
1684

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

1689
  @type daemon_name: string
1690
  @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
1691
  @rtype: int
1692

1693
  """
1694
  if daemon_name not in constants.DAEMONS_PORTS:
1695
    raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
1696

    
1697
  (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
1698
  try:
1699
    port = socket.getservbyname(daemon_name, proto)
1700
  except socket.error:
1701
    port = default_port
1702

    
1703
  return port
1704

    
1705

    
1706
def SetupLogging(logfile, debug=False, stderr_logging=False, program="",
1707
                 multithreaded=False):
1708
  """Configures the logging module.
1709

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

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

    
1733
  root_logger = logging.getLogger("")
1734
  root_logger.setLevel(logging.NOTSET)
1735

    
1736
  # Remove all previously setup handlers
1737
  for handler in root_logger.handlers:
1738
    handler.close()
1739
    root_logger.removeHandler(handler)
1740

    
1741
  if stderr_logging:
1742
    stderr_handler = logging.StreamHandler()
1743
    stderr_handler.setFormatter(formatter)
1744
    if debug:
1745
      stderr_handler.setLevel(logging.NOTSET)
1746
    else:
1747
      stderr_handler.setLevel(logging.CRITICAL)
1748
    root_logger.addHandler(stderr_handler)
1749

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

    
1769

    
1770
def IsNormAbsPath(path):
1771
  """Check whether a path is absolute and also normalized
1772

1773
  This avoids things like /dir/../../other/path to be valid.
1774

1775
  """
1776
  return os.path.normpath(path) == path and os.path.isabs(path)
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 display/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 process it
1812
  through a loop copied from the string repr sources in the python; we
1813
  don't use string_escape anymore since that escape single quotes and
1814
  backslashes too, and that is too much; and that escaping is not
1815
  stable, i.e. string_escape(string_escape(x)) != string_escape(x).
1816

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

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

    
1841

    
1842
def CommaJoin(names):
1843
  """Nicely join a set of identifiers.
1844

1845
  @param names: set, list or tuple
1846
  @return: a string with the formatted results
1847

1848
  """
1849
  return ", ".join(["'%s'" % val for val in names])
1850

    
1851

    
1852
def BytesToMebibyte(value):
1853
  """Converts bytes to mebibytes.
1854

1855
  @type value: int
1856
  @param value: Value in bytes
1857
  @rtype: int
1858
  @return: Value in mebibytes
1859

1860
  """
1861
  return int(round(value / (1024.0 * 1024.0), 0))
1862

    
1863

    
1864
def CalculateDirectorySize(path):
1865
  """Calculates the size of a directory recursively.
1866

1867
  @type path: string
1868
  @param path: Path to directory
1869
  @rtype: int
1870
  @return: Size in mebibytes
1871

1872
  """
1873
  size = 0
1874

    
1875
  for (curpath, _, files) in os.walk(path):
1876
    for filename in files:
1877
      st = os.lstat(os.path.join(curpath, filename))
1878
      size += st.st_size
1879

    
1880
  return BytesToMebibyte(size)
1881

    
1882

    
1883
def GetFreeFilesystemSpace(path):
1884
  """Returns the free space on a filesystem.
1885

1886
  @type path: string
1887
  @param path: Path on filesystem to be examined
1888
  @rtype: int
1889
  @return: Free space in mebibytes
1890

1891
  """
1892
  st = os.statvfs(path)
1893

    
1894
  return BytesToMebibyte(st.f_bavail * st.f_frsize)
1895

    
1896

    
1897
def LockedMethod(fn):
1898
  """Synchronized object access decorator.
1899

1900
  This decorator is intended to protect access to an object using the
1901
  object's own lock which is hardcoded to '_lock'.
1902

1903
  """
1904
  def _LockDebug(*args, **kwargs):
1905
    if debug_locks:
1906
      logging.debug(*args, **kwargs)
1907

    
1908
  def wrapper(self, *args, **kwargs):
1909
    assert hasattr(self, '_lock')
1910
    lock = self._lock
1911
    _LockDebug("Waiting for %s", lock)
1912
    lock.acquire()
1913
    try:
1914
      _LockDebug("Acquired %s", lock)
1915
      result = fn(self, *args, **kwargs)
1916
    finally:
1917
      _LockDebug("Releasing %s", lock)
1918
      lock.release()
1919
      _LockDebug("Released %s", lock)
1920
    return result
1921
  return wrapper
1922

    
1923

    
1924
def LockFile(fd):
1925
  """Locks a file using POSIX locks.
1926

1927
  @type fd: int
1928
  @param fd: the file descriptor we need to lock
1929

1930
  """
1931
  try:
1932
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
1933
  except IOError, err:
1934
    if err.errno == errno.EAGAIN:
1935
      raise errors.LockError("File already locked")
1936
    raise
1937

    
1938

    
1939
def FormatTime(val):
1940
  """Formats a time value.
1941

1942
  @type val: float or None
1943
  @param val: the timestamp as returned by time.time()
1944
  @return: a string value or N/A if we don't have a valid timestamp
1945

1946
  """
1947
  if val is None or not isinstance(val, (int, float)):
1948
    return "N/A"
1949
  # these two codes works on Linux, but they are not guaranteed on all
1950
  # platforms
1951
  return time.strftime("%F %T", time.localtime(val))
1952

    
1953

    
1954
def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
1955
  """Reads the watcher pause file.
1956

1957
  @type filename: string
1958
  @param filename: Path to watcher pause file
1959
  @type now: None, float or int
1960
  @param now: Current time as Unix timestamp
1961
  @type remove_after: int
1962
  @param remove_after: Remove watcher pause file after specified amount of
1963
    seconds past the pause end time
1964

1965
  """
1966
  if now is None:
1967
    now = time.time()
1968

    
1969
  try:
1970
    value = ReadFile(filename)
1971
  except IOError, err:
1972
    if err.errno != errno.ENOENT:
1973
      raise
1974
    value = None
1975

    
1976
  if value is not None:
1977
    try:
1978
      value = int(value)
1979
    except ValueError:
1980
      logging.warning(("Watcher pause file (%s) contains invalid value,"
1981
                       " removing it"), filename)
1982
      RemoveFile(filename)
1983
      value = None
1984

    
1985
    if value is not None:
1986
      # Remove file if it's outdated
1987
      if now > (value + remove_after):
1988
        RemoveFile(filename)
1989
        value = None
1990

    
1991
      elif now > value:
1992
        value = None
1993

    
1994
  return value
1995

    
1996

    
1997
class FileLock(object):
1998
  """Utility class for file locks.
1999

2000
  """
2001
  def __init__(self, filename):
2002
    """Constructor for FileLock.
2003

2004
    This will open the file denoted by the I{filename} argument.
2005

2006
    @type filename: str
2007
    @param filename: path to the file to be locked
2008

2009
    """
2010
    self.filename = filename
2011
    self.fd = open(self.filename, "w")
2012

    
2013
  def __del__(self):
2014
    self.Close()
2015

    
2016
  def Close(self):
2017
    """Close the file and release the lock.
2018

2019
    """
2020
    if self.fd:
2021
      self.fd.close()
2022
      self.fd = None
2023

    
2024
  def _flock(self, flag, blocking, timeout, errmsg):
2025
    """Wrapper for fcntl.flock.
2026

2027
    @type flag: int
2028
    @param flag: operation flag
2029
    @type blocking: bool
2030
    @param blocking: whether the operation should be done in blocking mode.
2031
    @type timeout: None or float
2032
    @param timeout: for how long the operation should be retried (implies
2033
                    non-blocking mode).
2034
    @type errmsg: string
2035
    @param errmsg: error message in case operation fails.
2036

2037
    """
2038
    assert self.fd, "Lock was closed"
2039
    assert timeout is None or timeout >= 0, \
2040
      "If specified, timeout must be positive"
2041

    
2042
    if timeout is not None:
2043
      flag |= fcntl.LOCK_NB
2044
      timeout_end = time.time() + timeout
2045

    
2046
    # Blocking doesn't have effect with timeout
2047
    elif not blocking:
2048
      flag |= fcntl.LOCK_NB
2049
      timeout_end = None
2050

    
2051
    retry = True
2052
    while retry:
2053
      try:
2054
        fcntl.flock(self.fd, flag)
2055
        retry = False
2056
      except IOError, err:
2057
        if err.errno in (errno.EAGAIN, ):
2058
          if timeout_end is not None and time.time() < timeout_end:
2059
            # Wait before trying again
2060
            time.sleep(max(0.1, min(1.0, timeout)))
2061
          else:
2062
            raise errors.LockError(errmsg)
2063
        else:
2064
          logging.exception("fcntl.flock failed")
2065
          raise
2066

    
2067
  def Exclusive(self, blocking=False, timeout=None):
2068
    """Locks the file in exclusive mode.
2069

2070
    @type blocking: boolean
2071
    @param blocking: whether to block and wait until we
2072
        can lock the file or return immediately
2073
    @type timeout: int or None
2074
    @param timeout: if not None, the duration to wait for the lock
2075
        (in blocking mode)
2076

2077
    """
2078
    self._flock(fcntl.LOCK_EX, blocking, timeout,
2079
                "Failed to lock %s in exclusive mode" % self.filename)
2080

    
2081
  def Shared(self, blocking=False, timeout=None):
2082
    """Locks the file in shared mode.
2083

2084
    @type blocking: boolean
2085
    @param blocking: whether to block and wait until we
2086
        can lock the file or return immediately
2087
    @type timeout: int or None
2088
    @param timeout: if not None, the duration to wait for the lock
2089
        (in blocking mode)
2090

2091
    """
2092
    self._flock(fcntl.LOCK_SH, blocking, timeout,
2093
                "Failed to lock %s in shared mode" % self.filename)
2094

    
2095
  def Unlock(self, blocking=True, timeout=None):
2096
    """Unlocks the file.
2097

2098
    According to C{flock(2)}, unlocking can also be a nonblocking
2099
    operation::
2100

2101
      To make a non-blocking request, include LOCK_NB with any of the above
2102
      operations.
2103

2104
    @type blocking: boolean
2105
    @param blocking: whether to block and wait until we
2106
        can lock the file or return immediately
2107
    @type timeout: int or None
2108
    @param timeout: if not None, the duration to wait for the lock
2109
        (in blocking mode)
2110

2111
    """
2112
    self._flock(fcntl.LOCK_UN, blocking, timeout,
2113
                "Failed to unlock %s" % self.filename)
2114

    
2115

    
2116
def SignalHandled(signums):
2117
  """Signal Handled decoration.
2118

2119
  This special decorator installs a signal handler and then calls the target
2120
  function. The function must accept a 'signal_handlers' keyword argument,
2121
  which will contain a dict indexed by signal number, with SignalHandler
2122
  objects as values.
2123

2124
  The decorator can be safely stacked with iself, to handle multiple signals
2125
  with different handlers.
2126

2127
  @type signums: list
2128
  @param signums: signals to intercept
2129

2130
  """
2131
  def wrap(fn):
2132
    def sig_function(*args, **kwargs):
2133
      assert 'signal_handlers' not in kwargs or \
2134
             kwargs['signal_handlers'] is None or \
2135
             isinstance(kwargs['signal_handlers'], dict), \
2136
             "Wrong signal_handlers parameter in original function call"
2137
      if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
2138
        signal_handlers = kwargs['signal_handlers']
2139
      else:
2140
        signal_handlers = {}
2141
        kwargs['signal_handlers'] = signal_handlers
2142
      sighandler = SignalHandler(signums)
2143
      try:
2144
        for sig in signums:
2145
          signal_handlers[sig] = sighandler
2146
        return fn(*args, **kwargs)
2147
      finally:
2148
        sighandler.Reset()
2149
    return sig_function
2150
  return wrap
2151

    
2152

    
2153
class SignalHandler(object):
2154
  """Generic signal handler class.
2155

2156
  It automatically restores the original handler when deconstructed or
2157
  when L{Reset} is called. You can either pass your own handler
2158
  function in or query the L{called} attribute to detect whether the
2159
  signal was sent.
2160

2161
  @type signum: list
2162
  @ivar signum: the signals we handle
2163
  @type called: boolean
2164
  @ivar called: tracks whether any of the signals have been raised
2165

2166
  """
2167
  def __init__(self, signum):
2168
    """Constructs a new SignalHandler instance.
2169

2170
    @type signum: int or list of ints
2171
    @param signum: Single signal number or set of signal numbers
2172

2173
    """
2174
    self.signum = set(signum)
2175
    self.called = False
2176

    
2177
    self._previous = {}
2178
    try:
2179
      for signum in self.signum:
2180
        # Setup handler
2181
        prev_handler = signal.signal(signum, self._HandleSignal)
2182
        try:
2183
          self._previous[signum] = prev_handler
2184
        except:
2185
          # Restore previous handler
2186
          signal.signal(signum, prev_handler)
2187
          raise
2188
    except:
2189
      # Reset all handlers
2190
      self.Reset()
2191
      # Here we have a race condition: a handler may have already been called,
2192
      # but there's not much we can do about it at this point.
2193
      raise
2194

    
2195
  def __del__(self):
2196
    self.Reset()
2197

    
2198
  def Reset(self):
2199
    """Restore previous handler.
2200

2201
    This will reset all the signals to their previous handlers.
2202

2203
    """
2204
    for signum, prev_handler in self._previous.items():
2205
      signal.signal(signum, prev_handler)
2206
      # If successful, remove from dict
2207
      del self._previous[signum]
2208

    
2209
  def Clear(self):
2210
    """Unsets the L{called} flag.
2211

2212
    This function can be used in case a signal may arrive several times.
2213

2214
    """
2215
    self.called = False
2216

    
2217
  def _HandleSignal(self, signum, frame):
2218
    """Actual signal handling function.
2219

2220
    """
2221
    # This is not nice and not absolutely atomic, but it appears to be the only
2222
    # solution in Python -- there are no atomic types.
2223
    self.called = True
2224

    
2225

    
2226
class FieldSet(object):
2227
  """A simple field set.
2228

2229
  Among the features are:
2230
    - checking if a string is among a list of static string or regex objects
2231
    - checking if a whole list of string matches
2232
    - returning the matching groups from a regex match
2233

2234
  Internally, all fields are held as regular expression objects.
2235

2236
  """
2237
  def __init__(self, *items):
2238
    self.items = [re.compile("^%s$" % value) for value in items]
2239

    
2240
  def Extend(self, other_set):
2241
    """Extend the field set with the items from another one"""
2242
    self.items.extend(other_set.items)
2243

    
2244
  def Matches(self, field):
2245
    """Checks if a field matches the current set
2246

2247
    @type field: str
2248
    @param field: the string to match
2249
    @return: either False or a regular expression match object
2250

2251
    """
2252
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
2253
      return m
2254
    return False
2255

    
2256
  def NonMatching(self, items):
2257
    """Returns the list of fields not matching the current set
2258

2259
    @type items: list
2260
    @param items: the list of fields to check
2261
    @rtype: list
2262
    @return: list of non-matching fields
2263

2264
    """
2265
    return [val for val in items if not self.Matches(val)]