Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ 19bed813

History | View | Annotate | Download (59.7 kB)

1
#
2
#
3

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

    
21

    
22
"""Ganeti utility module.
23

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

27
"""
28

    
29

    
30
import 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 signal
45

    
46
from cStringIO import StringIO
47

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

    
54
from ganeti import errors
55
from ganeti import constants
56

    
57

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

    
61
debug_locks = False
62

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

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

    
68

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

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

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

    
91

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

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

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

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

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

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

    
119

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

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

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

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

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

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

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

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

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

    
181

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

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

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

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

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

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

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

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

    
250

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

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

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

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

    
283

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

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

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

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

    
300

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

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

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

    
326

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

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

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

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

    
343
  f = open(filename)
344

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

    
351
    fp.update(data)
352

    
353
  return fp.hexdigest()
354

    
355

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

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

365
  """
366
  ret = {}
367

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

    
373
  return ret
374

    
375

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

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

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

    
391
  if not isinstance(target, dict):
392
    msg = "Expected dictionary, got '%s'" % target
393
    raise errors.TypeEnforcementError(msg)
394

    
395
  for key in target:
396
    if key not in key_types:
397
      msg = "Unknown key '%s'" % key
398
      raise errors.TypeEnforcementError(msg)
399

    
400
    if target[key] in allowed_values:
401
      continue
402

    
403
    ktype = key_types[key]
404
    if ktype not in constants.ENFORCEABLE_TYPES:
405
      msg = "'%s' has non-enforceable type %s" % (key, ktype)
406
      raise errors.ProgrammerError(msg)
407

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

    
442

    
443
def IsProcessAlive(pid):
444
  """Check if a given pid exists on the system.
445

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

453
  """
454
  if pid <= 0:
455
    return False
456

    
457
  try:
458
    os.stat("/proc/%d/status" % pid)
459
    return True
460
  except EnvironmentError, err:
461
    if err.errno in (errno.ENOENT, errno.ENOTDIR):
462
      return False
463
    raise
464

    
465

    
466
def ReadPidFile(pidfile):
467
  """Read a pid from a file.
468

469
  @type  pidfile: string
470
  @param pidfile: path to the file containing the pid
471
  @rtype: int
472
  @return: The process id, if the file exists and contains a valid PID,
473
           otherwise 0
474

475
  """
476
  try:
477
    raw_data = ReadFile(pidfile)
478
  except EnvironmentError, err:
479
    if err.errno != errno.ENOENT:
480
      logging.exception("Can't read pid file")
481
    return 0
482

    
483
  try:
484
    pid = int(raw_data)
485
  except ValueError, err:
486
    logging.info("Can't parse pid file contents", exc_info=True)
487
    return 0
488

    
489
  return pid
490

    
491

    
492
def MatchNameComponent(key, name_list):
493
  """Try to match a name against a list.
494

495
  This function will try to match a name like test1 against a list
496
  like C{['test1.example.com', 'test2.example.com', ...]}. Against
497
  this list, I{'test1'} as well as I{'test1.example'} will match, but
498
  not I{'test1.ex'}. A multiple match will be considered as no match
499
  at all (e.g. I{'test1'} against C{['test1.example.com',
500
  'test1.example.org']}), except when the key fully matches an entry
501
  (e.g. I{'test1'} against C{['test1', 'test1.example.com']}).
502

503
  @type key: str
504
  @param key: the name to be searched
505
  @type name_list: list
506
  @param name_list: the list of strings against which to search the key
507

508
  @rtype: None or str
509
  @return: None if there is no match I{or} if there are multiple matches,
510
      otherwise the element from the list which matches
511

512
  """
513
  if key in name_list:
514
    return key
515
  mo = re.compile("^%s(\..*)?$" % re.escape(key))
516
  names_filtered = [name for name in name_list if mo.match(name) is not None]
517
  if len(names_filtered) != 1:
518
    return None
519
  return names_filtered[0]
520

    
521

    
522
class HostInfo:
523
  """Class implementing resolver and hostname functionality
524

525
  """
526
  def __init__(self, name=None):
527
    """Initialize the host name object.
528

529
    If the name argument is not passed, it will use this system's
530
    name.
531

532
    """
533
    if name is None:
534
      name = self.SysName()
535

    
536
    self.query = name
537
    self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
538
    self.ip = self.ipaddrs[0]
539

    
540
  def ShortName(self):
541
    """Returns the hostname without domain.
542

543
    """
544
    return self.name.split('.')[0]
545

    
546
  @staticmethod
547
  def SysName():
548
    """Return the current system's name.
549

550
    This is simply a wrapper over C{socket.gethostname()}.
551

552
    """
553
    return socket.gethostname()
554

    
555
  @staticmethod
556
  def LookupHostname(hostname):
557
    """Look up hostname
558

559
    @type hostname: str
560
    @param hostname: hostname to look up
561

562
    @rtype: tuple
563
    @return: a tuple (name, aliases, ipaddrs) as returned by
564
        C{socket.gethostbyname_ex}
565
    @raise errors.ResolverError: in case of errors in resolving
566

567
    """
568
    try:
569
      result = socket.gethostbyname_ex(hostname)
570
    except socket.gaierror, err:
571
      # hostname not found in DNS
572
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
573

    
574
    return result
575

    
576

    
577
def ListVolumeGroups():
578
  """List volume groups and their size
579

580
  @rtype: dict
581
  @return:
582
       Dictionary with keys volume name and values
583
       the size of the volume
584

585
  """
586
  command = "vgs --noheadings --units m --nosuffix -o name,size"
587
  result = RunCmd(command)
588
  retval = {}
589
  if result.failed:
590
    return retval
591

    
592
  for line in result.stdout.splitlines():
593
    try:
594
      name, size = line.split()
595
      size = int(float(size))
596
    except (IndexError, ValueError), err:
597
      logging.error("Invalid output from vgs (%s): %s", err, line)
598
      continue
599

    
600
    retval[name] = size
601

    
602
  return retval
603

    
604

    
605
def BridgeExists(bridge):
606
  """Check whether the given bridge exists in the system
607

608
  @type bridge: str
609
  @param bridge: the bridge name to check
610
  @rtype: boolean
611
  @return: True if it does
612

613
  """
614
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
615

    
616

    
617
def NiceSort(name_list):
618
  """Sort a list of strings based on digit and non-digit groupings.
