Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ 2557ff82

History | View | Annotate | Download (33.8 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 small utilities
23

24
"""
25

    
26

    
27
import sys
28
import os
29
import sha
30
import time
31
import subprocess
32
import re
33
import socket
34
import tempfile
35
import shutil
36
import errno
37
import pwd
38
import itertools
39
import select
40
import fcntl
41
import resource
42
import logging
43
import signal
44

    
45
from cStringIO import StringIO
46

    
47
from ganeti import errors
48
from ganeti import constants
49

    
50

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

    
54
debug = False
55
no_fork = False
56

    
57

    
58
class RunResult(object):
59
  """Simple class for holding the result of running external programs.
60

61
  Instance variables:
62
    exit_code: the exit code of the program, or None (if the program
63
               didn't exit())
64
    signal: numeric signal that caused the program to finish, or None
65
            (if the program wasn't terminated by a signal)
66
    stdout: the standard output of the program
67
    stderr: the standard error of the program
68
    failed: a Boolean value which is True in case the program was
69
            terminated by a signal or exited with a non-zero exit code
70
    fail_reason: a string detailing the termination reason
71

72
  """
73
  __slots__ = ["exit_code", "signal", "stdout", "stderr",
74
               "failed", "fail_reason", "cmd"]
75

    
76

    
77
  def __init__(self, exit_code, signal_, stdout, stderr, cmd):
78
    self.cmd = cmd
79
    self.exit_code = exit_code
80
    self.signal = signal_
81
    self.stdout = stdout
82
    self.stderr = stderr
83
    self.failed = (signal_ is not None or exit_code != 0)
84

    
85
    if self.signal is not None:
86
      self.fail_reason = "terminated by signal %s" % self.signal
87
    elif self.exit_code is not None:
88
      self.fail_reason = "exited with exit code %s" % self.exit_code
89
    else:
90
      self.fail_reason = "unable to determine termination reason"
91

    
92
    if self.failed:
93
      logging.debug("Command '%s' failed (%s); output: %s",
94
                    self.cmd, self.fail_reason, self.output)
95

    
96
  def _GetOutput(self):
97
    """Returns the combined stdout and stderr for easier usage.
98

99
    """
100
    return self.stdout + self.stderr
101

    
102
  output = property(_GetOutput, None, None, "Return full output")
103

    
104

    
105
def RunCmd(cmd, env=None):
106
  """Execute a (shell) command.
107

108
  The command should not read from its standard input, as it will be
109
  closed.
110

111
  @param cmd: Command to run
112
  @type  cmd: string or list
113
  @param env: Additional environment
114
  @type env: dict
115
  @return: `RunResult` instance
116
  @rtype: RunResult
117

118
  """
119
  if no_fork:
120
    raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
121

    
122
  if isinstance(cmd, list):
123
    cmd = [str(val) for val in cmd]
124
    strcmd = " ".join(cmd)
125
    shell = False
126
  else:
127
    strcmd = cmd
128
    shell = True
129
  logging.debug("RunCmd '%s'", strcmd)
130

    
131
  cmd_env = os.environ.copy()
132
  cmd_env["LC_ALL"] = "C"
133
  if env is not None:
134
    cmd_env.update(env)
135

    
136
  poller = select.poll()
137
  child = subprocess.Popen(cmd, shell=shell,
138
                           stderr=subprocess.PIPE,
139
                           stdout=subprocess.PIPE,
140
                           stdin=subprocess.PIPE,
141
                           close_fds=True, env=cmd_env)
142

    
143
  child.stdin.close()
144
  poller.register(child.stdout, select.POLLIN)
145
  poller.register(child.stderr, select.POLLIN)
146
  out = StringIO()
147
  err = StringIO()
148
  fdmap = {
149
    child.stdout.fileno(): (out, child.stdout),
150
    child.stderr.fileno(): (err, child.stderr),
151
    }
152
  for fd in fdmap:
153
    status = fcntl.fcntl(fd, fcntl.F_GETFL)
154
    fcntl.fcntl(fd, fcntl.F_SETFL, status | os.O_NONBLOCK)
155

    
156
  while fdmap:
157
    for fd, event in poller.poll():
158
      if event & select.POLLIN or event & select.POLLPRI:
159
        data = fdmap[fd][1].read()
160
        # no data from read signifies EOF (the same as POLLHUP)
161
        if not data:
162
          poller.unregister(fd)
163
          del fdmap[fd]
164
          continue
165
        fdmap[fd][0].write(data)
166
      if (event & select.POLLNVAL or event & select.POLLHUP or
167
          event & select.POLLERR):
168
        poller.unregister(fd)
169
        del fdmap[fd]
170

    
171
  out = out.getvalue()
172
  err = err.getvalue()
173

    
174
  status = child.wait()
175
  if status >= 0:
176
    exitcode = status
177
    signal_ = None
178
  else:
179
    exitcode = None
180
    signal_ = -status
181

    
182
  return RunResult(exitcode, signal_, out, err, strcmd)
183

    
184

    
185
def RemoveFile(filename):
186
  """Remove a file ignoring some errors.
187

188
  Remove a file, ignoring non-existing ones or directories. Other
189
  errors are passed.
190

191
  """
192
  try:
193
    os.unlink(filename)
194
  except OSError, err:
195
    if err.errno not in (errno.ENOENT, errno.EISDIR):
196
      raise
197

    
198

    
199
def _FingerprintFile(filename):
200
  """Compute the fingerprint of a file.
201

202
  If the file does not exist, a None will be returned
203
  instead.
204

205
  Args:
206
    filename - Filename (str)
207

208
  """
209
  if not (os.path.exists(filename) and os.path.isfile(filename)):
210
    return None
211

    
212
  f = open(filename)
213

    
214
  fp = sha.sha()
215
  while True:
216
    data = f.read(4096)
217
    if not data:
218
      break
219

    
220
    fp.update(data)
221

    
222
  return fp.hexdigest()
223

    
224

    
225
def FingerprintFiles(files):
226
  """Compute fingerprints for a list of files.
227

228
  Args:
229
    files - array of filenames.  ( [str, ...] )
230

231
  Return value:
232
    dictionary of filename: fingerprint for the files that exist
233

234
  """
235
  ret = {}
236

    
237
  for filename in files:
238
    cksum = _FingerprintFile(filename)
239
    if cksum:
240
      ret[filename] = cksum
241

    
242
  return ret
243

    
244

    
245
def CheckDict(target, template, logname=None):
246
  """Ensure a dictionary has a required set of keys.
247

248
  For the given dictionaries `target` and `template`, ensure target
249
  has all the keys from template. Missing keys are added with values
250
  from template.
251

252
  Args:
253
    target   - the dictionary to check
254
    template - template dictionary
255
    logname  - a caller-chosen string to identify the debug log
256
               entry; if None, no logging will be done
257

258
  Returns value:
259
    None
260

261
  """
262
  missing = []
263
  for k in template:
264
    if k not in target:
265
      missing.append(k)
266
      target[k] = template[k]
267

    
268
  if missing and logname:
269
    logging.warning('%s missing keys %s', logname, ', '.join(missing))
270

    
271

    
272
def IsProcessAlive(pid):
273
  """Check if a given pid exists on the system.
274

275
  Returns: true or false, depending on if the pid exists or not
276

277
  Remarks: zombie processes treated as not alive, and giving a pid <=
278
  0 makes the function to return False.
279

280
  """
281
  if pid <= 0:
282
    return False
283

    
284
  try:
285
    f = open("/proc/%d/status" % pid)
286
  except IOError, err:
287
    if err.errno in (errno.ENOENT, errno.ENOTDIR):
288
      return False
289

    
290
  alive = True
291
  try:
292
    data = f.readlines()
293
    if len(data) > 1:
294
      state = data[1].split()
295
      if len(state) > 1 and state[1] == "Z":
296
        alive = False
297
  finally:
298
    f.close()
299

    
300
  return alive
301

    
302

    
303
def ReadPidFile(pidfile):
304
  """Read the pid from a file.
305

306
  @param pidfile: Path to a file containing the pid to be checked
307
  @type  pidfile: string (filename)
308
  @return: The process id, if the file exista and contains a valid PID,
309
           otherwise 0
310
  @rtype: int
311

312
  """
313
  try:
314
    pf = open(pidfile, 'r')
315
  except EnvironmentError, err:
316
    if err.errno != errno.ENOENT:
317
      logging.exception("Can't read pid file?!")
318
    return 0
319

    
320
  try:
321
    pid = int(pf.read())
322
  except ValueError, err:
323
    logging.info("Can't parse pid file contents", exc_info=True)
324
    return 0
325

    
326
  return pid
327

    
328

    
329
def MatchNameComponent(key, name_list):
330
  """Try to match a name against a list.
331

332
  This function will try to match a name like test1 against a list
333
  like ['test1.example.com', 'test2.example.com', ...]. Against this
334
  list, 'test1' as well as 'test1.example' will match, but not
335
  'test1.ex'. A multiple match will be considered as no match at all
336
  (e.g. 'test1' against ['test1.example.com', 'test1.example.org']).
337

338
  Args:
339
    key: the name to be searched
340
    name_list: the list of strings against which to search the key
341

342
  Returns:
343
    None if there is no match *or* if there are multiple matches
344
    otherwise the element from the list which matches
345

346
  """
347
  mo = re.compile("^%s(\..*)?$" % re.escape(key))
348
  names_filtered = [name for name in name_list if mo.match(name) is not None]
349
  if len(names_filtered) != 1:
350
    return None
351
  return names_filtered[0]
352

    
353

    
354
class HostInfo:
355
  """Class implementing resolver and hostname functionality
356

357
  """
358
  def __init__(self, name=None):
359
    """Initialize the host name object.
360

361
    If the name argument is not passed, it will use this system's
362
    name.
363

364
    """
365
    if name is None:
366
      name = self.SysName()
367

    
368
    self.query = name
369
    self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
370
    self.ip = self.ipaddrs[0]
371

    
372
  def ShortName(self):
373
    """Returns the hostname without domain.
374

375
    """
376
    return self.name.split('.')[0]
377

    
378
  @staticmethod
379
  def SysName():
380
    """Return the current system's name.
381

382
    This is simply a wrapper over socket.gethostname()
383

384
    """
385
    return socket.gethostname()
386

    
387
  @staticmethod
388
  def LookupHostname(hostname):
389
    """Look up hostname
390

391
    Args:
392
      hostname: hostname to look up
393

394
    Returns:
395
      a tuple (name, aliases, ipaddrs) as returned by socket.gethostbyname_ex
396
      in case of errors in resolving, we raise a ResolverError
397

398
    """
399
    try:
400
      result = socket.gethostbyname_ex(hostname)
401
    except socket.gaierror, err:
402
      # hostname not found in DNS
403
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
404

    
405
    return result
406

    
407

    
408
def ListVolumeGroups():
409
  """List volume groups and their size
410

411
  Returns:
412
     Dictionary with keys volume name and values the size of the volume
413

414
  """
415
  command = "vgs --noheadings --units m --nosuffix -o name,size"
416
  result = RunCmd(command)
417
  retval = {}
418
  if result.failed:
419
    return retval
420

    
421
  for line in result.stdout.splitlines():
422
    try:
423
      name, size = line.split()
424
      size = int(float(size))
425
    except (IndexError, ValueError), err:
426
      logging.error("Invalid output from vgs (%s): %s", err, line)
427
      continue
428

    
429
    retval[name] = size
430

    
431
  return retval
432

    
433

    
434
def BridgeExists(bridge):
435
  """Check whether the given bridge exists in the system
436

437
  Returns:
438
     True if it does, false otherwise.
439

440
  """
441
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
442

    
443

    
444
def NiceSort(name_list):
445
  """Sort a list of strings based on digit and non-digit groupings.
446

447
  Given a list of names ['a1', 'a10', 'a11', 'a2'] this function will
448
  sort the list in the logical order ['a1', 'a2', 'a10', 'a11'].
449

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

454
  Return value
455
    - a copy of the list sorted according to our algorithm
456

457
  """
458
  _SORTER_BASE = "(\D+|\d+)"
459
  _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
460
                                                  _SORTER_BASE, _SORTER_BASE,
461
                                                  _SORTER_BASE, _SORTER_BASE,
462
                                                  _SORTER_BASE, _SORTER_BASE)
463
  _SORTER_RE = re.compile(_SORTER_FULL)
464
  _SORTER_NODIGIT = re.compile("^\D*$")
465
  def _TryInt(val):
466
    """Attempts to convert a variable to integer."""
467
    if val is None or _SORTER_NODIGIT.match(val):
468
      return val
469
    rval = int(val)
470
    return rval
471

    
472
  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
473
             for name in name_list]
474
  to_sort.sort()
475
  return [tup[1] for tup in to_sort]
476

    
477

    
478
def TryConvert(fn, val):
479
  """Try to convert a value ignoring errors.
480

481
  This function tries to apply function `fn` to `val`. If no
482
  ValueError or TypeError exceptions are raised, it will return the
483
  result, else it will return the original value. Any other exceptions
484
  are propagated to the caller.
485

486
  """
487
  try:
488
    nv = fn(val)
489
  except (ValueError, TypeError), err:
490
    nv = val
491
  return nv
492

    
493

    
494
def IsValidIP(ip):
495
  """Verifies the syntax of an IP address.
496

497
  This function checks if the ip address passes is valid or not based
498
  on syntax (not ip range, class calculations or anything).
499

500
  """
501
  unit = "(0|[1-9]\d{0,2})"
502
  return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
503

    
504

    
505
def IsValidShellParam(word):
506
  """Verifies is the given word is safe from the shell's p.o.v.
507

508
  This means that we can pass this to a command via the shell and be
509
  sure that it doesn't alter the command line and is passed as such to
510
  the actual command.
511

512
  Note that we are overly restrictive here, in order to be on the safe
513
  side.
514

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

    
518

    
519
def BuildShellCmd(template, *args):
520
  """Build a safe shell command line from the given arguments.
521

522
  This function will check all arguments in the args list so that they
523
  are valid shell parameters (i.e. they don't contain shell
524
  metacharaters). If everything is ok, it will return the result of
525
  template % args.
526

527
  """
528
  for word in args:
529
    if not IsValidShellParam(word):
530
      raise errors.ProgrammerError("Shell argument '%s' contains"
531
                                   " invalid characters" % word)
532
  return template % args
533

    
534

    
535
def FormatUnit(value):
536
  """Formats an incoming number of MiB with the appropriate unit.
537

538
  Value needs to be passed as a numeric type. Return value is always a string.
539

540
  """
541
  if value < 1024:
542
    return "%dM" % round(value, 0)
543

    
544
  elif value < (1024 * 1024):
545
    return "%0.1fG" % round(float(value) / 1024, 1)
546

    
547
  else:
548
    return "%0.1fT" % round(float(value) / 1024 / 1024, 1)
549

    
550

    
551
def ParseUnit(input_string):
552
  """Tries to extract number and scale from the given string.
553

554
  Input must be in the format NUMBER+ [DOT NUMBER+] SPACE* [UNIT]. If no unit
555
  is specified, it defaults to MiB. Return value is always an int in MiB.
556

557
  """
558
  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', input_string)
559
  if not m:
560
    raise errors.UnitParseError("Invalid format")
561

    
562
  value = float(m.groups()[0])
563

    
564
  unit = m.groups()[1]
565
  if unit:
566
    lcunit = unit.lower()
567
  else:
568
    lcunit = 'm'
569

    
570
  if lcunit in ('m', 'mb', 'mib'):
571
    # Value already in MiB
572
    pass
573

    
574
  elif lcunit in ('g', 'gb', 'gib'):
575
    value *= 1024
576

    
577
  elif lcunit in ('t', 'tb', 'tib'):
578
    value *= 1024 * 1024
579

    
580
  else:
581
    raise errors.UnitParseError("Unknown unit: %s" % unit)
582

    
583
  # Make sure we round up
584
  if int(value) < value:
585
    value += 1
586

    
587
  # Round up to the next multiple of 4
588
  value = int(value)
589
  if value % 4:
590
    value += 4 - value % 4
591

    
592
  return value
593

    
594

    
595
def AddAuthorizedKey(file_name, key):
596
  """Adds an SSH public key to an authorized_keys file.
597

598
  Args:
599
    file_name: Path to authorized_keys file
600
    key: String containing key
601
  """
602
  key_fields = key.split()
603

    
604
  f = open(file_name, 'a+')
605
  try:
606
    nl = True
607
    for line in f:
608
      # Ignore whitespace changes
609
      if line.split() == key_fields:
610
        break
611
      nl = line.endswith('\n')
612
    else:
613
      if not nl:
614
        f.write("\n")
615
      f.write(key.rstrip('\r\n'))
616
      f.write("\n")
617
      f.flush()
618
  finally:
619
    f.close()
620

    
621

    
622
def RemoveAuthorizedKey(file_name, key):
623
  """Removes an SSH public key from an authorized_keys file.
624

625
  Args:
626
    file_name: Path to authorized_keys file
627
    key: String containing key
628
  """
629
  key_fields = key.split()
630

    
631
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
632
  try:
633
    out = os.fdopen(fd, 'w')
634
    try:
635
      f = open(file_name, 'r')
636
      try:
637
        for line in f:
638
          # Ignore whitespace changes while comparing lines
639
          if line.split() != key_fields:
640
            out.write(line)
641

    
642
        out.flush()
643
        os.rename(tmpname, file_name)
644
      finally:
645
        f.close()
646
    finally:
647
      out.close()
648
  except:
649
    RemoveFile(tmpname)
650
    raise
651

    
652

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

656
  """
657
  # Ensure aliases are unique
658
  aliases = UniqueSequence([hostname] + aliases)[1:]
659

    
660
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
661
  try:
662
    out = os.fdopen(fd, 'w')
663
    try:
664
      f = open(file_name, 'r')
665
      try:
666
        written = False
667
        for line in f:
668
          fields = line.split()
669
          if fields and not fields[0].startswith('#') and ip == fields[0]:
670
            continue
671
          out.write(line)
672

    
673
        out.write("%s\t%s" % (ip, hostname))
674
        if aliases:
675
          out.write(" %s" % ' '.join(aliases))
676
        out.write('\n')
677

    
678
        out.flush()
679
        os.fsync(out)
680
        os.rename(tmpname, file_name)
681
      finally:
682
        f.close()
683
    finally:
684
      out.close()
685
  except:
686
    RemoveFile(tmpname)
687
    raise
688

    
689

    
690
def AddHostToEtcHosts(hostname):
691
  """Wrapper around SetEtcHostsEntry.
692

693
  """
694
  hi = HostInfo(name=hostname)
695
  SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
696

    
697

    
698
def RemoveEtcHostsEntry(file_name, hostname):
699
  """Removes a hostname from /etc/hosts.
700

701
  IP addresses without names are removed from the file.
702
  """
703
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
704
  try:
705
    out = os.fdopen(fd, 'w')
706
    try:
707
      f = open(file_name, 'r')
708
      try:
709
        for line in f:
710
          fields = line.split()
711
          if len(fields) > 1 and not fields[0].startswith('#'):
712
            names = fields[1:]
713
            if hostname in names:
714
              while hostname in names:
715
                names.remove(hostname)
716
              if names:
717
                out.write("%s %s\n" % (fields[0], ' '.join(names)))
718
              continue
719

    
720
          out.write(line)
721

    
722
        out.flush()
723
        os.fsync(out)
724
        os.rename(tmpname, file_name)
725
      finally:
726
        f.close()
727
    finally:
728
      out.close()
729
  except:
730
    RemoveFile(tmpname)
731
    raise
732

    
733

    
734
def RemoveHostFromEtcHosts(hostname):
735
  """Wrapper around RemoveEtcHostsEntry.
736

737
  """
738
  hi = HostInfo(name=hostname)
739
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
740
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
741

    
742

    
743
def CreateBackup(file_name):
744
  """Creates a backup of a file.
745

746
  Returns: the path to the newly created backup file.
747

748
  """
749
  if not os.path.isfile(file_name):
750
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
751
                                file_name)
752

    
753
  prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
754
  dir_name = os.path.dirname(file_name)
755

    
756
  fsrc = open(file_name, 'rb')
757
  try:
758
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
759
    fdst = os.fdopen(fd, 'wb')
760
    try:
761
      shutil.copyfileobj(fsrc, fdst)
762
    finally:
763
      fdst.close()
764
  finally:
765
    fsrc.close()
766

    
767
  return backup_name
768

    
769

    
770
def ShellQuote(value):
771
  """Quotes shell argument according to POSIX.
772

773
  """
774
  if _re_shell_unquoted.match(value):
775
    return value
776
  else:
777
    return "'%s'" % value.replace("'", "'\\''")
778

    
779

    
780
def ShellQuoteArgs(args):
781
  """Quotes all given shell arguments and concatenates using spaces.
782

783
  """
784
  return ' '.join([ShellQuote(i) for i in args])
785

    
786

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

790
  Try to do a TCP connect(2) from an optional source IP to the
791
  specified target IP and the specified target port. If the optional
792
  parameter live_port_needed is set to true, requires the remote end
793
  to accept the connection. The timeout is specified in seconds and
794
  defaults to 10 seconds. If the source optional argument is not
795
  passed, the source address selection is left to the kernel,
796
  otherwise we try to connect using the passed address (failures to
797
  bind other than EADDRNOTAVAIL will be ignored).
798

799
  """
800
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
801

    
802
  sucess = False
803

    
804
  if source is not None:
805
    try:
806
      sock.bind((source, 0))
807
    except socket.error, (errcode, errstring):
808
      if errcode == errno.EADDRNOTAVAIL:
809
        success = False
810

    
811
  sock.settimeout(timeout)
812

    
813
  try:
814
    sock.connect((target, port))
815
    sock.close()
816
    success = True
817
  except socket.timeout:
818
    success = False
819
  except socket.error, (errcode, errstring):
820
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
821

    
822
  return success
823

    
824

    
825
def ListVisibleFiles(path):
826
  """Returns a list of all visible files in a directory.
827

828
  """
829
  files = [i for i in os.listdir(path) if not i.startswith(".")]
830
  files.sort()
831
  return files
832

    
833

    
834
def GetHomeDir(user, default=None):
835
  """Try to get the homedir of the given user.
836

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

841
  """
842
  try:
843
    if isinstance(user, basestring):
844
      result = pwd.getpwnam(user)
845
    elif isinstance(user, (int, long)):
846
      result = pwd.getpwuid(user)
847
    else:
848
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
849
                                   type(user))
850
  except KeyError:
851
    return default
852
  return result.pw_dir
853

    
854

    
855
def NewUUID():
856
  """Returns a random UUID.
857

858
  """
859
  f = open("/proc/sys/kernel/random/uuid", "r")
860
  try:
861
    return f.read(128).rstrip("\n")
862
  finally:
863
    f.close()
864

    
865

    
866
def WriteFile(file_name, fn=None, data=None,
867
              mode=None, uid=-1, gid=-1,
868
              atime=None, mtime=None, close=True,
869
              dry_run=False, backup=False,
870
              prewrite=None, postwrite=None):
871
  """(Over)write a file atomically.
872

873
  The file_name and either fn (a function taking one argument, the
874
  file descriptor, and which should write the data to it) or data (the
875
  contents of the file) must be passed. The other arguments are
876
  optional and allow setting the file mode, owner and group, and the
877
  mtime/atime of the file.
878

879
  If the function doesn't raise an exception, it has succeeded and the
880
  target file has the new contents. If the file has raised an
881
  exception, an existing target file should be unmodified and the
882
  temporary file should be removed.
883

884
  Args:
885
    file_name: New filename
886
    fn: Content writing function, called with file descriptor as parameter
887
    data: Content as string
888
    mode: File mode
889
    uid: Owner
890
    gid: Group
891
    atime: Access time
892
    mtime: Modification time
893
    close: Whether to close file after writing it
894
    prewrite: Function object called before writing content
895
    postwrite: Function object called after writing content
896

897
  Returns:
898
    None if "close" parameter evaluates to True, otherwise file descriptor.
899

900
  """
901
  if not os.path.isabs(file_name):
902
    raise errors.ProgrammerError("Path passed to WriteFile is not"
903
                                 " absolute: '%s'" % file_name)
904

    
905
  if [fn, data].count(None) != 1:
906
    raise errors.ProgrammerError("fn or data required")
907

    
908
  if [atime, mtime].count(None) == 1:
909
    raise errors.ProgrammerError("Both atime and mtime must be either"
910
                                 " set or None")
911

    
912
  if backup and not dry_run and os.path.isfile(file_name):
913
    CreateBackup(file_name)
914

    
915
  dir_name, base_name = os.path.split(file_name)
916
  fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
917
  # here we need to make sure we remove the temp file, if any error
918
  # leaves it in place
919
  try:
920
    if uid != -1 or gid != -1:
921
      os.chown(new_name, uid, gid)
922
    if mode:
923
      os.chmod(new_name, mode)
924
    if callable(prewrite):
925
      prewrite(fd)
926
    if data is not None:
927
      os.write(fd, data)
928
    else:
929
      fn(fd)
930
    if callable(postwrite):
931
      postwrite(fd)
932
    os.fsync(fd)
933
    if atime is not None and mtime is not None:
934
      os.utime(new_name, (atime, mtime))
935
    if not dry_run:
936
      os.rename(new_name, file_name)
937
  finally:
938
    if close:
939
      os.close(fd)
940
      result = None
941
    else:
942
      result = fd
943
    RemoveFile(new_name)
944

    
945
  return result
946

    
947

    
948
def FirstFree(seq, base=0):
949
  """Returns the first non-existing integer from seq.
950

951
  The seq argument should be a sorted list of positive integers. The
952
  first time the index of an element is smaller than the element
953
  value, the index will be returned.
954

955
  The base argument is used to start at a different offset,
956
  i.e. [3, 4, 6] with offset=3 will return 5.
957

958
  Example: [0, 1, 3] will return 2.
959

960
  """
961
  for idx, elem in enumerate(seq):
962
    assert elem >= base, "Passed element is higher than base offset"
963
    if elem > idx + base:
964
      # idx is not used
965
      return idx + base
966
  return None
967

    
968

    
969
def all(seq, pred=bool):
970
  "Returns True if pred(x) is True for every element in the iterable"
971
  for elem in itertools.ifilterfalse(pred, seq):
972
    return False
973
  return True
974

    
975

    
976
def any(seq, pred=bool):
977
  "Returns True if pred(x) is True for at least one element in the iterable"
978
  for elem in itertools.ifilter(pred, seq):
979
    return True
980
  return False
981

    
982

    
983
def UniqueSequence(seq):
984
  """Returns a list with unique elements.
985

986
  Element order is preserved.
987
  """
988
  seen = set()
989
  return [i for i in seq if i not in seen and not seen.add(i)]
990

    
991

    
992
def IsValidMac(mac):
993
  """Predicate to check if a MAC address is valid.
994

995
  Checks wether the supplied MAC address is formally correct, only
996
  accepts colon separated format.
997
  """
998
  mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$")
999
  return mac_check.match(mac) is not None
1000

    
1001

    
1002
def TestDelay(duration):
1003
  """Sleep for a fixed amount of time.
1004

1005
  """
1006
  if duration < 0:
1007
    return False
1008
  time.sleep(duration)
1009
  return True
1010

    
1011

    
1012
def Daemonize(logfile, noclose_fds=None):
1013
  """Daemonize the current process.
1014

1015
  This detaches the current process from the controlling terminal and
1016
  runs it in the background as a daemon.
1017

1018
  """
1019
  UMASK = 077
1020
  WORKDIR = "/"
1021
  # Default maximum for the number of available file descriptors.
1022
  if 'SC_OPEN_MAX' in os.sysconf_names:
1023
    try:
1024
      MAXFD = os.sysconf('SC_OPEN_MAX')
1025
      if MAXFD < 0:
1026
        MAXFD = 1024
1027
    except OSError:
1028
      MAXFD = 1024
1029
  else:
1030
    MAXFD = 1024
1031

    
1032
  # this might fail
1033
  pid = os.fork()
1034
  if (pid == 0):  # The first child.
1035
    os.setsid()
1036
    # this might fail
1037
    pid = os.fork() # Fork a second child.
1038
    if (pid == 0):  # The second child.
1039
      os.chdir(WORKDIR)
1040
      os.umask(UMASK)
1041
    else:
1042
      # exit() or _exit()?  See below.
1043
      os._exit(0) # Exit parent (the first child) of the second child.
1044
  else:
1045
    os._exit(0) # Exit parent of the first child.
1046
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1047
  if (maxfd == resource.RLIM_INFINITY):
1048
    maxfd = MAXFD
1049

    
1050
  # Iterate through and close all file descriptors.
1051
  for fd in range(0, maxfd):
1052
    if noclose_fds and fd in noclose_fds:
1053
      continue
1054
    try:
1055
      os.close(fd)
1056
    except OSError: # ERROR, fd wasn't open to begin with (ignored)
1057
      pass
1058
  os.open(logfile, os.O_RDWR|os.O_CREAT|os.O_APPEND, 0600)
1059
  # Duplicate standard input to standard output and standard error.
1060
  os.dup2(0, 1)     # standard output (1)
1061
  os.dup2(0, 2)     # standard error (2)
1062
  return 0
1063

    
1064

    
1065
def DaemonPidFileName(name):
1066
  """Compute a ganeti pid file absolute path, given the daemon name.
1067

1068
  """
1069
  return os.path.join(constants.RUN_GANETI_DIR, "%s.pid" % name)
1070

    
1071

    
1072
def WritePidFile(name):
1073
  """Write the current process pidfile.
1074

1075
  The file will be written to constants.RUN_GANETI_DIR/name.pid
1076

1077
  """
1078
  pid = os.getpid()
1079
  pidfilename = DaemonPidFileName(name)
1080
  if IsProcessAlive(ReadPidFile(pidfilename)):
1081
    raise errors.GenericError("%s contains a live process" % pidfilename)
1082

    
1083
  WriteFile(pidfilename, data="%d\n" % pid)
1084

    
1085

    
1086
def RemovePidFile(name):
1087
  """Remove the current process pidfile.
1088

1089
  Any errors are ignored.
1090

1091
  """
1092
  pid = os.getpid()
1093
  pidfilename = DaemonPidFileName(name)
1094
  # TODO: we could check here that the file contains our pid
1095
  try:
1096
    RemoveFile(pidfilename)
1097
  except:
1098
    pass
1099

    
1100

    
1101
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30):
1102
  """Kill a process given by its pid.
1103

1104
  @type pid: int
1105
  @param pid: The PID to terminate.
1106
  @type signal_: int
1107
  @param signal_: The signal to send, by default SIGTERM
1108
  @type timeout: int
1109
  @param timeout: The timeout after which, if the process is still alive,
1110
                  a SIGKILL will be sent. If not positive, no such checking
1111
                  will be done
1112

1113
  """
1114
  if pid <= 0:
1115
    # kill with pid=0 == suicide
1116
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1117

    
1118
  if not IsProcessAlive(pid):
1119
    return
1120
  os.kill(pid, signal_)
1121
  if timeout <= 0:
1122
    return
1123
  end = time.time() + timeout
1124
  while time.time() < end and IsProcessAlive(pid):
1125
    time.sleep(0.1)
1126
  if IsProcessAlive(pid):
1127
    os.kill(pid, signal.SIGKILL)
1128

    
1129

    
1130
def FindFile(name, search_path, test=os.path.exists):
1131
  """Look for a filesystem object in a given path.
1132

1133
  This is an abstract method to search for filesystem object (files,
1134
  dirs) under a given search path.
1135

1136
  Args:
1137
    - name: the name to look for
1138
    - search_path: list of directory names
1139
    - test: the test which the full path must satisfy
1140
      (defaults to os.path.exists)
1141

1142
  Returns:
1143
    - full path to the item if found
1144
    - None otherwise
1145

1146
  """
1147
  for dir_name in search_path:
1148
    item_name = os.path.sep.join([dir_name, name])
1149
    if test(item_name):
1150
      return item_name
1151
  return None
1152

    
1153

    
1154
def CheckVolumeGroupSize(vglist, vgname, minsize):
1155
  """Checks if the volume group list is valid.
1156

1157
  A non-None return value means there's an error, and the return value
1158
  is the error message.
1159

1160
  """
1161
  vgsize = vglist.get(vgname, None)
1162
  if vgsize is None:
1163
    return "volume group '%s' missing" % vgname
1164
  elif vgsize < minsize:
1165
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1166
            (vgname, minsize, vgsize))
