Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ e02b9114

History | View | Annotate | Download (72 kB)

1
#
2
#
3

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

    
21

    
22
"""Ganeti utility module.
23

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

27
"""
28

    
29

    
30
import os
31
import time
32
import subprocess
33
import re
34
import socket
35
import tempfile
36
import shutil
37
import errno
38
import pwd
39
import itertools
40
import select
41
import fcntl
42
import resource
43
import logging
44
import logging.handlers
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_locks = False
63

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

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

    
69

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

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

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

    
92

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

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

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

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

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

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

    
120

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

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

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

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

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

    
157
  if not reset_env:
158
    cmd_env = os.environ.copy()
159
    cmd_env["LC_ALL"] = "C"
160
  else:
161
    cmd_env = {}
162

    
163
  if env is not None:
164
    cmd_env.update(env)
165

    
166
  try:
167
    if output is None:
168
      out, err, status = _RunCmdPipe(cmd, cmd_env, shell, cwd)
169
    else:
170
      status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
171
      out = err = ""
172
  except OSError, err:
173
    if err.errno == errno.ENOENT:
174
      raise errors.OpExecError("Can't execute '%s': not found (%s)" %
175
                               (strcmd, err))
176
    else:
177
      raise
178

    
179
  if status >= 0:
180
    exitcode = status
181
    signal_ = None
182
  else:
183
    exitcode = None
184
    signal_ = -status
185

    
186
  return RunResult(exitcode, signal_, out, err, strcmd)
187

    
188

    
189
def _RunCmdPipe(cmd, env, via_shell, cwd):
190
  """Run a command and return its output.
191

192
  @type  cmd: string or list
193
  @param cmd: Command to run
194
  @type env: dict
195
  @param env: The environment to use
196
  @type via_shell: bool
197
  @param via_shell: if we should run via the shell
198
  @type cwd: string
199
  @param cwd: the working directory for the program
200
  @rtype: tuple
201
  @return: (out, err, status)
202

203
  """
204
  poller = select.poll()
205
  child = subprocess.Popen(cmd, shell=via_shell,
206
                           stderr=subprocess.PIPE,
207
                           stdout=subprocess.PIPE,
208
                           stdin=subprocess.PIPE,
209
                           close_fds=True, env=env,
210
                           cwd=cwd)
211

    
212
  child.stdin.close()
213
  poller.register(child.stdout, select.POLLIN)
214
  poller.register(child.stderr, select.POLLIN)
215
  out = StringIO()
216
  err = StringIO()
217
  fdmap = {
218
    child.stdout.fileno(): (out, child.stdout),
219
    child.stderr.fileno(): (err, child.stderr),
220
    }
221
  for fd in fdmap:
222
    status = fcntl.fcntl(fd, fcntl.F_GETFL)
223
    fcntl.fcntl(fd, fcntl.F_SETFL, status | os.O_NONBLOCK)
224

    
225
  while fdmap:
226
    try:
227
      pollresult = poller.poll()
228
    except EnvironmentError, eerr:
229
      if eerr.errno == errno.EINTR:
230
        continue
231
      raise
232
    except select.error, serr:
233
      if serr[0] == errno.EINTR:
234
        continue
235
      raise
236

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

    
251
  out = out.getvalue()
252
  err = err.getvalue()
253

    
254
  status = child.wait()
255
  return out, err, status
256

    
257

    
258
def _RunCmdFile(cmd, env, via_shell, output, cwd):
259
  """Run a command and save its output to a file.
260

261
  @type  cmd: string or list
262
  @param cmd: Command to run
263
  @type env: dict
264
  @param env: The environment to use
265
  @type via_shell: bool
266
  @param via_shell: if we should run via the shell
267
  @type output: str
268
  @param output: the filename in which to save the output
269
  @type cwd: string
270
  @param cwd: the working directory for the program
271
  @rtype: int
272
  @return: the exit status
273

274
  """
275
  fh = open(output, "a")
276
  try:
277
    child = subprocess.Popen(cmd, shell=via_shell,
278
                             stderr=subprocess.STDOUT,
279
                             stdout=fh,
280
                             stdin=subprocess.PIPE,
281
                             close_fds=True, env=env,
282
                             cwd=cwd)
283

    
284
    child.stdin.close()
285
    status = child.wait()
286
  finally:
287
    fh.close()
288
  return status
289

    
290

    
291
def RunParts(dir_name, env=None, reset_env=False):
292
  """Run Scripts or programs in a directory
293

294
  @type dir_name: string
295
  @param dir_name: absolute path to a directory
296
  @type env: dict
297
  @param env: The environment to use
298
  @type reset_env: boolean
299
  @param reset_env: whether to reset or keep the default os environment
300
  @rtype: list of tuples
301
  @return: list of (name, (one of RUNDIR_STATUS), RunResult)
302

303
  """
304
  rr = []
305

    
306
  try:
307
    dir_contents = ListVisibleFiles(dir_name)
308
  except OSError, err:
309
    logging.warning("RunParts: skipping %s (cannot list: %s)", dir_name, err)
310
    return rr
311

    
312
  for relname in sorted(dir_contents):
313
    fname = PathJoin(dir_name, relname)
314
    if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
315
            constants.EXT_PLUGIN_MASK.match(relname) is not None):
316
      rr.append((relname, constants.RUNPARTS_SKIP, None))
317
    else:
318
      try:
319
        result = RunCmd([fname], env=env, reset_env=reset_env)
320
      except Exception, err: # pylint: disable-msg=W0703
321
        rr.append((relname, constants.RUNPARTS_ERR, str(err)))
322
      else:
323
        rr.append((relname, constants.RUNPARTS_RUN, result))
324

    
325
  return rr
326

    
327

    
328
def RemoveFile(filename):
329
  """Remove a file ignoring some errors.
330

331
  Remove a file, ignoring non-existing ones or directories. Other
332
  errors are passed.
333

334
  @type filename: str
335
  @param filename: the file to be removed
336

337
  """
338
  try:
339
    os.unlink(filename)
340
  except OSError, err:
341
    if err.errno not in (errno.ENOENT, errno.EISDIR):
342
      raise
343

    
344

    
345
def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
346
  """Renames a file.
347

348
  @type old: string
349
  @param old: Original path
350
  @type new: string
351
  @param new: New path
352
  @type mkdir: bool
353
  @param mkdir: Whether to create target directory if it doesn't exist
354
  @type mkdir_mode: int
355
  @param mkdir_mode: Mode for newly created directories
356

357
  """
358
  try:
359
    return os.rename(old, new)
360
  except OSError, err:
361
    # In at least one use case of this function, the job queue, directory
362
    # creation is very rare. Checking for the directory before renaming is not
363
    # as efficient.
364
    if mkdir and err.errno == errno.ENOENT:
365
      # Create directory and try again
366
      dirname = os.path.dirname(new)
367
      try:
368
        os.makedirs(dirname, mode=mkdir_mode)
369
      except OSError, err:
370
        # Ignore EEXIST. This is only handled in os.makedirs as included in
371
        # Python 2.5 and above.
372
        if err.errno != errno.EEXIST or not os.path.exists(dirname):
373
          raise
374

    
375
      return os.rename(old, new)
376

    
377
    raise
378

    
379

    
380
def ResetTempfileModule():
381
  """Resets the random name generator of the tempfile module.
382

383
  This function should be called after C{os.fork} in the child process to
384
  ensure it creates a newly seeded random generator. Otherwise it would
385
  generate the same random parts as the parent process. If several processes
386
  race for the creation of a temporary file, this could lead to one not getting
387
  a temporary name.
388

389
  """
390
  # pylint: disable-msg=W0212
391
  if hasattr(tempfile, "_once_lock") and hasattr(tempfile, "_name_sequence"):
392
    tempfile._once_lock.acquire()
393
    try:
394
      # Reset random name generator
395
      tempfile._name_sequence = None
396
    finally:
397
      tempfile._once_lock.release()
398
  else:
399
    logging.critical("The tempfile module misses at least one of the"
400
                     " '_once_lock' and '_name_sequence' attributes")
401

    
402

    
403
def _FingerprintFile(filename):
404
  """Compute the fingerprint of a file.
405

406
  If the file does not exist, a None will be returned
407
  instead.
408

409
  @type filename: str
410
  @param filename: the filename to checksum
411
  @rtype: str
412
  @return: the hex digest of the sha checksum of the contents
413
      of the file
414

415
  """
416
  if not (os.path.exists(filename) and os.path.isfile(filename)):
417
    return None
418

    
419
  f = open(filename)
420

    
421
  fp = sha1()
422
  while True:
423
    data = f.read(4096)
424
    if not data:
425
      break
426

    
427
    fp.update(data)
428

    
429
  return fp.hexdigest()
430

    
431

    
432
def FingerprintFiles(files):
433
  """Compute fingerprints for a list of files.
434

435
  @type files: list
436
  @param files: the list of filename to fingerprint
437
  @rtype: dict
438
  @return: a dictionary filename: fingerprint, holding only
439
      existing files
440

441
  """
442
  ret = {}
443

    
444
  for filename in files:
445
    cksum = _FingerprintFile(filename)
446
    if cksum:
447
      ret[filename] = cksum
448

    
449
  return ret
450

    
451

    
452
def ForceDictType(target, key_types, allowed_values=None):
453
  """Force the values of a dict to have certain types.
454

455
  @type target: dict
456
  @param target: the dict to update
457
  @type key_types: dict
458
  @param key_types: dict mapping target dict keys to types
459
                    in constants.ENFORCEABLE_TYPES
460
  @type allowed_values: list
461
  @keyword allowed_values: list of specially allowed values
462

463
  """
464
  if allowed_values is None:
465
    allowed_values = []
466

    
467
  if not isinstance(target, dict):
468
    msg = "Expected dictionary, got '%s'" % target
469
    raise errors.TypeEnforcementError(msg)
470

    
471
  for key in target:
472
    if key not in key_types:
473
      msg = "Unknown key '%s'" % key
