Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ 17c61836

History | View | Annotate | Download (55.9 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 CheckDict(target, template, logname=None):
377
  """Ensure a dictionary has a required set of keys.
378

379
  For the given dictionaries I{target} and I{template}, ensure
380
  I{target} has all the keys from I{template}. Missing keys are added
381
  with values from template.
382

383
  @type target: dict
384
  @param target: the dictionary to update
385
  @type template: dict
386
  @param template: the dictionary holding the default values
387
  @type logname: str or None
388
  @param logname: if not None, causes the missing keys to be
389
      logged with this name
390

391
  """
392
  missing = []
393
  for k in template:
394
    if k not in target:
395
      missing.append(k)
396
      target[k] = template[k]
397

    
398
  if missing and logname:
399
    logging.warning('%s missing keys %s', logname, ', '.join(missing))
400

    
401

    
402
def ForceDictType(target, key_types, allowed_values=None):
403
  """Force the values of a dict to have certain types.
404

405
  @type target: dict
406
  @param target: the dict to update
407
  @type key_types: dict
408
  @param key_types: dict mapping target dict keys to types
409
                    in constants.ENFORCEABLE_TYPES
410
  @type allowed_values: list
411
  @keyword allowed_values: list of specially allowed values
412

413
  """
414
  if allowed_values is None:
415
    allowed_values = []
416

    
417
  for key in target:
418
    if key not in key_types:
419
      msg = "Unknown key '%s'" % key
420
      raise errors.TypeEnforcementError(msg)
421

    
422
    if target[key] in allowed_values:
423
      continue
424

    
425
    type = key_types[key]
426
    if type not in constants.ENFORCEABLE_TYPES:
427
      msg = "'%s' has non-enforceable type %s" % (key, type)
428
      raise errors.ProgrammerError(msg)
429

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

    
464

    
465
def IsProcessAlive(pid):
466
  """Check if a given pid exists on the system.
467

468
  @note: zombie status is not handled, so zombie processes
469
      will be returned as alive
470
  @type pid: int
471
  @param pid: the process ID to check
472
  @rtype: boolean
473
  @return: True if the process exists
474

475
  """
476
  if pid <= 0:
477
    return False
478

    
479
  try:
480
    os.stat("/proc/%d/status" % pid)
481
    return True
482
  except EnvironmentError, err:
483
    if err.errno in (errno.ENOENT, errno.ENOTDIR):
484
      return False
485
    raise
486

    
487

    
488
def ReadPidFile(pidfile):
489
  """Read a pid from a file.
490

491
  @type  pidfile: string
492
  @param pidfile: path to the file containing the pid
493
  @rtype: int
494
  @return: The process id, if the file exists and contains a valid PID,
495
           otherwise 0
496

497
  """
498
  try:
499
    pf = open(pidfile, 'r')
500
  except EnvironmentError, err:
501
    if err.errno != errno.ENOENT:
502
      logging.exception("Can't read pid file?!")
503
    return 0
504

    
505
  try:
506
    pid = int(pf.read())
507
  except ValueError, err:
508
    logging.info("Can't parse pid file contents", exc_info=True)
509
    return 0
510

    
511
  return pid
512

    
513

    
514
def MatchNameComponent(key, name_list):
515
  """Try to match a name against a list.
516

517
  This function will try to match a name like test1 against a list
518
  like C{['test1.example.com', 'test2.example.com', ...]}. Against
519
  this list, I{'test1'} as well as I{'test1.example'} will match, but
520
  not I{'test1.ex'}. A multiple match will be considered as no match
521
  at all (e.g. I{'test1'} against C{['test1.example.com',
522
  'test1.example.org']}).
523

524
  @type key: str
525
  @param key: the name to be searched
526
  @type name_list: list
527
  @param name_list: the list of strings against which to search the key
528

529
  @rtype: None or str
530
  @return: None if there is no match I{or} if there are multiple matches,
531
      otherwise the element from the list which matches
532

533
  """
534
  mo = re.compile("^%s(\..*)?$" % re.escape(key))
535
  names_filtered = [name for name in name_list if mo.match(name) is not None]
536
  if len(names_filtered) != 1:
537
    return None
538
  return names_filtered[0]
539

    
540

    
541
class HostInfo:
542
  """Class implementing resolver and hostname functionality
543

544
  """
545
  def __init__(self, name=None):
546
    """Initialize the host name object.
547

548
    If the name argument is not passed, it will use this system's
549
    name.
550

551
    """
552
    if name is None:
553
      name = self.SysName()
554

    
555
    self.query = name
556
    self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
557
    self.ip = self.ipaddrs[0]
558

    
559
  def ShortName(self):
560
    """Returns the hostname without domain.
561

562
    """
563
    return self.name.split('.')[0]
564

    
565
  @staticmethod
566
  def SysName():
567
    """Return the current system's name.
568

569
    This is simply a wrapper over C{socket.gethostname()}.
570

571
    """
572
    return socket.gethostname()
573

    
574
  @staticmethod
575
  def LookupHostname(hostname):
576
    """Look up hostname
577

578
    @type hostname: str
579
    @param hostname: hostname to look up
580

581
    @rtype: tuple
582
    @return: a tuple (name, aliases, ipaddrs) as returned by
583
        C{socket.gethostbyname_ex}
584
    @raise errors.ResolverError: in case of errors in resolving
585

586
    """
587
    try:
588
      result = socket.gethostbyname_ex(hostname)
589
    except socket.gaierror, err:
590
      # hostname not found in DNS
591
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
592

    
593
    return result
594

    
595

    
596
def ListVolumeGroups():
597
  """List volume groups and their size
598

599
  @rtype: dict
600
  @return:
601
       Dictionary with keys volume name and values
602
       the size of the volume
603

604
  """
605
  command = "vgs --noheadings --units m --nosuffix -o name,size"
606
  result = RunCmd(command)
607
  retval = {}
608
  if result.failed:
609
    return retval
610

    
611
  for line in result.stdout.splitlines():
612
    try:
613
      name, size = line.split()
614
      size = int(float(size))
615
    except (IndexError, ValueError), err:
616
      logging.error("Invalid output from vgs (%s): %s", err, line)
617
      continue
618

    
619
    retval[name] = size
620

    
621
  return retval
622

    
623

    
624
def BridgeExists(bridge):
625
  """Check whether the given bridge exists in the system
626

627
  @type bridge: str
628
  @param bridge: the bridge name to check
629
  @rtype: boolean
630
  @return: True if it does
631

632
  """
633
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
634

    
635

    
636
def NiceSort(name_list):
637
  """Sort a list of strings based on digit and non-digit groupings.
638

639
  Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
640
  will sort the list in the logical order C{['a1', 'a2', 'a10',
641
  'a11']}.
642

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

647
  @type name_list: list
648
  @param name_list: the names to be sorted
649
  @rtype: list
650
  @return: a copy of the name list sorted with our algorithm
651

652
  """
653
  _SORTER_BASE = "(\D+|\d+)"
654
  _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
655
                                                  _SORTER_BASE, _SORTER_BASE,
656
                                                  _SORTER_BASE, _SORTER_BASE,
657
                                                  _SORTER_BASE, _SORTER_BASE)