619

620
  Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
621
  will sort the list in the logical order C{['a1', 'a2', 'a10',
622
  'a11']}.
623

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

628
  @type name_list: list
629
  @param name_list: the names to be sorted
630
  @rtype: list
631
  @return: a copy of the name list sorted with our algorithm
632

633
  """
634
  _SORTER_BASE = "(\D+|\d+)"
635
  _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
636
                                                  _SORTER_BASE, _SORTER_BASE,
637
                                                  _SORTER_BASE, _SORTER_BASE,
638
                                                  _SORTER_BASE, _SORTER_BASE)
639
  _SORTER_RE = re.compile(_SORTER_FULL)
640
  _SORTER_NODIGIT = re.compile("^\D*$")
641
  def _TryInt(val):
642
    """Attempts to convert a variable to integer."""
643
    if val is None or _SORTER_NODIGIT.match(val):
644
      return val
645
    rval = int(val)
646
    return rval
647

    
648
  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
649
             for name in name_list]
650
  to_sort.sort()
651
  return [tup[1] for tup in to_sort]
652

    
653

    
654
def TryConvert(fn, val):
655
  """Try to convert a value ignoring errors.
656

657
  This function tries to apply function I{fn} to I{val}. If no
658
  C{ValueError} or C{TypeError} exceptions are raised, it will return
659
  the result, else it will return the original value. Any other
660
  exceptions are propagated to the caller.
661

662
  @type fn: callable
663
  @param fn: function to apply to the value
664
  @param val: the value to be converted
665
  @return: The converted value if the conversion was successful,
666
      otherwise the original value.
667

668
  """
669
  try:
670
    nv = fn(val)
671
  except (ValueError, TypeError):
672
    nv = val
673
  return nv
674

    
675

    
676
def IsValidIP(ip):
677
  """Verifies the syntax of an IPv4 address.
678

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

682
  @type ip: str
683
  @param ip: the address to be checked
684
  @rtype: a regular expression match object
685
  @return: a regular expression match object, or None if the
686
      address is not valid
687

688
  """
689
  unit = "(0|[1-9]\d{0,2})"
690
  #TODO: convert and return only boolean
691
  return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
692

    
693

    
694
def IsValidShellParam(word):
695
  """Verifies is the given word is safe from the shell's p.o.v.
696

697
  This means that we can pass this to a command via the shell and be
698
  sure that it doesn't alter the command line and is passed as such to
699
  the actual command.
700

701
  Note that we are overly restrictive here, in order to be on the safe
702
  side.
703

704
  @type word: str
705
  @param word: the word to check
706
  @rtype: boolean
707
  @return: True if the word is 'safe'
708

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

    
712

    
713
def BuildShellCmd(template, *args):
714
  """Build a safe shell command line from the given arguments.
715

716
  This function will check all arguments in the args list so that they
717
  are valid shell parameters (i.e. they don't contain shell
718
  metacharacters). If everything is ok, it will return the result of
719
  template % args.
720

721
  @type template: str
722
  @param template: the string holding the template for the
723
      string formatting
724
  @rtype: str
725
  @return: the expanded command line
726

727
  """
728
  for word in args:
729
    if not IsValidShellParam(word):
730
      raise errors.ProgrammerError("Shell argument '%s' contains"
731
                                   " invalid characters" % word)
732
  return template % args
733

    
734

    
735
def FormatUnit(value, units):
736
  """Formats an incoming number of MiB with the appropriate unit.
737

738
  @type value: int
739
  @param value: integer representing the value in MiB (1048576)
740
  @type units: char
741
  @param units: the type of formatting we should do:
742
      - 'h' for automatic scaling
743
      - 'm' for MiBs
744
      - 'g' for GiBs
745
      - 't' for TiBs
746
  @rtype: str
747
  @return: the formatted value (with suffix)
748

749
  """
750
  if units not in ('m', 'g', 't', 'h'):
751
    raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
752

    
753
  suffix = ''
754

    
755
  if units == 'm' or (units == 'h' and value < 1024):
756
    if units == 'h':
757
      suffix = 'M'
758
    return "%d%s" % (round(value, 0), suffix)
759

    
760
  elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
761
    if units == 'h':
762
      suffix = 'G'
763
    return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
764

    
765
  else:
766
    if units == 'h':
767
      suffix = 'T'
768
    return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
769

    
770

    
771
def ParseUnit(input_string):
772
  """Tries to extract number and scale from the given string.
773

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

778
  """
779
  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', str(input_string))
780
  if not m:
781
    raise errors.UnitParseError("Invalid format")
782

    
783
  value = float(m.groups()[0])
784

    
785
  unit = m.groups()[1]
786
  if unit:
787
    lcunit = unit.lower()
788
  else:
789
    lcunit = 'm'
790

    
791
  if lcunit in ('m', 'mb', 'mib'):
792
    # Value already in MiB
793
    pass
794

    
795
  elif lcunit in ('g', 'gb', 'gib'):
796
    value *= 1024
797

    
798
  elif lcunit in ('t', 'tb', 'tib'):
799
    value *= 1024 * 1024
800

    
801
  else:
802
    raise errors.UnitParseError("Unknown unit: %s" % unit)
803

    
804
  # Make sure we round up
805
  if int(value) < value:
806
    value += 1
807

    
808
  # Round up to the next multiple of 4
809
  value = int(value)
810
  if value % 4:
811
    value += 4 - value % 4
812

    
813
  return value
814

    
815

    
816
def AddAuthorizedKey(file_name, key):
817
  """Adds an SSH public key to an authorized_keys file.
818

819
  @type file_name: str
820
  @param file_name: path to authorized_keys file
821
  @type key: str
822
  @param key: string containing key
823

824
  """
825
  key_fields = key.split()
826

    
827
  f = open(file_name, 'a+')
828
  try:
829
    nl = True
830
    for line in f:
831
      # Ignore whitespace changes
832
      if line.split() == key_fields:
833
        break
834
      nl = line.endswith('\n')
835
    else:
836
      if not nl:
837
        f.write("\n")
838
      f.write(key.rstrip('\r\n'))
839
      f.write("\n")
840
      f.flush()
841
  finally:
842
    f.close()