474
      raise errors.TypeEnforcementError(msg)
475

    
476
    if target[key] in allowed_values:
477
      continue
478

    
479
    ktype = key_types[key]
480
    if ktype not in constants.ENFORCEABLE_TYPES:
481
      msg = "'%s' has non-enforceable type %s" % (key, ktype)
482
      raise errors.ProgrammerError(msg)
483

    
484
    if ktype == constants.VTYPE_STRING:
485
      if not isinstance(target[key], basestring):
486
        if isinstance(target[key], bool) and not target[key]:
487
          target[key] = ''
488
        else:
489
          msg = "'%s' (value %s) is not a valid string" % (key, target[key])
490
          raise errors.TypeEnforcementError(msg)
491
    elif ktype == constants.VTYPE_BOOL:
492
      if isinstance(target[key], basestring) and target[key]:
493
        if target[key].lower() == constants.VALUE_FALSE:
494
          target[key] = False
495
        elif target[key].lower() == constants.VALUE_TRUE:
496
          target[key] = True
497
        else:
498
          msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
499
          raise errors.TypeEnforcementError(msg)
500
      elif target[key]:
501
        target[key] = True
502
      else:
503
        target[key] = False
504
    elif ktype == constants.VTYPE_SIZE:
505
      try:
506
        target[key] = ParseUnit(target[key])
507
      except errors.UnitParseError, err:
508
        msg = "'%s' (value %s) is not a valid size. error: %s" % \
509
              (key, target[key], err)
510
        raise errors.TypeEnforcementError(msg)
511
    elif ktype == constants.VTYPE_INT:
512
      try:
513
        target[key] = int(target[key])
514
      except (ValueError, TypeError):
515
        msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
516
        raise errors.TypeEnforcementError(msg)
517

    
518

    
519
def IsProcessAlive(pid):
520
  """Check if a given pid exists on the system.
521

522
  @note: zombie status is not handled, so zombie processes
523
      will be returned as alive
524
  @type pid: int
525
  @param pid: the process ID to check
526
  @rtype: boolean
527
  @return: True if the process exists
528

529
  """
530
  if pid <= 0:
531
    return False
532

    
533
  try:
534
    os.stat("/proc/%d/status" % pid)
535
    return True
536
  except EnvironmentError, err:
537
    if err.errno in (errno.ENOENT, errno.ENOTDIR):
538
      return False
539
    raise
540

    
541

    
542
def ReadPidFile(pidfile):
543
  """Read a pid from a file.
544

545
  @type  pidfile: string
546
  @param pidfile: path to the file containing the pid
547
  @rtype: int
548
  @return: The process id, if the file exists and contains a valid PID,
549
           otherwise 0
550

551
  """
552
  try:
553
    raw_data = ReadFile(pidfile)
554
  except EnvironmentError, err:
555
    if err.errno != errno.ENOENT:
556
      logging.exception("Can't read pid file")
557
    return 0
558

    
559
  try:
560
    pid = int(raw_data)
561
  except (TypeError, ValueError), err:
562
    logging.info("Can't parse pid file contents", exc_info=True)
563
    return 0
564

    
565
  return pid
566

    
567

    
568
def MatchNameComponent(key, name_list, case_sensitive=True):
569
  """Try to match a name against a list.
570

571
  This function will try to match a name like test1 against a list
572
  like C{['test1.example.com', 'test2.example.com', ...]}. Against
573
  this list, I{'test1'} as well as I{'test1.example'} will match, but
574
  not I{'test1.ex'}. A multiple match will be considered as no match
575
  at all (e.g. I{'test1'} against C{['test1.example.com',
576
  'test1.example.org']}), except when the key fully matches an entry
577
  (e.g. I{'test1'} against C{['test1', 'test1.example.com']}).
578

579
  @type key: str
580
  @param key: the name to be searched
581
  @type name_list: list
582
  @param name_list: the list of strings against which to search the key
583
  @type case_sensitive: boolean
584
  @param case_sensitive: whether to provide a case-sensitive match
585

586
  @rtype: None or str
587
  @return: None if there is no match I{or} if there are multiple matches,
588
      otherwise the element from the list which matches
589

590
  """
591
  if key in name_list:
592
    return key
593

    
594
  re_flags = 0
595
  if not case_sensitive:
596
    re_flags |= re.IGNORECASE
597
    key = key.upper()
598
  mo = re.compile("^%s(\..*)?$" % re.escape(key), re_flags)
599
  names_filtered = []
600
  string_matches = []
601
  for name in name_list:
602
    if mo.match(name) is not None:
603
      names_filtered.append(name)
604
      if not case_sensitive and key == name.upper():
605
        string_matches.append(name)
606

    
607
  if len(string_matches) == 1:
608
    return string_matches[0]
609
  if len(names_filtered) == 1:
610
    return names_filtered[0]
611
  return None
612

    
613

    
614
class HostInfo:
615
  """Class implementing resolver and hostname functionality
616

617
  """
618
  def __init__(self, name=None):
619
    """Initialize the host name object.
620

621
    If the name argument is not passed, it will use this system's
622
    name.
623

624
    """
625
    if name is None:
626
      name = self.SysName()
627

    
628
    self.query = name
629
    self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
630
    self.ip = self.ipaddrs[0]
631

    
632
  def ShortName(self):
633
    """Returns the hostname without domain.
634

635
    """
636
    return self.name.split('.')[0]
637

    
638
  @staticmethod
639
  def SysName():
640
    """Return the current system's name.
641

642
    This is simply a wrapper over C{socket.gethostname()}.
643

644
    """
645
    return socket.gethostname()
646

    
647
  @staticmethod
648
  def LookupHostname(hostname):
649
    """Look up hostname
650

651
    @type hostname: str
652
    @param hostname: hostname to look up
653

654
    @rtype: tuple
655
    @return: a tuple (name, aliases, ipaddrs) as returned by
656
        C{socket.gethostbyname_ex}
657
    @raise errors.ResolverError: in case of errors in resolving
658

659
    """
660
    try:
661
      result = socket.gethostbyname_ex(hostname)
662
    except socket.gaierror, err:
663
      # hostname not found in DNS
664
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
665

    
666
    return result
667

    
668

    
669
def GetHostInfo(name=None):
670
  """Lookup host name and raise an OpPrereqError for failures"""
671

    
672
  try:
673
    return HostInfo(name)
674
  except errors.ResolverError, err:
675
    raise errors.OpPrereqError("The given name (%s) does not resolve: %s" %
676
                               (err[0], err[2]), errors.ECODE_RESOLVER)
677

    
678

    
679
def ListVolumeGroups():
680
  """List volume groups and their size
681

682
  @rtype: dict
683
  @return:
684
       Dictionary with keys volume name and values
685
       the size of the volume
686

687
  """
688
  command = "vgs --noheadings --units m --nosuffix -o name,size"
689
  result = RunCmd(command)
690
  retval = {}
691
  if result.failed:
692
    return retval
693

    
694
  for line in result.stdout.splitlines():
695
    try:
696
      name, size = line.split()
697
      size = int(float(size))
698
    except (IndexError, ValueError), err:
699
      logging.error("Invalid output from vgs (%s): %s", err, line)
700
      continue
701

    
702
    retval[name] = size
703

    
704
  return retval
705

    
706

    
707
def BridgeExists(bridge):
708
  """Check whether the given bridge exists in the system
709

710
  @type bridge: str
711
  @param bridge: the bridge name to check
712
  @rtype: boolean
713
  @return: True if it does
714

715
  """
716
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
717

    
718

    
719
def NiceSort(name_list):
720
  """Sort a list of strings based on digit and non-digit groupings.
721

722
  Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
723
  will sort the list in the logical order C{['a1', 'a2', 'a10',
724
  'a11']}.
725

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

730
  @type name_list: list
731
  @param name_list: the names to be sorted
732
  @rtype: list
733
  @return: a copy of the name list sorted with our algorithm
734

735
  """
736
  _SORTER_BASE = "(\D+|\d+)"
737
  _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
738
                                                  _SORTER_BASE, _SORTER_BASE,
739
                                                  _SORTER_BASE, _SORTER_BASE,
740
                                                  _SORTER_BASE, _SORTER_BASE)
741
  _SORTER_RE = re.compile(_SORTER_FULL)
742
  _SORTER_NODIGIT = re.compile("^\D*$")
743
  def _TryInt(val):
744
    """Attempts to convert a variable to integer."""
745
    if val is None or _SORTER_NODIGIT.match(val):
746
      return val
747
    rval = int(val)
748
    return rval
749

    
750
  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
751
             for name in name_list]
752
  to_sort.sort()
753
  return [tup[1] for tup in to_sort]
754

    
755

    
756
def TryConvert(fn, val):
757
  """Try to convert a value ignoring errors.
758

759
  This function tries to apply function I{fn} to I{val}. If no
760
  C{ValueError} or C{TypeError} exceptions are raised, it will return
761
  the result, else it will return the original value. Any other
762
  exceptions are propagated to the caller.
763

764
  @type fn: callable
765
  @param fn: function to apply to the value
766
  @param val: the value to be converted
767
  @return: The converted value if the conversion was successful,
768
      otherwise the original value.
769

770
  """
771
  try:
772
    nv = fn(val)
773
  except (ValueError, TypeError):
774
    nv = val
775
  return nv
776

    
777

    
778
def IsValidIP(ip):
779
  """Verifies the syntax of an IPv4 address.
780

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

784
  @type ip: str
785
  @param ip: the address to be checked
786
  @rtype: a regular expression match object
787
  @return: a regular expression match object, or None if the
788
      address is not valid
789

790
  """
791
  unit = "(0|[1-9]\d{0,2})"
792
  #TODO: convert and return only boolean
793
  return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
