Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ 2f31098c

History | View | Annotate | Download (21.1 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
import sys
27
import os
28
import sha
29
import time
30
import subprocess
31
import re
32
import socket
33
import tempfile
34
import shutil
35
import errno
36

    
37
from ganeti import logger
38
from ganeti import errors
39

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

    
43
class RunResult(object):
44
  """Simple class for holding the result of running external programs.
45

46
  Instance variables:
47
    exit_code: the exit code of the program, or None (if the program
48
               didn't exit())
49
    signal: numeric signal that caused the program to finish, or None
50
            (if the program wasn't terminated by a signal)
51
    stdout: the standard output of the program
52
    stderr: the standard error of the program
53
    failed: a Boolean value which is True in case the program was
54
            terminated by a signal or exited with a non-zero exit code
55
    fail_reason: a string detailing the termination reason
56

57
  """
58
  __slots__ = ["exit_code", "signal", "stdout", "stderr",
59
               "failed", "fail_reason", "cmd"]
60

    
61

    
62
  def __init__(self, exit_code, signal, stdout, stderr, cmd):
63
    self.cmd = cmd
64
    self.exit_code = exit_code
65
    self.signal = signal
66
    self.stdout = stdout
67
    self.stderr = stderr
68
    self.failed = (signal is not None or exit_code != 0)
69

    
70
    if self.signal is not None:
71
      self.fail_reason = "terminated by signal %s" % self.signal
72
    elif self.exit_code is not None:
73
      self.fail_reason = "exited with exit code %s" % self.exit_code
74
    else:
75
      self.fail_reason = "unable to determine termination reason"
76

    
77
  def _GetOutput(self):
78
    """Returns the combined stdout and stderr for easier usage.
79

80
    """
81
    return self.stdout + self.stderr
82

    
83
  output = property(_GetOutput, None, None, "Return full output")
84

    
85

    
86
def _GetLockFile(subsystem):
87
  """Compute the file name for a given lock name."""
88
  return "/var/lock/ganeti_lock_%s" % subsystem
89

    
90

    
91
def Lock(name, max_retries=None, debug=False):
92
  """Lock a given subsystem.
93

94
  In case the lock is already held by an alive process, the function
95
  will sleep indefintely and poll with a one second interval.
96

97
  When the optional integer argument 'max_retries' is passed with a
98
  non-zero value, the function will sleep only for this number of
99
  times, and then it will will raise a LockError if the lock can't be
100
  acquired. Passing in a negative number will cause only one try to
101
  get the lock. Passing a positive number will make the function retry
102
  for approximately that number of seconds.
103

104
  """
105
  lockfile = _GetLockFile(name)
106

    
107
  if name in _locksheld:
108
    raise errors.LockError('Lock "%s" already held!' % (name,))
109

    
110
  errcount = 0
111

    
112
  retries = 0
113
  while True:
114
    try:
115
      fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR | os.O_SYNC)
116
      break
117
    except OSError, creat_err:
118
      if creat_err.errno != errno.EEXIST:
119
        raise errors.LockError("Can't create the lock file. Error '%s'." %
120
                               str(creat_err))
121

    
122
      try:
123
        pf = open(lockfile, 'r')
124
      except IOError, open_err:
125
        errcount += 1
126
        if errcount >= 5:
127
          raise errors.LockError("Lock file exists but cannot be opened."
128
                                 " Error: '%s'." % str(open_err))
129
        time.sleep(1)
130
        continue
131

    
132
      try:
133
        pid = int(pf.read())
134
      except ValueError:
135
        raise errors.LockError("Invalid pid string in %s" %
136
                               (lockfile,))
137

    
138
      if not IsProcessAlive(pid):
139
        raise errors.LockError("Stale lockfile %s for pid %d?" %
140
                               (lockfile, pid))
141

    
142
      if max_retries and max_retries <= retries:
143
        raise errors.LockError("Can't acquire lock during the specified"
144
                               " time, aborting.")
145
      if retries == 5 and (debug or sys.stdin.isatty()):
146
        logger.ToStderr("Waiting for '%s' lock from pid %d..." % (name, pid))
147

    
148
      time.sleep(1)
149
      retries += 1
150
      continue
151

    
152
  os.write(fd, '%d\n' % (os.getpid(),))
153
  os.close(fd)
154

    
155
  _locksheld.append(name)
156

    
157

    
158
def Unlock(name):
159
  """Unlock a given subsystem.
160

161
  """
162
  lockfile = _GetLockFile(name)
163

    
164
  try:
165
    fd = os.open(lockfile, os.O_RDONLY)
166
  except OSError:
167
    raise errors.LockError('Lock "%s" not held.' % (name,))
168

    
169
  f = os.fdopen(fd, 'r')
170
  pid_str = f.read()
171

    
172
  try:
173
    pid = int(pid_str)
174
  except ValueError:
175
    raise errors.LockError('Unable to determine PID of locking process.')
176

    
177
  if pid != os.getpid():
178
    raise errors.LockError('Lock not held by me (%d != %d)' %
179
                           (os.getpid(), pid,))
180

    
181
  os.unlink(lockfile)
182
  _locksheld.remove(name)
183

    
184

    
185
def LockCleanup():
186
  """Remove all locks.
187

188
  """
189
  for lock in _locksheld:
190
    Unlock(lock)
191

    
192

    
193
def RunCmd(cmd):
194
  """Execute a (shell) command.
195

196
  The command should not read from its standard input, as it will be
197
  closed.
198

199
  Args:
200
    cmd: command to run. (str)
201

202
  Returns: `RunResult` instance
203

204
  """
205
  if isinstance(cmd, list):
206
    cmd = [str(val) for val in cmd]
207
    strcmd = " ".join(cmd)
208
    shell = False
209
  else:
210
    strcmd = cmd
211
    shell = True
212
  env = os.environ.copy()
213
  env["LC_ALL"] = "C"
214
  child = subprocess.Popen(cmd, shell=shell,
215
                           stderr=subprocess.PIPE,
216
                           stdout=subprocess.PIPE,
217
                           stdin=subprocess.PIPE,
218
                           close_fds=True, env=env)
219

    
220
  child.stdin.close()
221
  out = child.stdout.read()
222
  err = child.stderr.read()
223

    
224
  status = child.wait()
225
  if status >= 0:
226
    exitcode = status
227
    signal = None
228
  else:
229
    exitcode = None
230
    signal = -status
231

    
232
  return RunResult(exitcode, signal, out, err, strcmd)
233

    
234

    
235
def RunCmdUnlocked(cmd):
236
  """Execute a shell command without the 'cmd' lock.
237

238
  This variant of `RunCmd()` drops the 'cmd' lock before running the
239
  command and re-aquires it afterwards, thus it can be used to call
240
  other ganeti commands.
241

242
  The argument and return values are the same as for the `RunCmd()`
243
  function.
244

245
  Args:
246
    cmd - command to run. (str)
247

248
  Returns:
249
    `RunResult`
250

251
  """
252
  Unlock('cmd')
253
  ret = RunCmd(cmd)
254
  Lock('cmd')
255

    
256
  return ret
257

    
258

    
259
def RemoveFile(filename):
260
  """Remove a file ignoring some errors.
261

262
  Remove a file, ignoring non-existing ones or directories. Other
263
  errors are passed.
264

265
  """
266
  try:
267
    os.unlink(filename)
268
  except OSError, err:
269
    if err.errno not in (errno.ENOENT, errno.EISDIR):
270
      raise
271

    
272

    
273
def _FingerprintFile(filename):
274
  """Compute the fingerprint of a file.
275

276
  If the file does not exist, a None will be returned
277
  instead.
278

279
  Args:
280
    filename - Filename (str)
281

282
  """
