Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ 6956e9cd

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

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

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

85
  """
86
  __slots__ = ["exit_code", "signal", "stdout", "stderr",
87
               "failed", "fail_reason", "cmd"]
88

    
89

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

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

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

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

112
    """
113
    return self.stdout + self.stderr
114

    
115
  output = property(_GetOutput, None, None, "Return full output")
116

    
117

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

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

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

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

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

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

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

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

    
177
  return RunResult(exitcode, signal_, out, err, strcmd)
178

    
179

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

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

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

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

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

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

    
242
  out = out.getvalue()
243
  err = err.getvalue()
244

    
245
  status = child.wait()
246
  return out, err, status
247

    
248

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

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

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

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

    
281

    
282
def RemoveFile(filename):
283
  """Remove a file ignoring some errors.
284

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

288
  @type filename: str
289
  @param filename: the file to be removed
290

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

    
298

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

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

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

    
324

    
325
def _FingerprintFile(filename):
326
  """Compute the fingerprint of a file.
327

328
  If the file does not exist, a None will be returned
329
  instead.
330

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

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

    
341
  f = open(filename)
342

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

    
349
    fp.update(data)
350

    
351
  return fp.hexdigest()
352

    
353

    
354
def FingerprintFiles(files):
355
  """Compute fingerprints for a list of files.
356

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

363
  """
364
  ret = {}
365

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

    
371
  return ret
372

    
373

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

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

385
  """
386
  if allowed_values is None:
387
    allowed_values = []
388

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

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

    
398
    if target[key] in allowed_values:
399
      continue
400

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

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

    
440

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

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

451
  """
452
  if pid <= 0:
453
    return False
454

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

    
463

    
464
def ReadPidFile(pidfile):
465
  """Read a pid from a file.
466

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

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

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

    
487
  return pid
488

    
489

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

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

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

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

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

    
516

    
517
class HostInfo:
518
  """Class implementing resolver and hostname functionality
519

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

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

527
    """
528
    if name is None:
529
      name = self.SysName()
530

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

    
535
  def ShortName(self):
536
    """Returns the hostname without domain.
537

538
    """
539
    return self.name.split('.')[0]
540

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

545
    This is simply a wrapper over C{socket.gethostname()}.
546

547
    """
548
    return socket.gethostname()
549

    
550
  @staticmethod
551
  def LookupHostname(hostname):
552
    """Look up hostname
553

554
    @type hostname: str
555
    @param hostname: hostname to look up
556

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

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

    
569
    return result
570

    
571

    
572
def ListVolumeGroups():
573
  """List volume groups and their size
574

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

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

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

    
595
    retval[name] = size
596

    
597
  return retval
598

    
599

    
600
def BridgeExists(bridge):
601
  """Check whether the given bridge exists in the system
602

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

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

    
611

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

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

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

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

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

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

    
648

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

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

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

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

    
670

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

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

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

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

    
688

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

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

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

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

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

    
707

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

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

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

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

    
729

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

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

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

    
748
  suffix = ''
749

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

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

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

    
765

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

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

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

    
778
  value = float(m.groups()[0])
779

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

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

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

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

    
796
  else:
797
    raise errors.UnitParseError("Unknown unit: %s" % unit)
798

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

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

    
808
  return value
809

    
810

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

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

819
  """
820
  key_fields = key.split()
821

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

    
839

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

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

848
  """
849
  key_fields = key.split()
850

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

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

    
872

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

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

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

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

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

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

    
919

    
920
def AddHostToEtcHosts(hostname):
921
  """Wrapper around SetEtcHostsEntry.
922

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

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

    
931

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

935
  IP addresses without names are removed from the file.
936

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

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

    
961
          out.write(line)
962

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

    
975

    
976
def RemoveHostFromEtcHosts(hostname):
977
  """Wrapper around RemoveEtcHostsEntry.
978

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

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

    
989

    
990
def CreateBackup(file_name):
991
  """Creates a backup of a file.
992

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

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

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

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

    
1018
  return backup_name
1019

    
1020

    
1021
def ShellQuote(value):
1022
  """Quotes shell argument according to POSIX.
1023

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

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

    
1035

    
1036
def ShellQuoteArgs(args):
1037
  """Quotes a list of shell arguments.
1038

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

1044
  """
1045
  return ' '.join([ShellQuote(i) for i in args])
1046

    
1047

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

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

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

1068
  """
1069
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1070

    
1071
  success = False
1072

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

    
1080
  sock.settimeout(timeout)
1081

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

    
1091
  return success
1092

    
1093

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

1097
  Currently this is done by TCP-pinging the address from the loopback
1098
  address.
1099

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

1105
  """
1106
  return TcpPing(address, constants.DEFAULT_NODED_PORT,
1107
                 source=constants.LOCALHOST_IP_ADDRESS)
1108

    
1109

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

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

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

    
1123

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

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

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

    
1144

    
1145
def NewUUID():
1146
  """Returns a random UUID.
1147

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

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

    
1159

    
1160
def GenerateSecret(numbytes=20):
1161
  """Generates a random secret.
1162

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

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

1171
  """
1172
  return os.urandom(numbytes).encode('hex')
1173

    
1174

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

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

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

    
1192

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

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

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

    
1211

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

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

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

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

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

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

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

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

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

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

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

    
1308
  return result
1309

    
1310

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

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

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

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

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

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

    
1338

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

    
1345

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

    
1352

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

1356
  Element order is preserved.
1357

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

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

    
1367

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

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

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

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

    
1383

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

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

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

    
1398

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

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

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

    
1418

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

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

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

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

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

    
1450

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

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

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

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

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

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

    
1491

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

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

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

    
1504

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

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

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

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

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

    
1523

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

1527
  Any errors are ignored.
1528

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

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

    
1540

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

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

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

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

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

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

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

    
1597

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

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

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

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

    
1622

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

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

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

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

    
1647

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

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

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

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

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

    
1665

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

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

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

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

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

    
1683

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

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

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

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

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

    
1705
  return port
1706

    
1707

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

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

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

    
1735
  root_logger = logging.getLogger("")
1736
  root_logger.setLevel(logging.NOTSET)
1737

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

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

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

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

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

1776
  """
1777
  return os.path.normpath(path) == path and os.path.isabs(path)
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 file in files:
1877
      st = os.lstat(os.path.join(curpath, file))
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)]