Revision 16abfbc2

b/INSTALL
22 22
    http://www.openssh.com/portable.html
23 23
  - bridge utilities
24 24
    http://bridge.sourceforge.net/
25
  - fping
26
    http://fping.sourceforge.net/
27 25
  - iproute2
28 26
    http://developer.osdl.org/dev/iproute2
29 27
  - arping (part of iputils package)
b/daemons/ganeti-master
106 106
  """Starts the master.
107 107

  
108 108
  """
109
  result = utils.RunCmd(["fping", "-q", master_ip])
110
  if not result.failed:
111
    r2 = utils.RunCmd(["fping", "-q", "-S127.0.0.1", master_ip])
112
    if not r2.failed:
109
  if utils.TcpPing(utils.HostInfo().name, master_ip,
110
                   constants.DEFAULT_NODED_PORT):
111
    if utils.TcpPing(constants.LOCALHOST_IP_ADDRESS, master_ip,
112
                     constants.DEFAULT_NODED_PORT):
113 113
      # we already have the ip:
114 114
      if debug:
115 115
        sys.stderr.write("Notice: already started.\n")
b/daemons/ganeti-noded
38 38
from ganeti import objects
39 39
from ganeti import errors
40 40
from ganeti import ssconf
41
from ganeti import utils
41 42

  
42 43
from twisted.spread import pb
43 44
from twisted.internet import reactor
......
347 348
  # node --------------------------
348 349

  
349 350
  @staticmethod
351
  def perspective_node_tcp_ping(params):
352
    """Do a TcpPing on the remote node.
353

  
354
    """
355
    return utils.TcpPing(params[0], params[1], params[2],
356
                         timeout=params[3], live_port_needed=params[4])
357

  
358
  @staticmethod
350 359
  def perspective_node_info(params):
