xenserver: Fix typo in adding static routes in interface-reconfigure.
[sliver-openvswitch.git] / xenserver / opt_xensource_libexec_interface-reconfigure
index 74f06f8..49b306f 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/python
 #
-# Copyright (c) Citrix Systems 2008. All rights reserved.
+# Copyright (c) 2008,2009 Citrix Systems, Inc. All rights reserved.
 # Copyright (c) 2009 Nicira Networks.
 #
 """Usage:
@@ -63,6 +63,7 @@ import traceback
 import time
 import re
 import pickle
+import random
 
 output_directory = None
 
@@ -248,6 +249,33 @@ def check_allowed(pif):
 def interface_exists(i):
     return os.path.exists("/sys/class/net/" + i)
 
+def get_netdev_mac(device):
+    try:
+        return read_first_line_of_file("/sys/class/net/%s/address" % device)
+    except:
+        # Probably no such device.
+        return None
+
+def get_netdev_tx_queue_len(device):
+    try:
+        return int(read_first_line_of_file("/sys/class/net/%s/tx_queue_len"
+                                           % device))
+    except:
+        # Probably no such device.
+        return None
+
+def get_netdev_by_mac(mac):
+    maybe = None
+    for device in os.listdir("/sys/class/net"):
+        dev_mac = get_netdev_mac(device)
+        if dev_mac and mac.lower() == dev_mac.lower():
+            if get_netdev_tx_queue_len(device):
+                return device
+            if not maybe:
+                # Probably a datapath internal port.
+                maybe = device
+    return maybe
+
 class DatabaseCache(object):
     def __init__(self, session_ref=None, cache_file=None):
         if session_ref and cache_file:
@@ -433,24 +461,30 @@ The ipdev name is the same as the bridge name.
     pifrec = db.get_pif_record(pif)
     return bridge_name(pif)
 
-def physdev_names(pif):
-    """Return the name(s) of the physical network device(s) associated with pif.
-For a VLAN PIF, the physical devices are the VLAN slave's physical devices.
-For a bond master PIF, the physical devices are the bond slaves.
-For a non-VLAN, non-bond master PIF, the physical device is the PIF itself.
+def physdev_pifs(pif):
+    """Return the PIFs for the physical network device(s) associated with pif.
+For a VLAN PIF, this is the VLAN slave's physical device PIF.
+For a bond master PIF, these are the bond slave PIFs.
+For a non-VLAN, non-bond master PIF, the PIF is its own physical device PIF.
 """
 
     pifrec = db.get_pif_record(pif)
 
     if pifrec['VLAN'] != '-1':
-        return physdev_names(get_vlan_slave_of_pif(pif))
+        return [get_vlan_slave_of_pif(pif)]
     elif len(pifrec['bond_master_of']) != 0:
-        physdevs = []
-        for slave in get_bond_slaves_of_pif(pif):
-            physdevs += physdev_names(slave)
-        return physdevs
+        return get_bond_slaves_of_pif(pif)
     else:
-        return [pifrec['device']]
+        return [pif]
+
+def physdev_names(pif):
+    """Return the name(s) of the physical network device(s) associated with pif.
+For a VLAN PIF, the physical devices are the VLAN slave's physical devices.
+For a bond master PIF, the physical devices are the bond slaves.
+For a non-VLAN, non-bond master PIF, the physical device is the PIF itself.
+"""
+
+    return [db.get_pif_record(phys)['device'] for phys in physdev_pifs(pif)]
 
 def log_pif_action(action, pif):
     pifrec = db.get_pif_record(pif)
@@ -543,31 +577,76 @@ def run_command(command):
         return False
     return True
 
+def rename_netdev(old_name, new_name):
+    log("Changing the name of %s to %s" % (old_name, new_name))
+    run_command(['/sbin/ifconfig', old_name, 'down'])
+    if not run_command(['/sbin/ip', 'link', 'set', old_name,
+                        'name', new_name]):
+        raise Error("Could not rename %s to %s" % (old_name, new_name))
+
+# Check whether 'pif' exists and has the correct MAC.
+# If not, try to find a device with the correct MAC and rename it.
+# 'already_renamed' is used to avoid infinite recursion.
+def remap_pif(pif, already_renamed=[]):
+    pifrec = db.get_pif_record(pif)
+    device = pifrec['device']
+    mac = pifrec['MAC']
+
+    # Is there a network device named 'device' at all?
+    device_exists = interface_exists(device)
+    if device_exists:
+        # Yes.  Does it have MAC 'mac'?
+        found_mac = get_netdev_mac(device)
+        if found_mac and mac.lower() == found_mac.lower():
+            # Yes, everything checks out the way we want.  Nothing to do.
+            return
+    else:
+        log("No network device %s" % device)
+
+    # What device has MAC 'mac'?
+    cur_device = get_netdev_by_mac(mac)
+    if not cur_device:
+        log("No network device has MAC %s" % mac)
+        return
+
+    # First rename 'device', if it exists, to get it out of the way
+    # for 'cur_device' to replace it.
+    if device_exists:
+        rename_netdev(device, "dev%d" % random.getrandbits(24))
+
+    # Rename 'cur_device' to 'device'.
+    rename_netdev(cur_device, device)
+
+def read_first_line_of_file(name):
+    file = None
+    try:
+        file = open(name, 'r')
+        return file.readline().rstrip('\n')
+    finally:
+        if file != None:
+            file.close()
+
 def down_netdev(interface, deconfigure=True):
     if not interface_exists(interface):
         log("down_netdev: interface %s does not exist, ignoring" % interface)
         return