658
  _SORTER_RE = re.compile(_SORTER_FULL)
659
  _SORTER_NODIGIT = re.compile("^\D*$")
660
  def _TryInt(val):
661
    """Attempts to convert a variable to integer."""
662
    if val is None or _SORTER_NODIGIT.match(val):
663
      return val
664
    rval = int(val)
665
    return rval
666

    
667
  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
668
             for name in name_list]
669
  to_sort.sort()
670
  return [tup[1] for tup in to_sort]
671

    
672

    
673
def TryConvert(fn, val):
674
  """Try to convert a value ignoring errors.
675

676
  This function tries to apply function I{fn} to I{val}. If no
677
  C{ValueError} or C{TypeError} exceptions are raised, it will return
678
  the result, else it will return the original value. Any other
679
  exceptions are propagated to the caller.
680

681
  @type fn: callable
682
  @param fn: function to apply to the value
683
  @param val: the value to be converted
684
  @return: The converted value if the conversion was successful,
685
      otherwise the original value.
686

687
  """
688
  try:
689
    nv = fn(val)
690
  except (ValueError, TypeError), err:
691
    nv = val
692
  return nv
693

    
694

    
695
def IsValidIP(ip):
696
  """Verifies the syntax of an IPv4 address.
697

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

701
  @type ip: str
702
  @param ip: the address to be checked
703
  @rtype: a regular expression match object
704
  @return: a regular epression match object, or None if the
705
      address is not valid
706

707
  """
708
  unit = "(0|[1-9]\d{0,2})"
709
  #TODO: convert and return only boolean
710
  return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
711

    
712

    
713
def IsValidShellParam(word):
714
  """Verifies is the given word is safe from the shell's p.o.v.
715

716
  This means that we can pass this to a command via the shell and be
717
  sure that it doesn't alter the command line and is passed as such to
718
  the actual command.
719

720
  Note that we are overly restrictive here, in order to be on the safe
721
  side.
722

723
  @type word: str
724
  @param word: the word to check
725
  @rtype: boolean
726
  @return: True if the word is 'safe'
727

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

    
731

    
732
def BuildShellCmd(template, *args):
733
  """Build a safe shell command line from the given arguments.
734

735
  This function will check all arguments in the args list so that they
736
  are valid shell parameters (i.e. they don't contain shell
737
  metacharaters). If everything is ok, it will return the result of
738
  template % args.
739

740
  @type template: str
741
  @param template: the string holding the template for the
742
      string formatting
743
  @rtype: str
744
  @return: the expanded command line
745

746
  """
747
  for word in args:
748
    if not IsValidShellParam(word):
749
      raise errors.ProgrammerError("Shell argument '%s' contains"
750
                                   " invalid characters" % word)
751
  return template % args
752

    
753

    
754
def FormatUnit(value, units):
755
  """Formats an incoming number of MiB with the appropriate unit.
756

757
  @type value: int
758
  @param value: integer representing the value in MiB (1048576)
759
  @type units: char
760
  @param units: the type of formatting we should do:
761
      - 'h' for automatic scaling
762
      - 'm' for MiBs
763
      - 'g' for GiBs
764
      - 't' for TiBs
765
  @rtype: str
766
  @return: the formatted value (with suffix)
767

768
  """
769
  if units not in ('m', 'g', 't', 'h'):
770
    raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
771

    
772
  suffix = ''
773

    
774
  if units == 'm' or (units == 'h' and value < 1024):
775
    if units == 'h':
776
      suffix = 'M'
777
    return "%d%s" % (round(value, 0), suffix)
778

    
779
  elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
780
    if units == 'h':
781
      suffix = 'G'
782
    return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
783

    
784
  else:
785
    if units == 'h':
786
      suffix = 'T'
787
    return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
788

    
789

    
790
def ParseUnit(input_string):
791
  """Tries to extract number and scale from the given string.
792

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

797
  """
798
  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', str(input_string))
799
  if not m:
800
    raise errors.UnitParseError("Invalid format")
801

    
802
  value = float(m.groups()[0])
803

    
804
  unit = m.groups()[1]
805
  if unit:
806
    lcunit = unit.lower()
807
  else:
808
    lcunit = 'm'
809

    
810
  if lcunit in ('m', 'mb', 'mib'):
811
    # Value already in MiB
812
    pass
813

    
814
  elif lcunit in ('g', 'gb', 'gib'):
815
    value *= 1024
816

    
817
  elif lcunit in ('t', 'tb', 'tib'):
818
    value *= 1024 * 1024
819

    
820
  else:
821
    raise errors.UnitParseError("Unknown unit: %s" % unit)
822

    
823
  # Make sure we round up
824
  if int(value) < value:
825
    value += 1
826

    
827
  # Round up to the next multiple of 4
828
  value = int(value)
829
  if value % 4:
830
    value += 4 - value % 4
831

    
832
  return value
833

    
834

    
835
def AddAuthorizedKey(file_name, key):
836
  """Adds an SSH public key to an authorized_keys file.
837