351 360
    """Query node information.
352 361

  
b/doc/install.sgml
378 378
        </listitem>
379 379
        <listitem>
380 380
          <simpara><ulink
381
          url="http://fping.sourceforge.net/">fping</ulink></simpara>
382
        </listitem>
383
        <listitem>
384
          <simpara><ulink
385 381
          url="http://developer.osdl.org/dev/iproute2">iproute2</ulink></simpara>
386 382
        </listitem>
387 383
        <listitem>
......
429 425
      </formalpara>
430 426
      <screen>
431 427
# apt-get install lvm2 ssh bridge-utils iproute iputils-arping \
432
  fping python2.4 python-twisted-core python-pyopenssl openssl \
428
  python2.4 python-twisted-core python-pyopenssl openssl \
433 429
  mdadm
434 430
      </screen>
435 431

  
b/lib/cmdlib.py
567 567

  
568 568
    self.clustername = clustername = utils.HostInfo(self.op.cluster_name)
569 569

  
570
    result = utils.RunCmd(["fping", "-S127.0.0.1", "-q", hostname.ip])
571
    if result.failed:
570
    if not utils.TcpPing(constants.LOCALHOST_IP_ADDRESS, hostname.ip,
571
                         constants.DEFAULT_NODED_PORT):
572 572
      raise errors.OpPrereqError("Inconsistency: this host's name resolves"
573 573
                                 " to %s,\nbut this ip address does not"
574 574
                                 " belong to this host."
......
577 577
    secondary_ip = getattr(self.op, "secondary_ip", None)
578 578
    if secondary_ip and not utils.IsValidIP(secondary_ip):
579 579
      raise errors.OpPrereqError("Invalid secondary ip given")
580
    if secondary_ip and secondary_ip != hostname.ip:
581
      result = utils.RunCmd(["fping", "-S127.0.0.1", "-q", secondary_ip])
582
      if result.failed:
583
        raise errors.OpPrereqError("You gave %s as secondary IP,\n"
584
                                   "but it does not belong to this host." %
585
                                   secondary_ip)
580
    if (secondary_ip and
581
        secondary_ip != hostname.ip and
582
        (not utils.TcpPing(constants.LOCALHOST_IP_ADDRESS, secondary_ip,
583
                           constants.DEFAULT_NODED_PORT))):
584
      raise errors.OpPrereqError("You gave %s as secondary IP,\n"
585
                                 "but it does not belong to this host." %
586
                                 secondary_ip)
586 587
    self.secondary_ip = secondary_ip
587 588

  
588 589
    # checks presence of the volume group given
......
1428 1429
                                   " new node doesn't have one")
1429 1430

  
1430 1431
    # checks reachablity
1431
    command = ["fping", "-q", primary_ip]
1432
    result = utils.RunCmd(command)
1433
    if result.failed:
1432
    if not utils.TcpPing(utils.HostInfo().name,
1433
                         primary_ip,
1434
                         constants.DEFAULT_NODED_PORT):
1434 1435
      raise errors.OpPrereqError("Node not reachable by ping")
1435 1436

  
1436 1437
    if not newbie_singlehomed:
1437 1438
      # check reachability from my secondary ip to newbie's secondary ip
1438
      command = ["fping", "-S%s" % myself.secondary_ip, "-q", secondary_ip]
1439
      result = utils.RunCmd(command)
1440
      if result.failed:
1441
        raise errors.OpPrereqError("Node secondary ip not reachable by ping")
1439
      if not utils.TcpPing(myself.secondary_ip,
1440
                           secondary_ip,
1441
                           constants.DEFAULT_NODED_PORT):
1442
        raise errors.OpPrereqError(
1443
          "Node secondary ip not reachable by TCP based ping to noded port")
1442 1444

  
1443 1445
    self.new_node = objects.Node(name=node,
1444 1446
                                 primary_ip=primary_ip,
......
1529 1531
                      self.cfg.GetHostKey())
1530 1532

  
1531 1533
    if new_node.secondary_ip != new_node.primary_ip:
1532
      result = ssh.SSHCall(node, "root",
1533
                           "fping -S 127.0.0.1 -q %s" % new_node.secondary_ip)
1534
      if result.failed:
1534
      if not rpc.call_node_tcp_ping(new_node.name,
1535
                                    constants.LOCALHOST_IP_ADDRESS,
1536
                                    new_node.secondary_ip,
1537
                                    constants.DEFAULT_NODED_PORT,
1538
                                    10, False):
1535 1539
        raise errors.OpExecError("Node claims it doesn't have the"
1536 1540
                                 " secondary ip you gave (%s).\n"
1537 1541
                                 "Please fix and re-run this command." %
......
2844 2848
                                 " adding an instance in start mode")
2845 2849

  
2846 2850
    if self.op.ip_check:
2847
      command = ["fping", "-q", hostname1.ip]
2848
      result = utils.RunCmd(command)
2849
      if not result.failed:
2850
        raise errors.OpPrereqError("IP address %s of instance %s already"
2851
                                   " in use" % (hostname1.ip, instance_name))
2851
      if utils.TcpPing(utils.HostInfo().name, hostname1.ip,
2852
                       constants.DEFAULT_NODED_PORT):
2853
        raise errors.OpPrereqError("IP %s of instance %s already in use" %
2854
                                   (hostname1.ip, instance_name))
2852 2855

  
2853 2856
    # bridge verification
2854 2857
    bridge = getattr(self.op, "bridge", None)
b/lib/constants.py
106 106
# others
107 107
DEFAULT_BRIDGE = "xen-br0"
108 108
SYNC_SPEED = 30 * 1024
109
LOCALHOST_IP_ADDRESS="127.0.0.1"
110
TCP_PING_TIMEOUT = 10
b/lib/rpc.py
373 373
  return c.getresult()
374 374

  
375 375

  
376
def call_node_tcp_ping(node, source, target, port, timeout, live_port_needed):
377
  """Do a TcpPing on the remote node
378

  
379
  This is a single-node call.
380
  """
381
  c = Client("node_tcp_ping", [source, target, port, timeout,
382
                               live_port_needed])
383
  c.connect(node)
384
  c.run()
385
  return c.getresult().get(node, False)
386

  
387

  
376 388
def call_node_info(node_list, vg_name):
377 389
  """Return node information.
378 390

  
b/lib/utils.py
37 37
from ganeti import logger
38 38
from ganeti import errors
39 39

  
40

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

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

  
784 785

  
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 786

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

  
823 790
  Try to do a TCP connect(2) from the specified source IP to the specified
b/test/ganeti.config_unittest.py
55 55

  
56 56
  def _init_cluster(self, cfg):
57 57
    """Initializes the cfg object"""
58
    cfg.InitConfig(utils.HostInfo().name, '127.0.0.1', None, '', 'aa:00:00',
59
                   'xenvg', constants.DEFAULT_BRIDGE)
58
    cfg.InitConfig(utils.HostInfo().name, constants.LOCALHOST_IP_ADDRESS,
59
                   None, '', 'aa:00:00', 'xenvg',
60
                   constants.DEFAULT_BRIDGE)
60 61

  
61 62
  def _create_instance(self):
62 63
    """Create and return an instance object"""
b/test/ganeti.utils_unittest.py
31 31
import shutil
32 32

  
33 33
import ganeti
34
from ganeti import constants
34 35
from ganeti.utils import IsProcessAlive, Lock, Unlock, RunCmd, \
35 36
     RemoveFile, CheckDict, MatchNameComponent, FormatUnit, \
