Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ fdad8c4d

History | View | Annotate | Download (79.3 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
import datetime
47
import calendar
48
import collections
49

    
50
from cStringIO import StringIO
51

    
52
try:
53
  from hashlib import sha1
54
except ImportError:
55
  import sha
56
  sha1 = sha.new
57

    
58
from ganeti import errors
59
from ganeti import constants
60

    
61

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

    
65
debug_locks = False
66

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

    
70
_RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
71

    
72

    
73
class RunResult(object):
74
  """Holds the result of running external programs.
75

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

91
  """
92
  __slots__ = ["exit_code", "signal", "stdout", "stderr",
93
               "failed", "fail_reason", "cmd"]
94

    
95

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

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

    
111
    if self.failed:
112
      logging.debug("Command '%s' failed (%s); output: %s",
113
                    self.cmd, self.fail_reason, self.output)
114

    
115
  def _GetOutput(self):
116
    """Returns the combined stdout and stderr for easier usage.
117

118
    """
119
    return self.stdout + self.stderr
120

    
121
  output = property(_GetOutput, None, None, "Return full output")
122

    
123

    
124
def RunCmd(cmd, env=None, output=None, cwd='/', reset_env=False):
125
  """Execute a (shell) command.
126

127
  The command should not read from its standard input, as it will be
128
  closed.
129

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

147
  """
148
  if no_fork:
149
    raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
150

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

    
160
  if not reset_env:
161
    cmd_env = os.environ.copy()
162
    cmd_env["LC_ALL"] = "C"
163
  else:
164
    cmd_env = {}
165

    
166
  if env is not None:
167
    cmd_env.update(env)
168

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

    
182
  if status >= 0:
183
    exitcode = status
184
    signal_ = None
185
  else:
186
    exitcode = None
187
    signal_ = -status
188

    
189
  return RunResult(exitcode, signal_, out, err, strcmd)
190

    
191

    
192
def _RunCmdPipe(cmd, env, via_shell, cwd):
193
  """Run a command and return its output.
194

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

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

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

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

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

    
254
  out = out.getvalue()
255
  err = err.getvalue()
256

    
257
  status = child.wait()
258
  return out, err, status
259

    
260

    
261
def _RunCmdFile(cmd, env, via_shell, output, cwd):
262
  """Run a command and save its output to a file.
263

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

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

    
287
    child.stdin.close()
288
    status = child.wait()
289
  finally:
290
    fh.close()
291
  return status
292

    
293

    
294
def RunParts(dir_name, env=None, reset_env=False):
295
  """Run Scripts or programs in a directory
296

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

306
  """
307
  rr = []
308

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

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

    
328
  return rr
329

    
330

    
331
def RemoveFile(filename):
332
  """Remove a file ignoring some errors.
333

334
  Remove a file, ignoring non-existing ones or directories. Other
335
  errors are passed.
336

337
  @type filename: str
338
  @param filename: the file to be removed
339

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

    
347

    
348
def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
349
  """Renames a file.
350

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

360
  """
361
  try:
362
    return os.rename(old, new)
363
  except OSError, err:
364
    # In at least one use case of this function, the job queue, directory
365
    # creation is very rare. Checking for the directory before renaming is not
366
    # as efficient.
367
    if mkdir and err.errno == errno.ENOENT:
368
      # Create directory and try again
369
      Makedirs(os.path.dirname(new), mode=mkdir_mode)
370

    
371
      return os.rename(old, new)
372

    
373
    raise
374

    
375

    
376
def Makedirs(path, mode=0750):
377
  """Super-mkdir; create a leaf directory and all intermediate ones.
378

379
  This is a wrapper around C{os.makedirs} adding error handling not implemented
380
  before Python 2.5.
381

382
  """
383
  try:
384
    os.makedirs(path, mode)
385
  except OSError, err:
386
    # Ignore EEXIST. This is only handled in os.makedirs as included in
387
    # Python 2.5 and above.
388
    if err.errno != errno.EEXIST or not os.path.exists(path):
389
      raise
390

    
391

    
392
def ResetTempfileModule():
393
  """Resets the random name generator of the tempfile module.
394

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

401
  """
402
  # pylint: disable-msg=W0212
403
  if hasattr(tempfile, "_once_lock") and hasattr(tempfile, "_name_sequence"):
404
    tempfile._once_lock.acquire()
405
    try:
406
      # Reset random name generator
407
      tempfile._name_sequence = None
408
    finally:
409
      tempfile._once_lock.release()
410
  else:
411
    logging.critical("The tempfile module misses at least one of the"
412
                     " '_once_lock' and '_name_sequence' attributes")
413

    
414

    
415
def _FingerprintFile(filename):
416
  """Compute the fingerprint of a file.
417

418
  If the file does not exist, a None will be returned
419
  instead.
420

421
  @type filename: str
422
  @param filename: the filename to checksum
423
  @rtype: str
424
  @return: the hex digest of the sha checksum of the contents
425
      of the file
426

427
  """
428
  if not (os.path.exists(filename) and os.path.isfile(filename)):
429
    return None
430

    
431
  f = open(filename)
432

    
433
  fp = sha1()
434
  while True:
435
    data = f.read(4096)
436
    if not data:
437
      break
438

    
439
    fp.update(data)
440

    
441
  return fp.hexdigest()
442

    
443

    
444
def FingerprintFiles(files):
445
  """Compute fingerprints for a list of files.
446

447
  @type files: list
448
  @param files: the list of filename to fingerprint
449
  @rtype: dict
450
  @return: a dictionary filename: fingerprint, holding only
451
      existing files
452

453
  """
454
  ret = {}
455

    
456
  for filename in files:
457
    cksum = _FingerprintFile(filename)
458
    if cksum:
459
      ret[filename] = cksum
460

    
461
  return ret
462

    
463

    
464
def ForceDictType(target, key_types, allowed_values=None):
465
  """Force the values of a dict to have certain types.
466

467
  @type target: dict
468
  @param target: the dict to update
469
  @type key_types: dict
470
  @param key_types: dict mapping target dict keys to types
471
                    in constants.ENFORCEABLE_TYPES
472
  @type allowed_values: list
473
  @keyword allowed_values: list of specially allowed values
474

475
  """
476
  if allowed_values is None:
477
    allowed_values = []
478

    
479
  if not isinstance(target, dict):
480
    msg = "Expected dictionary, got '%s'" % target
481
    raise errors.TypeEnforcementError(msg)
482

    
483
  for key in target:
484
    if key not in key_types:
485
      msg = "Unknown key '%s'" % key
486
      raise errors.TypeEnforcementError(msg)
487

    
488
    if target[key] in allowed_values:
489
      continue
490

    
491
    ktype = key_types[key]
492
    if ktype not in constants.ENFORCEABLE_TYPES:
493
      msg = "'%s' has non-enforceable type %s" % (key, ktype)
494
      raise errors.ProgrammerError(msg)
495

    
496
    if ktype == constants.VTYPE_STRING:
497
      if not isinstance(target[key], basestring):
498
        if isinstance(target[key], bool) and not target[key]:
499
          target[key] = ''
500
        else:
501
          msg = "'%s' (value %s) is not a valid string" % (key, target[key])
502
          raise errors.TypeEnforcementError(msg)
503
    elif ktype == constants.VTYPE_BOOL:
504
      if isinstance(target[key], basestring) and target[key]:
505
        if target[key].lower() == constants.VALUE_FALSE:
506
          target[key] = False
507
        elif target[key].lower() == constants.VALUE_TRUE:
508
          target[key] = True
509
        else:
510
          msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
511
          raise errors.TypeEnforcementError(msg)
512
      elif target[key]:
513
        target[key] = True
514
      else:
515
        target[key] = False
516
    elif ktype == constants.VTYPE_SIZE:
517
      try:
518
        target[key] = ParseUnit(target[key])
519
      except errors.UnitParseError, err:
520
        msg = "'%s' (value %s) is not a valid size. error: %s" % \
521
              (key, target[key], err)
522
        raise errors.TypeEnforcementError(msg)
523
    elif ktype == constants.VTYPE_INT:
524
      try:
525
        target[key] = int(target[key])
526
      except (ValueError, TypeError):
527
        msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
528
        raise errors.TypeEnforcementError(msg)
529

    
530

    
531
def IsProcessAlive(pid):
532
  """Check if a given pid exists on the system.
533

534
  @note: zombie status is not handled, so zombie processes
535
      will be returned as alive
536
  @type pid: int
537
  @param pid: the process ID to check
538
  @rtype: boolean
539
  @return: True if the process exists
540

541
  """
542
  if pid <= 0:
543
    return False
544

    
545
  try:
546
    os.stat("/proc/%d/status" % pid)
547
    return True
548
  except EnvironmentError, err:
549
    if err.errno in (errno.ENOENT, errno.ENOTDIR):
550
      return False
551
    raise
552

    
553

    
554
def ReadPidFile(pidfile):
555
  """Read a pid from a file.
556

557
  @type  pidfile: string
558
  @param pidfile: path to the file containing the pid
559
  @rtype: int
560
  @return: The process id, if the file exists and contains a valid PID,
561
           otherwise 0
562

563
  """
564
  try:
565
    raw_data = ReadFile(pidfile)
566
  except EnvironmentError, err:
567
    if err.errno != errno.ENOENT:
568
      logging.exception("Can't read pid file")
569
    return 0
570

    
571
  try:
572
    pid = int(raw_data)
573
  except (TypeError, ValueError), err:
574
    logging.info("Can't parse pid file contents", exc_info=True)
575
    return 0
576

    
577
  return pid
578

    
579

    
580
def MatchNameComponent(key, name_list, case_sensitive=True):
581
  """Try to match a name against a list.
582

583
  This function will try to match a name like test1 against a list
584
  like C{['test1.example.com', 'test2.example.com', ...]}. Against
585
  this list, I{'test1'} as well as I{'test1.example'} will match, but
586
  not I{'test1.ex'}. A multiple match will be considered as no match
587
  at all (e.g. I{'test1'} against C{['test1.example.com',
588
  'test1.example.org']}), except when the key fully matches an entry
589
  (e.g. I{'test1'} against C{['test1', 'test1.example.com']}).
590

591
  @type key: str
592
  @param key: the name to be searched
593
  @type name_list: list
594
  @param name_list: the list of strings against which to search the key
595
  @type case_sensitive: boolean
596
  @param case_sensitive: whether to provide a case-sensitive match
597

598
  @rtype: None or str
599
  @return: None if there is no match I{or} if there are multiple matches,
600
      otherwise the element from the list which matches
601

602
  """
603
  if key in name_list:
604
    return key
605

    
606
  re_flags = 0
607
  if not case_sensitive:
608
    re_flags |= re.IGNORECASE
609
    key = key.upper()
610
  mo = re.compile("^%s(\..*)?$" % re.escape(key), re_flags)
611
  names_filtered = []
612
  string_matches = []
613
  for name in name_list:
614
    if mo.match(name) is not None:
615
      names_filtered.append(name)
616
      if not case_sensitive and key == name.upper():
617
        string_matches.append(name)
618

    
619
  if len(string_matches) == 1:
620
    return string_matches[0]
621
  if len(names_filtered) == 1:
622
    return names_filtered[0]
623
  return None
624

    
625

    
626
class HostInfo:
627
  """Class implementing resolver and hostname functionality
628

629
  """
630
  _VALID_NAME_RE = re.compile("^[a-z0-9._-]{1,255}$")
631

    
632
  def __init__(self, name=None):
633
    """Initialize the host name object.
634

635
    If the name argument is not passed, it will use this system's
636
    name.
637

638
    """
639
    if name is None:
640
      name = self.SysName()
641

    
642
    self.query = name
643
    self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
644
    self.ip = self.ipaddrs[0]
645

    
646
  def ShortName(self):
647
    """Returns the hostname without domain.
648

649
    """
650
    return self.name.split('.')[0]
651

    
652
  @staticmethod
653
  def SysName():
654
    """Return the current system's name.
655

656
    This is simply a wrapper over C{socket.gethostname()}.
657

658
    """
659
    return socket.gethostname()
660

    
661
  @staticmethod
662
  def LookupHostname(hostname):
663
    """Look up hostname
664

665
    @type hostname: str
666
    @param hostname: hostname to look up
667

668
    @rtype: tuple
669
    @return: a tuple (name, aliases, ipaddrs) as returned by
670
        C{socket.gethostbyname_ex}
671
    @raise errors.ResolverError: in case of errors in resolving
672

673
    """
674
    try:
675
      result = socket.gethostbyname_ex(hostname)
676
    except socket.gaierror, err:
677
      # hostname not found in DNS
678
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
679

    
680
    return result
681

    
682
  @classmethod
683
  def NormalizeName(cls, hostname):
684
    """Validate and normalize the given hostname.
685

686
    @attention: the validation is a bit more relaxed than the standards
687
        require; most importantly, we allow underscores in names
688
    @raise errors.OpPrereqError: when the name is not valid
689

690
    """
691
    hostname = hostname.lower()
692
    if (not cls._VALID_NAME_RE.match(hostname) or
693
        # double-dots, meaning empty label
694
        ".." in hostname or
695
        # empty initial label
696
        hostname.startswith(".")):
697
      raise errors.OpPrereqError("Invalid hostname '%s'" % hostname,
698
                                 errors.ECODE_INVAL)
699
    if hostname.endswith("."):
700
      hostname = hostname.rstrip(".")
701
    return hostname
702

    
703

    
704
def GetHostInfo(name=None):
705
  """Lookup host name and raise an OpPrereqError for failures"""
706

    
707
  try:
708
    return HostInfo(name)
709
  except errors.ResolverError, err:
710
    raise errors.OpPrereqError("The given name (%s) does not resolve: %s" %
711
                               (err[0], err[2]), errors.ECODE_RESOLVER)
712

    
713

    
714
def ListVolumeGroups():
715
  """List volume groups and their size
716

717
  @rtype: dict
718
  @return:
719
       Dictionary with keys volume name and values
720
       the size of the volume
721

722
  """
723
  command = "vgs --noheadings --units m --nosuffix -o name,size"
724
  result = RunCmd(command)
725
  retval = {}
726
  if result.failed:
727
    return retval
728

    
729
  for line in result.stdout.splitlines():
730
    try:
731
      name, size = line.split()
732
      size = int(float(size))
733
    except (IndexError, ValueError), err:
734
      logging.error("Invalid output from vgs (%s): %s", err, line)
735
      continue
736

    
737
    retval[name] = size
738

    
739
  return retval
740

    
741

    
742
def BridgeExists(bridge):
743
  """Check whether the given bridge exists in the system
744

745
  @type bridge: str
746
  @param bridge: the bridge name to check
747
  @rtype: boolean
748
  @return: True if it does
749

750
  """
751
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
752

    
753

    
754
def NiceSort(name_list):
755
  """Sort a list of strings based on digit and non-digit groupings.
756

757
  Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
758
  will sort the list in the logical order C{['a1', 'a2', 'a10',
759
  'a11']}.
760

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

765
  @type name_list: list
766
  @param name_list: the names to be sorted
767
  @rtype: list
768
  @return: a copy of the name list sorted with our algorithm
769

770
  """
771
  _SORTER_BASE = "(\D+|\d+)"
772
  _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
773
                                                  _SORTER_BASE, _SORTER_BASE,
774
                                                  _SORTER_BASE, _SORTER_BASE,
775
                                                  _SORTER_BASE, _SORTER_BASE)
776
  _SORTER_RE = re.compile(_SORTER_FULL)
777
  _SORTER_NODIGIT = re.compile("^\D*$")
778
  def _TryInt(val):
779
    """Attempts to convert a variable to integer."""
780
    if val is None or _SORTER_NODIGIT.match(val):
781
      return val
782
    rval = int(val)
783
    return rval
784

    
785
  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
786
             for name in name_list]
787
  to_sort.sort()
788
  return [tup[1] for tup in to_sort]
789

    
790

    
791
def TryConvert(fn, val):
792
  """Try to convert a value ignoring errors.
793

794
  This function tries to apply function I{fn} to I{val}. If no
795
  C{ValueError} or C{TypeError} exceptions are raised, it will return
796
  the result, else it will return the original value. Any other
797
  exceptions are propagated to the caller.
798

799
  @type fn: callable
800
  @param fn: function to apply to the value
801
  @param val: the value to be converted
802
  @return: The converted value if the conversion was successful,
803
      otherwise the original value.
804

805
  """
806
  try:
807
    nv = fn(val)
808
  except (ValueError, TypeError):
809
    nv = val
810
  return nv
811

    
812

    
813
def IsValidIP(ip):
814
  """Verifies the syntax of an IPv4 address.
815

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

819
  @type ip: str
820
  @param ip: the address to be checked
821
  @rtype: a regular expression match object
822
  @return: a regular expression match object, or None if the
823
      address is not valid
824

825
  """
826
  unit = "(0|[1-9]\d{0,2})"
827
  #TODO: convert and return only boolean
828
  return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
829

    
830

    
831
def IsValidShellParam(word):
832
  """Verifies is the given word is safe from the shell's p.o.v.
833

834
  This means that we can pass this to a command via the shell and be
835
  sure that it doesn't alter the command line and is passed as such to
836
  the actual command.
837

838
  Note that we are overly restrictive here, in order to be on the safe
839
  side.
840

841
  @type word: str
842
  @param word: the word to check
843
  @rtype: boolean
844
  @return: True if the word is 'safe'
845

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

    
849

    
850
def BuildShellCmd(template, *args):
851
  """Build a safe shell command line from the given arguments.
852

853
  This function will check all arguments in the args list so that they
854
  are valid shell parameters (i.e. they don't contain shell
855
  metacharacters). If everything is ok, it will return the result of
856
  template % args.
857

858
  @type template: str
859
  @param template: the string holding the template for the
860
      string formatting
861
  @rtype: str
862
  @return: the expanded command line
863

864
  """
865
  for word in args:
866
    if not IsValidShellParam(word):
867
      raise errors.ProgrammerError("Shell argument '%s' contains"
868
                                   " invalid characters" % word)
869
  return template % args
870

    
871

    
872
def FormatUnit(value, units):
873
  """Formats an incoming number of MiB with the appropriate unit.
874

875
  @type value: int
876
  @param value: integer representing the value in MiB (1048576)
877
  @type units: char
878
  @param units: the type of formatting we should do:
879
      - 'h' for automatic scaling
880
      - 'm' for MiBs
881
      - 'g' for GiBs
882
      - 't' for TiBs
883
  @rtype: str
884
  @return: the formatted value (with suffix)
885

886
  """
887
  if units not in ('m', 'g', 't', 'h'):
888
    raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
889

    
890
  suffix = ''
891

    
892
  if units == 'm' or (units == 'h' and value < 1024):
893
    if units == 'h':
894
      suffix = 'M'
895
    return "%d%s" % (round(value, 0), suffix)
896

    
897
  elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
898
    if units == 'h':
899
      suffix = 'G'
900
    return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
901

    
902
  else:
903
    if units == 'h':
904
      suffix = 'T'
905
    return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
906

    
907

    
908
def ParseUnit(input_string):
909
  """Tries to extract number and scale from the given string.
910

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

915
  """
916
  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', str(input_string))
917
  if not m:
918
    raise errors.UnitParseError("Invalid format")
919

    
920
  value = float(m.groups()[0])
921

    
922
  unit = m.groups()[1]
923
  if unit:
924
    lcunit = unit.lower()
925
  else:
926
    lcunit = 'm'
927

    
928
  if lcunit in ('m', 'mb', 'mib'):
929
    # Value already in MiB
930
    pass
931

    
932
  elif lcunit in ('g', 'gb', 'gib'):
933
    value *= 1024
934

    
935
  elif lcunit in ('t', 'tb', 'tib'):
936
    value *= 1024 * 1024
937

    
938
  else:
939
    raise errors.UnitParseError("Unknown unit: %s" % unit)
940

    
941
  # Make sure we round up
942
  if int(value) < value:
943
    value += 1
944

    
945
  # Round up to the next multiple of 4
946
  value = int(value)
947
  if value % 4:
948
    value += 4 - value % 4
949

    
950
  return value
951

    
952

    
953
def AddAuthorizedKey(file_name, key):
954
  """Adds an SSH public key to an authorized_keys file.
955

956
  @type file_name: str
957
  @param file_name: path to authorized_keys file
958
  @type key: str
959
  @param key: string containing key
960

961
  """
962
  key_fields = key.split()
963

    
964
  f = open(file_name, 'a+')
965
  try:
966
    nl = True
967
    for line in f:
968
      # Ignore whitespace changes
969
      if line.split() == key_fields:
970
        break
971
      nl = line.endswith('\n')
972
    else:
973
      if not nl:
974
        f.write("\n")
975
      f.write(key.rstrip('\r\n'))
976
      f.write("\n")
977
      f.flush()
978
  finally:
979
    f.close()
980

    
981

    
982
def RemoveAuthorizedKey(file_name, key):
983
  """Removes an SSH public key from an authorized_keys file.
984

985
  @type file_name: str
986
  @param file_name: path to authorized_keys file
987
  @type key: str
988
  @param key: string containing key
989

990
  """
991
  key_fields = key.split()
992

    
993
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
994
  try:
995
    out = os.fdopen(fd, 'w')
996
    try:
997
      f = open(file_name, 'r')
998
      try:
999
        for line in f:
1000
          # Ignore whitespace changes while comparing lines
1001
          if line.split() != key_fields:
1002
            out.write(line)
1003

    
1004
        out.flush()
1005
        os.rename(tmpname, file_name)
1006
      finally:
1007
        f.close()
1008
    finally:
1009
      out.close()
1010
  except:
1011
    RemoveFile(tmpname)
1012
    raise
1013

    
1014

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

1018
  @type file_name: str
1019
  @param file_name: path to the file to modify (usually C{/etc/hosts})
1020
  @type ip: str
1021
  @param ip: the IP address
1022
  @type hostname: str
1023
  @param hostname: the hostname to be added
1024
  @type aliases: list
1025
  @param aliases: the list of aliases to add for the hostname
1026

1027
  """
1028
  # FIXME: use WriteFile + fn rather than duplicating its efforts
1029
  # Ensure aliases are unique
1030
  aliases = UniqueSequence([hostname] + aliases)[1:]
1031

    
1032
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1033
  try:
1034
    out = os.fdopen(fd, 'w')
1035
    try:
1036
      f = open(file_name, 'r')
1037
      try:
1038
        for line in f:
1039
          fields = line.split()
1040
          if fields and not fields[0].startswith('#') and ip == fields[0]:
1041
            continue
1042
          out.write(line)
1043

    
1044
        out.write("%s\t%s" % (ip, hostname))
1045
        if aliases:
1046
          out.write(" %s" % ' '.join(aliases))
1047
        out.write('\n')
1048

    
1049
        out.flush()
1050
        os.fsync(out)
1051
        os.chmod(tmpname, 0644)
1052
        os.rename(tmpname, file_name)
1053
      finally:
1054
        f.close()
1055
    finally:
1056
      out.close()
1057
  except:
1058
    RemoveFile(tmpname)
1059
    raise
1060

    
1061

    
1062
def AddHostToEtcHosts(hostname):
1063
  """Wrapper around SetEtcHostsEntry.
1064

1065
  @type hostname: str
1066
  @param hostname: a hostname that will be resolved and added to
1067
      L{constants.ETC_HOSTS}
1068

1069
  """
1070
  hi = HostInfo(name=hostname)
1071
  SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
1072

    
1073

    
1074
def RemoveEtcHostsEntry(file_name, hostname):
1075
  """Removes a hostname from /etc/hosts.
1076

1077
  IP addresses without names are removed from the file.
1078

1079
  @type file_name: str
1080
  @param file_name: path to the file to modify (usually C{/etc/hosts})
1081
  @type hostname: str
1082
  @param hostname: the hostname to be removed
1083

1084
  """
1085
  # FIXME: use WriteFile + fn rather than duplicating its efforts
1086
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1087
  try:
1088
    out = os.fdopen(fd, 'w')
1089
    try:
1090
      f = open(file_name, 'r')
1091
      try:
1092
        for line in f:
1093
          fields = line.split()
1094
          if len(fields) > 1 and not fields[0].startswith('#'):
1095
            names = fields[1:]
1096
            if hostname in names:
1097
              while hostname in names:
1098
                names.remove(hostname)
1099
              if names:
1100
                out.write("%s %s\n" % (fields[0], ' '.join(names)))
1101
              continue
1102

    
1103
          out.write(line)
1104

    
1105
        out.flush()
1106
        os.fsync(out)
1107
        os.chmod(tmpname, 0644)
1108
        os.rename(tmpname, file_name)
1109
      finally:
1110
        f.close()
1111
    finally:
1112
      out.close()
1113
  except:
1114
    RemoveFile(tmpname)
1115
    raise
1116

    
1117

    
1118
def RemoveHostFromEtcHosts(hostname):
1119
  """Wrapper around RemoveEtcHostsEntry.
1120

1121
  @type hostname: str
1122
  @param hostname: hostname that will be resolved and its
1123
      full and shot name will be removed from
1124
      L{constants.ETC_HOSTS}
1125

1126
  """
1127
  hi = HostInfo(name=hostname)
1128
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
1129
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
1130

    
1131

    
1132
def TimestampForFilename():
1133
  """Returns the current time formatted for filenames.
1134

1135
  The format doesn't contain colons as some shells and applications them as
1136
  separators.
1137

1138
  """
1139
  return time.strftime("%Y-%m-%d_%H_%M_%S")
1140

    
1141

    
1142
def CreateBackup(file_name):
1143
  """Creates a backup of a file.
1144

1145
  @type file_name: str
1146
  @param file_name: file to be backed up
1147
  @rtype: str
1148
  @return: the path to the newly created backup
1149
  @raise errors.ProgrammerError: for invalid file names
1150

1151
  """
1152
  if not os.path.isfile(file_name):
1153
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
1154
                                file_name)
1155

    
1156
  prefix = ("%s.backup-%s." %
1157
            (os.path.basename(file_name), TimestampForFilename()))
1158
  dir_name = os.path.dirname(file_name)
1159

    
1160
  fsrc = open(file_name, 'rb')
1161
  try:
1162
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
1163
    fdst = os.fdopen(fd, 'wb')
1164
    try:
1165
      logging.debug("Backing up %s at %s", file_name, backup_name)
1166
      shutil.copyfileobj(fsrc, fdst)
1167
    finally:
1168
      fdst.close()
1169
  finally:
1170
    fsrc.close()
1171

    
1172
  return backup_name
1173

    
1174

    
1175
def ShellQuote(value):
1176
  """Quotes shell argument according to POSIX.
1177

1178
  @type value: str
1179
  @param value: the argument to be quoted
1180
  @rtype: str
1181
  @return: the quoted value
1182

1183
  """
1184
  if _re_shell_unquoted.match(value):
1185
    return value
1186
  else:
1187
    return "'%s'" % value.replace("'", "'\\''")
1188

    
1189

    
1190
def ShellQuoteArgs(args):
1191
  """Quotes a list of shell arguments.
1192

1193
  @type args: list
1194
  @param args: list of arguments to be quoted
1195
  @rtype: str
1196
  @return: the quoted arguments concatenated with spaces
1197

1198
  """
1199
  return ' '.join([ShellQuote(i) for i in args])
1200

    
1201

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

1205
  Check if the given IP is reachable by doing attempting a TCP connect
1206
  to it.
1207

1208
  @type target: str
1209
  @param target: the IP or hostname to ping
1210
  @type port: int
1211
  @param port: the port to connect to
1212
  @type timeout: int
1213
  @param timeout: the timeout on the connection attempt
1214
  @type live_port_needed: boolean
1215
  @param live_port_needed: whether a closed port will cause the
1216
      function to return failure, as if there was a timeout
1217
  @type source: str or None
1218
  @param source: if specified, will cause the connect to be made
1219
      from this specific source address; failures to bind other
1220
      than C{EADDRNOTAVAIL} will be ignored
1221

1222
  """
1223
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1224

    
1225
  success = False
1226

    
1227
  if source is not None:
1228
    try:
1229
      sock.bind((source, 0))
1230
    except socket.error, (errcode, _):
1231
      if errcode == errno.EADDRNOTAVAIL:
1232
        success = False
1233

    
1234
  sock.settimeout(timeout)
1235

    
1236
  try:
1237
    sock.connect((target, port))
1238
    sock.close()
1239
    success = True
1240
  except socket.timeout:
1241
    success = False
1242
  except socket.error, (errcode, _):
1243
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
1244

    
1245
  return success
1246

    
1247

    
1248
def OwnIpAddress(address):
1249
  """Check if the current host has the the given IP address.
1250

1251
  Currently this is done by TCP-pinging the address from the loopback
1252
  address.
1253

1254
  @type address: string
1255
  @param address: the address to check
1256
  @rtype: bool
1257
  @return: True if we own the address
1258

1259
  """
1260
  return TcpPing(address, constants.DEFAULT_NODED_PORT,
1261
                 source=constants.LOCALHOST_IP_ADDRESS)
1262

    
1263

    
1264
def ListVisibleFiles(path):
1265
  """Returns a list of visible files in a directory.
1266

1267
  @type path: str
1268
  @param path: the directory to enumerate
1269
  @rtype: list
1270
  @return: the list of all files not starting with a dot
1271
  @raise ProgrammerError: if L{path} is not an absolue and normalized path
1272

1273
  """
1274
  if not IsNormAbsPath(path):
1275
    raise errors.ProgrammerError("Path passed to ListVisibleFiles is not"
1276
                                 " absolute/normalized: '%s'" % path)
1277
  files = [i for i in os.listdir(path) if not i.startswith(".")]
1278
  files.sort()
1279
  return files
1280

    
1281

    
1282
def GetHomeDir(user, default=None):
1283
  """Try to get the homedir of the given user.
1284

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

1289
  """
1290
  try:
1291
    if isinstance(user, basestring):
1292
      result = pwd.getpwnam(user)
1293
    elif isinstance(user, (int, long)):
1294
      result = pwd.getpwuid(user)
1295
    else:
1296
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1297
                                   type(user))
