xenserver: Keep Centos network config files up-to-date even with vswitch.
authorBen Pfaff <blp@nicira.com>
Thu, 28 May 2009 21:50:54 +0000 (14:50 -0700)
committerBen Pfaff <blp@nicira.com>
Fri, 29 May 2009 00:18:55 +0000 (17:18 -0700)
When the vswitch is running, it doesn't care about, doesn't need, and
doesn't use the files in /etc/sysconfig/network-scripts.  But if you ever
remove the vswitch (e.g. "rpm -e vswitch") then the regular bridge code
does need them.  There is no guarantee that we will be able to get access
to the xapi database at "rpm -e" time, so it is better to keep these
configuration files up-to-date after each operation.  This commit does
that.

Given that we keep these files up-to-date after each operation now, there
is no need to call interface-reconfigure at RPM uninstall time, so this
commit also removes the script code that did that.

xenserver/opt_xensource_libexec_interface-reconfigure
xenserver/vswitch-xen.spec

index 3de1099d30a1e32f246969b41bdc87fa775dd527..b0dac5afb7c9a6955f4a3ca37d90c3be88784cc0 100755 (executable)
@@ -93,7 +93,7 @@ class ConfigurationFile(object):
                "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
                "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
     
-    def __init__(self, fname, path):
+    def __init__(self, fname, path="/etc/sysconfig/network-scripts"):
 
         self.__state = self.__STATE['OPEN']
         self.__fname = fname
@@ -778,6 +778,45 @@ def action_up(pif):
         bond_master = pif
     else:
         bond_master = None
+    bond_masters = get_bond_masters_of_pif(pif)
+
+    # Support "rpm -e vswitch" gracefully by keeping Centos configuration
+    # files up-to-date, even though we don't use them or need them.
+    f = configure_pif(pif)
+    mode = pifrec['ip_configuration_mode']
+    if bridge:
+        log("Configuring %s using %s configuration" % (bridge, mode))
+        br = open_network_ifcfg(pif)
+        configure_network(pif, br)
+        br.close()
+        f.attach_child(br)
+    else:
+        log("Configuring %s using %s configuration" % (interface, mode))
+        configure_network(pif, f)
+    f.close()
+    for master in bond_masters:
+        master_bridge = bridge_name(master)
+        removed = unconfigure_pif(master)
+        f.attach_child(removed)
+        if master_bridge:
+            removed = open_network_ifcfg(master)
+            log("Unlinking stale file %s" % removed.path())
+            removed.unlink()
+            f.attach_child(removed)
+
+    # /etc/xensource/scripts/vif needs to know where to add VIFs.
+    if vlan_slave:
+        if not os.path.exists(vswitch_config_dir):
+            os.mkdir(vswitch_config_dir)
+        br = ConfigurationFile("br-%s" % bridge, vswitch_config_dir)
+        br.write("VLAN_SLAVE=%s\n" % datapath)
+        br.write("VLAN_VID=%s\n" % pifrec['VLAN'])
+        br.close()
+        f.attach_child(br)
+
+    # Update all configuration files (both ours and Centos's).
+    f.apply()
+    f.commit()
 
     # "ifconfig down" the network device and delete its IP address, etc.
     down_netdev(ipdev)
@@ -785,7 +824,7 @@ def action_up(pif):
         down_netdev(physdev)
 
     # Remove all keys related to pif and any bond masters linked to PIF.
-    del_ports = [ipdev] + physdevs + get_bond_masters_of_pif(pif)
+    del_ports = [ipdev] + physdevs + bond_masters
     if vlan_slave and bond_master:
         del_ports += [interface_name(bond_master)]
     
@@ -811,7 +850,7 @@ def action_up(pif):
     #  - The bond masters for pif.  (Ordinarily pif shouldn't have any
     #    bond masters.  If it does then interface-reconfigure is
     #    implicitly being asked to take them down.)
-    del_ports = add_ports + physdevs + get_bond_masters_of_pif(pif)
+    del_ports = add_ports + physdevs + bond_masters
 
     # Now modify the ovs-vswitchd config file.
     argv = []
@@ -832,16 +871,6 @@ def action_up(pif):
         # xapi insists that its attempts to create the bridge succeed,
         # so force that to happen.
         argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)]