838
  @type file_name: str
839
  @param file_name: path to authorized_keys file
840
  @type key: str
841
  @param key: string containing key
842

843
  """
844
  key_fields = key.split()
845

    
846
  f = open(file_name, 'a+')
847
  try:
848
    nl = True
849
    for line in f:
850
      # Ignore whitespace changes
851
      if line.split() == key_fields:
852
        break
853
      nl = line.endswith('\n')
854
    else:
855
      if not nl:
856
        f.write("\n")
857
      f.write(key.rstrip('\r\n'))
858
      f.write("\n")
859
      f.flush()
860
  finally:
861
    f.close()
862

    
863

    
864
def RemoveAuthorizedKey(file_name, key):
865
  """Removes an SSH public key from an authorized_keys file.
866

867
  @type file_name: str
868
  @param file_name: path to authorized_keys file
869
  @type key: str
870
  @param key: string containing key
871

872
  """
873
  key_fields = key.split()
874

    
875
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
876
  try:
877
    out = os.fdopen(fd, 'w')
878
    try:
879
      f = open(file_name, 'r')
880
      try:
881
        for line in f:
882
          # Ignore whitespace changes while comparing lines
883
          if line.split() != key_fields:
884
            out.write(line)
885

    
886
        out.flush()
887
        os.rename(tmpname, file_name)
888
      finally:
889
        f.close()
890
    finally:
891
      out.close()
892
  except:
893
    RemoveFile(tmpname)
894
    raise
895

    
896

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

900
  @type file_name: str
901
  @param file_name: path to the file to modify (usually C{/etc/hosts})
902
  @type ip: str
903
  @param ip: the IP address
904
  @type hostname: str
905
  @param hostname: the hostname to be added
906
  @type aliases: list
907
  @param aliases: the list of aliases to add for the hostname
908

909
  """
910
  # FIXME: use WriteFile + fn rather than duplicating its efforts
911
  # Ensure aliases are unique
912
  aliases = UniqueSequence([hostname] + aliases)[1:]
913

    
914
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
915
  try:
916
    out = os.fdopen(fd, 'w')
917
    try:
918
      f = open(file_name, 'r')
919
      try:
920
        for line in f:
921
          fields = line.split()
922
          if fields and not fields[0].startswith('#') and ip == fields[0]:
923
            continue
924
          out.write(line)
925

    
926
        out.write("%s\t%s" % (ip, hostname))
927
        if aliases:
928
          out.write(" %s" % ' '.join(aliases))
929
        out.write('\n')
930

    
931
        out.flush()
932
        os.fsync(out)
933
        os.chmod(tmpname, 0644)
934
        os.rename(tmpname, file_name)
935
      finally:
936
        f.close()
937
    finally:
938
      out.close()
939
  except:
940
    RemoveFile(tmpname)
941
    raise
942

    
943

    
944
def AddHostToEtcHosts(hostname):
945
  """Wrapper around SetEtcHostsEntry.
946

947
  @type hostname: str
948
  @param hostname: a hostname that will be resolved and added to
949
      L{constants.ETC_HOSTS}
950

951
  """
952
  hi = HostInfo(name=hostname)
953
  SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
954

    
955

    
956
def RemoveEtcHostsEntry(file_name, hostname):
957
  """Removes a hostname from /etc/hosts.
958

959
  IP addresses without names are removed from the file.
960

961
  @type file_name: str
962
  @param file_name: path to the file to modify (usually C{/etc/hosts})
963
  @type hostname: str
964
  @param hostname: the hostname to be removed
965

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

    
985
          out.write(line)
986

    
987
        out.flush()
988
        os.fsync(out)
989
        os.chmod(tmpname, 0644)
990
        os.rename(tmpname, file_name)
991
      finally:
992
        f.close()
993
    finally:
994
      out.close()
995
  except:
996
    RemoveFile(tmpname)
997
    raise
998

    
999

    
1000
def RemoveHostFromEtcHosts(hostname):
1001
  """Wrapper around RemoveEtcHostsEntry.
1002

1003
  @type hostname: str
1004
  @param hostname: hostname that will be resolved and its
1005
      full and shot name will be removed from
1006
      L{constants.ETC_HOSTS}
1007

1008
  """
1009
  hi = HostInfo(name=hostname)
1010
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
1011
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
1012

    
1013

    
1014
def CreateBackup(file_name):
1015
  """Creates a backup of a file.
1016

1017
  @type file_name: str
1018
  @param file_name: file to be backed up
1019
  @rtype: str
1020
  @return: the path to the newly created backup
1021
  @raise errors.ProgrammerError: for invalid file names
1022

1023
  """
1024
  if not os.path.isfile(file_name):
1025
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
1026
                                file_name)
1027

    
1028
  prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
1029
  dir_name = os.path.dirname(file_name)
1030

    
1031
  fsrc = open(file_name, 'rb')
1032
  try:
1033
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
1034
    fdst = os.fdopen(fd, 'wb')
1035
    try:
1036
      shutil.copyfileobj(fsrc, fdst)
1037
    finally:
1038
      fdst.close()
1039
  finally:
1040
    fsrc.close()
1041

    
1042
  return backup_name
1043

    
1044

    
1045
def ShellQuote(value):
1046
  """Quotes shell argument according to POSIX.
1047

1048
  @type value: str
1049
  @param value: the argument to be quoted
1050
  @rtype: str
1051
  @return: the quoted value
1052

1053
  """
1054
  if _re_shell_unquoted.match(value):
1055
    return value
1056
  else:
1057
    return "'%s'" % value.replace("'", "'\\''")
1058

    
1059

    
1060
def ShellQuoteArgs(args):
1061
  """Quotes a list of shell arguments.
1062

1063
  @type args: list
1064
  @param args: list of arguments to be quoted
1065
  @rtype: str
1066
  @return: the quoted arguments concatenaned with spaces
1067

1068
  """
1069
  return ' '.join([ShellQuote(i) for i in args])
1070

    
1071

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

1075
  Check if the given IP is reachable by doing attempting a TCP connect
1076
  to it.
1077

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

1092
  """