843

    
844

    
845
def RemoveAuthorizedKey(file_name, key):
846
  """Removes an SSH public key from an authorized_keys file.
847

848
  @type file_name: str
849
  @param file_name: path to authorized_keys file
850
  @type key: str
851
  @param key: string containing key
852

853
  """
854
  key_fields = key.split()
855

    
856
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
857
  try:
858
    out = os.fdopen(fd, 'w')
859
    try:
860
      f = open(file_name, 'r')
861
      try:
862
        for line in f:
863
          # Ignore whitespace changes while comparing lines
864
          if line.split() != key_fields:
865
            out.write(line)
866

    
867
        out.flush()
868
        os.rename(tmpname, file_name)
869
      finally:
870
        f.close()
871
    finally:
872
      out.close()
873
  except:
874
    RemoveFile(tmpname)
875
    raise
876

    
877

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

881
  @type file_name: str
882
  @param file_name: path to the file to modify (usually C{/etc/hosts})
883
  @type ip: str
884
  @param ip: the IP address
885
  @type hostname: str
886
  @param hostname: the hostname to be added
887
  @type aliases: list
888
  @param aliases: the list of aliases to add for the hostname
889

890
  """
891
  # FIXME: use WriteFile + fn rather than duplicating its efforts
892
  # Ensure aliases are unique
893
  aliases = UniqueSequence([hostname] + aliases)[1:]
894

    
895
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
896
  try:
897
    out = os.fdopen(fd, 'w')
898
    try:
899
      f = open(file_name, 'r')
900
      try:
901
        for line in f:
902
          fields = line.split()
903
          if fields and not fields[0].startswith('#') and ip == fields[0]:
904
            continue
905
          out.write(line)
906

    
907
        out.write("%s\t%s" % (ip, hostname))
908
        if aliases:
909
          out.write(" %s" % ' '.join(aliases))
910
        out.write('\n')
911

    
912
        out.flush()
913
        os.fsync(out)
914
        os.chmod(tmpname, 0644)
915
        os.rename(tmpname, file_name)
916
      finally:
917
        f.close()
918
    finally:
919
      out.close()
920
  except:
921
    RemoveFile(tmpname)
922
    raise
923

    
924

    
925
def AddHostToEtcHosts(hostname):
926
  """Wrapper around SetEtcHostsEntry.
927

928
  @type hostname: str
929
  @param hostname: a hostname that will be resolved and added to
930
      L{constants.ETC_HOSTS}
931

932
  """
933
  hi = HostInfo(name=hostname)
934
  SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
935

    
936

    
937
def RemoveEtcHostsEntry(file_name, hostname):
938
  """Removes a hostname from /etc/hosts.
939

940
  IP addresses without names are removed from the file.
941

942
  @type file_name: str
943
  @param file_name: path to the file to modify (usually C{/etc/hosts})
944
  @type hostname: str
945
  @param hostname: the hostname to be removed
946

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

    
966
          out.write(line)
967

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

    
980

    
981
def RemoveHostFromEtcHosts(hostname):
982
  """Wrapper around RemoveEtcHostsEntry.
983

984
  @type hostname: str
985
  @param hostname: hostname that will be resolved and its
986
      full and shot name will be removed from
987
      L{constants.ETC_HOSTS}
988

989
  """
990
  hi = HostInfo(name=hostname)
991
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
992
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
993

    
994

    
995
def CreateBackup(file_name):
996
  """Creates a backup of a file.
997

998
  @type file_name: str
999
  @param file_name: file to be backed up
1000
  @rtype: str
1001
  @return: the path to the newly created backup
1002
  @raise errors.ProgrammerError: for invalid file names
1003

1004
  """
1005
  if not os.path.isfile(file_name):
1006
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
1007
                                file_name)
1008

    
1009
  prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
1010
  dir_name = os.path.dirname(file_name)
1011

    
1012
  fsrc = open(file_name, 'rb')
1013
  try:
1014
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
1015
    fdst = os.fdopen(fd, 'wb')
1016
    try:
1017
      shutil.copyfileobj(fsrc, fdst)
1018
    finally:
1019
      fdst.close()
1020
  finally:
1021
    fsrc.close()
1022

    
1023
  return backup_name
1024

    
1025

    
1026
def ShellQuote(value):
1027
  """Quotes shell argument according to POSIX.
1028

1029
  @type value: str
1030
  @param value: the argument to be quoted
1031
  @rtype: str
1032
  @return: the quoted value
1033

1034
  """
1035
  if _re_shell_unquoted.match(value):
1036
    return value
1037
  else:
1038
    return "'%s'" % value.replace("'", "'\\''")
1039

    
1040

    
1041
def ShellQuoteArgs(args):
1042
  """Quotes a list of shell arguments.
1043

1044
  @type args: list
1045
  @param args: list of arguments to be quoted
1046
  @rtype: str
1047
  @return: the quoted arguments concatenated with spaces
1048

1049
  """
1050
  return ' '.join([ShellQuote(i) for i in args])
1051

    
1052

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

1056
  Check if the given IP is reachable by doing attempting a TCP connect
1057
  to it.
1058

1059
  @type target: str
1060
  @param target: the IP or hostname to ping
1061
  @type port: int
1062
  @param port: the port to connect to
1063
  @type timeout: int
1064
  @param timeout: the timeout on the connection attempt
1065
  @type live_port_needed: boolean
1066
  @param live_port_needed: whether a closed port will cause the
1067
      function to return failure, as if there was a timeout
1068
  @type source: str or None
1069
  @param source: if specified, will cause the connect to be made
1070
      from this specific source address; failures to bind other
1071
      than C{EADDRNOTAVAIL} will be ignored
1072

1073
  """
1074
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1075

    
1076
  success = False
1077

    
1078
  if source is not None:
1079
    try:
1080
      sock.bind((source, 0))
1081
    except socket.error, (errcode, _):
1082
      if errcode == errno.EADDRNOTAVAIL:
1083
        success = False
1084

    
1085
  sock.settimeout(timeout)
1086

    
1087
  try:
1088
    sock.connect((target, port))
1089
    sock.close()
1090
    success = True
1091
  except socket.timeout:
1092
    success = False
1093
  except socket.error, (errcode, errstring):
1094
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
1095

    
1096
  return success
1097

    
1098

    
1099
def OwnIpAddress(address):
1100
  """Check if the current host has the the given IP address.
1101

1102
  Currently this is done by TCP-pinging the address from the loopback