283
  if not (os.path.exists(filename) and os.path.isfile(filename)):
284
    return None
285

    
286
  f = open(filename)
287

    
288
  fp = sha.sha()
289
  while True:
290
    data = f.read(4096)
291
    if not data:
292
      break
293

    
294
    fp.update(data)
295

    
296
  return fp.hexdigest()
297

    
298

    
299
def FingerprintFiles(files):
300
  """Compute fingerprints for a list of files.
301

302
  Args:
303
    files - array of filenames.  ( [str, ...] )
304

305
  Return value:
306
    dictionary of filename: fingerprint for the files that exist
307

308
  """
309
  ret = {}
310

    
311
  for filename in files:
312
    cksum = _FingerprintFile(filename)
313
    if cksum:
314
      ret[filename] = cksum
315

    
316
  return ret
317

    
318

    
319
def CheckDict(target, template, logname=None):
320
  """Ensure a dictionary has a required set of keys.
321

322
  For the given dictionaries `target` and `template`, ensure target
323
  has all the keys from template. Missing keys are added with values
324
  from template.
325

326
  Args:
327
    target   - the dictionary to check
328
    template - template dictionary
329
    logname  - a caller-chosen string to identify the debug log
330
               entry; if None, no logging will be done
331

332
  Returns value:
333
    None
334

335
  """
336
  missing = []
337
  for k in template:
338
    if k not in target:
339
      missing.append(k)
340
      target[k] = template[k]
341

    
342
  if missing and logname:
343
    logger.Debug('%s missing keys %s' %
344
                 (logname, ', '.join(missing)))
345

    
346

    
347
def IsProcessAlive(pid):
348
  """Check if a given pid exists on the system.
349

350
  Returns: true or false, depending on if the pid exists or not
351

352
  Remarks: zombie processes treated as not alive
353

354
  """
355
  try:
356
    f = open("/proc/%d/status" % pid)
357
  except IOError, err:
358
    if err.errno in (errno.ENOENT, errno.ENOTDIR):
359
      return False
360

    
361
  alive = True
362
  try:
363
    data = f.readlines()
364
    if len(data) > 1:
365
      state = data[1].split()
366
      if len(state) > 1 and state[1] == "Z":
367
        alive = False
368
  finally:
369
    f.close()
370

    
371
  return alive
372

    
373

    
374
def MatchNameComponent(key, name_list):
375
  """Try to match a name against a list.
376

377
  This function will try to match a name like test1 against a list
378
  like ['test1.example.com', 'test2.example.com', ...]. Against this
379
  list, 'test1' as well as 'test1.example' will match, but not
380
  'test1.ex'. A multiple match will be considered as no match at all
381
  (e.g. 'test1' against ['test1.example.com', 'test1.example.org']).
382

383
  Args:
384
    key: the name to be searched
385
    name_list: the list of strings against which to search the key
386

387
  Returns:
388
    None if there is no match *or* if there are multiple matches
389
    otherwise the element from the list which matches
390

391
  """
392
  mo = re.compile("^%s(\..*)?$" % re.escape(key))
393
  names_filtered = [name for name in name_list if mo.match(name) is not None]
394
  if len(names_filtered) != 1:
395
    return None
396
  return names_filtered[0]
397

    
398

    
399
class HostInfo:
400
  """Class implementing resolver and hostname functionality
401

402
  """
403
  def __init__(self, name=None):
404
    """Initialize the host name object.
405

406
    If the name argument is not passed, it will use this system's
407
    name.
408

409
    """
410
    if name is None:
411
      name = self.SysName()
412

    
413
    self.query = name
414
    self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
415
    self.ip = self.ipaddrs[0]
416

    
417
  @staticmethod
418
  def SysName():
419
    """Return the current system's name.
420

421
    This is simply a wrapper over socket.gethostname()
422

423
    """