1298
  except KeyError:
1299
    return default
1300
  return result.pw_dir
1301

    
1302

    
1303
def NewUUID():
1304
  """Returns a random UUID.
1305

1306
  @note: This is a Linux-specific method as it uses the /proc
1307
      filesystem.
1308
  @rtype: str
1309

1310
  """
1311
  return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
1312

    
1313

    
1314
def GenerateSecret(numbytes=20):
1315
  """Generates a random secret.
1316

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

1320
  @param numbytes: the number of bytes which will be represented by the returned
1321
      string (defaulting to 20, the length of a SHA1 hash)
1322
  @rtype: str
1323
  @return: an hex representation of the pseudo-random sequence
1324

1325
  """
1326
  return os.urandom(numbytes).encode('hex')
1327

    
1328

    
1329
def EnsureDirs(dirs):
1330
  """Make required directories, if they don't exist.
1331

1332
  @param dirs: list of tuples (dir_name, dir_mode)
1333
  @type dirs: list of (string, integer)
1334

1335
  """
1336
  for dir_name, dir_mode in dirs:
1337
    try:
1338
      os.mkdir(dir_name, dir_mode)
1339
    except EnvironmentError, err:
1340
      if err.errno != errno.EEXIST:
1341
        raise errors.GenericError("Cannot create needed directory"
1342
                                  " '%s': %s" % (dir_name, err))