1103
  address.
1104

1105
  @type address: string
1106
  @param address: the address to check
1107
  @rtype: bool
1108
  @return: True if we own the address
1109

1110
  """
1111
  return TcpPing(address, constants.DEFAULT_NODED_PORT,
1112
                 source=constants.LOCALHOST_IP_ADDRESS)
1113

    
1114

    
1115
def ListVisibleFiles(path):
1116
  """Returns a list of visible files in a directory.
1117

1118
  @type path: str
1119
  @param path: the directory to enumerate
1120
  @rtype: list
1121
  @return: the list of all files not starting with a dot
1122

1123
  """
1124
  files = [i for i in os.listdir(path) if not i.startswith(".")]
1125
  files.sort()
1126
  return files
1127

    
1128

    
1129
def GetHomeDir(user, default=None):
1130
  """Try to get the homedir of the given user.
1131

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

1136
  """
1137
  try:
1138
    if isinstance(user, basestring):
1139
      result = pwd.getpwnam(user)
1140
    elif isinstance(user, (int, long)):
1141
      result = pwd.getpwuid(user)
1142
    else:
1143
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1144
                                   type(user))
1145
  except KeyError:
1146
    return default
1147
  return result.pw_dir
1148

    
1149

    
1150
def NewUUID():
1151
  """Returns a random UUID.
1152

1153
  @note: This is a Linux-specific method as it uses the /proc
1154
      filesystem.
1155
  @rtype: str
1156

1157
  """
1158
  return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
1159

    
1160

    
1161
def GenerateSecret(numbytes=20):
1162
  """Generates a random secret.
1163

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

1167
  @param numbytes: the number of bytes which will be represented by the returned
1168
      string (defaulting to 20, the length of a SHA1 hash)
1169
  @rtype: str
1170
  @return: an hex representation of the pseudo-random sequence
1171

1172
  """
1173
  return os.urandom(numbytes).encode('hex')
1174

    
1175

    
1176
def EnsureDirs(dirs):
1177
  """Make required directories, if they don't exist.
1178

1179
  @param dirs: list of tuples (dir_name, dir_mode)
1180
  @type dirs: list of (string, integer)
1181

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

    
1193

    
1194
def ReadFile(file_name, size=None):
1195
  """Reads a file.
1196

1197
  @type size: None or int
1198
  @param size: Read at most size bytes
1199
  @rtype: str
1200
  @return: the (possibly partial) content of the file
1201

1202
  """
1203
  f = open(file_name, "r")
1204
  try:
1205
    if size is None:
1206
      return f.read()
1207
    else:
1208
      return f.read(size)
1209
  finally:
1210
    f.close()
1211

    
1212

    
1213
def WriteFile(file_name, fn=None, data=None,
1214
              mode=None, uid=-1, gid=-1,
1215
              atime=None, mtime=None, close=True,
1216
              dry_run=False, backup=False,
1217
              prewrite=None, postwrite=None):
1218
  """(Over)write a file atomically.
1219

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

1226
  If the function doesn't raise an exception, it has succeeded and the
1227
  target file has the new contents. If the function has raised an
1228
  exception, an existing target file should be unmodified and the
1229
  temporary file should be removed.
1230

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

1255
  @rtype: None or int
1256
  @return: None if the 'close' parameter evaluates to True,
1257
      otherwise the file descriptor
1258

1259
  @raise errors.ProgrammerError: if any of the arguments are not valid
1260

1261
  """
1262
  if not os.path.isabs(file_name):
1263
    raise errors.ProgrammerError("Path passed to WriteFile is not"
1264
                                 " absolute: '%s'" % file_name)
1265

    
1266
  if [fn, data].count(None) != 1:
1267
    raise errors.ProgrammerError("fn or data required")
1268

    
1269
  if [atime, mtime].count(None) == 1:
1270
    raise errors.ProgrammerError("Both atime and mtime must be either"
1271
                                 " set or None")
1272

    
1273
  if backup and not dry_run and os.path.isfile(file_name):
1274
    CreateBackup(file_name)
1275

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

    
1309
  return result
1310

    
1311

    
1312
def FirstFree(seq, base=0):
1313
  """Returns the first non-existing integer from seq.
1314

1315
  The seq argument should be a sorted list of positive integers. The
1316
  first time the index of an element is smaller than the element
1317
  value, the index will be returned.
1318

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

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

1324
  @type seq: sequence
1325
  @param seq: the sequence to be analyzed.
1326
  @type base: int
1327
  @param base: use this value as the base index of the sequence
1328
  @rtype: int
1329
  @return: the first non-used index in the sequence
1330

1331
  """
1332
  for idx, elem in enumerate(seq):
1333
    assert elem >= base, "Passed element is higher than base offset"
1334
    if elem > idx + base:
1335
      # idx is not used
1336
      return idx + base
1337
  return None
1338

    
1339

    
1340
def all(seq, pred=bool):
1341
  "Returns True if pred(x) is True for every element in the iterable"
1342
  for _ in itertools.ifilterfalse(pred, seq):
1343
    return False
1344
  return True
1345

    
1346

    
1347
def any(seq, pred=bool):
1348
  "Returns True if pred(x) is True for at least one element in the iterable"
1349
  for _ in itertools.ifilter(pred, seq):
1350
    return True
1351
  return False
1352

    
1353

    
1354
def UniqueSequence(seq):
1355
  """Returns a list with unique elements.
1356

1357
  Element order is preserved.
1358

1359
  @type seq: sequence
1360
  @param seq: the sequence with the source elements
1361
  @rtype: list
1362
  @return: list of unique elements from seq
1363

1364
  """
1365
  seen = set()
1366
  return [i for i in seq if i not in seen and not seen.add(i)]
1367

    
1368

    
1369
def IsValidMac(mac):
1370
  """Predicate to check if a MAC address is valid.
1371

1372
  Checks whether the supplied MAC address is formally correct, only
1373
  accepts colon separated format.
1374

1375
  @type mac: str
1376
  @param mac: the MAC to be validated
1377
  @rtype: boolean
1378
  @return: True is the MAC seems valid
1379

1380
  """
1381
  mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$")
1382
  return mac_check.match(mac) is not None