1093
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1094

    
1095
  success = False
1096

    
1097
  if source is not None:
1098
    try:
1099
      sock.bind((source, 0))
1100
    except socket.error, (errcode, errstring):
1101
      if errcode == errno.EADDRNOTAVAIL:
1102
        success = False
1103

    
1104
  sock.settimeout(timeout)
1105

    
1106
  try:
1107
    sock.connect((target, port))
1108
    sock.close()
1109
    success = True
1110
  except socket.timeout:
1111
    success = False
1112
  except socket.error, (errcode, errstring):
1113
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
1114

    
1115
  return success
1116

    
1117

    
1118
def OwnIpAddress(address):
1119
  """Check if the current host has the the given IP address.
1120

1121
  Currently this is done by TCP-pinging the address from the loopback
1122
  address.
1123

1124
  @type address: string
1125
  @param address: the addres to check
1126
  @rtype: bool
1127
  @return: True if we own the address
1128

1129
  """
1130
  return TcpPing(address, constants.DEFAULT_NODED_PORT,
1131
                 source=constants.LOCALHOST_IP_ADDRESS)
1132

    
1133

    
1134
def ListVisibleFiles(path):
1135
  """Returns a list of visible files in a directory.
1136

1137
  @type path: str
1138
  @param path: the directory to enumerate
1139
  @rtype: list
1140
  @return: the list of all files not starting with a dot
1141

1142
  """
1143
  files = [i for i in os.listdir(path) if not i.startswith(".")]
1144
  files.sort()
1145
  return files
1146

    
1147

    
1148
def GetHomeDir(user, default=None):
1149
  """Try to get the homedir of the given user.
1150

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

1155
  """
1156
  try:
1157
    if isinstance(user, basestring):
1158
      result = pwd.getpwnam(user)
1159
    elif isinstance(user, (int, long)):
1160
      result = pwd.getpwuid(user)
1161
    else:
1162
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1163
                                   type(user))
1164
  except KeyError:
1165
    return default
1166
  return result.pw_dir
1167

    
1168

    
1169
def NewUUID():
1170
  """Returns a random UUID.
1171

1172
  @note: This is a Linux-specific method as it uses the /proc
1173
      filesystem.
1174
  @rtype: str
1175

1176
  """
1177
  f = open("/proc/sys/kernel/random/uuid", "r")
1178
  try:
1179
    return f.read(128).rstrip("\n")
1180
  finally:
1181
    f.close()
1182

    
1183

    
1184
def GenerateSecret():
1185
  """Generates a random secret.
1186

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

1190
  @rtype: str
1191
  @return: a sha1 hexdigest of a block of 64 random bytes
1192

1193
  """
1194
  return sha1(os.urandom(64)).hexdigest()
1195

    
1196

    
1197
def EnsureDirs(dirs):
1198
  """Make required directories, if they don't exist.
1199

1200
  @param dirs: list of tuples (dir_name, dir_mode)
1201
  @type dirs: list of (string, integer)
1202

1203
  """
1204
  for dir_name, dir_mode in dirs:
1205
    try:
1206
      os.mkdir(dir_name, dir_mode)
1207
    except EnvironmentError, err:
1208
      if err.errno != errno.EEXIST:
1209
        raise errors.GenericError("Cannot create needed directory"
1210
                                  " '%s': %s" % (dir_name, err))
1211
    if not os.path.isdir(dir_name):
1212
      raise errors.GenericError("%s is not a directory" % dir_name)
1213

    
1214

    
1215
def ReadFile(file_name, size=None):
1216
  """Reads a file.
1217

1218
  @type size: None or int
1219
  @param size: Read at most size bytes
1220
  @rtype: str
1221
  @return: the (possibly partial) conent of the file
1222

1223
  """
1224
  f = open(file_name, "r")
1225
  try:
1226
    if size is None:
1227
      return f.read()
1228
    else:
1229
      return f.read(size)
1230
  finally:
1231
    f.close()
1232

    
1233

    
1234
def WriteFile(file_name, fn=None, data=None,
1235
              mode=None, uid=-1, gid=-1,
1236
              atime=None, mtime=None, close=True,
1237
              dry_run=False, backup=False,
1238
              prewrite=None, postwrite=None):
1239
  """(Over)write a file atomically.
1240

1241
  The file_name and either fn (a function taking one argument, the
1242
  file descriptor, and which should write the data to it) or data (the
1243
  contents of the file) must be passed. The other arguments are
1244
  optional and allow setting the file mode, owner and group, and the
1245
  mtime/atime of the file.
1246

1247
  If the function doesn't raise an exception, it has succeeded and the
1248
  target file has the new contents. If the function has raised an
1249
  exception, an existing target file should be unmodified and the
1250
  temporary file should be removed.
1251

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

1276
  @rtype: None or int
1277
  @return: None if the 'close' parameter evaluates to True,
1278
      otherwise the file descriptor
1279

1280
  @raise errors.ProgrammerError: if any of the arguments are not valid
1281

1282
  """
1283
  if not os.path.isabs(file_name):
1284
    raise errors.ProgrammerError("Path passed to WriteFile is not"
1285
                                 " absolute: '%s'" % file_name)
1286

    
1287
  if [fn, data].count(None) != 1:
1288
    raise errors.ProgrammerError("fn or data required")
1289

    
1290
  if [atime, mtime].count(None) == 1:
1291
    raise errors.ProgrammerError("Both atime and mtime must be either"
1292
                                 " set or None")
1293

    
1294
  if backup and not dry_run and os.path.isfile(file_name):
1295
    CreateBackup(file_name)
1296

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

    
1330
  return result
1331

    
1332

    
1333
def FirstFree(seq, base=0):
1334
  """Returns the first non-existing integer from seq.
1335

1336
  The seq argument should be a sorted list of positive integers. The
1337
  first time the index of an element is smaller than the element
1338
  value, the index will be returned.
1339

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

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

1345
  @type seq: sequence
1346
  @param seq: the sequence to be analyzed.
1347
  @type base: int
1348
  @param base: use this value as the base index of the sequence
1349
  @rtype: int
1350
  @return: the first non-used index in the sequence
1351

1352
  """