1343
    if not os.path.isdir(dir_name):
1344
      raise errors.GenericError("%s is not a directory" % dir_name)
1345

    
1346

    
1347
def ReadFile(file_name, size=-1):
1348
  """Reads a file.
1349

1350
  @type size: int
1351
  @param size: Read at most size bytes (if negative, entire file)
1352
  @rtype: str
1353
  @return: the (possibly partial) content of the file
1354

1355
  """
1356
  f = open(file_name, "r")
1357
  try:
1358
    return f.read(size)
1359
  finally:
1360
    f.close()
1361

    
1362

    
1363
def WriteFile(file_name, fn=None, data=None,
1364
              mode=None, uid=-1, gid=-1,
1365
              atime=None, mtime=None, close=True,
1366
              dry_run=False, backup=False,
1367
              prewrite=None, postwrite=None):
1368
  """(Over)write a file atomically.
1369

1370
  The file_name and either fn (a function taking one argument, the
1371
  file descriptor, and which should write the data to it) or data (the
1372
  contents of the file) must be passed. The other arguments are
1373
  optional and allow setting the file mode, owner and group, and the
1374
  mtime/atime of the file.
1375

1376
  If the function doesn't raise an exception, it has succeeded and the
1377
  target file has the new contents. If the function has raised an
1378
  exception, an existing target file should be unmodified and the
1379
  temporary file should be removed.
1380

1381
  @type file_name: str
1382
  @param file_name: the target filename
1383
  @type fn: callable
1384
  @param fn: content writing function, called with
1385
      file descriptor as parameter
1386
  @type data: str
1387
  @param data: contents of the file
1388
  @type mode: int
1389
  @param mode: file mode
1390
  @type uid: int
1391
  @param uid: the owner of the file
1392
  @type gid: int
1393
  @param gid: the group of the file
1394
  @type atime: int
1395
  @param atime: a custom access time to be set on the file
1396
  @type mtime: int
1397
  @param mtime: a custom modification time to be set on the file
1398
  @type close: boolean
1399
  @param close: whether to close file after writing it
1400
  @type prewrite: callable
1401
  @param prewrite: function to be called before writing content
1402
  @type postwrite: callable
1403
  @param postwrite: function to be called after writing content
1404

1405
  @rtype: None or int
1406
  @return: None if the 'close' parameter evaluates to True,
1407
      otherwise the file descriptor
1408

1409
  @raise errors.ProgrammerError: if any of the arguments are not valid
1410

1411
  """