424
    return socket.gethostname()
425

    
426
  @staticmethod
427
  def LookupHostname(hostname):
428
    """Look up hostname
429

430
    Args:
431
      hostname: hostname to look up
432

433
    Returns:
434
      a tuple (name, aliases, ipaddrs) as returned by socket.gethostbyname_ex
435
      in case of errors in resolving, we raise a ResolverError
436

437
    """
438
    try:
439
      result = socket.gethostbyname_ex(hostname)
440
    except socket.gaierror, err:
441
      # hostname not found in DNS
442
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
443

    
444
    return result
445

    
446

    
447
def ListVolumeGroups():
448
  """List volume groups and their size
449

450
  Returns:
451
     Dictionary with keys volume name and values the size of the volume
452

453
  """
454
  command = "vgs --noheadings --units m --nosuffix -o name,size"
455
  result = RunCmd(command)
456
  retval = {}
457
  if result.failed:
458
    return retval
459

    
460
  for line in result.stdout.splitlines():
461
    try:
462
      name, size = line.split()
463
      size = int(float(size))
464
    except (IndexError, ValueError), err:
465
      logger.Error("Invalid output from vgs (%s): %s" % (err, line))
466
      continue
467

    
468
    retval[name] = size
469

    
470
  return retval
471

    
472

    
473
def BridgeExists(bridge):
474
  """Check whether the given bridge exists in the system
475

476
  Returns:
477
     True if it does, false otherwise.
478

479
  """
480
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
481

    
482

    
483
def NiceSort(name_list):
484
  """Sort a list of strings based on digit and non-digit groupings.
485

486
  Given a list of names ['a1', 'a10', 'a11', 'a2'] this function will
487
  sort the list in the logical order ['a1', 'a2', 'a10', 'a11'].
488

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

493
  Return value
494
    - a copy of the list sorted according to our algorithm
495

496
  """
497
  _SORTER_BASE = "(\D+|\d+)"
498
  _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
499
                                                  _SORTER_BASE, _SORTER_BASE,
500
                                                  _SORTER_BASE, _SORTER_BASE,
501
                                                  _SORTER_BASE, _SORTER_BASE)
502
  _SORTER_RE = re.compile(_SORTER_FULL)
503
  _SORTER_NODIGIT = re.compile("^\D*$")
504
  def _TryInt(val):
505
    """Attempts to convert a variable to integer."""
506
    if val is None or _SORTER_NODIGIT.match(val):
507
      return val
508
    rval = int(val)
509
    return rval
510

    
511
  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
512
             for name in name_list]
513
  to_sort.sort()
514
  return [tup[1] for tup in to_sort]
515

    
516

    
517
def CheckDaemonAlive(pid_file, process_string):
518
  """Check wether the specified daemon is alive.
519

520
  Args:
521
   - pid_file: file to read the daemon pid from, the file is
522
               expected to contain only a single line containing
523
               only the PID
524
   - process_string: a substring that we expect to find in
525
                     the command line of the daemon process
526

527
  Returns:
528
   - True if the daemon is judged to be alive (that is:
529
      - the PID file exists, is readable and contains a number
530
      - a process of the specified PID is running
531
      - that process contains the specified string in its
532
        command line
533
      - the process is not in state Z (zombie))
534
   - False otherwise
535

536
  """
537
  try:
538
    pid_file = file(pid_file, 'r')
539
    try:
540
      pid = int(pid_file.readline())
541
    finally:
542
      pid_file.close()
543

    
544
    cmdline_file_path = "/proc/%s/cmdline" % (pid)
545
    cmdline_file = open(cmdline_file_path, 'r')
546
    try:
547
      cmdline = cmdline_file.readline()
548
    finally:
549
      cmdline_file.close()
550

    
551
    if not process_string in cmdline:
552
      return False
553

    
554
    stat_file_path =  "/proc/%s/stat" % (pid)