1167
  return None
1168

    
1169

    
1170
def SplitTime(seconds):
1171
  """Splits time as floating point number into a tuple.
1172

1173
  @param seconds: Time in seconds
1174
  @type seconds: int or float
1175
  @return: Tuple containing (seconds, milliseconds)
1176

1177
  """
1178
  (seconds, fraction) = divmod(seconds, 1.0)
1179
  return (int(seconds), int(round(fraction * 1000, 0)))
1180

    
1181

    
1182
def MergeTime(timetuple):
1183
  """Merges a tuple into time as a floating point number.
1184

1185
  @param timetuple: Time as tuple, (seconds, milliseconds)
1186
  @type timetuple: tuple
1187
  @return: Time as a floating point number expressed in seconds
1188

1189
  """
1190
  (seconds, milliseconds) = timetuple
1191

    
1192
  assert 0 <= seconds, "Seconds must be larger than 0"
1193
  assert 0 <= milliseconds <= 999, "Milliseconds must be 0-999"
1194

    
1195
  return float(seconds) + (float(1) / 1000 * milliseconds)
1196

    
1197

    
1198
def LockedMethod(fn):
1199
  """Synchronized object access decorator.
1200

1201
  This decorator is intended to protect access to an object using the
1202
  object's own lock which is hardcoded to '_lock'.
1203

1204
  """