-
-        # /etc/xensource/scripts/vif needs to know where to add VIFs.
-        if not os.path.exists(vswitch_config_dir):
-            os.mkdir(vswitch_config_dir)
-        f = ConfigurationFile("br-%s" % bridge, vswitch_config_dir)
-        f.write("VLAN_SLAVE=%s\n" % datapath)
-        f.write("VLAN_VID=%s\n" % pifrec['VLAN'])
-        f.close()
-        f.apply()
-        f.commit()
     else:
         try:
             os.unlink("%s/br-%s" % (vswitch_config_dir, bridge))
@@ -869,6 +898,22 @@ def action_down(pif):
     bridge = bridge_name(pif)
     ipdev = ipdev_name(pif)
 
+    # Support "rpm -e vswitch" gracefully by keeping Centos configuration
+    # files up-to-date, even though we don't use them or need them.
+    f = unconfigure_pif(pif)
+    if bridge:
+        br = open_network_ifcfg(pif)
+        log("Unlinking stale file %s" % br.path())
+        br.unlink()
+        f.attach_child(br)
+    try:
+        f.apply()
+        f.commit()
+    except Error, e:
+        log("action_down failed to apply changes: %s" % e.msg)
+        f.revert()
+        raise
+
     argv = []
     if rec['VLAN'] != '-1':
         # Get rid of the VLAN device itself.
@@ -926,8 +971,33 @@ def action_down(pif):
     modify_config(argv)
 
 def action_rewrite(pif):
+    # Support "rpm -e vswitch" gracefully by keeping Centos configuration
+    # files up-to-date, even though we don't use them or need them.
     pifrec = db.get_pif_record(pif)
-    db.save(dbcache_file, {'host': pifrec['host']})
+    f = configure_pif(pif)
+    interface = interface_name(pif)
+    bridge = bridge_name(pif)
+    mode = pifrec['ip_configuration_mode']
+    if bridge:
+        log("Configuring %s using %s configuration" % (bridge, mode))
+        br = open_network_ifcfg(pif)
+        configure_network(pif, br)
+        br.close()
+        f.attach_child(br)
+    else:
+        log("Configuring %s using %s configuration" % (interface, mode))
+        configure_network(pif, f)
+    f.close()
+    try:
+        f.apply()
+        f.commit()
+    except Error, e:
+        log("failed to apply changes: %s" % e.msg)
+        f.revert()
+        raise
+
+    # We have no code of our own to run here.
+    pass
 
 def main(argv=None):
     global output_directory, management_pif
@@ -1052,6 +1122,10 @@ def main(argv=None):
                 action_rewrite(pif)
             else:
                 raise Usage("Unknown action %s"  % action)
+
+            # Save cache.
+            pifrec = db.get_pif_record(pif)
+            db.save(dbcache_file, {'host': pifrec['host']})
         
     except Usage, err:
         print >>sys.stderr, err.msg
@@ -1062,7 +1136,403 @@ def main(argv=None):
         return 1
     
     return 0