555
    stat_file = open(stat_file_path, 'r')
556
    try:
557
      process_state = stat_file.readline().split()[2]
558
    finally:
559
      stat_file.close()
560

    
561
    if process_state == 'Z':
562
      return False
563

    
564
  except (IndexError, IOError, ValueError):
565
    return False
566

    
567
  return True
568

    
569

    
570
def TryConvert(fn, val):
571
  """Try to convert a value ignoring errors.
572

573
  This function tries to apply function `fn` to `val`. If no
574
  ValueError or TypeError exceptions are raised, it will return the
575
  result, else it will return the original value. Any other exceptions
576
  are propagated to the caller.
577

578
  """
579
  try:
580
    nv = fn(val)
581
  except (ValueError, TypeError), err:
582
    nv = val
583
  return nv
584

    
585

    
586
def IsValidIP(ip):
587
  """Verifies the syntax of an IP address.
588

589
  This function checks if the ip address passes is valid or not based
590
  on syntax (not ip range, class calculations or anything).
591

592
  """
593
  unit = "(0|[1-9]\d{0,2})"
594
  return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
595

    
596

    
597
def IsValidShellParam(word):
598
  """Verifies is the given word is safe from the shell's p.o.v.
599

600
  This means that we can pass this to a command via the shell and be
601
  sure that it doesn't alter the command line and is passed as such to
602
  the actual command.
603

604
  Note that we are overly restrictive here, in order to be on the safe
605
  side.
606

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

    
610

    
611
def BuildShellCmd(template, *args):
612
  """Build a safe shell command line from the given arguments.
613

614
  This function will check all arguments in the args list so that they
615
  are valid shell parameters (i.e. they don't contain shell
616
  metacharaters). If everything is ok, it will return the result of
617
  template % args.
618

619
  """
620
  for word in args:
621
    if not IsValidShellParam(word):
622
      raise errors.ProgrammerError("Shell argument '%s' contains"
623
                                   " invalid characters" % word)
624
  return template % args
625

    
626

    
627
def FormatUnit(value):
628
  """Formats an incoming number of MiB with the appropriate unit.
629

630
  Value needs to be passed as a numeric type. Return value is always a string.
631

632
  """
633
  if value < 1024:
634
    return "%dM" % round(value, 0)
635

    
636
  elif value < (1024 * 1024):
637
    return "%0.1fG" % round(float(value) / 1024, 1)
638

    
639
  else:
640
    return "%0.1fT" % round(float(value) / 1024 / 1024, 1)
641

    
642

    
643
def ParseUnit(input_string):
644
  """Tries to extract number and scale from the given string.
645

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

649
  """
650
  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', input_string)
651
  if not m:
652
    raise errors.UnitParseError("Invalid format")
653

    
654
  value = float(m.groups()[0])
655

    
656
  unit = m.groups()[1]
657
  if unit:
658
    lcunit = unit.lower()
659
  else:
660
    lcunit = 'm'
661

    
662
  if lcunit in ('m', 'mb', 'mib'):
663
    # Value already in MiB
664
    pass
665

    
666
  elif lcunit in ('g', 'gb', 'gib'):
667
    value *= 1024
668

    
669
  elif lcunit in ('t', 'tb', 'tib'):
670
    value *= 1024 * 1024
671

    
672
  else:
673
    raise errors.UnitParseError("Unknown unit: %s" % unit)
674

    
675
  # Make sure we round up
676
  if int(value) < value:
677
    value += 1
678

    
679
  # Round up to the next multiple of 4
680
  value = int(value)
681
  if value % 4:
682
    value += 4 - value % 4
683

    
684
  return value
685

    
686

    
687
def AddAuthorizedKey(file_name, key):
688
  """Adds an SSH public key to an authorized_keys file.
689

690
  Args:
691
    file_name: Path to authorized_keys file
692
    key: String containing key
