Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ 1b429e2a

History | View | Annotate | Download (78.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

    
49
from cStringIO import StringIO
50

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

    
57
from ganeti import errors
58
from ganeti import constants
59

    
60

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

    
64
debug_locks = False
65

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

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

    
71

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

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

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

    
94

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

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

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

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

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

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

    
122

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

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

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

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

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

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

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

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

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

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

    
190

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

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

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

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

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

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

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

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

    
259

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

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

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

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

    
292

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

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

305
  """
306
  rr = []
307

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

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

    
327
  return rr
328

    
329

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

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

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

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

    
346

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

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

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

    
370
      return os.rename(old, new)
371

    
372
    raise
373

    
374

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

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

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

    
390

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

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

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

    
413

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

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

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

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

    
430
  f = open(filename)
431

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

    
438
    fp.update(data)
439

    
440
  return fp.hexdigest()
441

    
442

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

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

452
  """
453
  ret = {}
454

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

    
460
  return ret
461

    
462

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

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

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

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

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

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

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

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

    
529

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

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

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

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

    
552

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

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

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

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

    
576
  return pid
577

    
578

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

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

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

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

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

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

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

    
624

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
679
    return result
680

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

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

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

    
702

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

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

    
712

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

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

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

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

    
736
    retval[name] = size
737

    
738
  return retval
739

    
740

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

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

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

    
752

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

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

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

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

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

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

    
789

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

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

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

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

    
811

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

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

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

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

    
829

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

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

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

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

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

    
848

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

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

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

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

    
870

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

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

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

    
889
  suffix = ''
890

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

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

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

    
906

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

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

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

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

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

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

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

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

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

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

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

    
949
  return value
950

    
951

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

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

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

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

    
980

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

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

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

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

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

    
1013

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

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

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

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

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

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

    
1060

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

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

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

    
1072

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

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

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

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

    
1102
          out.write(line)
1103

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

    
1116

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

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

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

    
1130

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

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

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

    
1140

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

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

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

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

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

    
1171
  return backup_name
1172

    
1173

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

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

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

    
1188

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

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

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

    
1200

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

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

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

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

    
1224
  success = False
1225

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

    
1233
  sock.settimeout(timeout)
1234

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

    
1244
  return success
1245

    
1246

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

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

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

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

    
1262

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

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

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

    
1280

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

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

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

    
1301

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

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

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

    
1312

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

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

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

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

    
1327

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

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

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

    
1345

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

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

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

    
1361

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

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

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

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

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

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

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

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

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

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

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

    
1458
  return result
1459

    
1460

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

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

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

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

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

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

    
1488

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

    
1495

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

    
1502

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

1506
  Immediately returns at the first interruption.
1507

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

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

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

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

    
1541

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

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

1549
  """
1550

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

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

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

    
1564

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

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

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

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

    
1593

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

    
1599

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

1603
  Element order is preserved.
1604

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

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

    
1614

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

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

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

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

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

    
1634
  return mac.lower()
1635

    
1636

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

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

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

    
1651

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

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

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

    
1671

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

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

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

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

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

    
1703

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

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

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

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

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

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

    
1746

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

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

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

    
1759

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

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

    
1770
  return True
1771

    
1772

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

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

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

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

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

    
1791

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

1795
  Any errors are ignored.
1796

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

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

    
1808

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

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

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

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

    
1840
  if not IsProcessAlive(pid):
1841
    return
1842

    
1843
  _helper(pid, signal_, waitpid)
1844

    
1845
  if timeout <= 0:
1846
    return
1847

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

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

    
1857
    if result_pid > 0:
1858
      return
1859

    
1860
    raise RetryAgain()
1861

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

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

    
1872

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

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

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

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

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

    
1906

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

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

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

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

    
1931

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

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

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

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

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

    
1949

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

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

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

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

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

    
1967

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

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

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

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

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

    
1989
  return port
1990

    
1991

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

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

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

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

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

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

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

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

    
2077

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

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

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

    
2086

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

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

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

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

    
2116

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

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

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

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

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

    
2142

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

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

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

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

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

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

    
2169

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

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

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

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

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

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

    
2203
  return (not_before, not_after)
2204

    
2205

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

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

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

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

    
2242

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

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

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

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

    
2284

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

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

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

    
2294

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

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

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

    
2306

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

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

2315
  """
2316
  size = 0
2317

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

    
2323
  return BytesToMebibyte(size)
2324

    
2325

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

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

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

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

    
2341

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

2345
  Note: Only boolean return values are supported.
2346

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

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

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

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

    
2370
  # Parent process
2371

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

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

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

    
2386
  return bool(exitcode)
2387

    
2388

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

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

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

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

    
2416

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

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

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

    
2431

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

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

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

    
2446

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

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

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

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

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

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

    
2484
      elif now > value:
2485
        value = None
2486

    
2487
  return value
2488

    
2489

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

2493
  """
2494

    
2495

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

2499
  """
2500

    
2501

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

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

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

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

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

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

    
2532
    self._next = start
2533

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

2537
    """
2538
    current = self._next
2539

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

    
2544
    return current
2545

    
2546

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

    
2550

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

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

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

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

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

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

    
2585
  end_time = _time_fn() + timeout
2586

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

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

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

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

    
2604
  assert calc_delay is None or callable(calc_delay)
2605

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

    
2616
    remaining_time = end_time - _time_fn()
2617

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

    
2621
    assert remaining_time >= 0.0
2622

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

    
2630

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2763

    
2764
def SignalHandled(signums):
2765
  """Signal Handled decoration.
2766

2767
  This special decorator installs a signal handler and then calls the target
2768
  function. The function must accept a 'signal_handlers' keyword argument,
2769
  which will contain a dict indexed by signal number, with SignalHandler
2770
  objects as values.
2771

2772
  The decorator can be safely stacked with iself, to handle multiple signals
2773
  with different handlers.
2774

2775
  @type signums: list
2776
  @param signums: signals to intercept
2777

2778
  """
2779
  def wrap(fn):
2780
    def sig_function(*args, **kwargs):
2781
      assert 'signal_handlers' not in kwargs or \
2782
             kwargs['signal_handlers'] is None or \
2783
             isinstance(kwargs['signal_handlers'], dict), \
2784
             "Wrong signal_handlers parameter in original function call"
2785
      if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
2786
        signal_handlers = kwargs['signal_handlers']
2787
      else:
2788
        signal_handlers = {}
2789
        kwargs['signal_handlers'] = signal_handlers
2790
      sighandler = SignalHandler(signums)
2791
      try:
2792
        for sig in signums:
2793
          signal_handlers[sig] = sighandler
2794
        return fn(*args, **kwargs)
2795
      finally:
2796
        sighandler.Reset()
2797
    return sig_function
2798
  return wrap
2799

    
2800

    
2801
class SignalHandler(object):
2802
  """Generic signal handler class.
2803

2804
  It automatically restores the original handler when deconstructed or
2805
  when L{Reset} is called. You can either pass your own handler
2806
  function in or query the L{called} attribute to detect whether the
2807
  signal was sent.
2808

2809
  @type signum: list
2810
  @ivar signum: the signals we handle
2811
  @type called: boolean
2812
  @ivar called: tracks whether any of the signals have been raised
2813

2814
  """
2815
  def __init__(self, signum):
2816
    """Constructs a new SignalHandler instance.
2817

2818
    @type signum: int or list of ints
2819
    @param signum: Single signal number or set of signal numbers
2820

2821
    """
2822
    self.signum = set(signum)
2823
    self.called = False
2824

    
2825
    self._previous = {}
2826
    try:
2827
      for signum in self.signum:
2828
        # Setup handler
2829
        prev_handler = signal.signal(signum, self._HandleSignal)
2830
        try:
2831
          self._previous[signum] = prev_handler
2832
        except:
2833
          # Restore previous handler
2834
          signal.signal(signum, prev_handler)
2835
          raise
2836
    except:
2837
      # Reset all handlers
2838
      self.Reset()
2839
      # Here we have a race condition: a handler may have already been called,
2840
      # but there's not much we can do about it at this point.
2841
      raise
2842

    
2843
  def __del__(self):
2844
    self.Reset()
2845

    
2846
  def Reset(self):
2847
    """Restore previous handler.
2848

2849
    This will reset all the signals to their previous handlers.
2850

2851
    """
2852
    for signum, prev_handler in self._previous.items():
2853
      signal.signal(signum, prev_handler)
2854
      # If successful, remove from dict
2855
      del self._previous[signum]
2856

    
2857
  def Clear(self):
2858
    """Unsets the L{called} flag.
2859

2860
    This function can be used in case a signal may arrive several times.
2861

2862
    """
2863
    self.called = False
2864

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

2869
    """
2870
    # This is not nice and not absolutely atomic, but it appears to be the only
2871
    # solution in Python -- there are no atomic types.
2872
    self.called = True
2873

    
2874

    
2875
class FieldSet(object):
2876
  """A simple field set.
2877

2878
  Among the features are:
2879
    - checking if a string is among a list of static string or regex objects
2880
    - checking if a whole list of string matches
2881
    - returning the matching groups from a regex match
2882

2883
  Internally, all fields are held as regular expression objects.
2884

2885
  """
2886
  def __init__(self, *items):
2887
    self.items = [re.compile("^%s$" % value) for value in items]
2888

    
2889
  def Extend(self, other_set):
2890
    """Extend the field set with the items from another one"""
2891
    self.items.extend(other_set.items)
2892

    
2893
  def Matches(self, field):
2894
    """Checks if a field matches the current set
2895

2896
    @type field: str
2897
    @param field: the string to match
2898
    @return: either None or a regular expression match object
2899

2900
    """
2901
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
2902
      return m
2903
    return None
2904

    
2905
  def NonMatching(self, items):
2906
    """Returns the list of fields not matching the current set
2907

2908
    @type items: list
2909
    @param items: the list of fields to check
2910
    @rtype: list
2911
    @return: list of non-matching fields
2912

2913
    """
2914
    return [val for val in items if not self.Matches(val)]