1353
  for idx, elem in enumerate(seq):
1354
    assert elem >= base, "Passed element is higher than base offset"
1355
    if elem > idx + base:
1356
      # idx is not used
1357
      return idx + base
1358
  return None
1359

    
1360

    
1361
def all(seq, pred=bool):
1362
  "Returns True if pred(x) is True for every element in the iterable"
1363
  for elem in itertools.ifilterfalse(pred, seq):
1364
    return False
1365
  return True
1366

    
1367

    
1368
def any(seq, pred=bool):
1369
  "Returns True if pred(x) is True for at least one element in the iterable"
1370
  for elem in itertools.ifilter(pred, seq):
1371
    return True
1372
  return False
1373

    
1374

    
1375
def UniqueSequence(seq):
1376
  """Returns a list with unique elements.
1377

1378
  Element order is preserved.
1379

1380
  @type seq: sequence
1381
  @param seq: the sequence with the source elementes
1382
  @rtype: list
1383
  @return: list of unique elements from seq
1384

1385
  """
1386
  seen = set()
1387
  return [i for i in seq if i not in seen and not seen.add(i)]
1388

    
1389

    
1390
def IsValidMac(mac):
1391
  """Predicate to check if a MAC address is valid.
1392

1393
  Checks wether the supplied MAC address is formally correct, only
1394
  accepts colon separated format.
1395

1396
  @type mac: str
1397
  @param mac: the MAC to be validated
1398
  @rtype: boolean
1399
  @return: True is the MAC seems valid
1400

1401
  """
1402
  mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$")
1403
  return mac_check.match(mac) is not None
1404

    
1405

    
1406
def TestDelay(duration):
1407
  """Sleep for a fixed amount of time.
1408

1409
  @type duration: float
1410
  @param duration: the sleep duration
1411
  @rtype: boolean
1412
  @return: False for negative value, True otherwise
1413

1414
  """
1415
  if duration < 0:
1416
    return False
1417
  time.sleep(duration)
1418
  return True
1419

    
1420

    
1421
def _CloseFDNoErr(fd, retries=5):
1422
  """Close a file descriptor ignoring errors.
1423

1424
  @type fd: int
1425
  @param fd: the file descriptor
1426
  @type retries: int
1427
  @param retries: how many retries to make, in case we get any
1428
      other error than EBADF
1429

1430
  """
1431
  try:
1432
    os.close(fd)
1433
  except OSError, err:
1434
    if err.errno != errno.EBADF:
1435
      if retries > 0:
1436
        _CloseFDNoErr(fd, retries - 1)
1437
    # else either it's closed already or we're out of retries, so we
1438
    # ignore this and go on
1439

    
1440

    
1441
def CloseFDs(noclose_fds=None):
1442
  """Close file descriptors.
1443

1444
  This closes all file descriptors above 2 (i.e. except
1445
  stdin/out/err).
1446

1447
  @type noclose_fds: list or None
1448
  @param noclose_fds: if given, it denotes a list of file descriptor
1449
      that should not be closed
1450

1451
  """
1452
  # Default maximum for the number of available file descriptors.
1453
  if 'SC_OPEN_MAX' in os.sysconf_names:
1454
    try:
1455
      MAXFD = os.sysconf('SC_OPEN_MAX')
1456
      if MAXFD < 0:
1457
        MAXFD = 1024
1458
    except OSError:
1459
      MAXFD = 1024
1460
  else:
1461
    MAXFD = 1024
1462
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1463
  if (maxfd == resource.RLIM_INFINITY):
1464
    maxfd = MAXFD
1465

    
1466
  # Iterate through and close all file descriptors (except the standard ones)
1467
  for fd in range(3, maxfd):
1468
    if noclose_fds and fd in noclose_fds:
1469
      continue
1470
    _CloseFDNoErr(fd)
1471

    
1472

    
1473
def Daemonize(logfile):
1474
  """Daemonize the current process.
1475

1476
  This detaches the current process from the controlling terminal and
1477
  runs it in the background as a daemon.
1478

1479
  @type logfile: str
1480
  @param logfile: the logfile to which we should redirect stdout/stderr
1481
  @rtype: int
1482
  @return: the value zero
1483

1484
  """
1485
  UMASK = 077
1486
  WORKDIR = "/"
1487

    
1488
  # this might fail
1489
  pid = os.fork()
1490
  if (pid == 0):  # The first child.
1491
    os.setsid()
1492
    # this might fail
1493
    pid = os.fork() # Fork a second child.
1494
    if (pid == 0):  # The second child.
1495
      os.chdir(WORKDIR)
1496
      os.umask(UMASK)
1497
    else:
1498
      # exit() or _exit()?  See below.
1499
      os._exit(0) # Exit parent (the first child) of the second child.
1500
  else:
1501
    os._exit(0) # Exit parent of the first child.
1502

    
1503
  for fd in range(3):
1504
    _CloseFDNoErr(fd)
1505
  i = os.open("/dev/null", os.O_RDONLY) # stdin
1506
  assert i == 0, "Can't close/reopen stdin"
1507
  i = os.open(logfile, os.O_WRONLY|os.O_CREAT|os.O_APPEND, 0600) # stdout
1508
  assert i == 1, "Can't close/reopen stdout"
1509
  # Duplicate standard output to standard error.
1510
  os.dup2(1, 2)
1511
  return 0
1512

    
1513

    
1514
def DaemonPidFileName(name):
1515
  """Compute a ganeti pid file absolute path
1516

1517
  @type name: str
1518
  @param name: the daemon name
1519
  @rtype: str
1520
  @return: the full path to the pidfile corresponding to the given
1521
      daemon name
1522

1523
  """
1524
  return os.path.join(constants.RUN_GANETI_DIR, "%s.pid" % name)
1525

    
1526

    
1527
def WritePidFile(name):
1528
  """Write the current process pidfile.
1529

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

1532
  @type name: str
1533
  @param name: the daemon name to use
1534
  @raise errors.GenericError: if the pid file already exists and
1535
      points to a live process
1536

1537
  """
1538
  pid = os.getpid()