693
  """
694
  key_fields = key.split()
695

    
696
  f = open(file_name, 'a+')
697
  try:
698
    nl = True
699
    for line in f:
700
      # Ignore whitespace changes
701
      if line.split() == key_fields:
702
        break
703
      nl = line.endswith('\n')
704
    else:
705
      if not nl:
706
        f.write("\n")
707
      f.write(key.rstrip('\r\n'))
708
      f.write("\n")
709
      f.flush()
710
  finally:
711
    f.close()
712

    
713

    
714
def RemoveAuthorizedKey(file_name, key):
715
  """Removes an SSH public key from an authorized_keys file.
716

717
  Args:
718
    file_name: Path to authorized_keys file
719
    key: String containing key
720
  """
721
  key_fields = key.split()
722

    
723
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
724
  out = os.fdopen(fd, 'w')
725
  try:
726
    f = open(file_name, 'r')
727
    try:
728
      for line in f:
729
        # Ignore whitespace changes while comparing lines
730
        if line.split() != key_fields:
731
          out.write(line)
732

    
733
      out.flush()
734
      os.rename(tmpname, file_name)
735
    finally:
736
      f.close()
737
  finally:
738
    out.close()
739

    
740

    
741
def CreateBackup(file_name):
742
  """Creates a backup of a file.
743

744
  Returns: the path to the newly created backup file.
745

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

    
751
  prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
752
  dir = os.path.dirname(file_name)
753

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

    
765
  return backup_name
766

    
767

    
768
def ShellQuote(value):
769
  """Quotes shell argument according to POSIX.
770

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

    
777

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

781
  """
782
  return ' '.join([ShellQuote(i) for i in args])
783

    
784

    
785
def _ParseIpOutput(output):
786
  """Parsing code for GetLocalIPAddresses().
787

788
  This function is split out, so we can unit test it.
789

790
  """
791
  re_ip = re.compile('^(\d+\.\d+\.\d+\.\d+)(?:/\d+)$')
792

    
793
  ips = []
794
  for line in output.splitlines(False):
795
    fields = line.split()
796
    if len(line) < 4:
797
      continue
798
    m = re_ip.match(fields[3])
799
    if m:
800
      ips.append(m.group(1))
801

    
802
  return ips
803

    
804

    
805
def GetLocalIPAddresses():
806
  """Gets a list of all local IP addresses.
807

808
  Should this break one day, a small Python module written in C could
809
  use the API call getifaddrs().
810

811
  """
812
  result = RunCmd(["ip", "-family", "inet", "-oneline", "addr", "show"])
813
  if result.failed:
814
    raise errors.OpExecError("Command '%s' failed, error: %s,"
815
      " output: %s" % (result.cmd, result.fail_reason, result.output))
816

    
817
  return _ParseIpOutput(result.output)
818

    
819

    
820
def TcpPing(source, target, port, timeout=10, live_port_needed=True):
821
  """Simple ping implementation using TCP connect(2).
822

823
  Try to do a TCP connect(2) from the specified source IP to the specified
824
  target IP and the specified target port. If live_port_needed is set to true,
825
  requires the remote end to accept the connection. The timeout is specified
826
  in seconds and defaults to 10 seconds
827

828
  """
829
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
830

    
831
  sucess = False
832

    
833
  try:
834
    sock.bind((source, 0))
835
  except socket.error, (errcode, errstring):
836
    if errcode == errno.EADDRNOTAVAIL:
837
      success = False
838

    
839
  sock.settimeout(timeout)
840

    
841
  try:
842
    sock.connect((target, port))
843
    sock.close()
844
    success = True
845
  except socket.timeout:
846
    success = False
847
  except socket.error, (errcode, errstring):
848
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
849

    
850
  return success
851

    
852

    
853
def ListVisibleFiles(path):
854
  """Returns a list of all visible files in a directory.
855

856
  """
857
  return [i for i in os.listdir(path) if not i.startswith(".")]