794

    
795

    
796
def IsValidShellParam(word):
797
  """Verifies is the given word is safe from the shell's p.o.v.
798

799
  This means that we can pass this to a command via the shell and be
800
  sure that it doesn't alter the command line and is passed as such to
801
  the actual command.
802

803
  Note that we are overly restrictive here, in order to be on the safe
804
  side.
805

806
  @type word: str
807
  @param word: the word to check
808
  @rtype: boolean
809
  @return: True if the word is 'safe'
810

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

    
814

    
815
def BuildShellCmd(template, *args):
816
  """Build a safe shell command line from the given arguments.
817

818
  This function will check all arguments in the args list so that they
819
  are valid shell parameters (i.e. they don't contain shell
820
  metacharacters). If everything is ok, it will return the result of
821
  template % args.
822

823
  @type template: str
824
  @param template: the string holding the template for the
825
      string formatting
826
  @rtype: str
827
  @return: the expanded command line
828

829
  """
830
  for word in args:
831
    if not IsValidShellParam(word):
832
      raise errors.ProgrammerError("Shell argument '%s' contains"
833
                                   " invalid characters" % word)
834
  return template % args
835

    
836

    
837
def FormatUnit(value, units):
838
  """Formats an incoming number of MiB with the appropriate unit.
839

840
  @type value: int
841
  @param value: integer representing the value in MiB (1048576)
842
  @type units: char
843
  @param units: the type of formatting we should do:
844
      - 'h' for automatic scaling
845
      - 'm' for MiBs
846
      - 'g' for GiBs
847
      - 't' for TiBs
848
  @rtype: str
849
  @return: the formatted value (with suffix)
850

851
  """
852
  if units not in ('m', 'g', 't', 'h'):
853
    raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
854

    
855
  suffix = ''
856

    
857
  if units == 'm' or (units == 'h' and value < 1024):
858
    if units == 'h':
859
      suffix = 'M'
860
    return "%d%s" % (round(value, 0), suffix)
861

    
862
  elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
863
    if units == 'h':
864
      suffix = 'G'
865
    return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
866

    
867
  else:
868
    if units == 'h':
869
      suffix = 'T'
870
    return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
871

    
872

    
873
def ParseUnit(input_string):
874
  """Tries to extract number and scale from the given string.
875

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

880
  """
881
  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', str(input_string))
882
  if not m:
883
    raise errors.UnitParseError("Invalid format")
884

    
885
  value = float(m.groups()[0])
886

    
887
  unit = m.groups()[1]
888
  if unit:
889
    lcunit = unit.lower()
890
  else:
891
    lcunit = 'm'
892

    
893
  if lcunit in ('m', 'mb', 'mib'):
894
    # Value already in MiB
895
    pass
896

    
897
  elif lcunit in ('g', 'gb', 'gib'):
898
    value *= 1024
899

    
900
  elif lcunit in ('t', 'tb', 'tib'):
901
    value *= 1024 * 1024
902

    
903
  else:
904
    raise errors.UnitParseError("Unknown unit: %s" % unit)
905

    
906
  # Make sure we round up
907
  if int(value) < value:
908
    value += 1
909

    
910
  # Round up to the next multiple of 4
911
  value = int(value)
912
  if value % 4:
913
    value += 4 - value % 4
914

    
915
  return value
916

    
917

    
918
def AddAuthorizedKey(file_name, key):
919
  """Adds an SSH public key to an authorized_keys file.
920

921
  @type file_name: str
922
  @param file_name: path to authorized_keys file
923
  @type key: str
924
  @param key: string containing key
925

926
  """
927
  key_fields = key.split()
928

    
929
  f = open(file_name, 'a+')
930
  try:
931
    nl = True
932
    for line in f:
933
      # Ignore whitespace changes
934
      if line.split() == key_fields:
935
        break
936
      nl = line.endswith('\n')
937
    else:
938
      if not nl:
939
        f.write("\n")
940
      f.write(key.rstrip('\r\n'))
941
      f.write("\n")
942
      f.flush()
943
  finally:
944
    f.close()
945

    
946

    
947
def RemoveAuthorizedKey(file_name, key):
948
  """Removes an SSH public key from an authorized_keys file.
949

950
  @type file_name: str
951
  @param file_name: path to authorized_keys file
952
  @type key: str
953
  @param key: string containing key
954

955
  """
956
  key_fields = key.split()
957

    
958
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
959
  try:
960
    out = os.fdopen(fd, 'w')
961
    try:
962
      f = open(file_name, 'r')
963
      try:
964
        for line in f:
965
          # Ignore whitespace changes while comparing lines
966
          if line.split() != key_fields:
967
            out.write(line)
968

    
969
        out.flush()
970
        os.rename(tmpname, file_name)
971
      finally:
972
        f.close()
973
    finally:
974
      out.close()
975
  except:
976
    RemoveFile(tmpname)
977
    raise
978

    
979

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

983
  @type file_name: str
984
  @param file_name: path to the file to modify (usually C{/etc/hosts})
985
  @type ip: str
986
  @param ip: the IP address
987
  @type hostname: str
988
  @param hostname: the hostname to be added
989
  @type aliases: list
990
  @param aliases: the list of aliases to add for the hostname
991

992
  """
993
  # FIXME: use WriteFile + fn rather than duplicating its efforts
994
  # Ensure aliases are unique
995
  aliases = UniqueSequence([hostname] + aliases)[1:]
996

    
997
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
998
  try:
999
    out = os.fdopen(fd, 'w')
1000
    try:
1001
      f = open(file_name, 'r')
1002
      try:
1003
        for line in f:
1004
          fields = line.split()
1005
          if fields and not fields[0].startswith('#') and ip == fields[0]:
1006
            continue
1007
          out.write(line)
1008

    
1009
        out.write("%s\t%s" % (ip, hostname))
1010
        if aliases:
1011
          out.write(" %s" % ' '.join(aliases))
1012
        out.write('\n')
1013

    
1014
        out.flush()
1015
        os.fsync(out)
1016
        os.chmod(tmpname, 0644)
1017
        os.rename(tmpname, file_name)
1018
      finally:
1019
        f.close()
1020
    finally:
1021
      out.close()
1022
  except:
1023
    RemoveFile(tmpname)
1024
    raise
1025

    
1026

    
1027
def AddHostToEtcHosts(hostname):
1028
  """Wrapper around SetEtcHostsEntry.
1029

1030
  @type hostname: str
1031
  @param hostname: a hostname that will be resolved and added to
1032
      L{constants.ETC_HOSTS}
1033

1034
  """
1035
  hi = HostInfo(name=hostname)
1036
  SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
1037

    
1038

    
1039
def RemoveEtcHostsEntry(file_name, hostname):
1040
  """Removes a hostname from /etc/hosts.
1041

1042
  IP addresses without names are removed from the file.
1043

1044
  @type file_name: str
1045
  @param file_name: path to the file to modify (usually C{/etc/hosts})
1046
  @type hostname: str
1047
  @param hostname: the hostname to be removed
1048

1049
  """
1050
  # FIXME: use WriteFile + fn rather than duplicating its efforts
1051
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1052
  try:
1053
    out = os.fdopen(fd, 'w')
1054
    try:
1055
      f = open(file_name, 'r')
1056
      try:
1057
        for line in f:
1058
          fields = line.split()
1059
          if len(fields) > 1 and not fields[0].startswith('#'):
1060
            names = fields[1:]
1061
            if hostname in names:
1062
              while hostname in names:
1063
                names.remove(hostname)
1064
              if names:
1065
                out.write("%s %s\n" % (fields[0], ' '.join(names)))
1066
              continue
1067

    
1068
          out.write(line)
1069

    
1070
        out.flush()
1071
        os.fsync(out)
1072
        os.chmod(tmpname, 0644)
1073
        os.rename(tmpname, file_name)
1074
      finally:
1075
        f.close()
1076
    finally:
1077
      out.close()
1078
  except:
1079
    RemoveFile(tmpname)
1080
    raise
1081

    
1082

    
1083
def RemoveHostFromEtcHosts(hostname):
1084
  """Wrapper around RemoveEtcHostsEntry.
1085

1086
  @type hostname: str
1087
  @param hostname: hostname that will be resolved and its
1088
      full and shot name will be removed from
1089
      L{constants.ETC_HOSTS}
1090

1091
  """
1092
  hi = HostInfo(name=hostname)
1093
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
1094
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
1095

    
1096

    
1097
def CreateBackup(file_name):
1098
  """Creates a backup of a file.
1099

1100
  @type file_name: str
1101
  @param file_name: file to be backed up
1102
  @rtype: str
1103
  @return: the path to the newly created backup
1104
  @raise errors.ProgrammerError: for invalid file names
1105

1106
  """
1107
  if not os.path.isfile(file_name):
1108
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
1109
                                file_name)
1110

    
1111
  prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
1112
  dir_name = os.path.dirname(file_name)
1113

    
1114
  fsrc = open(file_name, 'rb')
1115
  try:
1116
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
1117
    fdst = os.fdopen(fd, 'wb')
1118
    try:
1119
      shutil.copyfileobj(fsrc, fdst)
1120
    finally:
1121
      fdst.close()
1122
  finally:
1123
    fsrc.close()
1124

    
1125
  return backup_name
1126

    
1127

    
1128
def ShellQuote(value):
1129
  """Quotes shell argument according to POSIX.
1130

1131
  @type value: str
1132
  @param value: the argument to be quoted
1133
  @rtype: str
1134
  @return: the quoted value
1135

1136
  """
1137
  if _re_shell_unquoted.match(value):
1138
    return value
1139
  else:
1140
    return "'%s'" % value.replace("'", "'\\''")
1141

    
1142

    
1143
def ShellQuoteArgs(args):
1144
  """Quotes a list of shell arguments.
1145

1146
  @type args: list
1147
  @param args: list of arguments to be quoted
1148
  @rtype: str
1149
  @return: the quoted arguments concatenated with spaces
1150

1151
  """
1152
  return ' '.join([ShellQuote(i) for i in args])
1153

    
1154

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

1158
  Check if the given IP is reachable by doing attempting a TCP connect
1159
  to it.
1160

1161
  @type target: str
1162
  @param target: the IP or hostname to ping