1539
  pidfilename = DaemonPidFileName(name)
1540
  if IsProcessAlive(ReadPidFile(pidfilename)):
1541
    raise errors.GenericError("%s contains a live process" % pidfilename)
1542

    
1543
  WriteFile(pidfilename, data="%d\n" % pid)
1544

    
1545

    
1546
def RemovePidFile(name):
1547
  """Remove the current process pidfile.
1548

1549
  Any errors are ignored.
1550

1551
  @type name: str
1552
  @param name: the daemon name used to derive the pidfile name
1553

1554
  """
1555
  pid = os.getpid()
1556
  pidfilename = DaemonPidFileName(name)
1557
  # TODO: we could check here that the file contains our pid
1558
  try:
1559
    RemoveFile(pidfilename)
1560
  except:
1561
    pass
1562

    
1563

    
1564
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1565
                waitpid=False):
1566
  """Kill a process given by its pid.
1567

1568
  @type pid: int
1569
  @param pid: The PID to terminate.
1570
  @type signal_: int
1571
  @param signal_: The signal to send, by default SIGTERM
1572
  @type timeout: int
1573
  @param timeout: The timeout after which, if the process is still alive,
1574
                  a SIGKILL will be sent. If not positive, no such checking
1575
                  will be done
1576
  @type waitpid: boolean
1577
  @param waitpid: If true, we should waitpid on this process after
1578
      sending signals, since it's our own child and otherwise it
1579
      would remain as zombie
1580

1581
  """
1582
  def _helper(pid, signal_, wait):
1583
    """Simple helper to encapsulate the kill/waitpid sequence"""
1584
    os.kill(pid, signal_)
1585
    if wait:
1586
      try:
1587
        os.waitpid(pid, os.WNOHANG)
1588
      except OSError:
1589
        pass
1590

    
1591
  if pid <= 0:
1592
    # kill with pid=0 == suicide
1593
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1594

    
1595
  if not IsProcessAlive(pid):
1596
    return
1597
  _helper(pid, signal_, waitpid)
1598
  if timeout <= 0:
1599
    return
1600

    
1601
  # Wait up to $timeout seconds
1602
  end = time.time() + timeout
1603
  wait = 0.01
1604
  while time.time() < end and IsProcessAlive(pid):
1605
    try:
1606
      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
1607
      if result_pid > 0:
1608
        break
1609
    except OSError:
1610
      pass
1611
    time.sleep(wait)
1612
    # Make wait time longer for next try
1613
    if wait < 0.1:
1614
      wait *= 1.5
1615

    
1616
  if IsProcessAlive(pid):
1617
    # Kill process if it's still alive
1618
    _helper(pid, signal.SIGKILL, waitpid)
1619

    
1620

    
1621
def FindFile(name, search_path, test=os.path.exists):
1622
  """Look for a filesystem object in a given path.
1623

1624
  This is an abstract method to search for filesystem object (files,
1625
  dirs) under a given search path.
1626

1627
  @type name: str
1628
  @param name: the name to look for
1629
  @type search_path: str
1630
  @param search_path: location to start at
1631
  @type test: callable
1632
  @param test: a function taking one argument that should return True
1633
      if the a given object is valid; the default value is
1634
      os.path.exists, causing only existing files to be returned
1635
  @rtype: str or None
1636
  @return: full path to the object if found, None otherwise
1637

1638
  """
1639
  for dir_name in search_path:
1640
    item_name = os.path.sep.join([dir_name, name])
1641
    if test(item_name):
1642
      return item_name
1643
  return None
1644

    
1645

    
1646
def CheckVolumeGroupSize(vglist, vgname, minsize):
1647
  """Checks if the volume group list is valid.
1648

1649
  The function will check if a given volume group is in the list of
1650
  volume groups and has a minimum size.
1651

1652
  @type vglist: dict
1653
  @param vglist: dictionary of volume group names and their size
1654
  @type vgname: str
1655
  @param vgname: the volume group we should check
1656
  @type minsize: int
1657
  @param minsize: the minimum size we accept
1658
  @rtype: None or str
1659
  @return: None for success, otherwise the error message
1660

1661
  """
1662
  vgsize = vglist.get(vgname, None)
1663
  if vgsize is None:
1664
    return "volume group '%s' missing" % vgname
1665
  elif vgsize < minsize:
1666
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1667
            (vgname, minsize, vgsize))
1668
  return None
1669

    
1670

    
1671
def SplitTime(value):
1672
  """Splits time as floating point number into a tuple.
1673

1674
  @param value: Time in seconds
1675
  @type value: int or float
1676
  @return: Tuple containing (seconds, microseconds)
1677

1678
  """
1679
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1680

    
1681
  assert 0 <= seconds, \
1682
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1683
  assert 0 <= microseconds <= 999999, \
1684
    "Microseconds must be 0-999999, but are %s" % microseconds
1685

    
1686
  return (int(seconds), int(microseconds))
1687

    
1688

    
1689
def MergeTime(timetuple):
1690
  """Merges a tuple into time as a floating point number.
1691

1692
  @param timetuple: Time as tuple, (seconds, microseconds)
1693
  @type timetuple: tuple
1694
  @return: Time as a floating point number expressed in seconds
1695

1696
  """
1697
  (seconds, microseconds) = timetuple
1698

    
1699
  assert 0 <= seconds, \
1700
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1701
  assert 0 <= microseconds <= 999999, \
1702
    "Microseconds must be 0-999999, but are %s" % microseconds
1703

    
1704
  return float(seconds) + (float(microseconds) * 0.000001)
1705

    
1706

    
1707
def GetNodeDaemonPort():
1708
  """Get the node daemon port for this cluster.
1709

1710
  Note that this routine does not read a ganeti-specific file, but
1711
  instead uses C{socket.getservbyname} to allow pre-customization of
1712
  this parameter outside of Ganeti.
1713

1714
  @rtype: int
1715

1716
  """
1717
  try:
1718
    port = socket.getservbyname("ganeti-noded", "tcp")
1719
  except socket.error:
1720
    port = constants.DEFAULT_NODED_PORT