1412
  if not os.path.isabs(file_name):
1413
    raise errors.ProgrammerError("Path passed to WriteFile is not"
1414
                                 " absolute: '%s'" % file_name)
1415

    
1416
  if [fn, data].count(None) != 1:
1417
    raise errors.ProgrammerError("fn or data required")
1418

    
1419
  if [atime, mtime].count(None) == 1:
1420
    raise errors.ProgrammerError("Both atime and mtime must be either"
1421
                                 " set or None")
1422

    
1423
  if backup and not dry_run and os.path.isfile(file_name):
1424
    CreateBackup(file_name)
1425

    
1426
  dir_name, base_name = os.path.split(file_name)
1427
  fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
1428
  do_remove = True
1429
  # here we need to make sure we remove the temp file, if any error
1430
  # leaves it in place
1431
  try:
1432
    if uid != -1 or gid != -1:
1433
      os.chown(new_name, uid, gid)
1434
    if mode:
1435
      os.chmod(new_name, mode)
1436
    if callable(prewrite):
1437
      prewrite(fd)
1438
    if data is not None:
1439
      os.write(fd, data)
1440
    else:
1441
      fn(fd)
1442
    if callable(postwrite):
1443
      postwrite(fd)
1444
    os.fsync(fd)
1445
    if atime is not None and mtime is not None:
1446
      os.utime(new_name, (atime, mtime))
1447
    if not dry_run:
1448
      os.rename(new_name, file_name)
1449
      do_remove = False
1450
  finally:
1451
    if close:
1452
      os.close(fd)
1453
      result = None
1454
    else:
1455
      result = fd
1456
    if do_remove:
1457
      RemoveFile(new_name)
1458

    
1459
  return result
1460

    
1461

    
1462
def FirstFree(seq, base=0):
1463
  """Returns the first non-existing integer from seq.
1464

1465
  The seq argument should be a sorted list of positive integers. The
1466
  first time the index of an element is smaller than the element
1467
  value, the index will be returned.
1468

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

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

1474
  @type seq: sequence
1475
  @param seq: the sequence to be analyzed.
1476
  @type base: int
1477
  @param base: use this value as the base index of the sequence
1478
  @rtype: int
1479
  @return: the first non-used index in the sequence
1480

1481
  """
1482
  for idx, elem in enumerate(seq):
1483
    assert elem >= base, "Passed element is higher than base offset"
1484
    if elem > idx + base:
1485
      # idx is not used
1486
      return idx + base
1487
  return None
1488

    
1489

    
1490
def all(seq, pred=bool): # pylint: disable-msg=W0622
1491
  "Returns True if pred(x) is True for every element in the iterable"
1492
  for _ in itertools.ifilterfalse(pred, seq):
1493
    return False
1494
  return True
1495

    
1496

    
1497
def any(seq, pred=bool): # pylint: disable-msg=W0622
1498
  "Returns True if pred(x) is True for at least one element in the iterable"
1499
  for _ in itertools.ifilter(pred, seq):
1500
    return True
1501
  return False
1502

    
1503

    
1504
def SingleWaitForFdCondition(fdobj, event, timeout):
1505
  """Waits for a condition to occur on the socket.
1506

1507
  Immediately returns at the first interruption.
1508

1509
  @type fdobj: integer or object supporting a fileno() method
1510
  @param fdobj: entity to wait for events on
1511
  @type event: integer
1512
  @param event: ORed condition (see select module)
1513
  @type timeout: float or None
1514
  @param timeout: Timeout in seconds
1515
  @rtype: int or None
1516
  @return: None for timeout, otherwise occured conditions
1517

1518
  """
1519
  check = (event | select.POLLPRI |
1520
           select.POLLNVAL | select.POLLHUP | select.POLLERR)
1521

    
1522
  if timeout is not None:
1523
    # Poller object expects milliseconds
1524
    timeout *= 1000
1525

    
1526
  poller = select.poll()
1527
  poller.register(fdobj, event)
1528
  try:
1529
    # TODO: If the main thread receives a signal and we have no timeout, we
1530
    # could wait forever. This should check a global "quit" flag or something
1531
    # every so often.
1532
    io_events = poller.poll(timeout)
1533
  except select.error, err:
1534
    if err[0] != errno.EINTR:
1535
      raise
1536
    io_events = []
1537
  if io_events and io_events[0][1] & check:
1538
    return io_events[0][1]
1539
  else:
1540
    return None
1541

    
1542

    
1543
class FdConditionWaiterHelper(object):
1544
  """Retry helper for WaitForFdCondition.
1545

1546
  This class contains the retried and wait functions that make sure
1547
  WaitForFdCondition can continue waiting until the timeout is actually
1548
  expired.
1549

1550
  """
1551

    
1552
  def __init__(self, timeout):
1553
    self.timeout = timeout
1554

    
1555
  def Poll(self, fdobj, event):
1556
    result = SingleWaitForFdCondition(fdobj, event, self.timeout)
1557
    if result is None:
1558
      raise RetryAgain()
1559
    else:
1560
      return result
1561

    
1562
  def UpdateTimeout(self, timeout):
1563
    self.timeout = timeout
1564

    
1565

    
1566
def WaitForFdCondition(fdobj, event, timeout):
1567
  """Waits for a condition to occur on the socket.
1568

1569
  Retries until the timeout is expired, even if interrupted.
1570

1571
  @type fdobj: integer or object supporting a fileno() method
1572
  @param fdobj: entity to wait for events on
1573
  @type event: integer
1574
  @param event: ORed condition (see select module)
1575
  @type timeout: float or None
1576
  @param timeout: Timeout in seconds
1577
  @rtype: int or None
1578
  @return: None for timeout, otherwise occured conditions
1579

1580
  """
1581
  if timeout is not None:
1582
    retrywaiter = FdConditionWaiterHelper(timeout)
1583
    try:
1584
      result = Retry(retrywaiter.Poll, RETRY_REMAINING_TIME, timeout,
1585
                     args=(fdobj, event), wait_fn=retrywaiter.UpdateTimeout)
1586
    except RetryTimeout:
1587
      result = None
1588
  else:
1589
    result = None
1590
    while result is None:
1591
      result = SingleWaitForFdCondition(fdobj, event, timeout)
1592
  return result
1593

    
1594

    
1595
def partition(seq, pred=bool): # # pylint: disable-msg=W0622
1596
  "Partition a list in two, based on the given predicate"
1597
  return (list(itertools.ifilter(pred, seq)),
1598
          list(itertools.ifilterfalse(pred, seq)))
1599

    
1600

    
1601
def UniqueSequence(seq):
1602
  """Returns a list with unique elements.
1603

1604
  Element order is preserved.
1605

1606
  @type seq: sequence
1607
  @param seq: the sequence with the source elements
1608
  @rtype: list
1609
  @return: list of unique elements from seq
1610

1611
  """
1612
  seen = set()
1613
  return [i for i in seq if i not in seen and not seen.add(i)]
1614

    
1615

    
1616
def NormalizeAndValidateMac(mac):
1617
  """Normalizes and check if a MAC address is valid.
1618

1619
  Checks whether the supplied MAC address is formally correct, only
1620
  accepts colon separated format. Normalize it to all lower.
1621

1622
  @type mac: str
1623
  @param mac: the MAC to be validated
1624
  @rtype: str
1625
  @return: returns the normalized and validated MAC.
1626

1627
  @raise errors.OpPrereqError: If the MAC isn't valid
1628

1629
  """
1630
  mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$", re.I)
1631
  if not mac_check.match(mac):
1632
    raise errors.OpPrereqError("Invalid MAC address specified: %s" %
1633
                               mac, errors.ECODE_INVAL)
1634

    
1635
  return mac.lower()
1636

    
1637

    
1638
def TestDelay(duration):
1639
  """Sleep for a fixed amount of time.
1640

1641
  @type duration: float
1642
  @param duration: the sleep duration
1643
  @rtype: boolean
1644
  @return: False for negative value, True otherwise
1645

1646
  """
1647
  if duration < 0:
1648
    return False, "Invalid sleep duration"
1649
  time.sleep(duration)
1650
  return True, None
1651

    
1652

    
1653
def _CloseFDNoErr(fd, retries=5):
1654
  """Close a file descriptor ignoring errors.
1655

1656
  @type fd: int
1657
  @param fd: the file descriptor
1658
  @type retries: int
1659
  @param retries: how many retries to make, in case we get any
1660
      other error than EBADF
1661

1662
  """