1205
  def wrapper(self, *args, **kwargs):
1206
    assert hasattr(self, '_lock')
1207
    lock = self._lock
1208
    lock.acquire()
1209
    try:
1210
      result = fn(self, *args, **kwargs)
1211
    finally:
1212
      lock.release()
1213
    return result
1214
  return wrapper
1215

    
1216

    
1217
def LockFile(fd):
1218
  """Locks a file using POSIX locks.
1219

1220
  """
1221
  try:
1222
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
1223
  except IOError, err:
1224
    if err.errno == errno.EAGAIN:
1225
      raise errors.LockError("File already locked")
1226
    raise
1227

    
1228

    
1229
class FileLock(object):
1230
  """Utility class for file locks.
1231

1232
  """
1233
  def __init__(self, filename):
1234
    self.filename = filename
1235
    self.fd = open(self.filename, "w")
1236

    
1237
  def __del__(self):
1238
    self.Close()
1239

    
1240
  def Close(self):
1241
    if self.fd:
1242
      self.fd.close()
1243
      self.fd = None
1244

    
1245
  def _flock(self, flag, blocking, errmsg):
1246
    assert self.fd, "Lock was closed"
1247

    
1248
    if not blocking:
1249
      flag |= fcntl.LOCK_NB
1250

    
1251
    try:
1252
      fcntl.flock(self.fd, flag)
1253
    except IOError, err:
1254
      if err.errno in (errno.EAGAIN, ):
1255
        raise errors.LockError(errmsg)
1256
      else:
1257
        logging.exception("fcntl.flock failed")
1258
        raise
1259

    
1260
  def Exclusive(self, blocking=False):
1261
    """Locks the file in exclusive mode.
1262

1263
    """
1264
    self._flock(fcntl.LOCK_EX, blocking,
1265
                "Failed to lock %s in exclusive mode" % self.filename)
1266

    
1267
  def Shared(self, blocking=False):
1268
    """Locks the file in shared mode.
1269

1270
    """
1271
    self._flock(fcntl.LOCK_SH, blocking,
1272
                "Failed to lock %s in shared mode" % self.filename)
1273

    
1274
  def Unlock(self, blocking=True):
1275
    """Unlocks the file.
1276

1277
    According to "man flock", unlocking can also be a nonblocking operation:
1278
    "To make a non-blocking request, include LOCK_NB with any of the above
1279
    operations"
1280

1281
    """
1282
    self._flock(fcntl.LOCK_UN, blocking,
1283
                "Failed to unlock %s" % self.filename)
1284

    
1285

    
1286
class SignalHandler(object):
1287
  """Generic signal handler class.
1288

1289
  It automatically restores the original handler when deconstructed or when
1290
  Reset() is called. You can either pass your own handler function in or query
1291
  the "called" attribute to detect whether the signal was sent.
1292

1293
  """