1163
  @type port: int
1164
  @param port: the port to connect to
1165
  @type timeout: int
1166
  @param timeout: the timeout on the connection attempt
1167
  @type live_port_needed: boolean
1168
  @param live_port_needed: whether a closed port will cause the
1169
      function to return failure, as if there was a timeout
1170
  @type source: str or None
1171
  @param source: if specified, will cause the connect to be made
1172
      from this specific source address; failures to bind other
1173
      than C{EADDRNOTAVAIL} will be ignored
1174

1175
  """
1176
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1177

    
1178
  success = False
1179

    
1180
  if source is not None:
1181
    try:
1182
      sock.bind((source, 0))
1183
    except socket.error, (errcode, _):
1184
      if errcode == errno.EADDRNOTAVAIL:
1185
        success = False
1186

    
1187
  sock.settimeout(timeout)
1188

    
1189
  try:
1190
    sock.connect((target, port))
1191
    sock.close()
1192
    success = True
1193
  except socket.timeout:
1194
    success = False
1195
  except socket.error, (errcode, _):
1196
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
1197

    
1198
  return success
1199

    
1200

    
1201
def OwnIpAddress(address):
1202
  """Check if the current host has the the given IP address.
1203

1204
  Currently this is done by TCP-pinging the address from the loopback
1205
  address.
1206

1207
  @type address: string
1208
  @param address: the address to check
1209
  @rtype: bool
1210
  @return: True if we own the address
1211

1212
  """
1213
  return TcpPing(address, constants.DEFAULT_NODED_PORT,
1214
                 source=constants.LOCALHOST_IP_ADDRESS)
1215

    
1216

    
1217
def ListVisibleFiles(path):
1218
  """Returns a list of visible files in a directory.
1219

1220
  @type path: str
1221
  @param path: the directory to enumerate
1222
  @rtype: list
1223
  @return: the list of all files not starting with a dot
1224

1225
  """
1226
  files = [i for i in os.listdir(path) if not i.startswith(".")]
1227
  files.sort()
1228
  return files
1229

    
1230

    
1231
def GetHomeDir(user, default=None):
1232
  """Try to get the homedir of the given user.
1233

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

1238
  """
1239
  try:
1240
    if isinstance(user, basestring):
1241
      result = pwd.getpwnam(user)
1242
    elif isinstance(user, (int, long)):
1243
      result = pwd.getpwuid(user)
1244
    else:
1245
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1246
                                   type(user))
1247
  except KeyError:
1248
    return default
1249
  return result.pw_dir
1250

    
1251

    
1252
def NewUUID():
1253
  """Returns a random UUID.
1254

1255
  @note: This is a Linux-specific method as it uses the /proc
1256
      filesystem.
1257
  @rtype: str
1258

1259
  """
1260
  return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
1261

    
1262

    
1263
def GenerateSecret(numbytes=20):
1264
  """Generates a random secret.
1265

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

1269
  @param numbytes: the number of bytes which will be represented by the returned
1270
      string (defaulting to 20, the length of a SHA1 hash)
1271
  @rtype: str
1272
  @return: an hex representation of the pseudo-random sequence
1273

1274
  """
1275
  return os.urandom(numbytes).encode('hex')
1276

    
1277

    
1278
def EnsureDirs(dirs):
1279
  """Make required directories, if they don't exist.
1280

1281
  @param dirs: list of tuples (dir_name, dir_mode)
1282
  @type dirs: list of (string, integer)
1283

1284
  """
1285
  for dir_name, dir_mode in dirs:
1286
    try:
1287
      os.mkdir(dir_name, dir_mode)
1288
    except EnvironmentError, err:
1289
      if err.errno != errno.EEXIST:
1290
        raise errors.GenericError("Cannot create needed directory"
1291
                                  " '%s': %s" % (dir_name, err))
1292
    if not os.path.isdir(dir_name):
1293
      raise errors.GenericError("%s is not a directory" % dir_name)
1294

    
1295

    
1296
def ReadFile(file_name, size=-1):
1297
  """Reads a file.
1298

1299
  @type size: int
1300
  @param size: Read at most size bytes (if negative, entire file)
1301
  @rtype: str
1302
  @return: the (possibly partial) content of the file
1303

1304
  """
1305
  f = open(file_name, "r")
1306
  try:
1307
    return f.read(size)
1308
  finally:
1309
    f.close()
1310

    
1311

    
1312
def WriteFile(file_name, fn=None, data=None,
1313
              mode=None, uid=-1, gid=-1,
1314
              atime=None, mtime=None, close=True,
1315
              dry_run=False, backup=False,
1316
              prewrite=None, postwrite=None):
1317
  """(Over)write a file atomically.
1318

1319
  The file_name and either fn (a function taking one argument, the
1320
  file descriptor, and which should write the data to it) or data (the
1321
  contents of the file) must be passed. The other arguments are
1322
  optional and allow setting the file mode, owner and group, and the
1323
  mtime/atime of the file.
1324

1325
  If the function doesn't raise an exception, it has succeeded and the
1326
  target file has the new contents. If the function has raised an
1327
  exception, an existing target file should be unmodified and the
1328
  temporary file should be removed.
1329

1330
  @type file_name: str
1331
  @param file_name: the target filename
1332
  @type fn: callable
1333
  @param fn: content writing function, called with
1334
      file descriptor as parameter
1335
  @type data: str
1336
  @param data: contents of the file
1337
  @type mode: int
1338
  @param mode: file mode
1339
  @type uid: int
1340
  @param uid: the owner of the file
1341
  @type gid: int
1342
  @param gid: the group of the file
1343
  @type atime: int
1344
  @param atime: a custom access time to be set on the file
1345
  @type mtime: int
1346
  @param mtime: a custom modification time to be set on the file
1347
  @type close: boolean
1348
  @param close: whether to close file after writing it
1349
  @type prewrite: callable
1350
  @param prewrite: function to be called before writing content
1351
  @type postwrite: callable
1352
  @param postwrite: function to be called after writing content
1353

1354
  @rtype: None or int
1355
  @return: None if the 'close' parameter evaluates to True,
1356
      otherwise the file descriptor
1357

1358
  @raise errors.ProgrammerError: if any of the arguments are not valid
1359

1360
  """
1361
  if not os.path.isabs(file_name):
1362
    raise errors.ProgrammerError("Path passed to WriteFile is not"
1363
                                 " absolute: '%s'" % file_name)
1364

    
1365
  if [fn, data].count(None) != 1:
1366
    raise errors.ProgrammerError("fn or data required")
1367

    
1368
  if [atime, mtime].count(None) == 1:
1369
    raise errors.ProgrammerError("Both atime and mtime must be either"
1370
                                 " set or None")
1371

    
1372
  if backup and not dry_run and os.path.isfile(file_name):
1373
    CreateBackup(file_name)
1374

    
1375
  dir_name, base_name = os.path.split(file_name)
1376
  fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
1377
  do_remove = True
1378
  # here we need to make sure we remove the temp file, if any error
1379
  # leaves it in place
1380
  try:
1381
    if uid != -1 or gid != -1:
1382
      os.chown(new_name, uid, gid)
1383
    if mode:
1384
      os.chmod(new_name, mode)
1385
    if callable(prewrite):
1386
      prewrite(fd)
1387
    if data is not None:
1388
      os.write(fd, data)
1389
    else:
1390
      fn(fd)
1391
    if callable(postwrite):
1392
      postwrite(fd)
1393
    os.fsync(fd)
1394
    if atime is not None and mtime is not None:
1395
      os.utime(new_name, (atime, mtime))
1396
    if not dry_run:
1397
      os.rename(new_name, file_name)
1398
      do_remove = False
1399
  finally:
1400
    if close:
1401
      os.close(fd)
1402
      result = None
1403
    else:
1404
      result = fd
1405
    if do_remove:
1406
      RemoveFile(new_name)
1407

    
1408
  return result
1409

    
1410

    
1411
def FirstFree(seq, base=0):
1412
  """Returns the first non-existing integer from seq.
1413

1414
  The seq argument should be a sorted list of positive integers. The
1415
  first time the index of an element is smaller than the element
1416
  value, the index will be returned.
1417

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

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

1423
  @type seq: sequence
1424
  @param seq: the sequence to be analyzed.
1425
  @type base: int
1426
  @param base: use this value as the base index of the sequence
1427
  @rtype: int
1428
  @return: the first non-used index in the sequence
1429

1430
  """
1431
  for idx, elem in enumerate(seq):
1432
    assert elem >= base, "Passed element is higher than base offset"
1433
    if elem > idx + base:
1434
      # idx is not used
1435
      return idx + base
1436
  return None
1437

    
1438

    
1439
def all(seq, pred=bool): # pylint: disable-msg=W0622
1440
  "Returns True if pred(x) is True for every element in the iterable"
1441
  for _ in itertools.ifilterfalse(pred, seq):
1442
    return False
1443
  return True
1444

    
1445

    
1446
def any(seq, pred=bool): # pylint: disable-msg=W0622
1447
  "Returns True if pred(x) is True for at least one element in the iterable"
1448
  for _ in itertools.ifilter(pred, seq):
1449
    return True
1450
  return False
1451

    
1452

    
1453
def UniqueSequence(seq):
1454
  """Returns a list with unique elements.
1455

1456
  Element order is preserved.
1457

1458
  @type seq: sequence
1459
  @param seq: the sequence with the source elements
1460
  @rtype: list
1461
  @return: list of unique elements from seq
1462

1463
  """
1464
  seen = set()
1465
  return [i for i in seq if i not in seen and not seen.add(i)]
1466

    
1467

    
1468
def NormalizeAndValidateMac(mac):
1469
  """Normalizes and check if a MAC address is valid.
1470

1471
  Checks whether the supplied MAC address is formally correct, only
1472
  accepts colon separated format. Normalize it to all lower.
1473

1474
  @type mac: str
1475
  @param mac: the MAC to be validated
1476
  @rtype: str