-    argv = ["/sbin/ifconfig", interface, 'down']
     if deconfigure:
-        argv += ['0.0.0.0']
-
         # Kill dhclient.
         pidfile_name = '/var/run/dhclient-%s.pid' % interface
-        pidfile = None
         try:
-            pidfile = open(pidfile_name, 'r')
-            os.kill(int(pidfile.readline()), signal.SIGTERM)
+            os.kill(int(read_first_line_of_file(pidfile_name)), signal.SIGTERM)
         except:
             pass
-        if pidfile != None:
-            pidfile.close()
 
         # Remove dhclient pidfile.
         try:
             os.remove(pidfile_name)
         except:
             pass
-    run_command(argv)
+        
+        run_command(["/sbin/ifconfig", interface, '0.0.0.0'])
+
+    run_command(["/sbin/ifconfig", interface, 'down'])
 
 def up_netdev(interface):
     run_command(["/sbin/ifconfig", interface, 'up'])
@@ -679,12 +758,13 @@ def configure_netdev(pif):
     else:
         raise Error("Unknown IP-configuration-mode %s" % pifrec['ip_configuration_mode'])
 
-    oc = {}
-    if pifrec.has_key('other_config'):
-        oc = pifrec['other_config']
-        if oc.has_key('mtu'):
+    oc = pifrec['other_config']
+    if oc.has_key('mtu'):
+        try:
             int(oc['mtu'])      # Check that the value is an integer
             ifconfig_argv += ['mtu', oc['mtu']]
+        except ValueError, x:
+            log("Invalid value for mtu = %s" % mtu)
 
     run_command(ifconfig_argv)
     
@@ -708,7 +788,7 @@ def configure_netdev(pif):
         for line in oc['static-routes'].split(','):
             network, masklen, gateway = line.split('/')
             run_command(['/sbin/ip', 'route', 'add',
-                         '%s/%s' % (netmask, masklen), 'via', gateway,
+                         '%s/%s' % (network, masklen), 'via', gateway,
                          'dev', ipdev])
 
     settings, offload = ethtool_settings(oc)
@@ -749,6 +829,10 @@ def configure_bond(pif):
     argv = ['--del-match=bonding.%s.[!0-9]*' % interface]
     argv += ["--add=bonding.%s.slave=%s" % (interface, slave)
              for slave in physdevs]
+    argv += ['--add=bonding.%s.fake-iface=true']
+
+    if pifrec['MAC'] != "":
+        argv += ['--add=port.%s.mac=%s' % (interface, pifrec['MAC'])]
 
     # Bonding options.
     bond_options = { 
@@ -830,11 +914,25 @@ def action_up(pif):
     f.apply()
     f.commit()
 
+    # Check the MAC address of each network device and remap if
+    # necessary to make names match our expectations.
+    for physdev_pif in physdev_pifs(pif):
+        remap_pif(physdev_pif)
+
     # "ifconfig down" the network device and delete its IP address, etc.
     down_netdev(ipdev)
     for physdev in physdevs:
         down_netdev(physdev)
 
+    # If we are bringing up a bond, remove IP addresses from the
+    # slaves (because we are implicitly being asked to take them down).
+    # 
+    # Conversely, if we are bringing up an interface that has bond
+    # masters, remove IP addresses from the bond master (because we
+    # are implicitly being asked to take it down).
+    for bond_pif in bond_slaves + bond_masters:
+        run_command(["/sbin/ifconfig", ipdev_name(bond_pif), '0.0.0.0']) 
+
     # Remove all keys related to pif and any bond masters linked to PIF.
     del_ports = [ipdev] + physdevs + bond_masters
     if vlan_slave and bond_master:
@@ -917,9 +1015,6 @@ def action_up(pif):
         argv += configure_bond(bond_master)
     modify_config(argv)
 
-    # Configure network devices.
-    configure_netdev(pif)
-
     # Bring up VLAN slave, plus physical devices other than bond
     # slaves (which we brought up earlier).
     if vlan_slave:
@@ -927,8 +1022,24 @@ def action_up(pif):
     for physdev in set(physdevs) - set(bond_slave_physdevs):
         up_netdev(physdev)
 
+    # Configure network devices.
+    configure_netdev(pif)
+
     # Update /etc/issue (which contains the IP address of the management interface)
     os.system("/sbin/update-issue")
+
+    if bond_slaves:
+        # There seems to be a race somewhere: without this sleep, using
+        # XenCenter to create a bond that becomes the management interface
+        # fails with "The underlying connection was closed: A connection that
+        # was expected to be kept alive was closed by the server." on every
+        # second or third try, even though /var/log/messages doesn't show
+        # anything unusual.
+        #
+        # The race is probably present even without vswitch, but bringing up a
+        # bond without vswitch involves a built-in pause of 10 seconds or more
+        # to wait for the bond to transition from learning to forwarding state.
+        time.sleep(5)
         
 def action_down(pif):
     rec = db.get_pif_record(pif)