1294
  def __init__(self, signum):
1295
    """Constructs a new SignalHandler instance.
1296

1297
    @param signum: Single signal number or set of signal numbers
1298

1299
    """
1300
    if isinstance(signum, (int, long)):
1301
      self.signum = set([signum])
1302
    else:
1303
      self.signum = set(signum)
1304

    
1305
    self.called = False
1306

    
1307
    self._previous = {}
1308
    try:
1309
      for signum in self.signum:
1310
        # Setup handler
1311
        prev_handler = signal.signal(signum, self._HandleSignal)
1312
        try:
1313
          self._previous[signum] = prev_handler
1314
        except:
1315
          # Restore previous handler
1316
          signal.signal(signum, prev_handler)
1317
          raise
1318
    except:
1319
      # Reset all handlers
1320
      self.Reset()
1321
      # Here we have a race condition: a handler may have already been called,
1322
      # but there's not much we can do about it at this point.
1323
      raise
1324

    
1325
  def __del__(self):
1326
    self.Reset()
1327

    
1328
  def Reset(self):
1329
    """Restore previous handler.
1330

1331
    """
1332
    for signum, prev_handler in self._previous.items():
1333
      signal.signal(signum, prev_handler)
1334
      # If successful, remove from dict
1335
      del self._previous[signum]
1336

    
1337
  def Clear(self):
1338
    """Unsets "called" flag.
1339

1340
    This function can be used in case a signal may arrive several times.
1341

1342
    """
1343
    self.called = False
1344

    
1345
  def _HandleSignal(self, signum, frame):
1346
    """Actual signal handling function.
1347

1348
    """
1349
    # This is not nice and not absolutely atomic, but it appears to be the only
1350
    # solution in Python -- there are no atomic types.
1351
    self.called = True