1477
  @return: returns the normalized and validated MAC.
1478

1479
  @raise errors.OpPrereqError: If the MAC isn't valid
1480

1481
  """
1482
  mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$", re.I)
1483
  if not mac_check.match(mac):
1484
    raise errors.OpPrereqError("Invalid MAC address specified: %s" %
1485
                               mac, errors.ECODE_INVAL)
1486

    
1487
  return mac.lower()
1488

    
1489

    
1490
def TestDelay(duration):
1491
  """Sleep for a fixed amount of time.
1492

1493
  @type duration: float
1494
  @param duration: the sleep duration
1495
  @rtype: boolean
1496
  @return: False for negative value, True otherwise
1497

1498
  """
1499
  if duration < 0:
1500
    return False, "Invalid sleep duration"
1501
  time.sleep(duration)
1502
  return True, None
1503

    
1504

    
1505
def _CloseFDNoErr(fd, retries=5):
1506
  """Close a file descriptor ignoring errors.
1507

1508
  @type fd: int
1509
  @param fd: the file descriptor
1510
  @type retries: int
1511
  @param retries: how many retries to make, in case we get any
1512
      other error than EBADF
1513

1514
  """
1515
  try:
1516
    os.close(fd)
1517
  except OSError, err:
1518
    if err.errno != errno.EBADF:
1519
      if retries > 0:
1520
        _CloseFDNoErr(fd, retries - 1)
1521
    # else either it's closed already or we're out of retries, so we
1522
    # ignore this and go on
1523

    
1524

    
1525
def CloseFDs(noclose_fds=None):
1526
  """Close file descriptors.
1527

1528
  This closes all file descriptors above 2 (i.e. except
1529
  stdin/out/err).
1530

1531
  @type noclose_fds: list or None
1532
  @param noclose_fds: if given, it denotes a list of file descriptor
1533
      that should not be closed
1534

1535
  """
1536
  # Default maximum for the number of available file descriptors.
1537
  if 'SC_OPEN_MAX' in os.sysconf_names:
1538
    try:
1539
      MAXFD = os.sysconf('SC_OPEN_MAX')
1540
      if MAXFD < 0:
1541
        MAXFD = 1024
1542
    except OSError:
1543
      MAXFD = 1024
1544
  else:
1545
    MAXFD = 1024
1546
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1547
  if (maxfd == resource.RLIM_INFINITY):
1548
    maxfd = MAXFD
1549

    
1550
  # Iterate through and close all file descriptors (except the standard ones)
1551
  for fd in range(3, maxfd):
1552
    if noclose_fds and fd in noclose_fds:
1553
      continue
1554
    _CloseFDNoErr(fd)
1555

    
1556

    
1557
def Daemonize(logfile):
1558
  """Daemonize the current process.
1559

1560
  This detaches the current process from the controlling terminal and
1561
  runs it in the background as a daemon.
1562

1563
  @type logfile: str
1564
  @param logfile: the logfile to which we should redirect stdout/stderr
1565
  @rtype: int
1566
  @return: the value zero
1567

1568
  """
1569
  # pylint: disable-msg=W0212
1570
  # yes, we really want os._exit
1571
  UMASK = 077
1572
  WORKDIR = "/"
1573

    
1574
  # this might fail
1575
  pid = os.fork()
1576
  if (pid == 0):  # The first child.
1577
    os.setsid()
1578
    # this might fail
1579
    pid = os.fork() # Fork a second child.
1580
    if (pid == 0):  # The second child.
1581
      os.chdir(WORKDIR)
1582
      os.umask(UMASK)
1583
    else:
1584
      # exit() or _exit()?  See below.
1585
      os._exit(0) # Exit parent (the first child) of the second child.
1586
  else:
1587
    os._exit(0) # Exit parent of the first child.
1588

    
1589
  for fd in range(3):
1590
    _CloseFDNoErr(fd)
1591
  i = os.open("/dev/null", os.O_RDONLY) # stdin
1592
  assert i == 0, "Can't close/reopen stdin"
1593
  i = os.open(logfile, os.O_WRONLY|os.O_CREAT|os.O_APPEND, 0600) # stdout
1594
  assert i == 1, "Can't close/reopen stdout"
1595
  # Duplicate standard output to standard error.
1596
  os.dup2(1, 2)
1597
  return 0
1598

    
1599

    
1600
def DaemonPidFileName(name):
1601
  """Compute a ganeti pid file absolute path
1602

1603
  @type name: str
1604
  @param name: the daemon name
1605
  @rtype: str
1606
  @return: the full path to the pidfile corresponding to the given
1607
      daemon name
1608

1609
  """
1610
  return PathJoin(constants.RUN_GANETI_DIR, "%s.pid" % name)
1611

    
1612

    
1613
def EnsureDaemon(name):
1614
  """Check for and start daemon if not alive.
1615

1616
  """
1617
  result = RunCmd([constants.DAEMON_UTIL, "check-and-start", name])
1618
  if result.failed:
1619
    logging.error("Can't start daemon '%s', failure %s, output: %s",
1620
                  name, result.fail_reason, result.output)
1621
    return False
1622

    
1623
  return True
1624

    
1625

    
1626
def WritePidFile(name):
1627
  """Write the current process pidfile.
1628

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

1631
  @type name: str
1632
  @param name: the daemon name to use
1633
  @raise errors.GenericError: if the pid file already exists and
1634
      points to a live process
1635

1636
  """
1637
  pid = os.getpid()
1638
  pidfilename = DaemonPidFileName(name)
1639
  if IsProcessAlive(ReadPidFile(pidfilename)):
1640
    raise errors.GenericError("%s contains a live process" % pidfilename)
1641

    
1642
  WriteFile(pidfilename, data="%d\n" % pid)
1643

    
1644

    
1645
def RemovePidFile(name):
1646
  """Remove the current process pidfile.
1647

1648
  Any errors are ignored.
1649

1650
  @type name: str
1651
  @param name: the daemon name used to derive the pidfile name
1652

1653
  """
1654
  pidfilename = DaemonPidFileName(name)
1655
  # TODO: we could check here that the file contains our pid
1656
  try:
1657
    RemoveFile(pidfilename)
1658
  except: # pylint: disable-msg=W0702
1659
    pass
1660

    
1661

    
1662
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1663
                waitpid=False):
1664
  """Kill a process given by its pid.
1665

1666
  @type pid: int
1667
  @param pid: The PID to terminate.
1668
  @type signal_: int
1669
  @param signal_: The signal to send, by default SIGTERM
1670
  @type timeout: int
1671
  @param timeout: The timeout after which, if the process is still alive,
1672
                  a SIGKILL will be sent. If not positive, no such checking
1673
                  will be done
1674
  @type waitpid: boolean
1675
  @param waitpid: If true, we should waitpid on this process after
1676
      sending signals, since it's our own child and otherwise it
1677
      would remain as zombie
1678

1679
  """
1680
  def _helper(pid, signal_, wait):
1681
    """Simple helper to encapsulate the kill/waitpid sequence"""
1682
    os.kill(pid, signal_)
1683
    if wait:
1684
      try:
1685
        os.waitpid(pid, os.WNOHANG)
1686
      except OSError:
1687
        pass
1688

    
1689
  if pid <= 0:
1690
    # kill with pid=0 == suicide
1691
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1692

    
1693
  if not IsProcessAlive(pid):
1694
    return
1695

    
1696
  _helper(pid, signal_, waitpid)
1697

    
1698
  if timeout <= 0:
1699
    return
1700

    
1701
  def _CheckProcess():
1702
    if not IsProcessAlive(pid):
1703
      return
1704

    
1705
    try:
1706
      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
1707
    except OSError:
1708
      raise RetryAgain()
1709

    
1710
    if result_pid > 0:
1711
      return
1712

    
1713
    raise RetryAgain()
1714

    
1715
  try:
1716
    # Wait up to $timeout seconds
1717
    Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
1718
  except RetryTimeout:
1719
    pass
1720

    
1721
  if IsProcessAlive(pid):
1722
    # Kill process if it's still alive
1723
    _helper(pid, signal.SIGKILL, waitpid)
1724

    
1725

    
1726
def FindFile(name, search_path, test=os.path.exists):
1727
  """Look for a filesystem object in a given path.
1728

1729
  This is an abstract method to search for filesystem object (files,
1730
  dirs) under a given search path.
1731

1732
  @type name: str
1733
  @param name: the name to look for
1734
  @type search_path: str
1735
  @param search_path: location to start at
1736
  @type test: callable
1737
  @param test: a function taking one argument that should return True
1738
      if the a given object is valid; the default value is
1739
      os.path.exists, causing only existing files to be returned
1740
  @rtype: str or None
1741
  @return: full path to the object if found, None otherwise
1742

1743
  """
1744
  # validate the filename mask
1745
  if constants.EXT_PLUGIN_MASK.match(name) is None:
1746
    logging.critical("Invalid value passed for external script name: '%s'",
1747
                     name)
1748
    return None
1749

    
1750
  for dir_name in search_path:
1751
    # FIXME: investigate switch to PathJoin
1752
    item_name = os.path.sep.join([dir_name, name])
1753
    # check the user test and that we're indeed resolving to the given
1754
    # basename
1755
    if test(item_name) and os.path.basename(item_name) == name:
1756
      return item_name
1757
  return None
1758

    
1759

    
1760
def CheckVolumeGroupSize(vglist, vgname, minsize):
1761
  """Checks if the volume group list is valid.
1762

1763
  The function will check if a given volume group is in the list of
1764
  volume groups and has a minimum size.
1765

1766
  @type vglist: dict
1767
  @param vglist: dictionary of volume group names and their size
1768
  @type vgname: str
1769
  @param vgname: the volume group we should check
1770
  @type minsize: int
1771
  @param minsize: the minimum size we accept
1772
  @rtype: None or str
1773
  @return: None for success, otherwise the error message
1774