1663
  try:
1664
    os.close(fd)
1665
  except OSError, err:
1666
    if err.errno != errno.EBADF:
1667
      if retries > 0:
1668
        _CloseFDNoErr(fd, retries - 1)
1669
    # else either it's closed already or we're out of retries, so we
1670
    # ignore this and go on
1671

    
1672

    
1673
def CloseFDs(noclose_fds=None):
1674
  """Close file descriptors.
1675

1676
  This closes all file descriptors above 2 (i.e. except
1677
  stdin/out/err).
1678

1679
  @type noclose_fds: list or None
1680
  @param noclose_fds: if given, it denotes a list of file descriptor
1681
      that should not be closed
1682

1683
  """
1684
  # Default maximum for the number of available file descriptors.
1685
  if 'SC_OPEN_MAX' in os.sysconf_names:
1686
    try:
1687
      MAXFD = os.sysconf('SC_OPEN_MAX')
1688
      if MAXFD < 0:
1689
        MAXFD = 1024
1690
    except OSError:
1691
      MAXFD = 1024
1692
  else:
1693
    MAXFD = 1024
1694
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1695
  if (maxfd == resource.RLIM_INFINITY):
1696
    maxfd = MAXFD
1697

    
1698
  # Iterate through and close all file descriptors (except the standard ones)
1699
  for fd in range(3, maxfd):
1700
    if noclose_fds and fd in noclose_fds:
1701
      continue
1702
    _CloseFDNoErr(fd)
1703

    
1704

    
1705
def Daemonize(logfile):
1706
  """Daemonize the current process.
1707

1708
  This detaches the current process from the controlling terminal and
1709
  runs it in the background as a daemon.
1710

1711
  @type logfile: str
1712
  @param logfile: the logfile to which we should redirect stdout/stderr
1713
  @rtype: int
1714
  @return: the value zero
1715

1716
  """
1717
  # pylint: disable-msg=W0212
1718
  # yes, we really want os._exit
1719
  UMASK = 077
1720
  WORKDIR = "/"
1721

    
1722
  # this might fail
1723
  pid = os.fork()
1724
  if (pid == 0):  # The first child.
1725
    os.setsid()
1726
    # this might fail
1727
    pid = os.fork() # Fork a second child.
1728
    if (pid == 0):  # The second child.
1729
      os.chdir(WORKDIR)
1730
      os.umask(UMASK)
1731
    else:
1732
      # exit() or _exit()?  See below.
1733
      os._exit(0) # Exit parent (the first child) of the second child.
1734
  else:
1735
    os._exit(0) # Exit parent of the first child.
1736

    
1737
  for fd in range(3):
1738
    _CloseFDNoErr(fd)
1739
  i = os.open("/dev/null", os.O_RDONLY) # stdin
1740
  assert i == 0, "Can't close/reopen stdin"
1741
  i = os.open(logfile, os.O_WRONLY|os.O_CREAT|os.O_APPEND, 0600) # stdout
1742
  assert i == 1, "Can't close/reopen stdout"
1743
  # Duplicate standard output to standard error.
1744
  os.dup2(1, 2)
1745
  return 0
1746

    
1747

    
1748
def DaemonPidFileName(name):
1749
  """Compute a ganeti pid file absolute path
1750

1751
  @type name: str
1752
  @param name: the daemon name
1753
  @rtype: str
1754
  @return: the full path to the pidfile corresponding to the given
1755
      daemon name
1756

1757
  """
1758
  return PathJoin(constants.RUN_GANETI_DIR, "%s.pid" % name)
1759

    
1760

    
1761
def EnsureDaemon(name):
1762
  """Check for and start daemon if not alive.
1763

1764
  """
1765
  result = RunCmd([constants.DAEMON_UTIL, "check-and-start", name])
1766
  if result.failed:
1767
    logging.error("Can't start daemon '%s', failure %s, output: %s",
1768
                  name, result.fail_reason, result.output)
1769
    return False
1770

    
1771
  return True
1772

    
1773

    
1774
def WritePidFile(name):
1775
  """Write the current process pidfile.
1776

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

1779
  @type name: str
1780
  @param name: the daemon name to use
1781
  @raise errors.GenericError: if the pid file already exists and
1782
      points to a live process
1783

1784
  """
1785
  pid = os.getpid()
1786
  pidfilename = DaemonPidFileName(name)
1787
  if IsProcessAlive(ReadPidFile(pidfilename)):
1788
    raise errors.GenericError("%s contains a live process" % pidfilename)
1789

    
1790
  WriteFile(pidfilename, data="%d\n" % pid)
1791

    
1792

    
1793
def RemovePidFile(name):
1794
  """Remove the current process pidfile.
1795

1796
  Any errors are ignored.
1797

1798
  @type name: str
1799
  @param name: the daemon name used to derive the pidfile name
1800

1801
  """
1802
  pidfilename = DaemonPidFileName(name)
1803
  # TODO: we could check here that the file contains our pid
1804
  try:
1805
    RemoveFile(pidfilename)
1806
  except: # pylint: disable-msg=W0702
1807
    pass
1808

    
1809

    
1810
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1811
                waitpid=False):
1812
  """Kill a process given by its pid.
1813

1814
  @type pid: int
1815
  @param pid: The PID to terminate.
1816
  @type signal_: int
1817
  @param signal_: The signal to send, by default SIGTERM
1818
  @type timeout: int
1819
  @param timeout: The timeout after which, if the process is still alive,
1820
                  a SIGKILL will be sent. If not positive, no such checking
1821
                  will be done
1822
  @type waitpid: boolean
1823
  @param waitpid: If true, we should waitpid on this process after
1824
      sending signals, since it's our own child and otherwise it
1825
      would remain as zombie
1826

1827
  """
1828
  def _helper(pid, signal_, wait):
1829
    """Simple helper to encapsulate the kill/waitpid sequence"""
1830
    os.kill(pid, signal_)
1831
    if wait:
1832
      try:
1833
        os.waitpid(pid, os.WNOHANG)
1834
      except OSError:
1835
        pass
1836

    
1837
  if pid <= 0:
1838
    # kill with pid=0 == suicide
1839
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1840

    
1841
  if not IsProcessAlive(pid):
1842
    return
1843

    
1844
  _helper(pid, signal_, waitpid)
1845

    
1846
  if timeout <= 0:
1847
    return
1848

    
1849
  def _CheckProcess():
1850
    if not IsProcessAlive(pid):
1851
      return
1852

    
1853
    try:
1854
      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
1855
    except OSError:
1856
      raise RetryAgain()
1857

    
1858
    if result_pid > 0:
1859
      return
1860

    
1861
    raise RetryAgain()
1862

    
1863
  try:
1864
    # Wait up to $timeout seconds
1865
    Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
1866
  except RetryTimeout:
1867
    pass
1868

    
1869
  if IsProcessAlive(pid):
1870
    # Kill process if it's still alive
1871
    _helper(pid, signal.SIGKILL, waitpid)
1872

    
1873

    
1874
def FindFile(name, search_path, test=os.path.exists):
1875
  """Look for a filesystem object in a given path.
1876

1877
  This is an abstract method to search for filesystem object (files,
1878
  dirs) under a given search path.
1879

1880
  @type name: str
1881
  @param name: the name to look for
1882
  @type search_path: str
1883
  @param search_path: location to start at
1884
  @type test: callable
1885
  @param test: a function taking one argument that should return True
1886
      if the a given object is valid; the default value is
1887
      os.path.exists, causing only existing files to be returned
1888
  @rtype: str or None
1889
  @return: full path to the object if found, None otherwise
1890

1891
  """
1892
  # validate the filename mask
1893
  if constants.EXT_PLUGIN_MASK.match(name) is None:
1894
    logging.critical("Invalid value passed for external script name: '%s'",
1895
                     name)
1896
    return None
1897

    
1898
  for dir_name in search_path:
1899
    # FIXME: investigate switch to PathJoin
1900
    item_name = os.path.sep.join([dir_name, name])
1901
    # check the user test and that we're indeed resolving to the given
1902
    # basename
1903
    if test(item_name) and os.path.basename(item_name) == name:
1904
      return item_name
1905
  return None
1906

    
1907

    
1908
def CheckVolumeGroupSize(vglist, vgname, minsize):
1909
  """Checks if the volume group list is valid.
1910

1911
  The function will check if a given volume group is in the list of
1912
  volume groups and has a minimum size.
1913

1914
  @type vglist: dict
1915
  @param vglist: dictionary of volume group names and their size
1916
  @type vgname: str
1917
  @param vgname: the volume group we should check
1918
  @type minsize: int
1919
  @param minsize: the minimum size we accept
1920
  @rtype: None or str
1921
  @return: None for success, otherwise the error message
1922

1923
  """
1924
  vgsize = vglist.get(vgname, None)
1925
  if vgsize is None:
1926
    return "volume group '%s' missing" % vgname
1927
  elif vgsize < minsize:
1928
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1929
            (vgname, minsize, vgsize))
1930
  return None
1931

    
1932

    
1933
def SplitTime(value):
1934
  """Splits time as floating point number into a tuple.
1935

1936
  @param value: Time in seconds
1937
  @type value: int or float
1938
  @return: Tuple containing (seconds, microseconds)
1939

1940
  """
1941
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1942

    
1943
  assert 0 <= seconds, \
1944
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1945
  assert 0 <= microseconds <= 999999, \
1946
    "Microseconds must be 0-999999, but are %s" % microseconds
1947

    
1948
  return (int(seconds), int(microseconds))
1949

    
1950

    
1951
def MergeTime(timetuple):
1952
  """Merges a tuple into time as a floating point number.
1953

1954
  @param timetuple: Time as tuple, (seconds, microseconds)
1955
  @type timetuple: tuple
1956
  @return: Time as a floating point number expressed in seconds
1957

1958
  """
1959
  (seconds, microseconds) = timetuple
1960

    
1961
  assert 0 <= seconds, \
1962
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1963
  assert 0 <= microseconds <= 999999, \
1964
    "Microseconds must be 0-999999, but are %s" % microseconds
1965

    
1966
  return float(seconds) + (float(microseconds) * 0.000001)
1967

    
1968

    
1969
def GetDaemonPort(daemon_name):
1970
  """Get the daemon port for this cluster.
1971

1972
  Note that this routine does not read a ganeti-specific file, but
1973
  instead uses C{socket.getservbyname} to allow pre-customization of
1974
  this parameter outside of Ganeti.
1975

1976
  @type daemon_name: string
1977
  @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
1978
  @rtype: int
1979

1980
  """
1981
  if daemon_name not in constants.DAEMONS_PORTS:
