Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ 29921401

History | View | Annotate | Download (55.7 kB)

1
#
2
#
3

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

    
21

    
22
"""Ganeti utility module.
23

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

27
"""
28

    
29

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

    
47
from cStringIO import StringIO
48

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

    
55
from ganeti import errors
56
from ganeti import constants
57

    
58

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

    
62
debug = False
63
debug_locks = False
64

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

    
68

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

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

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

    
91

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

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

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

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

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

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

    
119

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

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

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

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

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

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

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

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

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

    
181

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

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

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

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

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

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

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

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

    
250

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

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

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

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

    
283

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

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

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

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

    
300

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

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

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

    
326

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

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

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

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

    
343
  f = open(filename)
344

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

    
351
    fp.update(data)
352

    
353
  return fp.hexdigest()
354

    
355

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

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

365
  """
366
  ret = {}
367

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

    
373
  return ret
374

    
375

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

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

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

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

    
396
    if target[key] in allowed_values:
397
      continue
398

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

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

    
438

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

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

449
  """
450
  if pid <= 0:
451
    return False
452

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

    
461

    
462
def ReadPidFile(pidfile):
463
  """Read a pid from a file.
464

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

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

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

    
485
  return pid
486

    
487

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

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

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

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

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

    
514

    
515
class HostInfo:
516
  """Class implementing resolver and hostname functionality
517

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

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

525
    """
526
    if name is None:
527
      name = self.SysName()
528

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

    
533
  def ShortName(self):
534
    """Returns the hostname without domain.
535

536
    """
537
    return self.name.split('.')[0]
538

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

543
    This is simply a wrapper over C{socket.gethostname()}.
544

545
    """
546
    return socket.gethostname()
547

    
548
  @staticmethod
549
  def LookupHostname(hostname):
550
    """Look up hostname
551

552
    @type hostname: str
553
    @param hostname: hostname to look up
554

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

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

    
567
    return result
568

    
569

    
570
def ListVolumeGroups():
571
  """List volume groups and their size
572

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

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

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

    
593
    retval[name] = size
594

    
595
  return retval
596

    
597

    
598
def BridgeExists(bridge):
599
  """Check whether the given bridge exists in the system
600

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

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

    
609

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

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

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

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

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

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

    
646

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

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

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

661
  """
662
  try:
663
    nv = fn(val)
664
  except (ValueError, TypeError), err:
665
    nv = val
666
  return nv
667

    
668

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

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

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

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

    
686

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

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

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

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

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

    
705

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

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

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

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

    
727

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

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

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

    
746
  suffix = ''
747

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

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

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

    
763

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

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

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

    
776
  value = float(m.groups()[0])
777

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

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

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

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

    
794
  else:
795
    raise errors.UnitParseError("Unknown unit: %s" % unit)
796

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

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

    
806
  return value
807

    
808

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

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

817
  """
818
  key_fields = key.split()
819

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

    
837

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

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

846
  """
847
  key_fields = key.split()
848

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

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

    
870

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

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

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

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

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

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

    
917

    
918
def AddHostToEtcHosts(hostname):
919
  """Wrapper around SetEtcHostsEntry.
920

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

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

    
929

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

933
  IP addresses without names are removed from the file.
934

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

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

    
959
          out.write(line)
960

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

    
973

    
974
def RemoveHostFromEtcHosts(hostname):
975
  """Wrapper around RemoveEtcHostsEntry.
976

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

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

    
987

    
988
def CreateBackup(file_name):
989
  """Creates a backup of a file.
990

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

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

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

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

    
1016
  return backup_name
1017

    
1018

    
1019
def ShellQuote(value):
1020
  """Quotes shell argument according to POSIX.
1021

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

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

    
1033

    
1034
def ShellQuoteArgs(args):
1035
  """Quotes a list of shell arguments.
1036

1037
  @type args: list
1038
  @param args: list of arguments to be quoted
1039
  @rtype: str
1040
  @return: the quoted arguments concatenaned with spaces
1041

1042
  """
1043
  return ' '.join([ShellQuote(i) for i in args])