1775
  """
1776
  vgsize = vglist.get(vgname, None)
1777
  if vgsize is None:
1778
    return "volume group '%s' missing" % vgname
1779
  elif vgsize < minsize:
1780
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1781
            (vgname, minsize, vgsize))
1782
  return None
1783

    
1784

    
1785
def SplitTime(value):
1786
  """Splits time as floating point number into a tuple.
1787

1788
  @param value: Time in seconds
1789
  @type value: int or float
1790
  @return: Tuple containing (seconds, microseconds)
1791

1792
  """
1793
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1794

    
1795
  assert 0 <= seconds, \
1796
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1797
  assert 0 <= microseconds <= 999999, \
1798
    "Microseconds must be 0-999999, but are %s" % microseconds
1799

    
1800
  return (int(seconds), int(microseconds))
1801

    
1802

    
1803
def MergeTime(timetuple):
1804
  """Merges a tuple into time as a floating point number.
1805

1806
  @param timetuple: Time as tuple, (seconds, microseconds)
1807
  @type timetuple: tuple
1808
  @return: Time as a floating point number expressed in seconds
1809

1810
  """
1811
  (seconds, microseconds) = timetuple
1812

    
1813
  assert 0 <= seconds, \
1814
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1815
  assert 0 <= microseconds <= 999999, \
1816
    "Microseconds must be 0-999999, but are %s" % microseconds
1817

    
1818
  return float(seconds) + (float(microseconds) * 0.000001)
1819

    
1820

    
1821
def GetDaemonPort(daemon_name):
1822
  """Get the daemon port for this cluster.
1823

1824
  Note that this routine does not read a ganeti-specific file, but
1825
  instead uses C{socket.getservbyname} to allow pre-customization of
1826
  this parameter outside of Ganeti.
1827

1828
  @type daemon_name: string
1829
  @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
1830
  @rtype: int
1831

1832
  """
1833
  if daemon_name not in constants.DAEMONS_PORTS:
1834
    raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
1835

    
1836
  (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
1837
  try:
1838
    port = socket.getservbyname(daemon_name, proto)
1839
  except socket.error:
1840
    port = default_port
1841

    
1842
  return port
1843

    
1844

    
1845
def SetupLogging(logfile, debug=0, stderr_logging=False, program="",
1846
                 multithreaded=False, syslog=constants.SYSLOG_USAGE):
1847
  """Configures the logging module.
1848

1849
  @type logfile: str
1850
  @param logfile: the filename to which we should log
1851
  @type debug: integer
1852
  @param debug: if greater than zero, enable debug messages, otherwise
1853
      only those at C{INFO} and above level
1854
  @type stderr_logging: boolean
1855
  @param stderr_logging: whether we should also log to the standard error
1856
  @type program: str
1857
  @param program: the name under which we should log messages
1858
  @type multithreaded: boolean
1859
  @param multithreaded: if True, will add the thread name to the log file
1860
  @type syslog: string
1861
  @param syslog: one of 'no', 'yes', 'only':
1862
      - if no, syslog is not used
1863
      - if yes, syslog is used (in addition to file-logging)
1864
      - if only, only syslog is used
1865
  @raise EnvironmentError: if we can't open the log file and
1866
      syslog/stderr logging is disabled
1867

1868
  """
1869
  fmt = "%(asctime)s: " + program + " pid=%(process)d"
1870
  sft = program + "[%(process)d]:"
1871
  if multithreaded:
1872
    fmt += "/%(threadName)s"
1873
    sft += " (%(threadName)s)"
1874
  if debug:
1875
    fmt += " %(module)s:%(lineno)s"
1876
    # no debug info for syslog loggers
1877
  fmt += " %(levelname)s %(message)s"
1878
  # yes, we do want the textual level, as remote syslog will probably
1879
  # lose the error level, and it's easier to grep for it
1880
  sft += " %(levelname)s %(message)s"
1881
  formatter = logging.Formatter(fmt)
1882
  sys_fmt = logging.Formatter(sft)
1883

    
1884
  root_logger = logging.getLogger("")
1885
  root_logger.setLevel(logging.NOTSET)
1886

    
1887
  # Remove all previously setup handlers
1888
  for handler in root_logger.handlers:
1889
    handler.close()
1890
    root_logger.removeHandler(handler)
1891

    
1892
  if stderr_logging:
1893
    stderr_handler = logging.StreamHandler()
1894
    stderr_handler.setFormatter(formatter)
1895
    if debug:
1896
      stderr_handler.setLevel(logging.NOTSET)
1897
    else:
1898
      stderr_handler.setLevel(logging.CRITICAL)
1899
    root_logger.addHandler(stderr_handler)
1900

    
1901
  if syslog in (constants.SYSLOG_YES, constants.SYSLOG_ONLY):
1902
    facility = logging.handlers.SysLogHandler.LOG_DAEMON
1903
    syslog_handler = logging.handlers.SysLogHandler(constants.SYSLOG_SOCKET,
1904
                                                    facility)
1905
    syslog_handler.setFormatter(sys_fmt)
1906
    # Never enable debug over syslog
1907
    syslog_handler.setLevel(logging.INFO)
1908
    root_logger.addHandler(syslog_handler)
1909

    
1910
  if syslog != constants.SYSLOG_ONLY:
1911
    # this can fail, if the logging directories are not setup or we have
1912
    # a permisssion problem; in this case, it's best to log but ignore
1913
    # the error if stderr_logging is True, and if false we re-raise the
1914
    # exception since otherwise we could run but without any logs at all
1915
    try:
1916
      logfile_handler = logging.FileHandler(logfile)
1917
      logfile_handler.setFormatter(formatter)
1918
      if debug:
1919
        logfile_handler.setLevel(logging.DEBUG)
1920
      else:
1921
        logfile_handler.setLevel(logging.INFO)
1922
      root_logger.addHandler(logfile_handler)
1923
    except EnvironmentError:
1924
      if stderr_logging or syslog == constants.SYSLOG_YES:
1925
        logging.exception("Failed to enable logging to file '%s'", logfile)
1926
      else:
1927
        # we need to re-raise the exception
1928
        raise
1929

    
1930

    
1931
def IsNormAbsPath(path):
1932
  """Check whether a path is absolute and also normalized
1933

1934
  This avoids things like /dir/../../other/path to be valid.
1935

1936
  """
1937
  return os.path.normpath(path) == path and os.path.isabs(path)
1938

    
1939

    
1940
def PathJoin(*args):
1941
  """Safe-join a list of path components.
1942

1943
  Requirements:
1944
      - the first argument must be an absolute path
1945
      - no component in the path must have backtracking (e.g. /../),
1946
        since we check for normalization at the end
1947

1948
  @param args: the path components to be joined
1949
  @raise ValueError: for invalid paths
1950

1951
  """
1952
  # ensure we're having at least one path passed in
1953
  assert args
1954
  # ensure the first component is an absolute and normalized path name
1955
  root = args[0]
1956
  if not IsNormAbsPath(root):
1957
    raise ValueError("Invalid parameter to PathJoin: '%s'" % str(args[0]))
1958
  result = os.path.join(*args)
1959
  # ensure that the whole path is normalized
1960
  if not IsNormAbsPath(result):
1961
    raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
1962
  # check that we're still under the original prefix
1963
  prefix = os.path.commonprefix([root, result])
1964
  if prefix != root:
1965
    raise ValueError("Error: path joining resulted in different prefix"
1966
                     " (%s != %s)" % (prefix, root))
1967
  return result
1968

    
1969

    
1970
def TailFile(fname, lines=20):
1971
  """Return the last lines from a file.
1972

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

1977
  @param fname: the file name
1978
  @type lines: int
1979
  @param lines: the (maximum) number of lines to return
1980

1981
  """
1982
  fd = open(fname, "r")
1983
  try:
1984
    fd.seek(0, 2)
1985
    pos = fd.tell()
1986
    pos = max(0, pos-4096)
1987
    fd.seek(pos, 0)
1988
    raw_data = fd.read()
1989
  finally:
1990
    fd.close()
1991

    
1992
  rows = raw_data.splitlines()
1993
  return rows[-lines:]
1994

    
1995

    
1996
def SafeEncode(text):
1997
  """Return a 'safe' version of a source string.
1998

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

2008
  @type text: str or unicode
2009
  @param text: input data
2010
  @rtype: str
2011
  @return: a safe version of text
2012

2013
  """
2014
  if isinstance(text, unicode):
2015
    # only if unicode; if str already, we handle it below
2016
    text = text.encode('ascii', 'backslashreplace')
2017
  resu = ""
2018
  for char in text:
2019
    c = ord(char)
2020
    if char  == '\t':
2021
      resu += r'\t'
2022
    elif char == '\n':
2023
      resu += r'\n'
2024
    elif char == '\r':
2025
      resu += r'\'r'
2026
    elif c < 32 or c >= 127: # non-printable
2027
      resu += "\\x%02x" % (c & 0xff)
2028
    else:
2029
      resu += char
2030
  return resu
2031

    
2032

    
2033
def UnescapeAndSplit(text, sep=","):
2034
  """Split and unescape a string based on a given separator.
2035

2036
  This function splits a string based on a separator where the
2037
  separator itself can be escape in order to be an element of the
2038
  elements. The escaping rules are (assuming coma being the
2039
  separator):
2040
    - a plain , separates the elements
2041
    - a sequence \\\\, (double backslash plus comma) is handled as a
2042
      backslash plus a separator comma
2043
    - a sequence \, (backslash plus comma) is handled as a
2044
      non-separator comma
2045

2046
  @type text: string
2047
  @param text: the string to split
2048
  @type sep: string
2049
  @param text: the separator
2050
  @rtype: string
2051
  @return: a list of strings
2052