1721

    
1722
  return port
1723

    
1724

    
1725
def SetupLogging(logfile, debug=False, stderr_logging=False, program="",
1726
                 multithreaded=False):
1727
  """Configures the logging module.
1728

1729
  @type logfile: str
1730
  @param logfile: the filename to which we should log
1731
  @type debug: boolean
1732
  @param debug: whether to enable debug messages too or
1733
      only those at C{INFO} and above level
1734
  @type stderr_logging: boolean
1735
  @param stderr_logging: whether we should also log to the standard error
1736
  @type program: str
1737
  @param program: the name under which we should log messages
1738
  @type multithreaded: boolean
1739
  @param multithreaded: if True, will add the thread name to the log file
1740
  @raise EnvironmentError: if we can't open the log file and
1741
      stderr logging is disabled
1742

1743
  """
1744
  fmt = "%(asctime)s: " + program + " pid=%(process)d"
1745
  if multithreaded:
1746
    fmt += "/%(threadName)s"
1747
  if debug:
1748
    fmt += " %(module)s:%(lineno)s"
1749
  fmt += " %(levelname)s %(message)s"
1750
  formatter = logging.Formatter(fmt)
1751

    
1752
  root_logger = logging.getLogger("")
1753
  root_logger.setLevel(logging.NOTSET)
1754

    
1755
  # Remove all previously setup handlers
1756
  for handler in root_logger.handlers:
1757
    handler.close()
1758
    root_logger.removeHandler(handler)
1759

    
1760
  if stderr_logging:
1761
    stderr_handler = logging.StreamHandler()
1762
    stderr_handler.setFormatter(formatter)
1763
    if debug:
1764
      stderr_handler.setLevel(logging.NOTSET)
1765
    else:
1766
      stderr_handler.setLevel(logging.CRITICAL)
1767
    root_logger.addHandler(stderr_handler)
1768

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

    
1788
def IsNormAbsPath(path):
1789
  """Check whether a path is absolute and also normalized
1790

1791
  This avoids things like /dir/../../other/path to be valid.
1792

1793
  """
1794
  return os.path.normpath(path) == path and os.path.isabs(path)
1795

    
1796
def TailFile(fname, lines=20):
1797
  """Return the last lines from a file.
1798

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

1803
  @param fname: the file name
1804
  @type lines: int
1805
  @param lines: the (maximum) number of lines to return
1806

1807
  """
1808
  fd = open(fname, "r")
1809
  try:
1810
    fd.seek(0, 2)
1811
    pos = fd.tell()
1812
    pos = max(0, pos-4096)
1813
    fd.seek(pos, 0)
1814
    raw_data = fd.read()
1815
  finally:
1816
    fd.close()
1817

    
1818
  rows = raw_data.splitlines()
1819
  return rows[-lines:]
1820

    
1821

    
1822
def SafeEncode(text):
1823
  """Return a 'safe' version of a source string.
1824

1825
  This function mangles the input string and returns a version that
1826
  should be safe to disply/encode as ASCII. To this end, we first
1827
  convert it to ASCII using the 'backslashreplace' encoding which
1828
  should get rid of any non-ASCII chars, and then we again encode it
1829
  via 'string_escape' which converts '\n' into '\\n' so that log
1830
  messages remain one-line.
1831

1832
  @type text: str or unicode
1833
  @param text: input data
1834
  @rtype: str
1835
  @return: a safe version of text
1836

1837
  """
1838
  text = text.encode('ascii', 'backslashreplace')
1839
  text = text.encode('string_escape')
1840
  return text
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 LockedMethod(fn):
1854
  """Synchronized object access decorator.
1855

1856
  This decorator is intended to protect access to an object using the
1857
  object's own lock which is hardcoded to '_lock'.
1858

1859
  """
1860
  def _LockDebug(*args, **kwargs):
1861
    if debug_locks:
1862
      logging.debug(*args, **kwargs)
1863

    
1864
  def wrapper(self, *args, **kwargs):
1865
    assert hasattr(self, '_lock')
1866
    lock = self._lock
1867
    _LockDebug("Waiting for %s", lock)
1868
    lock.acquire()
1869
    try:
1870
      _LockDebug("Acquired %s", lock)
1871
      result = fn(self, *args, **kwargs)
1872
    finally:
1873
      _LockDebug("Releasing %s", lock)
1874
      lock.release()
1875
      _LockDebug("Released %s", lock)
1876
    return result
1877
  return wrapper
1878

    
1879

    
1880
def LockFile(fd):
1881
  """Locks a file using POSIX locks.
1882

1883
  @type fd: int
1884
  @param fd: the file descriptor we need to lock
1885

1886
  """
1887
  try:
1888
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
1889
  except IOError, err:
1890
    if err.errno == errno.EAGAIN:
1891
      raise errors.LockError("File already locked")
1892
    raise
1893

    
1894

    
1895
class FileLock(object):
1896
  """Utility class for file locks.
1897

1898
  """
1899
  def __init__(self, filename):
1900
    """Constructor for FileLock.
1901

1902
    This will open the file denoted by the I{filename} argument.
1903

1904
    @type filename: str
1905
    @param filename: path to the file to be locked
1906

1907
    """
1908
    self.filename = filename
1909
    self.fd = open(self.filename, "w")
1910

    
1911
  def __del__(self):
1912
    self.Close()
1913

    
1914
  def Close(self):
1915
    """Close the file and release the lock.
1916

1917
    """
1918
    if self.fd:
1919
      self.fd.close()
1920
      self.fd = None
1921

    
1922
  def _flock(self, flag, blocking, timeout, errmsg):
1923
    """Wrapper for fcntl.flock.
1924

1925
    @type flag: int
1926
    @param flag: operation flag
1927
    @type blocking: bool
1928
    @param blocking: whether the operation should be done in blocking mode.
1929
    @type timeout: None or float
1930
    @param timeout: for how long the operation should be retried (implies
1931
                    non-blocking mode).
1932
    @type errmsg: string
1933
    @param errmsg: error message in case operation fails.
1934

1935
    """
1936
    assert self.fd, "Lock was closed"