+\f
+# The following code allows interface-reconfigure to keep Centos
+# network configuration files up-to-date, even though the vswitch
+# never uses them.  In turn, that means that "rpm -e vswitch" does not
+# have to update any configuration files.
+
+def configure_ethtool(oc, f):
+    # Options for "ethtool -s"
+    settings = None
+    setting_opts = ["autoneg", "speed", "duplex"]
+    # Options for "ethtool -K"
+    offload = None
+    offload_opts = ["rx", "tx", "sg", "tso", "ufo", "gso"]
+    
+    for opt in [opt for opt in setting_opts + offload_opts if oc.has_key("ethtool-" + opt)]:
+        val = oc["ethtool-" + opt]
+
+        if opt in ["speed"]:
+            if val in ["10", "100", "1000"]:
+                val = "speed " + val
+            else:
+                log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
+                val = None
+        elif opt in ["duplex"]:
+            if val in ["half", "full"]:
+                val = "duplex " + val
+            else:
+                log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
+                val = None
+        elif opt in ["autoneg"] + offload_opts:
+            if val in ["true", "on"]:
+                val = opt + " on"
+            elif val in ["false", "off"]:
+                val = opt + " off"
+            else:
+                log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
+                val = None
+
+        if opt in setting_opts:
+            if val and settings:
+                settings = settings + " " + val
+            else:
+                settings = val
+        elif opt in offload_opts:
+            if val and offload:
+                offload = offload + " " + val
+            else:
+                offload = val
+
+    if settings:
+        f.write("ETHTOOL_OPTS=\"%s\"\n" % settings)
+    if offload:
+        f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % offload)
+
+def configure_mtu(oc, f):
+    if not oc.has_key('mtu'):
+        return
+    
+    try:
+        mtu = int(oc['mtu'])
+        f.write("MTU=%d\n" % mtu)
+    except ValueError, x:
+        log("Invalid value for mtu = %s" % mtu)
+
+def configure_static_routes(interface, oc, f):
+    """Open a route-<interface> file for static routes.
+    
+    Opens the static routes configuration file for interface and writes one
+    line for each route specified in the network's other config "static-routes" value.
+    E.g. if
+           interface ( RO): xenbr1
+           other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
+
+    Then route-xenbr1 should be
+          172.16.0.0/15 via 192.168.0.3 dev xenbr1
+          172.18.0.0/16 via 192.168.0.4 dev xenbr1
+    """
+    fname = "route-%s" % interface
+    if oc.has_key('static-routes'):
+        # The key is present - extract comma seperates entries
+        lines = oc['static-routes'].split(',')
+    else:
+        # The key is not present, i.e. there are no static routes
+        lines = []
+
+    child = ConfigurationFile(fname)
+    child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
+            (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
+
+    try:
+        for l in lines:
+            network, masklen, gateway = l.split('/')
+            child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
+            
+        f.attach_child(child)
+        child.close()
+
+    except ValueError, e:
+        log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
+
+def __open_ifcfg(interface):
+    """Open a network interface configuration file.
+
+    Opens the configuration file for interface, writes a header and
+    common options and returns the file object.
+    """
+    fname = "ifcfg-%s" % interface
+    f = ConfigurationFile(fname)
+    
+    f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
+            (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
+    f.write("XEMANAGED=yes\n")
+    f.write("DEVICE=%s\n" % interface)
+    f.write("ONBOOT=no\n")
+    
+    return f
+
+def open_network_ifcfg(pif):    
+    bridge = bridge_name(pif)
+    interface = interface_name(pif)
+    if bridge:
+        return __open_ifcfg(bridge)
+    else:
+        return __open_ifcfg(interface)
+
+
+def open_pif_ifcfg(pif):
+    pifrec = db.get_pif_record(pif)
+    
+    log("Configuring %s (%s)" % (interface_name(pif), pifrec['MAC']))
+    
+    f = __open_ifcfg(interface_name(pif))
+
+    if pifrec.has_key('other_config'):
+        configure_ethtool(pifrec['other_config'], f)
+        configure_mtu(pifrec['other_config'], f)
+
+    return f
+
+def configure_network(pif, f):
+    """Write the configuration file for a network.
+
+    Writes configuration derived from the network object into the relevant 
+    ifcfg file.  The configuration file is passed in, but if the network is 
+    bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
+
+    This routine may also write ifcfg files of the networks corresponding to other PIFs
+    in order to maintain consistency.
+
+    params:
+        pif:  Opaque_ref of pif
+        f :   ConfigurationFile(/path/to/ifcfg) to which we append network configuration
+    """
+    
+    pifrec = db.get_pif_record(pif)
+    host = pifrec['host']
+    nw = pifrec['network']
+    nwrec = db.get_network_record(nw)
+    oc = None
+    bridge = bridge_name(pif)
+    interface = interface_name(pif)
+    if bridge:
+        device = bridge
+    else:
+        device = interface
+
+    if nwrec.has_key('other_config'):
+        configure_ethtool(nwrec['other_config'], f)
+        configure_mtu(nwrec['other_config'], f)
+        configure_static_routes(device, nwrec['other_config'], f)
+
+    
+    if pifrec.has_key('other_config'):
+        oc = pifrec['other_config']
+
+    if device == bridge:
+        f.write("TYPE=Bridge\n")
+        f.write("DELAY=0\n")
+        f.write("STP=off\n")
+        f.write("PIFDEV=%s\n" % interface_name(pif))
+
+    if pifrec['ip_configuration_mode'] == "DHCP":
+        f.write("BOOTPROTO=dhcp\n")
+        f.write("PERSISTENT_DHCLIENT=yes\n")
+    elif pifrec['ip_configuration_mode'] == "Static":
+        f.write("BOOTPROTO=none\n") 
+        f.write("NETMASK=%(netmask)s\n" % pifrec)
+        f.write("IPADDR=%(IP)s\n" % pifrec)
+        f.write("GATEWAY=%(gateway)s\n" % pifrec)
+    elif pifrec['ip_configuration_mode'] == "None":
+        f.write("BOOTPROTO=none\n")
+    else:
+        raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
+
+    if pifrec.has_key('DNS') and pifrec['DNS'] != "":
+        ServerList = pifrec['DNS'].split(",")
+        for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
+    if oc and oc.has_key('domain'):
+        f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
+
+    # We only allow one ifcfg-xenbr* to have PEERDNS=yes and there can be only one GATEWAYDEV in /etc/sysconfig/network.
+    # The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
+    # The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
+
+    # Work out which pif on this host should be the one with PEERDNS=yes and which should be the GATEWAYDEV
+    #
+    # Note: we prune out the bond master pif (if it exists).
+    # This is because when we are called to bring up an interface with a bond master, it is implicit that
+    # we should bring down that master.
+    pifs_on_host = [ __pif for __pif in db.get_all_pifs() if
+                     db.get_pif_record(__pif)['host'] == host and 
+                     (not  __pif in get_bond_masters_of_pif(pif)) ]
+    other_pifs_on_host = [ __pif for __pif in pifs_on_host if __pif != pif ]
+
+    peerdns_pif = None
+    defaultroute_pif = None
+    
+    # loop through all the pifs on this host looking for one with
+    #   other-config:peerdns = true, and one with
+    #   other-config:default-route=true
+    for __pif in pifs_on_host:
+        __pifrec = db.get_pif_record(__pif)
+        __oc = __pifrec['other_config']
+        if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
+            if peerdns_pif == None:
+                peerdns_pif = __pif
+            else:
+                log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
+                        (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
+        if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
+            if defaultroute_pif == None:
+                defaultroute_pif = __pif
+            else:
+                log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
+                        (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
+    
+    # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
+    if peerdns_pif == None:
+        peerdns_pif = management_pif
+    if defaultroute_pif == None:
+        defaultroute_pif = management_pif
+        
+    # Update all the other network's ifcfg files and ensure consistency
+    for __pif in other_pifs_on_host:
+        __f = open_network_ifcfg(__pif)
+        peerdns_line_wanted = 'PEERDNS=%s\n' % ((__pif == peerdns_pif) and 'yes' or 'no')
+        lines =  __f.readlines()
+
+        if not peerdns_line_wanted in lines:
+            # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting
+            for line in lines:
+                if not line.lstrip().startswith('PEERDNS'):
+                    __f.write(line)
+            log("Setting %s in %s" % (peerdns_line_wanted.strip(), __f.path()))
+            __f.write(peerdns_line_wanted)
+            __f.close()
+            f.attach_child(__f)
+
+        else:
+            # There is no need to change this ifcfg file.  So don't attach_child.
+            pass
+        
+    # ... and for this pif too
+    f.write('PEERDNS=%s\n' % ((pif == peerdns_pif) and 'yes' or 'no'))
+    
+    # Update gatewaydev
+    fnetwork = ConfigurationFile("network", "/etc/sysconfig")
+    for line in fnetwork.readlines():
+        if line.lstrip().startswith('GATEWAY') :
+            continue
+        fnetwork.write(line)
+    if defaultroute_pif:
+        gatewaydev = bridge_name(defaultroute_pif)
+        if not gatewaydev:
+            gatewaydev = interface_name(defaultroute_pif)
+        fnetwork.write('GATEWAYDEV=%s\n' % gatewaydev)
+    fnetwork.close()
+    f.attach_child(fnetwork)
+
+    return
+
+
+def configure_physical_interface(pif):
+    """Write the configuration for a physical interface.
+
+    Writes the configuration file for the physical interface described by
+    the pif object.
+
+    Returns the open file handle for the interface configuration file.
+    """
 
+    pifrec = db.get_pif_record(pif)
+
+    f = open_pif_ifcfg(pif)
+    
+    f.write("TYPE=Ethernet\n")
+    f.write("HWADDR=%(MAC)s\n" % pifrec)
+
+    return f
+
+def configure_bond_interface(pif):
+    """Write the configuration for a bond interface.
+
+    Writes the configuration file for the bond interface described by
+    the pif object. Handles writing the configuration for the slave
+    interfaces.
+
+    Returns the open file handle for the bond interface configuration
+    file.
+    """
+
+    pifrec = db.get_pif_record(pif)
+    oc = pifrec['other_config']
+    f = open_pif_ifcfg(pif)
+
+    if pifrec['MAC'] != "":
+        f.write("MACADDR=%s\n" % pifrec['MAC'])
+
+    for slave in get_bond_slaves_of_pif(pif):
+        s = configure_physical_interface(slave)
+        s.write("MASTER=%(device)s\n" % pifrec)
+        s.write("SLAVE=yes\n")
+        s.close()
+        f.attach_child(s)
+
+    # The bond option defaults
+    bond_options = { 
+        "mode":   "balance-slb",
+        "miimon": "100",
+        "downdelay": "200",
+        "updelay": "31000",
+        "use_carrier": "1",
+        }
+
+    # override defaults with values from other-config whose keys being with "bond-"
+    overrides = filter(lambda (key,val): key.startswith("bond-"), oc.items())
+    overrides = map(lambda (key,val): (key[5:], val), overrides)
+    bond_options.update(overrides)
+
+    # write the bond options to ifcfg-bondX
+    f.write('BONDING_OPTS="')
+    for (name,val) in bond_options.items():
+        f.write("%s=%s " % (name,val))
+    f.write('"\n')
+    return f
+
+def configure_vlan_interface(pif):
+    """Write the configuration for a VLAN interface.
+
+    Writes the configuration file for the VLAN interface described by
+    the pif object. Handles writing the configuration for the master
+    interface if necessary.
+
+    Returns the open file handle for the VLAN interface configuration
+    file.
+    """
+
+    slave = configure_pif(get_vlan_slave_of_pif(pif))
+    slave.close()
+
+    f = open_pif_ifcfg(pif)
+    f.write("VLAN=yes\n")
+    f.attach_child(slave)
+    
+    return f
+
+def configure_pif(pif):
+    """Write the configuration for a PIF object.
+
+    Writes the configuration file the PIF and all dependent
+    interfaces (bond slaves and VLAN masters etc).
+
+    Returns the open file handle for the interface configuration file.
+    """
+
+    pifrec = db.get_pif_record(pif)
+
+    if pifrec['VLAN'] != '-1':
+        f = configure_vlan_interface(pif)
+    elif len(pifrec['bond_master_of']) != 0:
+        f = configure_bond_interface(pif)
+    else:
+        f = configure_physical_interface(pif)
+
+    bridge = bridge_name(pif)
+    if bridge:
+        f.write("BRIDGE=%s\n" % bridge)
+
+    return f
+
+def unconfigure_pif(pif):
+    """Clear up the files created by configure_pif"""
+    f = open_pif_ifcfg(pif)
+    log("Unlinking stale file %s" % f.path())
+    f.unlink()
+    return f
+\f
 if __name__ == "__main__":
     rc = 1
     try:
index 5c705d421a304db57ab00bc6726451da46e89553..b2e5ec18c221c5ce7383cedcc6db836f511ad0e0 100644 (file)
@@ -218,11 +218,6 @@ if [ "$1" = "0" ]; then     # $1 = 1 for upgrade
     for s in vswitch vswitch-xapi-update; do
         chkconfig --del $s || printf "Could not remove $s init script."
     done
-    # Restore standard Xen interface-reconfigure compatible conf files
-    source /etc/xensource-inventory
-    for pif in $(xe pif-list host-uuid=$INSTALLATION_UUID params=uuid | awk '{print $5}'); do
-        %{_prefix}/xs-original/interface-reconfigure --pif-uuid $pif rewrite
-    done
 fi