2053
  """
2054
  # we split the list by sep (with no escaping at this stage)
2055
  slist = text.split(sep)
2056
  # next, we revisit the elements and if any of them ended with an odd
2057
  # number of backslashes, then we join it with the next
2058
  rlist = []
2059
  while slist:
2060
    e1 = slist.pop(0)
2061
    if e1.endswith("\\"):
2062
      num_b = len(e1) - len(e1.rstrip("\\"))
2063
      if num_b % 2 == 1:
2064
        e2 = slist.pop(0)
2065
        # here the backslashes remain (all), and will be reduced in
2066
        # the next step
2067
        rlist.append(e1 + sep + e2)
2068
        continue
2069
    rlist.append(e1)
2070
  # finally, replace backslash-something with something
2071
  rlist = [re.sub(r"\\(.)", r"\1", v) for v in rlist]
2072
  return rlist
2073

    
2074

    
2075
def CommaJoin(names):
2076
  """Nicely join a set of identifiers.
2077

2078
  @param names: set, list or tuple
2079
  @return: a string with the formatted results
2080

2081
  """
2082
  return ", ".join([str(val) for val in names])
2083

    
2084

    
2085
def BytesToMebibyte(value):
2086
  """Converts bytes to mebibytes.
2087

2088
  @type value: int
2089
  @param value: Value in bytes
2090
  @rtype: int
2091
  @return: Value in mebibytes
2092

2093
  """
2094
  return int(round(value / (1024.0 * 1024.0), 0))
2095

    
2096

    
2097
def CalculateDirectorySize(path):
2098
  """Calculates the size of a directory recursively.
2099

2100
  @type path: string
2101
  @param path: Path to directory
2102
  @rtype: int
2103
  @return: Size in mebibytes
2104

2105
  """
2106
  size = 0
2107

    
2108
  for (curpath, _, files) in os.walk(path):
2109
    for filename in files:
2110
      st = os.lstat(PathJoin(curpath, filename))
2111
      size += st.st_size
2112

    
2113
  return BytesToMebibyte(size)
2114

    
2115

    
2116
def GetFilesystemStats(path):
2117
  """Returns the total and free space on a filesystem.
2118

2119
  @type path: string
2120
  @param path: Path on filesystem to be examined
2121
  @rtype: int
2122
  @return: tuple of (Total space, Free space) in mebibytes
2123

2124
  """
2125
  st = os.statvfs(path)
2126

    
2127
  fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
2128
  tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
2129
  return (tsize, fsize)
2130

    
2131

    
2132
def RunInSeparateProcess(fn):
2133
  """Runs a function in a separate process.
2134

2135
  Note: Only boolean return values are supported.
2136

2137
  @type fn: callable
2138
  @param fn: Function to be called
2139
  @rtype: tuple of (int/None, int/None)
2140
  @return: Exit code and signal number
2141

2142
  """
2143
  pid = os.fork()
2144
  if pid == 0:
2145
    # Child process
2146
    try:
2147
      # In case the function uses temporary files
2148
      ResetTempfileModule()
2149

    
2150
      # Call function
2151
      result = int(bool(fn()))
2152
      assert result in (0, 1)
2153
    except: # pylint: disable-msg=W0702
2154
      logging.exception("Error while calling function in separate process")
2155
      # 0 and 1 are reserved for the return value
2156
      result = 33
2157

    
2158
    os._exit(result) # pylint: disable-msg=W0212
2159

    
2160
  # Parent process
2161

    
2162
  # Avoid zombies and check exit code
2163
  (_, status) = os.waitpid(pid, 0)
2164

    
2165
  if os.WIFSIGNALED(status):
2166
    exitcode = None
2167
    signum = os.WTERMSIG(status)
2168
  else:
2169
    exitcode = os.WEXITSTATUS(status)
2170
    signum = None
2171

    
2172
  if not (exitcode in (0, 1) and signum is None):
2173
    raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
2174
                              (exitcode, signum))
2175

    
2176
  return bool(exitcode)
2177

    
2178

    
2179
def LockedMethod(fn):
2180
  """Synchronized object access decorator.
2181

2182
  This decorator is intended to protect access to an object using the
2183
  object's own lock which is hardcoded to '_lock'.
2184

2185
  """
2186
  def _LockDebug(*args, **kwargs):
2187
    if debug_locks:
2188
      logging.debug(*args, **kwargs)
2189

    
2190
  def wrapper(self, *args, **kwargs):
2191
    # pylint: disable-msg=W0212
2192
    assert hasattr(self, '_lock')
2193
    lock = self._lock
2194
    _LockDebug("Waiting for %s", lock)
2195
    lock.acquire()
2196
    try:
2197
      _LockDebug("Acquired %s", lock)
2198
      result = fn(self, *args, **kwargs)
2199
    finally:
2200
      _LockDebug("Releasing %s", lock)
2201
      lock.release()
2202
      _LockDebug("Released %s", lock)
2203
    return result
2204
  return wrapper
2205

    
2206

    
2207
def LockFile(fd):
2208
  """Locks a file using POSIX locks.
2209

2210
  @type fd: int
2211
  @param fd: the file descriptor we need to lock
2212

2213
  """
2214
  try:
2215
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
2216
  except IOError, err:
2217
    if err.errno == errno.EAGAIN:
2218
      raise errors.LockError("File already locked")
2219
    raise
2220

    
2221

    
2222
def FormatTime(val):
2223
  """Formats a time value.
2224

2225
  @type val: float or None
2226
  @param val: the timestamp as returned by time.time()
2227
  @return: a string value or N/A if we don't have a valid timestamp
2228

2229
  """
2230
  if val is None or not isinstance(val, (int, float)):
2231
    return "N/A"
2232
  # these two codes works on Linux, but they are not guaranteed on all
2233
  # platforms
2234
  return time.strftime("%F %T", time.localtime(val))
2235

    
2236

    
2237
def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
2238
  """Reads the watcher pause file.
2239

2240
  @type filename: string
2241
  @param filename: Path to watcher pause file
2242
  @type now: None, float or int
2243
  @param now: Current time as Unix timestamp
2244
  @type remove_after: int
2245
  @param remove_after: Remove watcher pause file after specified amount of
2246
    seconds past the pause end time
2247

2248
  """
2249
  if now is None:
2250
    now = time.time()
2251

    
2252
  try:
2253
    value = ReadFile(filename)
2254
  except IOError, err:
2255
    if err.errno != errno.ENOENT:
2256
      raise
2257
    value = None
2258

    
2259
  if value is not None:
2260
    try:
2261
      value = int(value)
2262
    except ValueError:
2263
      logging.warning(("Watcher pause file (%s) contains invalid value,"
2264
                       " removing it"), filename)
2265
      RemoveFile(filename)
2266
      value = None
2267

    
2268
    if value is not None:
2269
      # Remove file if it's outdated
2270
      if now > (value + remove_after):
2271
        RemoveFile(filename)
2272
        value = None
2273

    
2274
      elif now > value:
2275
        value = None
2276

    
2277
  return value
2278

    
2279

    
2280
class RetryTimeout(Exception):
2281
  """Retry loop timed out.
2282

2283
  """
2284

    
2285

    
2286
class RetryAgain(Exception):
2287
  """Retry again.
2288

2289
  """
2290

    
2291

    
2292
class _RetryDelayCalculator(object):
2293
  """Calculator for increasing delays.
2294

2295
  """
2296
  __slots__ = [
2297
    "_factor",
2298
    "_limit",
2299
    "_next",
2300
    "_start",
2301
    ]
2302

    
2303
  def __init__(self, start, factor, limit):
2304
    """Initializes this class.
2305

2306
    @type start: float
2307
    @param start: Initial delay
2308
    @type factor: float
2309
    @param factor: Factor for delay increase
2310
    @type limit: float or None
2311
    @param limit: Upper limit for delay or None for no limit
2312

2313
    """
2314
    assert start > 0.0
2315
    assert factor >= 1.0
2316
    assert limit is None or limit >= 0.0
2317

    
2318
    self._start = start
2319
    self._factor = factor
2320
    self._limit = limit
2321

    
2322
    self._next = start
2323

    
2324
  def __call__(self):
2325
    """Returns current delay and calculates the next one.
2326

2327
    """
2328
    current = self._next
2329

    
2330
    # Update for next run
2331
    if self._limit is None or self._next < self._limit:
2332
      self._next = min(self._limit, self._next * self._factor)
2333

    
2334
    return current
2335

    
2336

    
2337
#: Special delay to specify whole remaining timeout
2338
RETRY_REMAINING_TIME = object()
2339

    
2340

    
2341
def Retry(fn, delay, timeout, args=None, wait_fn=time.sleep,
2342
          _time_fn=time.time):
2343
  """Call a function repeatedly until it succeeds.
2344

2345
  The function C{fn} is called repeatedly until it doesn't throw L{RetryAgain}
2346
  anymore. Between calls a delay, specified by C{delay}, is inserted. After a
2347
  total of C{timeout} seconds, this function throws L{RetryTimeout}.
2348

2349
  C{delay} can be one of the following:
2350
    - callable returning the delay length as a float
2351
    - Tuple of (start, factor, limit)
2352
    - L{RETRY_REMAINING_TIME} to sleep until the timeout expires (this is
2353
      useful when overriding L{wait_fn} to wait for an external event)
2354
    - A static delay as a number (int or float)
2355

2356
  @type fn: callable
2357
  @param fn: Function to be called
2358
  @param delay: Either a callable (returning the delay), a tuple of (start,
2359
                factor, limit) (see L{_RetryDelayCalculator}),
2360
                L{RETRY_REMAINING_TIME} or a number (int or float)
2361
  @type timeout: float
2362
  @param timeout: Total timeout
2363
  @type wait_fn: callable
2364
  @param wait_fn: Waiting function
2365
  @return: Return value of function
2366