1383

    
1384

    
1385
def TestDelay(duration):
1386
  """Sleep for a fixed amount of time.
1387

1388
  @type duration: float
1389
  @param duration: the sleep duration
1390
  @rtype: boolean
1391
  @return: False for negative value, True otherwise
1392

1393
  """
1394
  if duration < 0:
1395
    return False, "Invalid sleep duration"
1396
  time.sleep(duration)
1397
  return True, None
1398

    
1399

    
1400
def _CloseFDNoErr(fd, retries=5):
1401
  """Close a file descriptor ignoring errors.
1402

1403
  @type fd: int
1404
  @param fd: the file descriptor
1405
  @type retries: int
1406
  @param retries: how many retries to make, in case we get any
1407
      other error than EBADF
1408

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

    
1419

    
1420
def CloseFDs(noclose_fds=None):
1421
  """Close file descriptors.
1422

1423
  This closes all file descriptors above 2 (i.e. except
1424
  stdin/out/err).
1425

1426
  @type noclose_fds: list or None
1427
  @param noclose_fds: if given, it denotes a list of file descriptor
1428
      that should not be closed
1429

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

    
1445
  # Iterate through and close all file descriptors (except the standard ones)
1446
  for fd in range(3, maxfd):
1447
    if noclose_fds and fd in noclose_fds:
1448
      continue
1449
    _CloseFDNoErr(fd)
1450

    
1451

    
1452
def Daemonize(logfile):
1453
  """Daemonize the current process.
1454

1455
  This detaches the current process from the controlling terminal and
1456
  runs it in the background as a daemon.
1457

1458
  @type logfile: str
1459
  @param logfile: the logfile to which we should redirect stdout/stderr
1460
  @rtype: int
1461
  @return: the value zero
1462

1463
  """
1464
  UMASK = 077
1465
  WORKDIR = "/"
1466

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

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

    
1492

    
1493
def DaemonPidFileName(name):
1494
  """Compute a ganeti pid file absolute path
1495

1496
  @type name: str
1497
  @param name: the daemon name
1498
  @rtype: str
1499
  @return: the full path to the pidfile corresponding to the given
1500
      daemon name
1501

1502
  """
1503
  return os.path.join(constants.RUN_GANETI_DIR, "%s.pid" % name)
1504

    
1505

    
1506
def WritePidFile(name):
1507
  """Write the current process pidfile.
1508

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

1511
  @type name: str
1512
  @param name: the daemon name to use
1513
  @raise errors.GenericError: if the pid file already exists and
1514
      points to a live process
1515

1516
  """
1517
  pid = os.getpid()
1518
  pidfilename = DaemonPidFileName(name)
1519
  if IsProcessAlive(ReadPidFile(pidfilename)):
1520
    raise errors.GenericError("%s contains a live process" % pidfilename)
1521

    
1522
  WriteFile(pidfilename, data="%d\n" % pid)
1523

    
1524

    
1525
def RemovePidFile(name):
1526
  """Remove the current process pidfile.
1527

1528
  Any errors are ignored.
1529

1530
  @type name: str
1531
  @param name: the daemon name used to derive the pidfile name
1532

1533
  """
1534
  pidfilename = DaemonPidFileName(name)
1535
  # TODO: we could check here that the file contains our pid
1536
  try:
1537
    RemoveFile(pidfilename)
1538
  except:
1539
    pass
1540

    
1541

    
1542
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1543
                waitpid=False):
1544
  """Kill a process given by its pid.
1545

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

1559
  """
1560
  def _helper(pid, signal_, wait):
1561
    """Simple helper to encapsulate the kill/waitpid sequence"""
1562
    os.kill(pid, signal_)
1563
    if wait:
1564
      try:
1565
        os.waitpid(pid, os.WNOHANG)
1566
      except OSError:
1567
        pass
1568

    
1569
  if pid <= 0:
1570
    # kill with pid=0 == suicide
1571
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1572

    
1573
  if not IsProcessAlive(pid):
1574
    return
1575
  _helper(pid, signal_, waitpid)
1576
  if timeout <= 0:
1577
    return
1578

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

    
1594
  if IsProcessAlive(pid):
1595
    # Kill process if it's still alive
1596
    _helper(pid, signal.SIGKILL, waitpid)
1597

    
1598

    
1599
def FindFile(name, search_path, test=os.path.exists):
1600
  """Look for a filesystem object in a given path.
1601

1602
  This is an abstract method to search for filesystem object (files,
1603
  dirs) under a given search path.
1604

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

1616
  """
1617
  for dir_name in search_path:
1618
    item_name = os.path.sep.join([dir_name, name])
1619
    if test(item_name):
1620
      return item_name
1621
  return None
1622

    
1623

    
1624
def CheckVolumeGroupSize(vglist, vgname, minsize):
1625
  """Checks if the volume group list is valid.
1626

1627
  The function will check if a given volume group is in the list of
1628
  volume groups and has a minimum size.
1629

1630
  @type vglist: dict
1631
  @param vglist: dictionary of volume group names and their size
1632
  @type vgname: str
1633
  @param vgname: the volume group we should check
1634
  @type minsize: int
1635
  @param minsize: the minimum size we accept
1636
  @rtype: None or str
1637
  @return: None for success, otherwise the error message
1638

1639
  """
1640
  vgsize = vglist.get(vgname, None)
1641
  if vgsize is None:
1642
    return "volume group '%s' missing" % vgname
1643
  elif vgsize < minsize:
1644
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1645
            (vgname, minsize, vgsize))
1646
  return None
1647

    
1648

    
1649
def SplitTime(value):
1650
  """Splits time as floating point number into a tuple.
1651

1652
  @param value: Time in seconds
1653
  @type value: int or float
1654
  @return: Tuple containing (seconds, microseconds)
1655

1656
  """
1657
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1658

    
1659
  assert 0 <= seconds, \
1660
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1661
  assert 0 <= microseconds <= 999999, \
1662
    "Microseconds must be 0-999999, but are %s" % microseconds
1663

    
1664
  return (int(seconds), int(microseconds))
1665

    
1666

    
1667
def MergeTime(timetuple):
1668
  """Merges a tuple into time as a floating point number.
1669

1670
  @param timetuple: Time as tuple, (seconds, microseconds)
1671
  @type timetuple: tuple
1672
  @return: Time as a floating point number expressed in seconds
1673

1674
  """
1675
  (seconds, microseconds) = timetuple