1044

    
1045

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

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

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

1066
  """
1067
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1068

    
1069
  success = False
1070

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

    
1078
  sock.settimeout(timeout)
1079

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

    
1089
  return success
1090

    
1091

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

1095
  Currently this is done by TCP-pinging the address from the loopback
1096
  address.
1097

1098
  @type address: string
1099
  @param address: the addres to check
1100
  @rtype: bool
1101
  @return: True if we own the address
1102

1103
  """
1104
  return TcpPing(address, constants.DEFAULT_NODED_PORT,
1105
                 source=constants.LOCALHOST_IP_ADDRESS)
1106

    
1107

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

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

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

    
1121

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

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

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

    
1142

    
1143
def NewUUID():
1144
  """Returns a random UUID.
1145

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

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

    
1157

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

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

1164
  @rtype: str
1165
  @return: a sha1 hexdigest of a block of 64 random bytes
1166

1167
  """
1168
  return sha1(os.urandom(64)).hexdigest()
1169

    
1170

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

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

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

    
1188

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

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

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

    
1207

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

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

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

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

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

1254
  @raise errors.ProgrammerError: if any of the arguments are not valid
1255

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

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

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

    
1268
  if backup and not dry_run and os.path.isfile(file_name):
1269
    CreateBackup(file_name)
1270

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

    
1304
  return result
1305

    
1306

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

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

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

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

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

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

    
1334

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

    
1341

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

    
1348

    
1349
def UniqueSequence(seq):
1350
  """Returns a list with unique elements.
1351

1352
  Element order is preserved.
1353

1354
  @type seq: sequence
1355
  @param seq: the sequence with the source elementes
1356
  @rtype: list
1357
  @return: list of unique elements from seq
1358

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

    
1363

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

1367
  Checks wether the supplied MAC address is formally correct, only
1368
  accepts colon separated format.
1369

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

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

    
1379

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

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

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

    
1394

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

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

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

    
1414

    
1415
def CloseFDs(noclose_fds=None):
1416
  """Close file descriptors.
1417

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

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

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

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

    
1446

    
1447
def Daemonize(logfile):
1448
  """Daemonize the current process.
1449

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

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

1458
  """
1459
  UMASK = 077
1460
  WORKDIR = "/"
1461

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

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

    
1487

    
1488
def DaemonPidFileName(name):
1489
  """Compute a ganeti pid file absolute path
1490

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

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

    
1500

    
1501
def WritePidFile(name):
1502
  """Write the current process pidfile.
1503

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

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

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

    
1517
  WriteFile(pidfilename, data="%d\n" % pid)
1518

    
1519

    
1520
def RemovePidFile(name):
1521
  """Remove the current process pidfile.
1522

1523
  Any errors are ignored.
1524

1525
  @type name: str
1526
  @param name: the daemon name used to derive the pidfile name
1527

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

    
1536

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

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

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

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

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

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

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

    
1593

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

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

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

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

    
1618

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

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

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

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

    
1643

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

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

1651
  """
1652
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1653

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

    
1659
  return (int(seconds), int(microseconds))
1660

    
1661

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

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

1669
  """
1670
  (seconds, microseconds) = timetuple
1671

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

    
1677
  return float(seconds) + (float(microseconds) * 0.000001)
1678

    
1679

    
1680
def GetNodeDaemonPort():
1681
  """Get the node daemon port for this cluster.
1682

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

1687
  @rtype: int
1688

1689
  """
1690
  try:
1691
    port = socket.getservbyname("ganeti-noded", "tcp")
1692
  except socket.error:
1693
    port = constants.DEFAULT_NODED_PORT
1694

    
1695
  return port
1696

    
1697

    
1698
def SetupLogging(logfile, debug=False, stderr_logging=False, program="",
1699
                 multithreaded=False):
1700
  """Configures the logging module.
1701

1702
  @type logfile: str
1703
  @param logfile: the filename to which we should log
1704
  @type debug: boolean
1705
  @param debug: whether to enable debug messages too or