1937
    assert timeout is None or timeout >= 0, \
1938
      "If specified, timeout must be positive"
1939

    
1940
    if timeout is not None:
1941
      flag |= fcntl.LOCK_NB
1942
      timeout_end = time.time() + timeout
1943

    
1944
    # Blocking doesn't have effect with timeout
1945
    elif not blocking:
1946
      flag |= fcntl.LOCK_NB
1947
      timeout_end = None
1948

    
1949
    retry = True
1950
    while retry:
1951
      try:
1952
        fcntl.flock(self.fd, flag)
1953
        retry = False
1954
      except IOError, err:
1955
        if err.errno in (errno.EAGAIN, ):
1956
          if timeout_end is not None and time.time() < timeout_end:
1957
            # Wait before trying again
1958
            time.sleep(max(0.1, min(1.0, timeout)))
1959
          else:
1960
            raise errors.LockError(errmsg)
1961
        else:
1962
          logging.exception("fcntl.flock failed")
1963
          raise
1964

    
1965
  def Exclusive(self, blocking=False, timeout=None):
1966
    """Locks the file in exclusive mode.
1967

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

1975
    """
1976
    self._flock(fcntl.LOCK_EX, blocking, timeout,
1977
                "Failed to lock %s in exclusive mode" % self.filename)
1978

    
1979
  def Shared(self, blocking=False, timeout=None):
1980
    """Locks the file in shared mode.
1981

1982
    @type blocking: boolean
1983
    @param blocking: whether to block and wait until we
1984
        can lock the file or return immediately
1985
    @type timeout: int or None
1986
    @param timeout: if not None, the duration to wait for the lock
1987
        (in blocking mode)
1988

1989
    """
1990
    self._flock(fcntl.LOCK_SH, blocking, timeout,
1991
                "Failed to lock %s in shared mode" % self.filename)
1992

    
1993
  def Unlock(self, blocking=True, timeout=None):
1994
    """Unlocks the file.
1995

1996
    According to C{flock(2)}, unlocking can also be a nonblocking
1997
    operation::
1998

1999
      To make a non-blocking request, include LOCK_NB with any of the above
2000
      operations.
2001

2002
    @type blocking: boolean
2003
    @param blocking: whether to block and wait until we
2004
        can lock the file or return immediately
2005
    @type timeout: int or None
2006
    @param timeout: if not None, the duration to wait for the lock
2007
        (in blocking mode)
2008

2009
    """
2010
    self._flock(fcntl.LOCK_UN, blocking, timeout,
2011
                "Failed to unlock %s" % self.filename)
2012

    
2013

    
2014
class SignalHandler(object):
2015
  """Generic signal handler class.
2016

2017
  It automatically restores the original handler when deconstructed or
2018
  when L{Reset} is called. You can either pass your own handler
2019
  function in or query the L{called} attribute to detect whether the
2020
  signal was sent.
2021

2022
  @type signum: list
2023
  @ivar signum: the signals we handle
2024
  @type called: boolean
2025
  @ivar called: tracks whether any of the signals have been raised
2026

2027
  """
2028
  def __init__(self, signum):
2029
    """Constructs a new SignalHandler instance.
2030

2031
    @type signum: int or list of ints
2032
    @param signum: Single signal number or set of signal numbers
2033

2034
    """
2035
    if isinstance(signum, (int, long)):
2036
      self.signum = set([signum])
2037
    else:
2038
      self.signum = set(signum)
2039

    
2040
    self.called = False
2041

    
2042
    self._previous = {}
2043
    try:
2044
      for signum in self.signum:
2045
        # Setup handler
2046
        prev_handler = signal.signal(signum, self._HandleSignal)
2047
        try:
2048
          self._previous[signum] = prev_handler
2049
        except:
2050
          # Restore previous handler
2051
          signal.signal(signum, prev_handler)
2052
          raise
2053
    except:
2054
      # Reset all handlers
2055
      self.Reset()
2056
      # Here we have a race condition: a handler may have already been called,
2057
      # but there's not much we can do about it at this point.
2058
      raise
2059

    
2060
  def __del__(self):
2061
    self.Reset()
2062

    
2063
  def Reset(self):
2064
    """Restore previous handler.
2065

2066
    This will reset all the signals to their previous handlers.
2067

2068
    """
2069
    for signum, prev_handler in self._previous.items():
2070
      signal.signal(signum, prev_handler)
2071
      # If successful, remove from dict
2072
      del self._previous[signum]
2073

    
2074
  def Clear(self):
2075
    """Unsets the L{called} flag.
2076

2077
    This function can be used in case a signal may arrive several times.
2078

2079
    """
2080
    self.called = False
2081

    
2082
  def _HandleSignal(self, signum, frame):
2083
    """Actual signal handling function.
2084

2085
    """
2086
    # This is not nice and not absolutely atomic, but it appears to be the only
2087
    # solution in Python -- there are no atomic types.
2088
    self.called = True
2089

    
2090

    
2091
class FieldSet(object):
2092
  """A simple field set.
2093

2094
  Among the features are:
2095
    - checking if a string is among a list of static string or regex objects
2096
    - checking if a whole list of string matches
2097
    - returning the matching groups from a regex match
2098

2099
  Internally, all fields are held as regular expression objects.
2100

2101
  """
2102
  def __init__(self, *items):
2103
    self.items = [re.compile("^%s$" % value) for value in items]
2104

    
2105
  def Extend(self, other_set):
2106
    """Extend the field set with the items from another one"""
2107
    self.items.extend(other_set.items)
2108

    
2109
  def Matches(self, field):
2110
    """Checks if a field matches the current set
2111

2112
    @type field: str
2113
    @param field: the string to match
2114
    @return: either False or a regular expression match object
2115

2116
    """
2117
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
2118
      return m
2119
    return False
2120

    
2121
  def NonMatching(self, items):
2122
    """Returns the list of fields not matching the current set
2123

2124
    @type items: list
2125
    @param items: the list of fields to check
2126
    @rtype: list
2127
    @return: list of non-matching fields
2128

2129
    """
2130
    return [val for val in items if not self.Matches(val)]