1676

    
1677
  assert 0 <= seconds, \
1678
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1679
  assert 0 <= microseconds <= 999999, \
1680
    "Microseconds must be 0-999999, but are %s" % microseconds
1681

    
1682
  return float(seconds) + (float(microseconds) * 0.000001)
1683

    
1684

    
1685
def GetDaemonPort(daemon_name):
1686
  """Get the daemon port for this cluster.
1687

1688
  Note that this routine does not read a ganeti-specific file, but
1689
  instead uses C{socket.getservbyname} to allow pre-customization of
1690
  this parameter outside of Ganeti.
1691

1692
  @type daemon_name: string
1693
  @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
1694
  @rtype: int
1695

1696
  """
1697
  if daemon_name not in constants.DAEMONS_PORTS:
1698
    raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
1699

    
1700
  (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
1701
  try:
1702
    port = socket.getservbyname(daemon_name, proto)
1703
  except socket.error:
1704
    port = default_port
1705

    
1706
  return port
1707

    
1708

    
1709
def SetupLogging(logfile, debug=False, stderr_logging=False, program="",
1710
                 multithreaded=False):
1711
  """Configures the logging module.
1712

1713
  @type logfile: str
1714
  @param logfile: the filename to which we should log
1715
  @type debug: boolean
1716
  @param debug: whether to enable debug messages too or
1717
      only those at C{INFO} and above level
1718
  @type stderr_logging: boolean
1719
  @param stderr_logging: whether we should also log to the standard error
1720
  @type program: str
1721
  @param program: the name under which we should log messages
1722
  @type multithreaded: boolean
1723
  @param multithreaded: if True, will add the thread name to the log file
1724
  @raise EnvironmentError: if we can't open the log file and
1725
      stderr logging is disabled
1726

1727
  """
1728
  fmt = "%(asctime)s: " + program + " pid=%(process)d"
1729
  if multithreaded:
1730
    fmt += "/%(threadName)s"
1731
  if debug:
1732
    fmt += " %(module)s:%(lineno)s"
1733
  fmt += " %(levelname)s %(message)s"
1734
  formatter = logging.Formatter(fmt)
1735

    
1736
  root_logger = logging.getLogger("")
1737
  root_logger.setLevel(logging.NOTSET)
1738

    
1739
  # Remove all previously setup handlers
1740
  for handler in root_logger.handlers:
1741
    handler.close()
1742
    root_logger.removeHandler(handler)
1743

    
1744
  if stderr_logging:
1745
    stderr_handler = logging.StreamHandler()
1746
    stderr_handler.setFormatter(formatter)
1747
    if debug:
1748
      stderr_handler.setLevel(logging.NOTSET)
1749
    else:
1750
      stderr_handler.setLevel(logging.CRITICAL)
1751
    root_logger.addHandler(stderr_handler)
1752

    
1753
  # this can fail, if the logging directories are not setup or we have
1754
  # a permisssion problem; in this case, it's best to log but ignore
1755
  # the error if stderr_logging is True, and if false we re-raise the
1756
  # exception since otherwise we could run but without any logs at all
1757
  try:
1758
    logfile_handler = logging.FileHandler(logfile)
1759
    logfile_handler.setFormatter(formatter)
1760
    if debug:
1761
      logfile_handler.setLevel(logging.DEBUG)
1762
    else:
1763
      logfile_handler.setLevel(logging.INFO)
1764
    root_logger.addHandler(logfile_handler)
1765
  except EnvironmentError:
1766
    if stderr_logging:
1767
      logging.exception("Failed to enable logging to file '%s'", logfile)
1768
    else:
1769
      # we need to re-raise the exception
1770
      raise
1771

    
1772

    
1773
def IsNormAbsPath(path):
1774
  """Check whether a path is absolute and also normalized
1775

1776
  This avoids things like /dir/../../other/path to be valid.
1777

1778
  """
1779
  return os.path.normpath(path) == path and os.path.isabs(path)
1780

    
1781

    
1782
def TailFile(fname, lines=20):
1783
  """Return the last lines from a file.
1784

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

1789
  @param fname: the file name
1790
  @type lines: int
1791
  @param lines: the (maximum) number of lines to return
1792

1793
  """
1794
  fd = open(fname, "r")
1795
  try:
1796
    fd.seek(0, 2)
1797
    pos = fd.tell()
1798
    pos = max(0, pos-4096)
1799
    fd.seek(pos, 0)
1800
    raw_data = fd.read()
1801
  finally:
1802
    fd.close()
1803

    
1804
  rows = raw_data.splitlines()
1805
  return rows[-lines:]
1806

    
1807

    
1808
def SafeEncode(text):
1809
  """Return a 'safe' version of a source string.
1810

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

1820
  @type text: str or unicode
1821
  @param text: input data
1822
  @rtype: str
1823
  @return: a safe version of text
1824

1825
  """
1826
  if isinstance(text, unicode):
1827
    # only if unicode; if str already, we handle it below
1828
    text = text.encode('ascii', 'backslashreplace')
1829
  resu = ""
1830
  for char in text:
1831
    c = ord(char)
1832
    if char  == '\t':
1833
      resu += r'\t'
1834
    elif char == '\n':
1835
      resu += r'\n'
1836
    elif char == '\r':
1837
      resu += r'\'r'
1838
    elif c < 32 or c >= 127: # non-printable
1839
      resu += "\\x%02x" % (c & 0xff)
1840
    else:
1841
      resu += char
1842
  return resu
1843

    
1844

    
1845
def CommaJoin(names):
1846
  """Nicely join a set of identifiers.
1847

1848
  @param names: set, list or tuple
1849
  @return: a string with the formatted results
1850

1851
  """
1852
  return ", ".join(["'%s'" % val for val in names])
1853

    
1854

    
1855
def BytesToMebibyte(value):
1856
  """Converts bytes to mebibytes.
1857

1858
  @type value: int
1859
  @param value: Value in bytes
1860
  @rtype: int
1861
  @return: Value in mebibytes
1862

1863
  """
1864
  return int(round(value / (1024.0 * 1024.0), 0))
1865

    
1866

    
1867
def CalculateDirectorySize(path):
1868
  """Calculates the size of a directory recursively.
1869

1870
  @type path: string
1871
  @param path: Path to directory
1872
  @rtype: int
1873
  @return: Size in mebibytes
1874

1875
  """
1876
  size = 0
1877

    
1878
  for (curpath, _, files) in os.walk(path):
1879
    for filename in files:
1880
      st = os.lstat(os.path.join(curpath, filename))
1881
      size += st.st_size
1882

    
1883
  return BytesToMebibyte(size)
1884

    
1885

    
1886
def GetFreeFilesystemSpace(path):
1887
  """Returns the free space on a filesystem.
1888

1889
  @type path: string
1890
  @param path: Path on filesystem to be examined
1891
  @rtype: int
1892
  @return: Free space in mebibytes
1893

1894
  """
1895
  st = os.statvfs(path)
1896

    
1897
  return BytesToMebibyte(st.f_bavail * st.f_frsize)
1898

    
1899

    
1900
def LockedMethod(fn):
1901
  """Synchronized object access decorator.
1902

1903
  This decorator is intended to protect access to an object using the
1904
  object's own lock which is hardcoded to '_lock'.
1905

1906
  """
1907
  def _LockDebug(*args, **kwargs):
1908
    if debug_locks:
1909
      logging.debug(*args, **kwargs)
1910

    
1911
  def wrapper(self, *args, **kwargs):
1912
    assert hasattr(self, '_lock')
1913
    lock = self._lock
1914
    _LockDebug("Waiting for %s", lock)
1915
    lock.acquire()
1916
    try:
1917
      _LockDebug("Acquired %s", lock)
1918
      result = fn(self, *args, **kwargs)
1919
    finally:
1920
      _LockDebug("Releasing %s", lock)
1921
      lock.release()
1922
      _LockDebug("Released %s", lock)
1923
    return result
1924
  return wrapper
1925

    
1926

    
1927
def LockFile(fd):
1928
  """Locks a file using POSIX locks.
1929

1930
  @type fd: int
1931
  @param fd: the file descriptor we need to lock
1932

1933
  """
1934
  try:
1935
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
1936
  except IOError, err:
1937
    if err.errno == errno.EAGAIN:
1938
      raise errors.LockError("File already locked")
1939
    raise
1940

    
1941

    
1942
def FormatTime(val):
1943
  """Formats a time value.
1944

1945
  @type val: float or None
1946
  @param val: the timestamp as returned by time.time()
1947
  @return: a string value or N/A if we don't have a valid timestamp
1948

1949
  """
1950
  if val is None or not isinstance(val, (int, float)):
1951
    return "N/A"
1952
  # these two codes works on Linux, but they are not guaranteed on all
1953
  # platforms
1954
  return time.strftime("%F %T", time.localtime(val))
1955

    
1956

    
1957
def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
1958
  """Reads the watcher pause file.
1959

1960
  @type filename: string
1961
  @param filename: Path to watcher pause file
1962
  @type now: None, float or int
1963
  @param now: Current time as Unix timestamp
1964
  @type remove_after: int
1965
  @param remove_after: Remove watcher pause file after specified amount of
1966
    seconds past the pause end time
1967

1968
  """
1969
  if now is None:
1970
    now = time.time()
1971

    
1972
  try:
1973
    value = ReadFile(filename)
1974
  except IOError, err:
1975
    if err.errno != errno.ENOENT:
1976
      raise
1977
    value = None
1978

    
1979
  if value is not None:
1980
    try:
1981
      value = int(value)
1982
    except ValueError:
1983
      logging.warning(("Watcher pause file (%s) contains invalid value,"
1984
                       " removing it"), filename)
1985
      RemoveFile(filename)
1986
      value = None
1987

    
1988
    if value is not None:
1989
      # Remove file if it's outdated
1990
      if now > (value + remove_after):
1991
        RemoveFile(filename)
1992
        value = None
1993

    
1994
      elif now > value:
1995
        value = None
1996

    
1997
  return value
1998

    
1999

    
2000
class FileLock(object):
2001
  """Utility class for file locks.
2002

2003
  """
2004
  def __init__(self, filename):
2005
    """Constructor for FileLock.
2006

2007
    This will open the file denoted by the I{filename} argument.
2008

2009
    @type filename: str
2010
    @param filename: path to the file to be locked
2011

2012
    """
2013
    self.filename = filename
2014
    self.fd = open(self.filename, "w")
2015

    
2016
  def __del__(self):
2017
    self.Close()
2018

    
2019
  def Close(self):
2020
    """Close the file and release the lock.
2021

2022
    """
2023
    if self.fd:
2024
      self.fd.close()
2025
      self.fd = None
2026

    
2027
  def _flock(self, flag, blocking, timeout, errmsg):
2028
    """Wrapper for fcntl.flock.
2029

2030
    @type flag: int
2031
    @param flag: operation flag
2032
    @type blocking: bool
2033
    @param blocking: whether the operation should be done in blocking mode.
2034
    @type timeout: None or float
2035
    @param timeout: for how long the operation should be retried (implies
2036
                    non-blocking mode).
2037
    @type errmsg: string
2038
    @param errmsg: error message in case operation fails.
2039

2040
    """
2041
    assert self.fd, "Lock was closed"
2042
    assert timeout is None or timeout >= 0, \
2043
      "If specified, timeout must be positive"
2044

    
2045
    if timeout is not None:
2046
      flag |= fcntl.LOCK_NB
2047
      timeout_end = time.time() + timeout
2048

    
2049
    # Blocking doesn't have effect with timeout
2050
    elif not blocking:
2051
      flag |= fcntl.LOCK_NB
2052
      timeout_end = None
2053

    
2054
    retry = True
2055
    while retry:
2056
      try:
2057
        fcntl.flock(self.fd, flag)
2058
        retry = False
2059
      except IOError, err:
2060
        if err.errno in (errno.EAGAIN, ):
2061
          if timeout_end is not None and time.time() < timeout_end:
2062
            # Wait before trying again
2063
            time.sleep(max(0.1, min(1.0, timeout)))
2064
          else:
2065
            raise errors.LockError(errmsg)
2066
        else:
2067
          logging.exception("fcntl.flock failed")
2068
          raise
2069

    
2070
  def Exclusive(self, blocking=False, timeout=None):
2071
    """Locks the file in exclusive mode.
2072

2073
    @type blocking: boolean
2074
    @param blocking: whether to block and wait until we
2075
        can lock the file or return immediately
2076
    @type timeout: int or None
2077
    @param timeout: if not None, the duration to wait for the lock
2078
        (in blocking mode)
2079

2080
    """
2081
    self._flock(fcntl.LOCK_EX, blocking, timeout,
2082
                "Failed to lock %s in exclusive mode" % self.filename)
2083

    
2084
  def Shared(self, blocking=False, timeout=None):
2085
    """Locks the file in shared mode.
2086

2087
    @type blocking: boolean
2088
    @param blocking: whether to block and wait until we
2089
        can lock the file or return immediately
2090
    @type timeout: int or None
2091
    @param timeout: if not None, the duration to wait for the lock
2092
        (in blocking mode)
2093

2094
    """
2095
    self._flock(fcntl.LOCK_SH, blocking, timeout,
2096
                "Failed to lock %s in shared mode" % self.filename)
2097

    
2098
  def Unlock(self, blocking=True, timeout=None):
2099
    """Unlocks the file.
2100

2101
    According to C{flock(2)}, unlocking can also be a nonblocking
2102
    operation::
2103

2104
      To make a non-blocking request, include LOCK_NB with any of the above
2105
      operations.
2106

2107
    @type blocking: boolean
2108
    @param blocking: whether to block and wait until we
2109
        can lock the file or return immediately
2110
    @type timeout: int or None
2111
    @param timeout: if not None, the duration to wait for the lock
2112
        (in blocking mode)
2113

2114
    """
2115
    self._flock(fcntl.LOCK_UN, blocking, timeout,
2116
                "Failed to unlock %s" % self.filename)
2117

    
2118

    
2119
def SignalHandled(signums):
2120
  """Signal Handled decoration.
2121

2122
  This special decorator installs a signal handler and then calls the target
2123
  function. The function must accept a 'signal_handlers' keyword argument,
2124
  which will contain a dict indexed by signal number, with SignalHandler
2125
  objects as values.
2126

2127
  The decorator can be safely stacked with iself, to handle multiple signals
2128
  with different handlers.
2129

2130
  @type signums: list
2131
  @param signums: signals to intercept
2132

2133
  """
2134
  def wrap(fn):
2135
    def sig_function(*args, **kwargs):
2136
      assert 'signal_handlers' not in kwargs or \
2137
             kwargs['signal_handlers'] is None or \
2138
             isinstance(kwargs['signal_handlers'], dict), \
2139
             "Wrong signal_handlers parameter in original function call"
2140
      if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
2141
        signal_handlers = kwargs['signal_handlers']
2142
      else:
2143
        signal_handlers = {}
2144
        kwargs['signal_handlers'] = signal_handlers
2145
      sighandler = SignalHandler(signums)
2146
      try:
2147
        for sig in signums:
2148
          signal_handlers[sig] = sighandler
2149
        return fn(*args, **kwargs)
2150
      finally:
2151
        sighandler.Reset()
2152
    return sig_function
2153
  return wrap
2154

    
2155

    
2156
class SignalHandler(object):
2157
  """Generic signal handler class.
2158

2159
  It automatically restores the original handler when deconstructed or
2160
  when L{Reset} is called. You can either pass your own handler
2161
  function in or query the L{called} attribute to detect whether the
2162
  signal was sent.
2163

2164
  @type signum: list
2165
  @ivar signum: the signals we handle
2166
  @type called: boolean
2167
  @ivar called: tracks whether any of the signals have been raised
2168

2169
  """
2170
  def __init__(self, signum):
2171
    """Constructs a new SignalHandler instance.
2172

2173
    @type signum: int or list of ints
2174
    @param signum: Single signal number or set of signal numbers
2175

2176
    """
2177
    self.signum = set(signum)
2178
    self.called = False
2179

    
2180
    self._previous = {}
2181
    try:
2182
      for signum in self.signum:
2183
        # Setup handler
2184
        prev_handler = signal.signal(signum, self._HandleSignal)
2185
        try:
2186
          self._previous[signum] = prev_handler
2187
        except:
2188
          # Restore previous handler
2189
          signal.signal(signum, prev_handler)
2190
          raise
2191
    except:
2192
      # Reset all handlers
2193
      self.Reset()
2194
      # Here we have a race condition: a handler may have already been called,
2195
      # but there's not much we can do about it at this point.
2196
      raise
2197

    
2198
  def __del__(self):
2199
    self.Reset()
2200

    
2201
  def Reset(self):
2202
    """Restore previous handler.
2203

2204
    This will reset all the signals to their previous handlers.
2205

2206
    """
2207
    for signum, prev_handler in self._previous.items():
2208
      signal.signal(signum, prev_handler)
2209
      # If successful, remove from dict
2210
      del self._previous[signum]
2211

    
2212
  def Clear(self):
2213
    """Unsets the L{called} flag.
2214

2215
    This function can be used in case a signal may arrive several times.
2216

2217
    """
2218
    self.called = False
2219

    
2220
  def _HandleSignal(self, signum, frame):
2221
    """Actual signal handling function.
2222

2223
    """
2224
    # This is not nice and not absolutely atomic, but it appears to be the only
2225
    # solution in Python -- there are no atomic types.
2226
    self.called = True
2227

    
2228

    
2229
class FieldSet(object):
2230
  """A simple field set.
2231

2232
  Among the features are:
2233
    - checking if a string is among a list of static string or regex objects
2234
    - checking if a whole list of string matches
2235
    - returning the matching groups from a regex match
2236

2237
  Internally, all fields are held as regular expression objects.
2238

2239
  """
2240
  def __init__(self, *items):
2241
    self.items = [re.compile("^%s$" % value) for value in items]
2242

    
2243
  def Extend(self, other_set):
2244
    """Extend the field set with the items from another one"""
2245
    self.items.extend(other_set.items)
2246

    
2247
  def Matches(self, field):
2248
    """Checks if a field matches the current set
2249

2250
    @type field: str
2251
    @param field: the string to match
2252
    @return: either False or a regular expression match object
2253

2254
    """
2255
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
2256
      return m
2257
    return False
2258

    
2259
  def NonMatching(self, items):
2260
    """Returns the list of fields not matching the current set
2261

2262
    @type items: list
2263
    @param items: the list of fields to check
2264
    @rtype: list
2265
    @return: list of non-matching fields
2266

2267
    """
2268
    return [val for val in items if not self.Matches(val)]