From 5ce35027e7e95253e71a6733cf408a53fbf23085 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Thu, 28 May 2009 14:50:54 -0700 Subject: [PATCH] xenserver: Keep Centos network config files up-to-date even with vswitch. 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. --- ...pt_xensource_libexec_interface-reconfigure | 498 +++++++++++++++++- xenserver/vswitch-xen.spec | 5 - 2 files changed, 484 insertions(+), 19 deletions(-) diff --git a/xenserver/opt_xensource_libexec_interface-reconfigure b/xenserver/opt_xensource_libexec_interface-reconfigure index 3de1099d..b0dac5af 100755 --- a/xenserver/opt_xensource_libexec_interface-reconfigure +++ b/xenserver/opt_xensource_libexec_interface-reconfigure @@ -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 + +# 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- 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-, otherwise it will be ifcfg-. + + 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 + if __name__ == "__main__": rc = 1 try: diff --git a/xenserver/vswitch-xen.spec b/xenserver/vswitch-xen.spec index 5c705d42..b2e5ec18 100644 --- a/xenserver/vswitch-xen.spec +++ b/xenserver/vswitch-xen.spec @@ -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 -- 2.30.2