1706
      only those at C{INFO} and above level
1707
  @type stderr_logging: boolean
1708
  @param stderr_logging: whether we should also log to the standard error
1709
  @type program: str
1710
  @param program: the name under which we should log messages
1711
  @type multithreaded: boolean
1712
  @param multithreaded: if True, will add the thread name to the log file
1713
  @raise EnvironmentError: if we can't open the log file and
1714
      stderr logging is disabled
1715

1716
  """
1717
  fmt = "%(asctime)s: " + program + " pid=%(process)d"
1718
  if multithreaded:
1719
    fmt += "/%(threadName)s"
1720
  if debug:
1721
    fmt += " %(module)s:%(lineno)s"
1722
  fmt += " %(levelname)s %(message)s"
1723
  formatter = logging.Formatter(fmt)
1724

    
1725
  root_logger = logging.getLogger("")
1726
  root_logger.setLevel(logging.NOTSET)
1727

    
1728
  # Remove all previously setup handlers
1729
  for handler in root_logger.handlers:
1730
    handler.close()
1731
    root_logger.removeHandler(handler)
1732

    
1733
  if stderr_logging:
1734
    stderr_handler = logging.StreamHandler()
1735
    stderr_handler.setFormatter(formatter)
1736
    if debug:
1737
      stderr_handler.setLevel(logging.NOTSET)
1738
    else:
1739
      stderr_handler.setLevel(logging.CRITICAL)
1740
    root_logger.addHandler(stderr_handler)
1741

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

    
1761
def IsNormAbsPath(path):
1762
  """Check whether a path is absolute and also normalized
1763

1764
  This avoids things like /dir/../../other/path to be valid.
1765

1766
  """
1767
  return os.path.normpath(path) == path and os.path.isabs(path)
1768

    
1769
def TailFile(fname, lines=20):
1770
  """Return the last lines from a file.
1771

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

1776
  @param fname: the file name
1777
  @type lines: int
1778
  @param lines: the (maximum) number of lines to return
1779

1780
  """
1781
  fd = open(fname, "r")
1782
  try:
1783
    fd.seek(0, 2)
1784
    pos = fd.tell()
1785
    pos = max(0, pos-4096)
1786
    fd.seek(pos, 0)
1787
    raw_data = fd.read()
1788
  finally:
1789
    fd.close()
1790

    
1791
  rows = raw_data.splitlines()
1792
  return rows[-lines:]
1793

    
1794

    
1795
def SafeEncode(text):
1796
  """Return a 'safe' version of a source string.
1797

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

1807
  @type text: str or unicode
1808
  @param text: input data
1809
  @rtype: str
1810
  @return: a safe version of text
1811

1812
  """
1813
  if isinstance(text, unicode):
1814
    # onli if unicode; if str already, we handle it below
1815
    text = text.encode('ascii', 'backslashreplace')
1816
  resu = ""
1817
  for char in text:
1818
    c = ord(char)
1819
    if char  == '\t':
1820
      resu += r'\t'
1821
    elif char == '\n':
1822
      resu += r'\n'
1823
    elif char == '\r':
1824
      resu += r'\'r'
1825
    elif c < 32 or c >= 127: # non-printable
1826
      resu += "\\x%02x" % (c & 0xff)
1827
    else:
1828
      resu += char
1829
  return resu
1830

    
1831

    
1832
def CommaJoin(names):
1833
  """Nicely join a set of identifiers.
1834

1835
  @param names: set, list or tuple
1836
  @return: a string with the formatted results
1837

1838
  """
1839
  return ", ".join(["'%s'" % val for val in names])
1840

    
1841

    
1842
def LockedMethod(fn):
1843
  """Synchronized object access decorator.
1844

1845
  This decorator is intended to protect access to an object using the
1846
  object's own lock which is hardcoded to '_lock'.
1847

