Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ 016d04b3

History | View | Annotate | Download (60 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
import imp
46

    
47
from cStringIO import StringIO
48

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

    
55
from ganeti import errors
56
from ganeti import constants
57

    
58

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

    
62
debug_locks = False
63

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

    
67
_RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
68

    
69

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

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

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

    
92

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

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

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

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

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

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

    
120

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

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

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

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

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

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

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

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

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

    
182

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

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

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

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

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

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

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

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

    
251

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

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

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

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

    
284

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

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

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

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

    
301

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

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

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

    
327

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

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

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

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

    
344
  f = open(filename)
345

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

    
352
    fp.update(data)
353

    
354
  return fp.hexdigest()
355

    
356

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

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

366
  """
367
  ret = {}
368

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

    
374
  return ret
375

    
376

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

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

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

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

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

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

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

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

    
443

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

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

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

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

    
466

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

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

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

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

    
490
  return pid
491

    
492

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

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

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

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

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

    
519

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
572
    return result
573

    
574

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

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

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

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

    
598
    retval[name] = size
599

    
600
  return retval
601

    
602

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

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

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

    
614

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

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

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

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

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

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

    
651

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

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

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

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

    
673

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

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

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

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

    
691

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

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

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

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

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

    
710

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

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

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

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

    
732

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

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

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

    
751
  suffix = ''
752

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

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

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

    
768

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

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

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

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

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

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

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

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

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

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

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

    
811
  return value
812

    
813

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

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

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

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

    
842

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

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

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

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

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

    
875

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

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

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

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

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

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

    
922

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

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

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

    
934

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

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

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

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

    
964
          out.write(line)
965

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

    
978

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

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

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

    
992

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

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

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

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

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

    
1021
  return backup_name
1022

    
1023

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

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

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

    
1038

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

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

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

    
1050

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

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

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

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

    
1074
  success = False
1075

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

    
1083
  sock.settimeout(timeout)
1084

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

    
1094
  return success
1095

    
1096

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

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

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

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

    
1112

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

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

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

    
1126

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

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

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

    
1147

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

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

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

    
1158

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

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

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

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

    
1173

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

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

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

    
1191

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

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

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

    
1210

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

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

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

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

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

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

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

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

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

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

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

    
1307
  return result
1308

    
1309

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

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

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

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

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

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

    
1337

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

    
1344

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

    
1351

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

1355
  Element order is preserved.
1356

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

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

    
1366

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

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

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

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

    
1382

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

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

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

    
1397

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

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

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

    
1417

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

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

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

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

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

    
1449

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

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

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

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

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

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

    
1490

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

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

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

    
1503

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

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

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

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

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

    
1522

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

1526
  Any errors are ignored.
1527

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

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

    
1539

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

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

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

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

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

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

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

    
1596

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

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

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

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

    
1621

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

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

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

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

    
1646

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

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

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

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

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

    
1664

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

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

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

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

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

    
1682

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

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

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

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

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

    
1704
  return port
1705

    
1706

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

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

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

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

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

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

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

    
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

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

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

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

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

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

    
1805

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

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

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

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

    
1842

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

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

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

    
1852

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

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

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

    
1864

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

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

1873
  """
1874
  size = 0
1875

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

    
1881
  return BytesToMebibyte(size)
1882

    
1883

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

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

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

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

    
1897

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

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

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

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

    
1924

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

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

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

    
1939

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

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

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

    
1954

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

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

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

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

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

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

    
1992
      elif now > value:
1993
        value = None
1994

    
1995
  return value
1996

    
1997

    
1998
def LoadModule(filename):
1999
  """Loads an external module by filename.
2000

2001
  Use this function with caution. Python will always write the compiled source
2002
  to a file named "${filename}c".
2003

2004
  @type filename: string
2005
  @param filename: Path to module
2006

2007
  """
2008
  (name, ext) = os.path.splitext(filename)
2009

    
2010
  fh = open(filename, "U")
2011
  try:
2012
    return imp.load_module(name, fh, filename, (ext, "U", imp.PY_SOURCE))
2013
  finally:
2014
    fh.close()
2015

    
2016

    
2017
class FileLock(object):
2018
  """Utility class for file locks.
2019

2020
  """
2021
  def __init__(self, filename):
2022
    """Constructor for FileLock.
2023

2024
    This will open the file denoted by the I{filename} argument.
2025

2026
    @type filename: str
2027
    @param filename: path to the file to be locked
2028

2029
    """
2030
    self.filename = filename
2031
    self.fd = open(self.filename, "w")
2032

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

    
2036
  def Close(self):
2037
    """Close the file and release the lock.
2038

2039
    """
2040
    if self.fd:
2041
      self.fd.close()
2042
      self.fd = None
2043

    
2044
  def _flock(self, flag, blocking, timeout, errmsg):
2045
    """Wrapper for fcntl.flock.
2046

2047
    @type flag: int
2048
    @param flag: operation flag
2049
    @type blocking: bool
2050
    @param blocking: whether the operation should be done in blocking mode.
2051
    @type timeout: None or float
2052
    @param timeout: for how long the operation should be retried (implies
2053
                    non-blocking mode).
2054
    @type errmsg: string
2055
    @param errmsg: error message in case operation fails.
2056

2057
    """
2058
    assert self.fd, "Lock was closed"
2059
    assert timeout is None or timeout >= 0, \
2060
      "If specified, timeout must be positive"
2061

    
2062
    if timeout is not None:
2063
      flag |= fcntl.LOCK_NB
2064
      timeout_end = time.time() + timeout
2065

    
2066
    # Blocking doesn't have effect with timeout
2067
    elif not blocking:
2068
      flag |= fcntl.LOCK_NB
2069
      timeout_end = None
2070

    
2071
    retry = True
2072
    while retry:
2073
      try:
2074
        fcntl.flock(self.fd, flag)
2075
        retry = False
2076
      except IOError, err:
2077
        if err.errno in (errno.EAGAIN, ):
2078
          if timeout_end is not None and time.time() < timeout_end:
2079
            # Wait before trying again
2080
            time.sleep(max(0.1, min(1.0, timeout)))
2081
          else:
2082
            raise errors.LockError(errmsg)
2083
        else:
2084
          logging.exception("fcntl.flock failed")
2085
          raise
2086

    
2087
  def Exclusive(self, blocking=False, timeout=None):
2088
    """Locks the file in exclusive mode.
2089

2090
    @type blocking: boolean
2091
    @param blocking: whether to block and wait until we
2092
        can lock the file or return immediately
2093
    @type timeout: int or None
2094
    @param timeout: if not None, the duration to wait for the lock
2095
        (in blocking mode)
2096

2097
    """
2098
    self._flock(fcntl.LOCK_EX, blocking, timeout,
2099
                "Failed to lock %s in exclusive mode" % self.filename)
2100

    
2101
  def Shared(self, blocking=False, timeout=None):
2102
    """Locks the file in shared mode.
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_SH, blocking, timeout,
2113
                "Failed to lock %s in shared mode" % self.filename)
2114

    
2115
  def Unlock(self, blocking=True, timeout=None):
2116
    """Unlocks the file.
2117

2118
    According to C{flock(2)}, unlocking can also be a nonblocking
2119
    operation::
2120

2121
      To make a non-blocking request, include LOCK_NB with any of the above
2122
      operations.
2123

2124
    @type blocking: boolean
2125
    @param blocking: whether to block and wait until we
2126
        can lock the file or return immediately
2127
    @type timeout: int or None
2128
    @param timeout: if not None, the duration to wait for the lock
2129
        (in blocking mode)
2130

2131
    """
2132
    self._flock(fcntl.LOCK_UN, blocking, timeout,
2133
                "Failed to unlock %s" % self.filename)
2134

    
2135

    
2136
def SignalHandled(signums):
2137
  """Signal Handled decoration.
2138

2139
  This special decorator installs a signal handler and then calls the target
2140
  function. The function must accept a 'signal_handlers' keyword argument,
2141
  which will contain a dict indexed by signal number, with SignalHandler
2142
  objects as values.
2143

2144
  The decorator can be safely stacked with iself, to handle multiple signals
2145
  with different handlers.
2146

2147
  @type signums: list
2148
  @param signums: signals to intercept
2149

2150
  """
2151
  def wrap(fn):
2152
    def sig_function(*args, **kwargs):
2153
      assert 'signal_handlers' not in kwargs or \
2154
             kwargs['signal_handlers'] is None or \
2155
             isinstance(kwargs['signal_handlers'], dict), \
2156
             "Wrong signal_handlers parameter in original function call"
2157
      if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
2158
        signal_handlers = kwargs['signal_handlers']
2159
      else:
2160
        signal_handlers = {}
2161
        kwargs['signal_handlers'] = signal_handlers
2162
      sighandler = SignalHandler(signums)
2163
      try:
2164
        for sig in signums:
2165
          signal_handlers[sig] = sighandler
2166
        return fn(*args, **kwargs)
2167
      finally:
2168
        sighandler.Reset()
2169
    return sig_function
2170
  return wrap
2171

    
2172

    
2173
class SignalHandler(object):
2174
  """Generic signal handler class.
2175

2176
  It automatically restores the original handler when deconstructed or
2177
  when L{Reset} is called. You can either pass your own handler
2178
  function in or query the L{called} attribute to detect whether the
2179
  signal was sent.
2180

2181
  @type signum: list
2182
  @ivar signum: the signals we handle
2183
  @type called: boolean
2184
  @ivar called: tracks whether any of the signals have been raised
2185

2186
  """
2187
  def __init__(self, signum):
2188
    """Constructs a new SignalHandler instance.
2189

2190
    @type signum: int or list of ints
2191
    @param signum: Single signal number or set of signal numbers
2192

2193
    """
2194
    self.signum = set(signum)
2195
    self.called = False
2196

    
2197
    self._previous = {}
2198
    try:
2199
      for signum in self.signum:
2200
        # Setup handler
2201
        prev_handler = signal.signal(signum, self._HandleSignal)
2202
        try:
2203
          self._previous[signum] = prev_handler
2204
        except:
2205
          # Restore previous handler
2206
          signal.signal(signum, prev_handler)
2207
          raise
2208
    except:
2209
      # Reset all handlers
2210
      self.Reset()
2211
      # Here we have a race condition: a handler may have already been called,
2212
      # but there's not much we can do about it at this point.
2213
      raise
2214

    
2215
  def __del__(self):
2216
    self.Reset()
2217

    
2218
  def Reset(self):
2219
    """Restore previous handler.
2220

2221
    This will reset all the signals to their previous handlers.
2222

2223
    """
2224
    for signum, prev_handler in self._previous.items():
2225
      signal.signal(signum, prev_handler)
2226
      # If successful, remove from dict
2227
      del self._previous[signum]
2228

    
2229
  def Clear(self):
2230
    """Unsets the L{called} flag.
2231

2232
    This function can be used in case a signal may arrive several times.
2233

2234
    """
2235
    self.called = False
2236

    
2237
  def _HandleSignal(self, signum, frame):
2238
    """Actual signal handling function.
2239

2240
    """
2241
    # This is not nice and not absolutely atomic, but it appears to be the only
2242
    # solution in Python -- there are no atomic types.
2243
    self.called = True
2244

    
2245

    
2246
class FieldSet(object):
2247
  """A simple field set.
2248

2249
  Among the features are:
2250
    - checking if a string is among a list of static string or regex objects
2251
    - checking if a whole list of string matches
2252
    - returning the matching groups from a regex match
2253

2254
  Internally, all fields are held as regular expression objects.
2255

2256
  """
2257
  def __init__(self, *items):
2258
    self.items = [re.compile("^%s$" % value) for value in items]
2259

    
2260
  def Extend(self, other_set):
2261
    """Extend the field set with the items from another one"""
2262
    self.items.extend(other_set.items)
2263

    
2264
  def Matches(self, field):
2265
    """Checks if a field matches the current set
2266

2267
    @type field: str
2268
    @param field: the string to match
2269
    @return: either False or a regular expression match object
2270

2271
    """
2272
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
2273
      return m
2274
    return False
2275

    
2276
  def NonMatching(self, items):
2277
    """Returns the list of fields not matching the current set
2278

2279
    @type items: list
2280
    @param items: the list of fields to check
2281
    @rtype: list
2282
    @return: list of non-matching fields
2283

2284
    """
2285
    return [val for val in items if not self.Matches(val)]