1982
    raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
1983

    
1984
  (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
1985
  try:
1986
    port = socket.getservbyname(daemon_name, proto)
1987
  except socket.error:
1988
    port = default_port
1989

    
1990
  return port
1991

    
1992

    
1993
def SetupLogging(logfile, debug=0, stderr_logging=False, program="",
1994
                 multithreaded=False, syslog=constants.SYSLOG_USAGE):
1995
  """Configures the logging module.
1996

1997
  @type logfile: str
1998
  @param logfile: the filename to which we should log
1999
  @type debug: integer
2000
  @param debug: if greater than zero, enable debug messages, otherwise
2001
      only those at C{INFO} and above level
2002
  @type stderr_logging: boolean
2003
  @param stderr_logging: whether we should also log to the standard error
2004
  @type program: str
2005
  @param program: the name under which we should log messages
2006
  @type multithreaded: boolean
2007
  @param multithreaded: if True, will add the thread name to the log file
2008
  @type syslog: string
2009
  @param syslog: one of 'no', 'yes', 'only':
2010
      - if no, syslog is not used
2011
      - if yes, syslog is used (in addition to file-logging)
2012
      - if only, only syslog is used
2013
  @raise EnvironmentError: if we can't open the log file and
2014
      syslog/stderr logging is disabled
2015

2016
  """
2017
  fmt = "%(asctime)s: " + program + " pid=%(process)d"
2018
  sft = program + "[%(process)d]:"
2019
  if multithreaded:
2020
    fmt += "/%(threadName)s"
2021
    sft += " (%(threadName)s)"
2022
  if debug:
2023
    fmt += " %(module)s:%(lineno)s"
2024
    # no debug info for syslog loggers
2025
  fmt += " %(levelname)s %(message)s"
2026
  # yes, we do want the textual level, as remote syslog will probably
2027
  # lose the error level, and it's easier to grep for it
2028
  sft += " %(levelname)s %(message)s"
2029
  formatter = logging.Formatter(fmt)
2030
  sys_fmt = logging.Formatter(sft)
2031

    
2032
  root_logger = logging.getLogger("")
2033
  root_logger.setLevel(logging.NOTSET)
2034

    
2035
  # Remove all previously setup handlers
2036
  for handler in root_logger.handlers:
2037
    handler.close()
2038
    root_logger.removeHandler(handler)
2039

    
2040
  if stderr_logging:
2041
    stderr_handler = logging.StreamHandler()
2042
    stderr_handler.setFormatter(formatter)
2043
    if debug:
2044
      stderr_handler.setLevel(logging.NOTSET)
2045
    else:
2046
      stderr_handler.setLevel(logging.CRITICAL)
2047
    root_logger.addHandler(stderr_handler)
2048

    
2049
  if syslog in (constants.SYSLOG_YES, constants.SYSLOG_ONLY):
2050
    facility = logging.handlers.SysLogHandler.LOG_DAEMON
2051
    syslog_handler = logging.handlers.SysLogHandler(constants.SYSLOG_SOCKET,
2052
                                                    facility)
2053
    syslog_handler.setFormatter(sys_fmt)
2054
    # Never enable debug over syslog
2055
    syslog_handler.setLevel(logging.INFO)
2056
    root_logger.addHandler(syslog_handler)
2057

    
2058
  if syslog != constants.SYSLOG_ONLY:
2059
    # this can fail, if the logging directories are not setup or we have
2060
    # a permisssion problem; in this case, it's best to log but ignore
2061
    # the error if stderr_logging is True, and if false we re-raise the
2062
    # exception since otherwise we could run but without any logs at all
2063
    try:
2064
      logfile_handler = logging.FileHandler(logfile)
2065
      logfile_handler.setFormatter(formatter)
2066
      if debug:
2067
        logfile_handler.setLevel(logging.DEBUG)
2068
      else:
2069
        logfile_handler.setLevel(logging.INFO)
2070
      root_logger.addHandler(logfile_handler)
2071
    except EnvironmentError:
2072
      if stderr_logging or syslog == constants.SYSLOG_YES:
2073
        logging.exception("Failed to enable logging to file '%s'", logfile)
2074
      else:
2075
        # we need to re-raise the exception
2076
        raise
2077

    
2078

    
2079
def IsNormAbsPath(path):
2080
  """Check whether a path is absolute and also normalized
2081

2082
  This avoids things like /dir/../../other/path to be valid.
2083

2084
  """
2085
  return os.path.normpath(path) == path and os.path.isabs(path)
2086

    
2087

    
2088
def PathJoin(*args):
2089
  """Safe-join a list of path components.
2090

2091
  Requirements:
2092
      - the first argument must be an absolute path
2093
      - no component in the path must have backtracking (e.g. /../),
2094
        since we check for normalization at the end
2095

2096
  @param args: the path components to be joined
2097
  @raise ValueError: for invalid paths
2098

2099
  """
2100
  # ensure we're having at least one path passed in
2101
  assert args
2102
  # ensure the first component is an absolute and normalized path name
2103
  root = args[0]
2104
  if not IsNormAbsPath(root):
2105
    raise ValueError("Invalid parameter to PathJoin: '%s'" % str(args[0]))
2106
  result = os.path.join(*args)
2107
  # ensure that the whole path is normalized
2108
  if not IsNormAbsPath(result):
2109
    raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
2110
  # check that we're still under the original prefix
2111
  prefix = os.path.commonprefix([root, result])
2112
  if prefix != root:
2113
    raise ValueError("Error: path joining resulted in different prefix"
2114
                     " (%s != %s)" % (prefix, root))
2115
  return result
2116

    
2117

    
2118
def TailFile(fname, lines=20):
2119
  """Return the last lines from a file.
2120

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

2125
  @param fname: the file name
2126
  @type lines: int
2127
  @param lines: the (maximum) number of lines to return
2128

2129
  """
2130
  fd = open(fname, "r")
2131
  try:
2132
    fd.seek(0, 2)
2133
    pos = fd.tell()
2134
    pos = max(0, pos-4096)
2135
    fd.seek(pos, 0)
2136
    raw_data = fd.read()
2137
  finally:
2138
    fd.close()
2139

    
2140
  rows = raw_data.splitlines()
2141
  return rows[-lines:]
2142

    
2143

    
2144
def _ParseAsn1Generalizedtime(value):
2145
  """Parses an ASN1 GENERALIZEDTIME timestamp as used by pyOpenSSL.
2146

2147
  @type value: string
2148
  @param value: ASN1 GENERALIZEDTIME timestamp
2149

2150
  """
2151
  m = re.match(r"^(\d+)([-+]\d\d)(\d\d)$", value)
2152
  if m:
2153
    # We have an offset
2154
    asn1time = m.group(1)
2155
    hours = int(m.group(2))
2156
    minutes = int(m.group(3))
2157
    utcoffset = (60 * hours) + minutes
2158
  else:
2159
    if not value.endswith("Z"):
2160
      raise ValueError("Missing timezone")
2161
    asn1time = value[:-1]
2162
    utcoffset = 0
2163

    
2164
  parsed = time.strptime(asn1time, "%Y%m%d%H%M%S")
2165

    
2166
  tt = datetime.datetime(*(parsed[:7])) - datetime.timedelta(minutes=utcoffset)
2167

    
2168
  return calendar.timegm(tt.utctimetuple())
2169

    
2170

    
2171
def GetX509CertValidity(cert):
2172
  """Returns the validity period of the certificate.
2173

2174
  @type cert: OpenSSL.crypto.X509
2175
  @param cert: X509 certificate object
2176

2177
  """
2178
  # The get_notBefore and get_notAfter functions are only supported in
2179
  # pyOpenSSL 0.7 and above.
2180
  try:
2181
    get_notbefore_fn = cert.get_notBefore
2182
  except AttributeError:
2183
    not_before = None
2184
  else:
2185
    not_before_asn1 = get_notbefore_fn()
2186

    
2187
    if not_before_asn1 is None:
2188
      not_before = None
2189
    else:
2190
      not_before = _ParseAsn1Generalizedtime(not_before_asn1)
2191

    
2192
  try:
2193
    get_notafter_fn = cert.get_notAfter
2194
  except AttributeError:
2195
    not_after = None
2196
  else:
2197
    not_after_asn1 = get_notafter_fn()
2198

    
2199
    if not_after_asn1 is None:
2200
      not_after = None
2201
    else:
2202
      not_after = _ParseAsn1Generalizedtime(not_after_asn1)
2203

    
2204
  return (not_before, not_after)
2205

    
2206

    
2207
def SafeEncode(text):
2208
  """Return a 'safe' version of a source string.
2209

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

2219
  @type text: str or unicode
2220
  @param text: input data
2221
  @rtype: str
2222
  @return: a safe version of text
2223

2224
  """
2225
  if isinstance(text, unicode):
2226
    # only if unicode; if str already, we handle it below
2227
    text = text.encode('ascii', 'backslashreplace')
2228
  resu = ""
2229
  for char in text:
2230
    c = ord(char)
2231
    if char  == '\t':
2232
      resu += r'\t'
2233
    elif char == '\n':
2234
      resu += r'\n'
2235
    elif char == '\r':
2236
      resu += r'\'r'
2237
    elif c < 32 or c >= 127: # non-printable
2238
      resu += "\\x%02x" % (c & 0xff)
2239
    else:
2240
      resu += char
2241
  return resu
2242

    
2243

    
2244
def UnescapeAndSplit(text, sep=","):
2245
  """Split and unescape a string based on a given separator.
2246

2247
  This function splits a string based on a separator where the
2248
  separator itself can be escape in order to be an element of the
2249
  elements. The escaping rules are (assuming coma being the
2250
  separator):
2251
    - a plain , separates the elements
2252
    - a sequence \\\\, (double backslash plus comma) is handled as a
2253
      backslash plus a separator comma
2254
    - a sequence \, (backslash plus comma) is handled as a
2255
      non-separator comma
2256

2257
  @type text: string
2258
  @param text: the string to split
2259
  @type sep: string
2260
  @param text: the separator
2261
  @rtype: string
2262
  @return: a list of strings
2263

2264
  """
2265
  # we split the list by sep (with no escaping at this stage)
2266
  slist = text.split(sep)
2267
  # next, we revisit the elements and if any of them ended with an odd
2268
  # number of backslashes, then we join it with the next
2269
  rlist = []
2270
  while slist:
2271
    e1 = slist.pop(0)
2272
    if e1.endswith("\\"):
2273
      num_b = len(e1) - len(e1.rstrip("\\"))
2274
      if num_b % 2 == 1:
2275
        e2 = slist.pop(0)
2276
        # here the backslashes remain (all), and will be reduced in
2277
        # the next step
2278
        rlist.append(e1 + sep + e2)
2279
        continue
2280
    rlist.append(e1)
2281
  # finally, replace backslash-something with something
2282
  rlist = [re.sub(r"\\(.)", r"\1", v) for v in rlist]
2283
  return rlist
2284

    
2285

    
2286
def CommaJoin(names):
2287
  """Nicely join a set of identifiers.
2288

2289
  @param names: set, list or tuple
2290
  @return: a string with the formatted results
2291

2292
  """
2293
  return ", ".join([str(val) for val in names])
2294

    
2295

    
2296
def BytesToMebibyte(value):
2297
  """Converts bytes to mebibytes.
2298

2299
  @type value: int
2300
  @param value: Value in bytes
2301
  @rtype: int
2302
  @return: Value in mebibytes
2303

2304
  """
2305
  return int(round(value / (1024.0 * 1024.0), 0))
2306

    
2307

    
2308
def CalculateDirectorySize(path):
2309
  """Calculates the size of a directory recursively.
2310

2311
  @type path: string
2312
  @param path: Path to directory
2313
  @rtype: int
2314
  @return: Size in mebibytes
2315

2316
  """
2317
  size = 0
2318

    
2319
  for (curpath, _, files) in os.walk(path):
2320
    for filename in files:
2321
      st = os.lstat(PathJoin(curpath, filename))
2322
      size += st.st_size
2323

    
2324
  return BytesToMebibyte(size)
2325

    
2326

    
2327
def GetFilesystemStats(path):
2328
  """Returns the total and free space on a filesystem.
2329

2330
  @type path: string
2331
  @param path: Path on filesystem to be examined
2332
  @rtype: int
2333
  @return: tuple of (Total space, Free space) in mebibytes
2334

2335
  """
2336
  st = os.statvfs(path)
2337

    
2338
  fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
2339
  tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
2340
  return (tsize, fsize)
2341

    
2342

    
2343
def RunInSeparateProcess(fn, *args):
2344
  """Runs a function in a separate process.
2345

2346
  Note: Only boolean return values are supported.
2347

2348
  @type fn: callable
2349
  @param fn: Function to be called
2350
  @rtype: bool
2351
  @return: Function's result
2352

2353
  """
2354
  pid = os.fork()
2355
  if pid == 0:
2356
    # Child process
2357
    try:
2358
      # In case the function uses temporary files
2359
      ResetTempfileModule()
2360

    
2361
      # Call function
2362
      result = int(bool(fn(*args)))
2363
      assert result in (0, 1)
2364
    except: # pylint: disable-msg=W0702
2365
      logging.exception("Error while calling function in separate process")
2366
      # 0 and 1 are reserved for the return value
2367
      result = 33
2368

    
2369
    os._exit(result) # pylint: disable-msg=W0212
2370

    
2371
  # Parent process
2372

    
2373
  # Avoid zombies and check exit code
2374
  (_, status) = os.waitpid(pid, 0)
2375

    
2376
  if os.WIFSIGNALED(status):
2377
    exitcode = None
2378
    signum = os.WTERMSIG(status)
2379
  else:
2380
    exitcode = os.WEXITSTATUS(status)
2381
    signum = None
2382

    
2383
  if not (exitcode in (0, 1) and signum is None):
2384
    raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
2385
                              (exitcode, signum))