1848
  """
1849
  def _LockDebug(*args, **kwargs):
1850
    if debug_locks:
1851
      logging.debug(*args, **kwargs)
1852

    
1853
  def wrapper(self, *args, **kwargs):
1854
    assert hasattr(self, '_lock')
1855
    lock = self._lock
1856
    _LockDebug("Waiting for %s", lock)
1857
    lock.acquire()
1858
    try:
1859
      _LockDebug("Acquired %s", lock)
1860
      result = fn(self, *args, **kwargs)
1861
    finally:
1862
      _LockDebug("Releasing %s", lock)
1863
      lock.release()
1864
      _LockDebug("Released %s", lock)
1865
    return result
1866
  return wrapper
1867

    
1868

    
1869
def LockFile(fd):
1870
  """Locks a file using POSIX locks.
1871

1872
  @type fd: int
1873
  @param fd: the file descriptor we need to lock
1874

1875
  """
1876
  try:
1877
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
1878
  except IOError, err:
1879
    if err.errno == errno.EAGAIN:
1880
      raise errors.LockError("File already locked")
1881
    raise
1882

    
1883

    
1884
class FileLock(object):
1885
  """Utility class for file locks.
1886

1887
  """
1888
  def __init__(self, filename):
1889
    """Constructor for FileLock.
1890

1891
    This will open the file denoted by the I{filename} argument.
1892

1893
    @type filename: str
1894
    @param filename: path to the file to be locked
1895

1896
    """
1897
    self.filename = filename
1898
    self.fd = open(self.filename, "w")
1899

    
1900
  def __del__(self):
1901
    self.Close()
1902

    
1903
  def Close(self):
1904
    """Close the file and release the lock.
1905

1906
    """
1907
    if self.fd:
1908
      self.fd.close()
1909
      self.fd = None
1910

    
1911
  def _flock(self, flag, blocking, timeout, errmsg):
1912
    """Wrapper for fcntl.flock.
1913

1914
    @type flag: int
1915
    @param flag: operation flag
1916
    @type blocking: bool
1917
    @param blocking: whether the operation should be done in blocking mode.
1918
    @type timeout: None or float
1919
    @param timeout: for how long the operation should be retried (implies
1920
                    non-blocking mode).
1921
    @type errmsg: string
1922
    @param errmsg: error message in case operation fails.
1923

1924
    """
1925
    assert self.fd, "Lock was closed"
1926
    assert timeout is None or timeout >= 0, \
1927
      "If specified, timeout must be positive"
1928

    
1929
    if timeout is not None:
1930
      flag |= fcntl.LOCK_NB
1931
      timeout_end = time.time() + timeout
1932

    
1933
    # Blocking doesn't have effect with timeout
1934
    elif not blocking:
1935
      flag |= fcntl.LOCK_NB
1936
      timeout_end = None
1937

    
1938
    retry = True
1939
    while retry:
1940
      try:
1941
        fcntl.flock(self.fd, flag)
1942
        retry = False
1943
      except IOError, err:
1944
        if err.errno in (errno.EAGAIN, ):
1945
          if timeout_end is not None and time.time() < timeout_end:
1946
            # Wait before trying again
1947
            time.sleep(max(0.1, min(1.0, timeout)))
1948
          else:
1949
            raise errors.LockError(errmsg)
1950
        else:
1951
          logging.exception("fcntl.flock failed")
1952
          raise
1953

    
1954
  def Exclusive(self, blocking=False, timeout=None):
1955
    """Locks the file in exclusive mode.
1956

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

1964
    """
1965
    self._flock(fcntl.LOCK_EX, blocking, timeout,
1966
                "Failed to lock %s in exclusive mode" % self.filename)
1967

    
1968
  def Shared(self, blocking=False, timeout=None):
1969
    """Locks the file in shared mode.
1970

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

1978
    """
1979
    self._flock(fcntl.LOCK_SH, blocking, timeout,
1980
                "Failed to lock %s in shared mode" % self.filename)
1981

    
1982
  def Unlock(self, blocking=True, timeout=None):
1983
    """Unlocks the file.
1984

1985
    According to C{flock(2)}, unlocking can also be a nonblocking
1986
    operation::
1987

1988
      To make a non-blocking request, include LOCK_NB with any of the above