2367
  """
2368
  assert callable(fn)
2369
  assert callable(wait_fn)
2370
  assert callable(_time_fn)
2371

    
2372
  if args is None:
2373
    args = []
2374

    
2375
  end_time = _time_fn() + timeout
2376

    
2377
  if callable(delay):
2378
    # External function to calculate delay
2379
    calc_delay = delay
2380

    
2381
  elif isinstance(delay, (tuple, list)):
2382
    # Increasing delay with optional upper boundary
2383
    (start, factor, limit) = delay
2384
    calc_delay = _RetryDelayCalculator(start, factor, limit)
2385

    
2386
  elif delay is RETRY_REMAINING_TIME:
2387
    # Always use the remaining time
2388
    calc_delay = None
2389

    
2390
  else:
2391
    # Static delay
2392
    calc_delay = lambda: delay
2393

    
2394
  assert calc_delay is None or callable(calc_delay)
2395

    
2396
  while True:
2397
    try:
2398
      # pylint: disable-msg=W0142
2399
      return fn(*args)
2400
    except RetryAgain:
2401
      pass
2402

    
2403
    remaining_time = end_time - _time_fn()
2404

    
2405
    if remaining_time < 0.0:
2406
      raise RetryTimeout()
2407

    
2408
    assert remaining_time >= 0.0
2409

    
2410
    if calc_delay is None:
2411
      wait_fn(remaining_time)
2412
    else:
2413
      current_delay = calc_delay()
2414
      if current_delay > 0.0:
2415
        wait_fn(current_delay)
2416

    
2417

    
2418
class FileLock(object):
2419
  """Utility class for file locks.
2420

2421
  """
2422
  def __init__(self, filename):
2423
    """Constructor for FileLock.
2424

2425
    This will open the file denoted by the I{filename} argument.
2426

2427
    @type filename: str
2428
    @param filename: path to the file to be locked
2429

2430
    """
2431
    self.filename = filename
2432
    self.fd = open(self.filename, "w")
2433

    
2434
  def __del__(self):
2435
    self.Close()
2436

    
2437
  def Close(self):
2438
    """Close the file and release the lock.
2439

2440
    """
2441
    if hasattr(self, "fd") and self.fd:
2442
      self.fd.close()
2443
      self.fd = None
2444

    
2445
  def _flock(self, flag, blocking, timeout, errmsg):
2446
    """Wrapper for fcntl.flock.
2447

2448
    @type flag: int
2449
    @param flag: operation flag
2450
    @type blocking: bool
2451
    @param blocking: whether the operation should be done in blocking mode.
2452
    @type timeout: None or float
2453
    @param timeout: for how long the operation should be retried (implies
2454
                    non-blocking mode).
2455
    @type errmsg: string
2456
    @param errmsg: error message in case operation fails.
2457

2458
    """
2459
    assert self.fd, "Lock was closed"
2460
    assert timeout is None or timeout >= 0, \
2461
      "If specified, timeout must be positive"
2462

    
2463
    if timeout is not None:
2464
      flag |= fcntl.LOCK_NB
2465
      timeout_end = time.time() + timeout
2466

    
2467
    # Blocking doesn't have effect with timeout
2468
    elif not blocking:
2469
      flag |= fcntl.LOCK_NB
2470
      timeout_end = None
2471

    
2472
    # TODO: Convert to utils.Retry
2473

    
2474
    retry = True
2475
    while retry:
2476
      try:
2477
        fcntl.flock(self.fd, flag)
2478
        retry = False
2479
      except IOError, err:
2480
        if err.errno in (errno.EAGAIN, ):
2481
          if timeout_end is not None and time.time() < timeout_end:
2482
            # Wait before trying again
2483
            time.sleep(max(0.1, min(1.0, timeout)))
2484
          else:
2485
            raise errors.LockError(errmsg)
2486
        else:
2487
          logging.exception("fcntl.flock failed")
2488
          raise
2489

    
2490
  def Exclusive(self, blocking=False, timeout=None):
2491
    """Locks the file in exclusive mode.
2492

2493
    @type blocking: boolean
2494
    @param blocking: whether to block and wait until we
2495
        can lock the file or return immediately
2496
    @type timeout: int or None
2497
    @param timeout: if not None, the duration to wait for the lock
2498
        (in blocking mode)
2499

2500
    """
2501
    self._flock(fcntl.LOCK_EX, blocking, timeout,
2502
                "Failed to lock %s in exclusive mode" % self.filename)
2503

    
2504
  def Shared(self, blocking=False, timeout=None):
2505
    """Locks the file in shared mode.
2506

2507
    @type blocking: boolean
2508
    @param blocking: whether to block and wait until we
2509
        can lock the file or return immediately
2510
    @type timeout: int or None
2511
    @param timeout: if not None, the duration to wait for the lock
2512
        (in blocking mode)
2513

2514
    """
2515
    self._flock(fcntl.LOCK_SH, blocking, timeout,
2516
                "Failed to lock %s in shared mode" % self.filename)
2517

    
2518
  def Unlock(self, blocking=True, timeout=None):
2519
    """Unlocks the file.
2520

2521
    According to C{flock(2)}, unlocking can also be a nonblocking
2522
    operation::
2523

2524
      To make a non-blocking request, include LOCK_NB with any of the above
2525
      operations.
2526

2527
    @type blocking: boolean
2528
    @param blocking: whether to block and wait until we
2529
        can lock the file or return immediately
2530
    @type timeout: int or None
2531
    @param timeout: if not None, the duration to wait for the lock
2532
        (in blocking mode)
2533

2534
    """
2535
    self._flock(fcntl.LOCK_UN, blocking, timeout,
2536
                "Failed to unlock %s" % self.filename)
2537

    
2538

    
2539
def SignalHandled(signums):
2540
  """Signal Handled decoration.
2541

2542
  This special decorator installs a signal handler and then calls the target
2543
  function. The function must accept a 'signal_handlers' keyword argument,
2544
  which will contain a dict indexed by signal number, with SignalHandler
2545
  objects as values.
2546

2547
  The decorator can be safely stacked with iself, to handle multiple signals
2548
  with different handlers.
2549

2550
  @type signums: list
2551
  @param signums: signals to intercept
2552

2553
  """
2554
  def wrap(fn):
2555
    def sig_function(*args, **kwargs):
2556
      assert 'signal_handlers' not in kwargs or \
2557
             kwargs['signal_handlers'] is None or \
2558
             isinstance(kwargs['signal_handlers'], dict), \
2559
             "Wrong signal_handlers parameter in original function call"
2560
      if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
2561
        signal_handlers = kwargs['signal_handlers']
2562
      else:
2563
        signal_handlers = {}
2564
        kwargs['signal_handlers'] = signal_handlers
2565
      sighandler = SignalHandler(signums)
2566
      try:
2567
        for sig in signums:
2568
          signal_handlers[sig] = sighandler
2569
        return fn(*args, **kwargs)
2570
      finally:
2571
        sighandler.Reset()
2572
    return sig_function
2573
  return wrap
2574

    
2575

    
2576
class SignalHandler(object):
2577
  """Generic signal handler class.
2578

2579
  It automatically restores the original handler when deconstructed or
2580
  when L{Reset} is called. You can either pass your own handler
2581
  function in or query the L{called} attribute to detect whether the
2582
  signal was sent.
2583

2584
  @type signum: list
2585
  @ivar signum: the signals we handle
2586
  @type called: boolean
2587
  @ivar called: tracks whether any of the signals have been raised
2588

2589
  """
2590
  def __init__(self, signum):
2591
    """Constructs a new SignalHandler instance.
2592

2593
    @type signum: int or list of ints
2594
    @param signum: Single signal number or set of signal numbers
2595

2596
    """
2597
    self.signum = set(signum)
2598
    self.called = False
2599

    
2600
    self._previous = {}
2601
    try:
2602
      for signum in self.signum:
2603
        # Setup handler
2604
        prev_handler = signal.signal(signum, self._HandleSignal)
2605
        try:
2606
          self._previous[signum] = prev_handler
2607
        except:
2608
          # Restore previous handler
2609
          signal.signal(signum, prev_handler)
2610
          raise
2611
    except:
2612
      # Reset all handlers
2613
      self.Reset()
2614
      # Here we have a race condition: a handler may have already been called,
2615
      # but there's not much we can do about it at this point.
2616
      raise
2617

    
2618
  def __del__(self):
2619
    self.Reset()
2620

    
2621
  def Reset(self):
2622
    """Restore previous handler.
2623

2624
    This will reset all the signals to their previous handlers.
2625

2626
    """
2627
    for signum, prev_handler in self._previous.items():
2628
      signal.signal(signum, prev_handler)
2629
      # If successful, remove from dict
2630
      del self._previous[signum]
2631

    
2632
  def Clear(self):
2633
    """Unsets the L{called} flag.
2634

2635
    This function can be used in case a signal may arrive several times.
2636

2637
    """
2638
    self.called = False
2639

    
2640
  # we don't care about arguments, but we leave them named for the future
2641
  def _HandleSignal(self, signum, frame): # pylint: disable-msg=W0613
2642
    """Actual signal handling function.
2643

2644
    """
2645
    # This is not nice and not absolutely atomic, but it appears to be the only
2646
    # solution in Python -- there are no atomic types.
2647
    self.called = True
2648

    
2649

    
2650
class FieldSet(object):
2651
  """A simple field set.
2652

2653
  Among the features are:
2654
    - checking if a string is among a list of static string or regex objects
2655
    - checking if a whole list of string matches
2656
    - returning the matching groups from a regex match
2657

2658
  Internally, all fields are held as regular expression objects.
2659

2660
  """
2661
  def __init__(self, *items):
2662
    self.items = [re.compile("^%s$" % value) for value in items]
2663

    
2664
  def Extend(self, other_set):
2665
    """Extend the field set with the items from another one"""
2666
    self.items.extend(other_set.items)
2667

    
2668
  def Matches(self, field):
2669
    """Checks if a field matches the current set
2670

2671
    @type field: str
2672
    @param field: the string to match
2673
    @return: either None or a regular expression match object
2674

2675
    """
2676
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
2677
      return m
2678
    return None
2679

    
2680
  def NonMatching(self, items):
2681
    """Returns the list of fields not matching the current set
2682

2683
    @type items: list
2684
    @param items: the list of fields to check
2685
    @rtype: list
2686
    @return: list of non-matching fields
2687

2688
    """
2689
    return [val for val in items if not self.Matches(val)]