2386

    
2387
  return bool(exitcode)
2388

    
2389

    
2390
def LockedMethod(fn):
2391
  """Synchronized object access decorator.
2392

2393
  This decorator is intended to protect access to an object using the
2394
  object's own lock which is hardcoded to '_lock'.
2395

2396
  """
2397
  def _LockDebug(*args, **kwargs):
2398
    if debug_locks:
2399
      logging.debug(*args, **kwargs)
2400

    
2401
  def wrapper(self, *args, **kwargs):
2402
    # pylint: disable-msg=W0212
2403
    assert hasattr(self, '_lock')
2404
    lock = self._lock
2405
    _LockDebug("Waiting for %s", lock)
2406
    lock.acquire()
2407
    try:
2408
      _LockDebug("Acquired %s", lock)
2409
      result = fn(self, *args, **kwargs)
2410
    finally:
2411
      _LockDebug("Releasing %s", lock)
2412
      lock.release()
2413
      _LockDebug("Released %s", lock)
2414
    return result
2415
  return wrapper
2416

    
2417

    
2418
def LockFile(fd):
2419
  """Locks a file using POSIX locks.
2420

2421
  @type fd: int
2422
  @param fd: the file descriptor we need to lock
2423

2424
  """
2425
  try:
2426
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
2427
  except IOError, err:
2428
    if err.errno == errno.EAGAIN:
2429
      raise errors.LockError("File already locked")
2430
    raise
2431

    
2432

    
2433
def FormatTime(val):
2434
  """Formats a time value.
2435

2436
  @type val: float or None
2437
  @param val: the timestamp as returned by time.time()
2438
  @return: a string value or N/A if we don't have a valid timestamp
2439

2440
  """
2441
  if val is None or not isinstance(val, (int, float)):
2442
    return "N/A"
2443
  # these two codes works on Linux, but they are not guaranteed on all
2444
  # platforms
2445
  return time.strftime("%F %T", time.localtime(val))
2446

    
2447

    
2448
def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
2449
  """Reads the watcher pause file.
2450

2451
  @type filename: string
2452
  @param filename: Path to watcher pause file
2453
  @type now: None, float or int
2454
  @param now: Current time as Unix timestamp
2455
  @type remove_after: int
2456
  @param remove_after: Remove watcher pause file after specified amount of
2457
    seconds past the pause end time
2458

2459
  """
2460
  if now is None:
2461
    now = time.time()
2462

    
2463
  try:
2464
    value = ReadFile(filename)
2465
  except IOError, err:
2466
    if err.errno != errno.ENOENT:
2467
      raise
2468
    value = None
2469

    
2470
  if value is not None:
2471
    try:
2472
      value = int(value)
2473
    except ValueError:
2474
      logging.warning(("Watcher pause file (%s) contains invalid value,"
2475
                       " removing it"), filename)
2476
      RemoveFile(filename)
2477
      value = None
2478

    
2479
    if value is not None:
2480
      # Remove file if it's outdated
2481
      if now > (value + remove_after):
2482
        RemoveFile(filename)
2483
        value = None
2484

    
2485
      elif now > value:
2486
        value = None
2487

    
2488
  return value
2489

    
2490

    
2491
class RetryTimeout(Exception):
2492
  """Retry loop timed out.
2493

2494
  """
2495

    
2496

    
2497
class RetryAgain(Exception):
2498
  """Retry again.
2499

2500
  """
2501

    
2502

    
2503
class _RetryDelayCalculator(object):
2504
  """Calculator for increasing delays.
2505

2506
  """
2507
  __slots__ = [
2508
    "_factor",
2509
    "_limit",
2510
    "_next",
2511
    "_start",
2512
    ]
2513

    
2514
  def __init__(self, start, factor, limit):
2515
    """Initializes this class.
2516

2517
    @type start: float
2518
    @param start: Initial delay
2519
    @type factor: float
2520
    @param factor: Factor for delay increase
2521
    @type limit: float or None
2522
    @param limit: Upper limit for delay or None for no limit
2523

2524
    """
2525
    assert start > 0.0
2526
    assert factor >= 1.0
2527
    assert limit is None or limit >= 0.0
2528

    
2529
    self._start = start
2530
    self._factor = factor
2531
    self._limit = limit
2532

    
2533
    self._next = start
2534

    
2535
  def __call__(self):
2536
    """Returns current delay and calculates the next one.
2537

2538
    """
2539
    current = self._next
2540

    
2541
    # Update for next run
2542
    if self._limit is None or self._next < self._limit:
2543
      self._next = min(self._limit, self._next * self._factor)
2544

    
2545
    return current
2546

    
2547

    
2548
#: Special delay to specify whole remaining timeout
2549
RETRY_REMAINING_TIME = object()
2550

    
2551

    
2552
def Retry(fn, delay, timeout, args=None, wait_fn=time.sleep,
2553
          _time_fn=time.time):
2554
  """Call a function repeatedly until it succeeds.
2555

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

2560
  C{delay} can be one of the following:
2561
    - callable returning the delay length as a float
2562
    - Tuple of (start, factor, limit)
2563
    - L{RETRY_REMAINING_TIME} to sleep until the timeout expires (this is
2564
      useful when overriding L{wait_fn} to wait for an external event)
2565
    - A static delay as a number (int or float)
2566

2567
  @type fn: callable
2568
  @param fn: Function to be called
2569
  @param delay: Either a callable (returning the delay), a tuple of (start,
2570
                factor, limit) (see L{_RetryDelayCalculator}),
2571
                L{RETRY_REMAINING_TIME} or a number (int or float)
2572
  @type timeout: float
2573
  @param timeout: Total timeout
2574
  @type wait_fn: callable
2575
  @param wait_fn: Waiting function
2576
  @return: Return value of function
2577

2578
  """
2579
  assert callable(fn)
2580
  assert callable(wait_fn)
2581
  assert callable(_time_fn)
2582

    
2583
  if args is None:
2584
    args = []
2585

    
2586
  end_time = _time_fn() + timeout
2587

    
2588
  if callable(delay):
2589
    # External function to calculate delay
2590
    calc_delay = delay
2591

    
2592
  elif isinstance(delay, (tuple, list)):
2593
    # Increasing delay with optional upper boundary
2594
    (start, factor, limit) = delay
2595
    calc_delay = _RetryDelayCalculator(start, factor, limit)
2596

    
2597
  elif delay is RETRY_REMAINING_TIME:
2598
    # Always use the remaining time
2599
    calc_delay = None
2600

    
2601
  else:
2602
    # Static delay
2603
    calc_delay = lambda: delay
2604

    
2605
  assert calc_delay is None or callable(calc_delay)
2606

    
2607
  while True:
2608
    try:
2609
      # pylint: disable-msg=W0142
2610
      return fn(*args)
2611
    except RetryAgain:
2612
      pass
2613
    except RetryTimeout:
2614
      raise errors.ProgrammerError("Nested retry loop detected that didn't"
2615
                                   " handle RetryTimeout")
2616

    
2617
    remaining_time = end_time - _time_fn()
2618

    
2619
    if remaining_time < 0.0:
2620
      raise RetryTimeout()
2621

    
2622
    assert remaining_time >= 0.0
2623

    
2624
    if calc_delay is None:
2625
      wait_fn(remaining_time)
2626
    else:
2627
      current_delay = calc_delay()
2628
      if current_delay > 0.0:
2629
        wait_fn(current_delay)