36 37
     ParseUnit, AddAuthorizedKey, RemoveAuthorizedKey, \
37
     ShellQuote, ShellQuoteArgs, _ParseIpOutput, TcpPing, \
38
     ListVisibleFiles
38
     ShellQuote, ShellQuoteArgs, TcpPing, ListVisibleFiles
39 39
from ganeti.errors import LockError, UnitParseError
40 40

  
41 41

  
......
445 445
    self.assertEqual(ShellQuoteArgs(['a', 'b\'', 'c']), "a 'b'\\\''' c")
446 446

  
447 447

  
448
class TestIpAdressList(unittest.TestCase):
449
  """Test case for local IP addresses"""
450

  
451
  def _test(self, output, required):
452
    ips = _ParseIpOutput(output)
453

  
454
    # Sort the output, so our check below works in all cases
455
    ips.sort()
456
    required.sort()
457

  
458
    self.assertEqual(required, ips)
459

  
460
  def testSingleIpAddress(self):
461
    output = \
462
      ("3: lo    inet 127.0.0.1/8 brd 127.255.255.255 scope host lo\n"
463
       "5: eth0    inet 10.0.0.1/24 brd 172.30.15.127 scope global eth0\n")
464
    self._test(output, ['127.0.0.1', '10.0.0.1'])
465

  
466
  def testMultipleIpAddresses(self):
467
    output = \
468
      ("3: lo    inet 127.0.0.1/8 brd 127.255.255.255 scope host lo\n"
469
       "5: eth0    inet 10.0.0.1/24 brd 172.30.15.127 scope global eth0\n"
470
       "5: eth0    inet 1.2.3.4/8 brd 1.255.255.255 scope global eth0:test\n")
471
    self._test(output, ['127.0.0.1', '10.0.0.1', '1.2.3.4'])
472

  
473

  
474 448
class TestTcpPing(unittest.TestCase):
475 449
  """Testcase for TCP version of ping - against listen(2)ing port"""
476 450

  
477 451
  def setUp(self):
478 452
    self.listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
479
    self.listener.bind(("127.0.0.1", 0))
453
    self.listener.bind((constants.LOCALHOST_IP_ADDRESS, 0))
480 454
    self.listenerport = self.listener.getsockname()[1]
481 455
    self.listener.listen(1)
482 456

  
......
486 460
    del self.listenerport
487 461

  
488 462
  def testTcpPingToLocalHostAccept(self):
489
    self.assert_(TcpPing("127.0.0.1",
490
                         "127.0.0.1",
463
    self.assert_(TcpPing(constants.LOCALHOST_IP_ADDRESS,
464
                         constants.LOCALHOST_IP_ADDRESS,
491 465
                         self.listenerport,
492 466
                         timeout=10,
493 467
                         live_port_needed=True),
......
499 473

  
500 474
  def setUp(self):
501 475
    self.deaflistener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
502
    self.deaflistener.bind(("127.0.0.1", 0))
476
    self.deaflistener.bind((constants.LOCALHOST_IP_ADDRESS, 0))
503 477
    self.deaflistenerport = self.deaflistener.getsockname()[1]
504 478

  
505 479
  def tearDown(self):
......
507 481
    del self.deaflistenerport
508 482

  
509 483
  def testTcpPingToLocalHostAcceptDeaf(self):
510
    self.failIf(TcpPing("127.0.0.1",
511
                        "127.0.0.1",
484
    self.failIf(TcpPing(constants.LOCALHOST_IP_ADDRESS,
485
                        constants.LOCALHOST_IP_ADDRESS,
512 486
                        self.deaflistenerport,
513
                        timeout=10,  # timeout for blocking operations
487
                        timeout=constants.TCP_PING_TIMEOUT,
514 488
                        live_port_needed=True), # need successful connect(2)
515 489
                "successfully connected to deaf listener")
516 490

  
517 491
  def testTcpPingToLocalHostNoAccept(self):
518
    self.assert_(TcpPing("127.0.0.1",
519
                         "127.0.0.1",
492
    self.assert_(TcpPing(constants.LOCALHOST_IP_ADDRESS,
493
                         constants.LOCALHOST_IP_ADDRESS,
520 494
                         self.deaflistenerport,
521
                         timeout=10, # timeout for blocking operations
495
                         timeout=constants.TCP_PING_TIMEOUT,
522 496
                         live_port_needed=False), # ECONNREFUSED is OK
523 497
                 "failed to ping alive host on deaf port")
524 498

  

Also available in: Unified diff