1989
      operations.
1990

1991
    @type blocking: boolean
1992
    @param blocking: whether to block and wait until we
1993
        can lock the file or return immediately
1994
    @type timeout: int or None
1995
    @param timeout: if not None, the duration to wait for the lock
1996
        (in blocking mode)
1997

1998
    """
1999
    self._flock(fcntl.LOCK_UN, blocking, timeout,
2000
                "Failed to unlock %s" % self.filename)
2001

    
2002

    
2003
class SignalHandler(object):
2004
  """Generic signal handler class.
2005

2006
  It automatically restores the original handler when deconstructed or
2007
  when L{Reset} is called. You can either pass your own handler
2008
  function in or query the L{called} attribute to detect whether the
2009
  signal was sent.
2010

2011
  @type signum: list
2012
  @ivar signum: the signals we handle
2013
  @type called: boolean
2014
  @ivar called: tracks whether any of the signals have been raised
2015

2016
  """
2017
  def __init__(self, signum):
2018
    """Constructs a new SignalHandler instance.
2019

2020
    @type signum: int or list of ints
2021
    @param signum: Single signal number or set of signal numbers
2022

2023
    """
2024
    if isinstance(signum, (int, long)):
2025
      self.signum = set([signum])
2026
    else:
2027
      self.signum = set(signum)
2028

    
2029
    self.called = False
2030

    
2031
    self._previous = {}
2032
    try:
2033
      for signum in self.signum:
2034
        # Setup handler
2035
        prev_handler = signal.signal(signum, self._HandleSignal)
2036
        try:
2037
          self._previous[signum] = prev_handler
2038
        except:
2039
          # Restore previous handler
2040
          signal.signal(signum, prev_handler)
2041
          raise
2042
    except:
2043
      # Reset all handlers
2044
      self.Reset()
2045
      # Here we have a race condition: a handler may have already been called,
2046
      # but there's not much we can do about it at this point.
2047
      raise
2048

    
2049
  def __del__(self):
2050
    self.Reset()
2051

    
2052
  def Reset(self):
2053
    """Restore previous handler.
2054

2055
    This will reset all the signals to their previous handlers.
2056

2057
    """
2058
    for signum, prev_handler in self._previous.items():
2059
      signal.signal(signum, prev_handler)
2060
      # If successful, remove from dict
2061
      del self._previous[signum]
2062

    
2063
  def Clear(self):
2064
    """Unsets the L{called} flag.
2065

2066
    This function can be used in case a signal may arrive several times.
2067

2068
    """
2069
    self.called = False
2070

    
2071
  def _HandleSignal(self, signum, frame):
2072
    """Actual signal handling function.
2073

2074
    """
2075
    # This is not nice and not absolutely atomic, but it appears to be the only
2076
    # solution in Python -- there are no atomic types.
2077
    self.called = True
2078

    
2079

    
2080
class FieldSet(object):
2081
  """A simple field set.
2082

2083
  Among the features are:
2084
    - checking if a string is among a list of static string or regex objects
2085
    - checking if a whole list of string matches
2086
    - returning the matching groups from a regex match
2087

2088
  Internally, all fields are held as regular expression objects.
2089

2090
  """
2091
  def __init__(self, *items):
2092
    self.items = [re.compile("^%s$" % value) for value in items]
2093

    
2094
  def Extend(self, other_set):
2095
    """Extend the field set with the items from another one"""
2096
    self.items.extend(other_set.items)
2097

    
2098
  def Matches(self, field):
2099
    """Checks if a field matches the current set
2100

2101
    @type field: str
2102
    @param field: the string to match
2103
    @return: either False or a regular expression match object
2104

2105
    """
2106
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
2107
      return m
2108
    return False
2109

    
2110
  def NonMatching(self, items):
2111
    """Returns the list of fields not matching the current set
2112

2113
    @type items: list
2114
    @param items: the list of fields to check
2115
    @rtype: list
2116
    @return: list of non-matching fields
2117

2118
    """
2119
    return [val for val in items if not self.Matches(val)]