2630

    
2631

    
2632
class FileLock(object):
2633
  """Utility class for file locks.
2634

2635
  """
2636
  def __init__(self, fd, filename):
2637
    """Constructor for FileLock.
2638

2639
    @type fd: file
2640
    @param fd: File object
2641
    @type filename: str
2642
    @param filename: Path of the file opened at I{fd}
2643

2644
    """
2645
    self.fd = fd
2646
    self.filename = filename
2647

    
2648
  @classmethod
2649
  def Open(cls, filename):
2650
    """Creates and opens a file to be used as a file-based lock.
2651

2652
    @type filename: string
2653
    @param filename: path to the file to be locked
2654

2655
    """
2656
    # Using "os.open" is necessary to allow both opening existing file
2657
    # read/write and creating if not existing. Vanilla "open" will truncate an
2658
    # existing file -or- allow creating if not existing.
2659
    return cls(os.fdopen(os.open(filename, os.O_RDWR | os.O_CREAT), "w+"),
2660
               filename)
2661

    
2662
  def __del__(self):
2663
    self.Close()
2664

    
2665
  def Close(self):
2666
    """Close the file and release the lock.
2667

2668
    """
2669
    if hasattr(self, "fd") and self.fd:
2670
      self.fd.close()
2671
      self.fd = None
2672

    
2673
  def _flock(self, flag, blocking, timeout, errmsg):
2674
    """Wrapper for fcntl.flock.
2675

2676
    @type flag: int
2677
    @param flag: operation flag
2678
    @type blocking: bool
2679
    @param blocking: whether the operation should be done in blocking mode.
2680
    @type timeout: None or float
2681
    @param timeout: for how long the operation should be retried (implies
2682
                    non-blocking mode).
2683
    @type errmsg: string
2684
    @param errmsg: error message in case operation fails.
2685

2686
    """
2687
    assert self.fd, "Lock was closed"
2688
    assert timeout is None or timeout >= 0, \
2689
      "If specified, timeout must be positive"
2690
    assert not (flag & fcntl.LOCK_NB), "LOCK_NB must not be set"
2691

    
2692
    # When a timeout is used, LOCK_NB must always be set
2693
    if not (timeout is None and blocking):
2694
      flag |= fcntl.LOCK_NB
2695

    
2696
    if timeout is None:
2697
      self._Lock(self.fd, flag, timeout)
2698
    else:
2699
      try:
2700
        Retry(self._Lock, (0.1, 1.2, 1.0), timeout,
2701
              args=(self.fd, flag, timeout))
2702
      except RetryTimeout:
2703
        raise errors.LockError(errmsg)
2704

    
2705
  @staticmethod
2706
  def _Lock(fd, flag, timeout):
2707
    try:
2708
      fcntl.flock(fd, flag)
2709
    except IOError, err:
2710
      if timeout is not None and err.errno == errno.EAGAIN:
2711
        raise RetryAgain()
2712

    
2713
      logging.exception("fcntl.flock failed")
2714
      raise
2715

    
2716
  def Exclusive(self, blocking=False, timeout=None):
2717
    """Locks the file in exclusive mode.
2718

2719
    @type blocking: boolean
2720
    @param blocking: whether to block and wait until we
2721
        can lock the file or return immediately
2722
    @type timeout: int or None
2723
    @param timeout: if not None, the duration to wait for the lock
2724
        (in blocking mode)
2725

2726
    """
2727
    self._flock(fcntl.LOCK_EX, blocking, timeout,
2728
                "Failed to lock %s in exclusive mode" % self.filename)
2729

    
2730
  def Shared(self, blocking=False, timeout=None):
2731
    """Locks the file in shared mode.
2732

2733
    @type blocking: boolean
2734
    @param blocking: whether to block and wait until we
2735
        can lock the file or return immediately
2736
    @type timeout: int or None
2737
    @param timeout: if not None, the duration to wait for the lock
2738
        (in blocking mode)
2739

2740
    """
2741
    self._flock(fcntl.LOCK_SH, blocking, timeout,
2742
                "Failed to lock %s in shared mode" % self.filename)
2743

    
2744
  def Unlock(self, blocking=True, timeout=None):
2745
    """Unlocks the file.
2746

2747
    According to C{flock(2)}, unlocking can also be a nonblocking
2748
    operation::
2749

2750
      To make a non-blocking request, include LOCK_NB with any of the above
2751
      operations.
2752

2753
    @type blocking: boolean
2754
    @param blocking: whether to block and wait until we
2755
        can lock the file or return immediately
2756
    @type timeout: int or None
2757
    @param timeout: if not None, the duration to wait for the lock
2758
        (in blocking mode)
2759

2760
    """
2761
    self._flock(fcntl.LOCK_UN, blocking, timeout,
2762
                "Failed to unlock %s" % self.filename)
2763

    
2764

    
2765
class LineSplitter:
2766
  """Splits data chunks into lines separated by newline.
2767

2768
  Instances provide a file-like interface.
2769

2770
  """
2771
  def __init__(self, line_fn, *args):
2772
    """Initializes this class.
2773

2774
    @type line_fn: callable
2775
    @param line_fn: Function called for each line, first parameter is line
2776
    @param args: Extra arguments for L{line_fn}
2777

2778
    """
2779
    assert callable(line_fn)
2780

    
2781
    if args:
2782
      # Python 2.4 doesn't have functools.partial yet
2783
      self._line_fn = \
2784
        lambda line: line_fn(line, *args) # pylint: disable-msg=W0142
2785
    else:
2786
      self._line_fn = line_fn
2787

    
2788
    self._lines = collections.deque()
2789
    self._buffer = ""
2790

    
2791
  def write(self, data):
2792
    parts = (self._buffer + data).split("\n")
2793
    self._buffer = parts.pop()
2794
    self._lines.extend(parts)
2795

    
2796
  def flush(self):
2797
    while self._lines:
2798
      self._line_fn(self._lines.popleft().rstrip("\r\n"))
2799

    
2800
  def close(self):
2801
    self.flush()
2802
    if self._buffer:
2803
      self._line_fn(self._buffer)
2804

    
2805

    
2806
def SignalHandled(signums):
2807
  """Signal Handled decoration.
2808

2809
  This special decorator installs a signal handler and then calls the target
2810
  function. The function must accept a 'signal_handlers' keyword argument,
2811
  which will contain a dict indexed by signal number, with SignalHandler
2812
  objects as values.
2813

2814
  The decorator can be safely stacked with iself, to handle multiple signals
2815
  with different handlers.
2816

2817
  @type signums: list
2818
  @param signums: signals to intercept
2819

2820
  """
2821
  def wrap(fn):
2822
    def sig_function(*args, **kwargs):
2823
      assert 'signal_handlers' not in kwargs or \
2824
             kwargs['signal_handlers'] is None or \
2825
             isinstance(kwargs['signal_handlers'], dict), \
2826
             "Wrong signal_handlers parameter in original function call"
2827
      if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
2828
        signal_handlers = kwargs['signal_handlers']
2829
      else:
2830
        signal_handlers = {}
2831
        kwargs['signal_handlers'] = signal_handlers
2832
      sighandler = SignalHandler(signums)
2833
      try:
2834
        for sig in signums:
2835
          signal_handlers[sig] = sighandler
2836
        return fn(*args, **kwargs)
2837
      finally:
2838
        sighandler.Reset()
2839
    return sig_function
2840
  return wrap
2841

    
2842

    
2843
class SignalHandler(object):
2844
  """Generic signal handler class.
2845

2846
  It automatically restores the original handler when deconstructed or
2847
  when L{Reset} is called. You can either pass your own handler
2848
  function in or query the L{called} attribute to detect whether the
2849
  signal was sent.
2850

2851
  @type signum: list
2852
  @ivar signum: the signals we handle
2853
  @type called: boolean
2854
  @ivar called: tracks whether any of the signals have been raised
2855

2856
  """
2857
  def __init__(self, signum):
2858
    """Constructs a new SignalHandler instance.
2859

2860
    @type signum: int or list of ints
2861
    @param signum: Single signal number or set of signal numbers
2862

2863
    """
2864
    self.signum = set(signum)
2865
    self.called = False
2866

    
2867
    self._previous = {}
2868
    try:
2869
      for signum in self.signum:
2870
        # Setup handler
2871
        prev_handler = signal.signal(signum, self._HandleSignal)
2872
        try:
2873
          self._previous[signum] = prev_handler
2874
        except:
2875
          # Restore previous handler
2876
          signal.signal(signum, prev_handler)
2877
          raise
2878
    except:
2879
      # Reset all handlers
2880
      self.Reset()
2881
      # Here we have a race condition: a handler may have already been called,
2882
      # but there's not much we can do about it at this point.
2883
      raise
2884

    
2885
  def __del__(self):
2886
    self.Reset()
2887

    
2888
  def Reset(self):
2889
    """Restore previous handler.
2890

2891
    This will reset all the signals to their previous handlers.
2892

2893
    """
2894
    for signum, prev_handler in self._previous.items():
2895
      signal.signal(signum, prev_handler)
2896
      # If successful, remove from dict
2897
      del self._previous[signum]
2898

    
2899
  def Clear(self):
2900
    """Unsets the L{called} flag.
2901

2902
    This function can be used in case a signal may arrive several times.
2903

2904
    """
2905
    self.called = False
2906

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

2911
    """
2912
    # This is not nice and not absolutely atomic, but it appears to be the only
2913
    # solution in Python -- there are no atomic types.
2914
    self.called = True
2915

    
2916

    
2917
class FieldSet(object):
2918
  """A simple field set.
2919

2920
  Among the features are:
2921
    - checking if a string is among a list of static string or regex objects
2922
    - checking if a whole list of string matches
2923
    - returning the matching groups from a regex match
2924

2925
  Internally, all fields are held as regular expression objects.
2926

2927
  """
2928
  def __init__(self, *items):
2929
    self.items = [re.compile("^%s$" % value) for value in items]
2930

    
2931
  def Extend(self, other_set):
2932
    """Extend the field set with the items from another one"""
2933
    self.items.extend(other_set.items)
2934

    
2935
  def Matches(self, field):
2936
    """Checks if a field matches the current set
2937

2938
    @type field: str
2939
    @param field: the string to match
2940
    @return: either None or a regular expression match object
2941

2942
    """
2943
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
2944
      return m
2945
    return None
2946

    
2947
  def NonMatching(self, items):
2948
    """Returns the list of fields not matching the current set
2949

2950
    @type items: list
2951
    @param items: the list of fields to check
2952
    @rtype: list
2953
    @return: list of non-matching fields
2954

2955
    """
2956
    return [val for val in items if not self.Matches(val)]