X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=xenserver%2Fopt_xensource_libexec_interface-reconfigure;h=3b5c8617aec89a072a75fde24db121f1ffb7c3c0;hb=HEAD;hp=44a2b1e0c1337a844cfd821d5a72f427153438ec;hpb=88acec3bb517fda9c1f5f32d6eec92a153e12b1c;p=sliver-openvswitch.git diff --git a/xenserver/opt_xensource_libexec_interface-reconfigure b/xenserver/opt_xensource_libexec_interface-reconfigure index 44a2b1e0c..3b5c8617a 100755 --- a/xenserver/opt_xensource_libexec_interface-reconfigure +++ b/xenserver/opt_xensource_libexec_interface-reconfigure @@ -1,240 +1,97 @@ -#!/usr/bin/python +#!/usr/bin/env python # -# Copyright (c) 2008,2009 Citrix Systems, Inc. All rights reserved. -# Copyright (c) 2009 Nicira Networks. +# Copyright (c) 2008,2009 Citrix Systems, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation; version 2.1 only. with the special +# exception on linking described in file LICENSE. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. # """Usage: - %(command-name)s --session --pif [up|down|rewrite] - %(command-name)s --force [up|down|rewrite ] - %(command-name)s --force all down + %(command-name)s up + %(command-name)s down + %(command-name)s rewrite + %(command-name)s --force up + %(command-name)s --force down + %(command-name)s --force rewrite --device= --mac= - where, - = --device= --mode=dhcp - = --device= --mode=static --ip= --netmask= [--gateway=] + where is one of: + --session --pif + --pif-uuid + and is one of: + --mode=dhcp + --mode=static --ip= --netmask= [--gateway=] Options: - --session A session reference to use to access the xapi DB - --pif A PIF reference. - --force-interface An interface name. Mutually exclusive with --session/--pif. - - Either both --session and --pif or just --pif-uuid. - - is either "up" or "down" or "rewrite" + --session A session reference to use to access the xapi DB + --pif A PIF reference within the session. + --pif-uuid The UUID of a PIF. + --force An interface name. + --root-prefix=DIR Use DIR as alternate root directory (for testing). + --no-syslog Write log messages to stderr instead of system log. """ -# -# Undocumented parameters for test & dev: -# -# --output-directory= Write configuration to . Also disables actually -# raising/lowering the interfaces -# --pif-uuid A PIF UUID, use instead of --session/--pif. -# -# -# # Notes: # 1. Every pif belongs to exactly one network # 2. Every network has zero or one pifs # 3. A network may have an associated bridge, allowing vifs to be attached # 4. A network may be bridgeless (there's no point having a bridge over a storage pif) -# XXX: --force-interface=all down - -# XXX: --force-interface rewrite - -# XXX: Sometimes this leaves "orphaned" datapaths, e.g. a datapath whose -# only port is the local port. Should delete those. - -# XXX: This can leave crud in ovs-vswitchd.conf in this scenario: -# - Create bond in XenCenter. -# - Create VLAN on bond in XenCenter. -# - Attempt to delete bond in XenCenter (this will fail because there -# is a VLAN on the bond, although the error may not be reported -# until the next step) -# - Delete VLAN in XenCenter. -# - Delete bond in XenCenter. -# At this point there will still be some configuration data for the bond -# or the VLAN in ovs-vswitchd.conf. +from InterfaceReconfigure import * -import XenAPI -import os, sys, getopt, time, signal +import os, sys, getopt import syslog import traceback -import time import re -import pickle import random +import syslog -output_directory = None - -db = None management_pif = None -dbcache_file = "/etc/ovs-vswitch.dbcache" -vswitch_config_dir = "/etc/openvswitch" +dbcache_file = "/var/xapi/network.dbcache" + +# +# Logging. +# + +def log_pif_action(action, pif): + pifrec = db().get_pif_record(pif) + rec = {} + rec['uuid'] = pifrec['uuid'] + rec['ip_configuration_mode'] = pifrec['ip_configuration_mode'] + rec['action'] = action + rec['pif_netdev_name'] = pif_netdev_name(pif) + rec['message'] = "Bring %(action)s PIF %(uuid)s" % rec + log("%(message)s: %(pif_netdev_name)s configured as %(ip_configuration_mode)s" % rec) + +# +# Exceptions. +# class Usage(Exception): def __init__(self, msg): Exception.__init__(self) self.msg = msg -class Error(Exception): - def __init__(self, msg): - Exception.__init__(self) - self.msg = msg +# +# Boot from Network filesystem or device. +# -class ConfigurationFile(object): - """Write a file, tracking old and new versions. +def check_allowed(pif): + """Determine whether interface-reconfigure should be manipulating this PIF. - Supports writing a new version of a file and applying and - reverting those changes. + Used to prevent system PIFs (such as network root disk) from being interfered with. """ - __STATE = {"OPEN":"OPEN", - "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED", - "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"} - - def __init__(self, fname, path="/etc/sysconfig/network-scripts"): - - self.__state = self.__STATE['OPEN'] - self.__fname = fname - self.__children = [] - - if debug_mode(): - dirname = output_directory - else: - dirname = path - - self.__path = os.path.join(dirname, fname) - self.__oldpath = os.path.join(dirname, "." + fname + ".xapi-old") - self.__newpath = os.path.join(dirname, "." + fname + ".xapi-new") - self.__unlink = False - - self.__f = open(self.__newpath, "w") - - def attach_child(self, child): - self.__children.append(child) - - def path(self): - return self.__path - - def readlines(self): - try: - return open(self.path()).readlines() - except: - return "" - - def write(self, args): - if self.__state != self.__STATE['OPEN']: - raise Error("Attempt to write to file in state %s" % self.__state) - self.__f.write(args) - - def unlink(self): - if self.__state != self.__STATE['OPEN']: - raise Error("Attempt to unlink file in state %s" % self.__state) - self.__unlink = True - self.__f.close() - self.__state = self.__STATE['NOT-APPLIED'] - - def close(self): - if self.__state != self.__STATE['OPEN']: - raise Error("Attempt to close file in state %s" % self.__state) - - self.__f.close() - self.__state = self.__STATE['NOT-APPLIED'] - - def changed(self): - if self.__state != self.__STATE['NOT-APPLIED']: - raise Error("Attempt to compare file in state %s" % self.__state) - - return True - - def apply(self): - if self.__state != self.__STATE['NOT-APPLIED']: - raise Error("Attempt to apply configuration from state %s" % self.__state) - - for child in self.__children: - child.apply() - - log("Applying changes to %s configuration" % self.__fname) - - # Remove previous backup. - if os.access(self.__oldpath, os.F_OK): - os.unlink(self.__oldpath) - - # Save current configuration. - if os.access(self.__path, os.F_OK): - os.link(self.__path, self.__oldpath) - os.unlink(self.__path) - - # Apply new configuration. - assert(os.path.exists(self.__newpath)) - if not self.__unlink: - os.link(self.__newpath, self.__path) - else: - pass # implicit unlink of original file - - # Remove temporary file. - os.unlink(self.__newpath) - - self.__state = self.__STATE['APPLIED'] - - def revert(self): - if self.__state != self.__STATE['APPLIED']: - raise Error("Attempt to revert configuration from state %s" % self.__state) - - for child in self.__children: - child.revert() - - log("Reverting changes to %s configuration" % self.__fname) - - # Remove existing new configuration - if os.access(self.__newpath, os.F_OK): - os.unlink(self.__newpath) - - # Revert new configuration. - if os.access(self.__path, os.F_OK): - os.link(self.__path, self.__newpath) - os.unlink(self.__path) - - # Revert to old configuration. - if os.access(self.__oldpath, os.F_OK): - os.link(self.__oldpath, self.__path) - os.unlink(self.__oldpath) - - # Leave .*.xapi-new as an aid to debugging. - - self.__state = self.__STATE['REVERTED'] - - def commit(self): - if self.__state != self.__STATE['APPLIED']: - raise Error("Attempt to commit configuration from state %s" % self.__state) - - for child in self.__children: - child.commit() - - log("Committing changes to %s configuration" % self.__fname) - - if os.access(self.__oldpath, os.F_OK): - os.unlink(self.__oldpath) - if os.access(self.__newpath, os.F_OK): - os.unlink(self.__newpath) - - self.__state = self.__STATE['COMMITTED'] - -def debug_mode(): - return output_directory is not None - -def log(s): - if debug_mode(): - print >>sys.stderr, s - else: - syslog.syslog(s) - -def check_allowed(pif): - pifrec = db.get_pif_record(pif) + pifrec = db().get_pif_record(pif) try: - f = open("/proc/ardence") + f = open(root_prefix() + "/proc/ardence") macline = filter(lambda x: x.startswith("HWaddr:"), f.readlines()) f.close() if len(macline) == 1: @@ -246,1127 +103,134 @@ def check_allowed(pif): pass return True -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: - raise Error("can't specify session reference and cache file") - - if cache_file == None: - session = XenAPI.xapi_local() - - if not session_ref: - log("No session ref given on command line, logging in.") - session.xenapi.login_with_password("root", "") - else: - session._session = session_ref - - try: - self.__vlans = session.xenapi.VLAN.get_all_records() - self.__bonds = session.xenapi.Bond.get_all_records() - self.__pifs = session.xenapi.PIF.get_all_records() - self.__networks = session.xenapi.network.get_all_records() - finally: - if not session_ref: - session.xenapi.session.logout() - else: - log("Loading xapi database cache from %s" % cache_file) - f = open(cache_file, 'r') - members = pickle.load(f) - self.extras = pickle.load(f) - f.close() - - self.__vlans = members['vlans'] - self.__bonds = members['bonds'] - self.__pifs = members['pifs'] - self.__networks = members['networks'] - - def save(self, cache_file, extras): - f = open(cache_file, 'w') - pickle.dump({'vlans': self.__vlans, - 'bonds': self.__bonds, - 'pifs': self.__pifs, - 'networks': self.__networks}, f) - pickle.dump(extras, f) - f.close() +# +# Bare Network Devices -- network devices without IP configuration +# - def get_pif_by_uuid(self, uuid): - pifs = map(lambda (ref,rec): ref, - filter(lambda (ref,rec): uuid == rec['uuid'], - self.__pifs.items())) - if len(pifs) == 0: - raise Error("Unknown PIF \"%s\"" % uuid) - elif len(pifs) > 1: - raise Error("Non-unique PIF \"%s\"" % uuid) - - return pifs[0] - - def get_pifs_by_record(self, record): - """record is partial pif record. - Get the pif(s) whose record matches. - """ - def match(pifrec): - for key in record: - if record[key] != pifrec[key]: - return False - return True - - return map(lambda (ref,rec): ref, - filter(lambda (ref,rec): match(rec), - self.__pifs.items())) - - def get_pif_by_record(self, record): - """record is partial pif record. - Get the pif whose record matches. - """ - pifs = self.get_pifs_by_record(record) - if len(pifs) == 0: - raise Error("No matching PIF \"%s\"" % str(record)) - elif len(pifs) > 1: - raise Error("Multiple matching PIFs \"%s\"" % str(record)) - - return pifs[0] - - def get_pif_by_bridge(self, host, bridge): - networks = map(lambda (ref,rec): ref, - filter(lambda (ref,rec): rec['bridge'] == bridge, - self.__networks.items())) - if len(networks) == 0: - raise Error("No matching network \"%s\"") - - answer = None - for network in networks: - nwrec = self.get_network_record(network) - for pif in nwrec['PIFs']: - pifrec = self.get_pif_record(pif) - if pifrec['host'] != host: - continue - if answer: - raise Error("Multiple PIFs on %s for network %s" % (host, bridge)) - answer = pif - if not answer: - raise Error("No PIF on %s for network %s" % (host, bridge)) - return answer - - def get_pif_record(self, pif): - if self.__pifs.has_key(pif): - return self.__pifs[pif] - raise Error("Unknown PIF \"%s\"" % pif) - def get_all_pifs(self): - return self.__pifs - def pif_exists(self, pif): - return self.__pifs.has_key(pif) - - def get_management_pif(self, host): - """ Returns the management pif on host - """ - all = self.get_all_pifs() - for pif in all: - pifrec = self.get_pif_record(pif) - if pifrec['management'] and pifrec['host'] == host : - return pif - return None +def netdev_remap_name(pif, already_renamed=[]): + """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 get_network_record(self, network): - if self.__networks.has_key(network): - return self.__networks[network] - raise Error("Unknown network \"%s\"" % network) - def get_all_networks(self): - return self.__networks + def read1(name): + file = None + try: + file = open(name, 'r') + return file.readline().rstrip('\n') + finally: + if file != None: + file.close() - def get_bond_record(self, bond): - if self.__bonds.has_key(bond): - return self.__bonds[bond] - else: - return None - - def get_vlan_record(self, vlan): - if self.__vlans.has_key(vlan): - return self.__vlans[vlan] - else: + def get_netdev_mac(device): + try: + return read1("%s/sys/class/net/%s/address" % (root_prefix(), device)) + except: + # Probably no such device. return None - -def bridge_name(pif): - """Return the bridge name associated with pif, or None if network is bridgeless""" - pifrec = db.get_pif_record(pif) - nwrec = db.get_network_record(pifrec['network']) - - if nwrec['bridge']: - # TODO: sanity check that nwrec['bridgeless'] != 'true' - return nwrec['bridge'] - else: - # TODO: sanity check that nwrec['bridgeless'] == 'true' - return None - -def interface_name(pif): - """Construct an interface name from the given PIF record.""" - - pifrec = db.get_pif_record(pif) - - if pifrec['VLAN'] == '-1': - return pifrec['device'] - else: - return "%(device)s.%(VLAN)s" % pifrec - -def datapath_name(pif): - """Return the OpenFlow datapath name associated with pif. -For a non-VLAN PIF, the datapath name is the bridge name. -For a VLAN PIF, the datapath name is the bridge name for the PIF's VLAN slave. -(xapi will create a datapath named with the bridge name even though we won't -use it.) -""" - - pifrec = db.get_pif_record(pif) - - if pifrec['VLAN'] == '-1': - return bridge_name(pif) - else: - return bridge_name(get_vlan_slave_of_pif(pif)) - -def ipdev_name(pif): - """Return the the name of the network device that carries the -IP configuration (if any) associated with pif. -The ipdev name is the same as the bridge name. -""" - - pifrec = db.get_pif_record(pif) - return bridge_name(pif) - -def get_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 [get_vlan_slave_of_pif(pif)] - elif len(pifrec['bond_master_of']) != 0: - return get_bond_slaves_of_pif(pif) - else: - return [pif] - -def get_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 get_physdev_pifs(pif)] - -def log_pif_action(action, pif): - pifrec = db.get_pif_record(pif) - pifrec['action'] = action - pifrec['interface-name'] = interface_name(pif) - if action == "rewrite": - pifrec['message'] = "Rewrite PIF %(uuid)s configuration" % pifrec - else: - pifrec['message'] = "Bring %(action)s PIF %(uuid)s" % pifrec - log("%(message)s: %(interface-name)s configured as %(ip_configuration_mode)s" % pifrec) - -def get_bond_masters_of_pif(pif): - """Returns a list of PIFs which are bond masters of this PIF""" - - pifrec = db.get_pif_record(pif) - - bso = pifrec['bond_slave_of'] - - # bond-slave-of is currently a single reference but in principle a - # PIF could be a member of several bonds which are not - # concurrently attached. Be robust to this possibility. - if not bso or bso == "OpaqueRef:NULL": - bso = [] - elif not type(bso) == list: - bso = [bso] - - bondrecs = [db.get_bond_record(bond) for bond in bso] - bondrecs = [rec for rec in bondrecs if rec] - - return [bond['master'] for bond in bondrecs] - -def get_bond_slaves_of_pif(pif): - """Returns a list of PIFs which make up the given bonded pif.""" - - pifrec = db.get_pif_record(pif) - host = pifrec['host'] - bmo = pifrec['bond_master_of'] - if len(bmo) > 1: - raise Error("Bond-master-of contains too many elements") - - if len(bmo) == 0: - return [] - - bondrec = db.get_bond_record(bmo[0]) - if not bondrec: - raise Error("No bond record for bond master PIF") - - return bondrec['slaves'] - -def get_vlan_slave_of_pif(pif): - """Find the PIF which is the VLAN slave of pif. - -Returns the 'physical' PIF underneath the a VLAN PIF @pif.""" - - pifrec = db.get_pif_record(pif) - - vlan = pifrec['VLAN_master_of'] - if not vlan or vlan == "OpaqueRef:NULL": - raise Error("PIF is not a VLAN master") - - vlanrec = db.get_vlan_record(vlan) - if not vlanrec: - raise Error("No VLAN record found for PIF") + def get_netdev_tx_queue_len(device): + try: + return int(read1("%s/sys/class/net/%s/tx_queue_len" % (root_prefix(), device))) + except: + # Probably no such device. + return None - return vlanrec['tagged_PIF'] + def get_netdev_by_mac(mac): + for device in os.listdir(root_prefix() + "/sys/class/net"): + dev_mac = get_netdev_mac(device) + if (dev_mac and mac.lower() == dev_mac.lower() and + get_netdev_tx_queue_len(device)): + return device + return None -def get_vlan_masters_of_pif(pif): - """Returns a list of PIFs which are VLANs on top of the given pif.""" - - pifrec = db.get_pif_record(pif) - vlans = [db.get_vlan_record(v) for v in pifrec['VLAN_slave_of']] - return [v['untagged_PIF'] for v in vlans if v and db.pif_exists(v['untagged_PIF'])] - -def interface_deconfigure_commands(interface): - # The use of [!0-9] keeps an interface of 'eth0' from matching - # VLANs attached to eth0 (such as 'eth0.123'), which are distinct - # interfaces. - return ['--del-match=bridge.*.port=%s' % interface, - '--del-match=bonding.%s.[!0-9]*' % interface, - '--del-match=bonding.*.slave=%s' % interface, - '--del-match=vlan.%s.[!0-9]*' % interface, - '--del-match=port.%s.[!0-9]*' % interface, - '--del-match=iface.%s.[!0-9]*' % interface] - -def run_command(command): - log("Running command: " + ' '.join(command)) - if os.spawnl(os.P_WAIT, command[0], *command) != 0: - log("Command failed: " + ' '.join(command)) - return False - return True + def rename_netdev(old_name, new_name): + raise Error("Trying to rename %s to %s - This functionality has been removed" % (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)) -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) + 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) + device_exists = netdev_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 - if deconfigure: - # Kill dhclient. - pidfile_name = '/var/run/dhclient-%s.pid' % interface - try: - os.kill(int(read_first_line_of_file(pidfile_name)), signal.SIGTERM) - except: - pass - - # Remove dhclient pidfile. - try: - os.remove(pidfile_name) - except: - pass - - 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']) - -def find_distinguished_pifs(pif): - """Returns the PIFs on host that own DNS and the default route. -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. - -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.""" - - pifrec = db.get_pif_record(pif) - host = pifrec['host'] - - 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)) ] - - 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 - - return peerdns_pif, defaultroute_pif - -def run_ethtool(device, oc): - # Run "ethtool -s" if there are any settings. - settings = [] - if oc.has_key('ethtool-speed'): - val = oc['ethtool-speed'] - if val in ["10", "100", "1000"]: - settings += ['speed', val] - else: - log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val) - if oc.has_key('ethtool-duplex'): - val = oc['ethtool-duplex'] - if val in ["10", "100", "1000"]: - settings += ['duplex', 'val'] - else: - log("Invalid value for ethtool-duplex = %s. Must be half|full." % val) - if oc.has_key('ethtool-autoneg'): - val = oc['ethtool-autoneg'] - if val in ["true", "on"]: - settings += ['autoneg', 'on'] - elif val in ["false", "off"]: - settings += ['autoneg', 'off'] - else: - log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val) - if settings: - run_command(['/sbin/ethtool', '-s', device] + settings) - - # Run "ethtool -K" if there are any offload settings. - offload = [] - for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"): - if oc.has_key("ethtool-" + opt): - val = oc["ethtool-" + opt] - if val in ["true", "on"]: - offload += [opt, 'on'] - elif val in ["false", "off"]: - offload += [opt, 'off'] - else: - log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val)) - if offload: - run_command(['/sbin/ethtool', '-K', device] + offload) - -def mtu_setting(oc): - if oc.has_key('mtu'): - try: - int(oc['mtu']) # Check that the value is an integer - return ['mtu', oc['mtu']] - except ValueError, x: - log("Invalid value for mtu = %s" % mtu) - return [] - -def configure_local_port(pif): - pifrec = db.get_pif_record(pif) - datapath = datapath_name(pif) - ipdev = ipdev_name(pif) - - host = pifrec['host'] - nw = pifrec['network'] - nwrec = db.get_network_record(nw) - - pif_oc = pifrec['other_config'] - nw_oc = nwrec['other_config'] - - # IP (except DHCP) and MTU. - ifconfig_argv = ['/sbin/ifconfig', ipdev, 'up'] - gateway = '' - if pifrec['ip_configuration_mode'] == "DHCP": - pass - elif pifrec['ip_configuration_mode'] == "Static": - ifconfig_argv += [pifrec['IP']] - ifconfig_argv += ['netmask', pifrec['netmask']] - gateway = pifrec['gateway'] - elif pifrec['ip_configuration_mode'] == "None": - # Nothing to do. - pass - else: - raise Error("Unknown IP-configuration-mode %s" % pifrec['ip_configuration_mode']) - ifconfig_argv += mtu_setting(nw_oc) - run_command(ifconfig_argv) - - (peerdns_pif, defaultroute_pif) = find_distinguished_pifs(pif) - - # /etc/resolv.conf - if peerdns_pif == pif: - f = ConfigurationFile('resolv.conf', "/etc") - if pif_oc.has_key('domain'): - f.write("search %s\n" % pif_oc['domain']) - for dns in pifrec['DNS'].split(","): - f.write("nameserver %s\n" % dns) - f.close() - f.apply() - f.commit() - - # Routing. - if defaultroute_pif == pif and gateway != '': - run_command(['/sbin/ip', 'route', 'replace', 'default', - 'via', gateway, 'dev', ipdev]) - if nw_oc.has_key('static-routes'): - for line in nw_oc['static-routes'].split(','): - network, masklen, gateway = line.split('/') - run_command(['/sbin/ip', 'route', 'add', - '%s/%s' % (network, masklen), 'via', gateway, - 'dev', ipdev]) - - # Ethtool. - run_ethtool(ipdev, nw_oc) - - # DHCP. - if pifrec['ip_configuration_mode'] == "DHCP": - print - print "Determining IP information for %s..." % ipdev, - argv = ['/sbin/dhclient', '-q', - '-lf', '/var/lib/dhclient/dhclient-%s.leases' % ipdev, - '-pf', '/var/run/dhclient-%s.pid' % ipdev, - ipdev] - if run_command(argv): - print 'done.' - else: - print 'failed.' - -def configure_physdev(pif): - pifrec = db.get_pif_record(pif) - device = pifrec['device'] - oc = pifrec['other_config'] - - run_command(['/sbin/ifconfig', device, 'up'] + mtu_setting(oc)) - run_ethtool(device, oc) - -def modify_config(commands): - run_command(['/root/vswitch/bin/ovs-cfg-mod', '-vANY:console:emer', - '-F', '/etc/ovs-vswitchd.conf'] - + commands + ['-c']) - run_command(['/sbin/service', 'vswitch', 'reload']) - -def is_bond_pif(pif): - pifrec = db.get_pif_record(pif) - return len(pifrec['bond_master_of']) != 0 - -def configure_bond(pif): - pifrec = db.get_pif_record(pif) - interface = interface_name(pif) - ipdev = ipdev_name(pif) - datapath = datapath_name(pif) - physdev_names = get_physdev_names(pif) - - argv = ['--del-match=bonding.%s.[!0-9]*' % interface] - argv += ["--add=bonding.%s.slave=%s" % (interface, slave) - for slave in physdev_names] - argv += ['--add=bonding.%s.fake-iface=true'] - - if pifrec['MAC'] != "": - argv += ['--add=port.%s.mac=%s' % (interface, pifrec['MAC'])] - - # Bonding options. - 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-" - oc = pifrec['other_config'] - overrides = filter(lambda (key,val): - key.startswith("bond-"), oc.items()) - overrides = map(lambda (key,val): (key[5:], val), overrides) - bond_options.update(overrides) - for (name,val) in bond_options.items(): - argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)] - return argv - -def action_up(pif): - pifrec = db.get_pif_record(pif) - - bridge = bridge_name(pif) - interface = interface_name(pif) - ipdev = ipdev_name(pif) - datapath = datapath_name(pif) - physdev_pifs = get_physdev_pifs(pif) - physdev_names = get_physdev_names(pif) - vlan_slave = None - if pifrec['VLAN'] != '-1': - vlan_slave = get_vlan_slave_of_pif(pif) - if vlan_slave and is_bond_pif(vlan_slave): - bond_master = vlan_slave - elif is_bond_pif(pif): - bond_master = pif - else: - bond_master = None - if bond_master: - bond_slaves = get_bond_slaves_of_pif(bond_master) - else: - bond_slaves = [] - 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() - - # Check the MAC address of each network device and remap if - # necessary to make names match our expectations. - for physdev_pif in physdev_pifs: - remap_pif(physdev_pif) - - # "ifconfig down" the network device and delete its IP address, etc. - down_netdev(ipdev) - for physdev_name in physdev_names: - down_netdev(physdev_name) - - # 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] + physdev_names + bond_masters - if vlan_slave and bond_master: - del_ports += [interface_name(bond_master)] - - # What ports do we need to add to the datapath? - # - # We definitely need the ipdev, and ordinarily we want the - # physical devices too, but for bonds we need the bond as bridge - # port. - add_ports = [ipdev, datapath] - if not bond_master: - add_ports += physdev_names - else: - add_ports += [interface_name(bond_master)] - - # What ports do we need to delete? - # - # - All the ports that we add, to avoid duplication and to drop - # them from another datapath in case they're misassigned. - # - # - The physical devices, since they will either be in add_ports - # or added to the bonding device (see below). - # - # - 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 + physdev_names + bond_masters - - # What networks does this datapath carry? - # - # - The network corresponding to the datapath's PIF. - # - # - The networks corresponding to any VLANs attached to the - # datapath's PIF. - network_uuids = [] - for nwpif in db.get_pifs_by_record({'device': pifrec['device'], - 'host': pifrec['host']}): - net = db.get_pif_record(nwpif)['network'] - network_uuids += [db.get_network_record(net)['uuid']] - - # Bring up bond slaves early, because ovs-vswitchd initially - # enables or disables bond slaves based on whether carrier is - # detected when they are added, and a network device that is down - # always reports "no carrier". - bond_slave_physdev_pifs = [] - for slave in bond_slaves: - bond_slave_physdev_pifs += get_physdev_pifs(slave) - for slave_physdev_pif in set(bond_slave_physdev_pifs): - configure_physdev(slave_physdev_pif) - - # Now modify the ovs-vswitchd config file. - argv = [] - for port in set(del_ports): - argv += interface_deconfigure_commands(port) - for port in set(add_ports): - argv += ['--add=bridge.%s.port=%s' % (datapath, port)] - if vlan_slave: - argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])] - argv += ['--add=iface.%s.internal=true' % (ipdev)] - - # xapi creates a bridge by the name of the ipdev and requires - # that the IP address will be on it. We need to delete this - # bridge because we need that device to be a member of our - # datapath. - argv += ['--del-match=bridge.%s.[!0-9]*' % ipdev] - - # xapi insists that its attempts to create the bridge succeed, - # so force that to happen. - argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)] - else: - try: - os.unlink("%s/br-%s" % (vswitch_config_dir, bridge)) - except OSError: - pass - argv += ['--del-match=bridge.%s.xs-network-uuids=*' % datapath] - argv += ['--add=bridge.%s.xs-network-uuids=%s' % (datapath, uuid) - for uuid in set(network_uuids)] - if bond_master: - argv += configure_bond(bond_master) - modify_config(argv) - - # Bring up VLAN slave, plus physical devices other than bond - # slaves (which we brought up earlier). - if vlan_slave: - up_netdev(ipdev_name(vlan_slave)) - for physdev_pif in set(physdev_pifs) - set(bond_slave_physdev_pifs): - configure_physdev(physdev_pif) - - # Configure network device for local port. - configure_local_port(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) - interface = interface_name(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. - down_netdev(ipdev) - argv += interface_deconfigure_commands(ipdev) - - # If the VLAN's slave is attached, stop here. - slave = get_vlan_slave_of_pif(pif) - if db.get_pif_record(slave)['currently_attached']: - log("VLAN slave is currently attached") - modify_config(argv) - return - - # If the VLAN's slave has other VLANs that are attached, stop here. - masters = get_vlan_masters_of_pif(slave) - for m in masters: - if m != pif and db.get_pif_record(m)['currently_attached']: - log("VLAN slave has other master %s" % interface_naem(m)) - modify_config(argv) - return - - # Otherwise, take down the VLAN's slave too. - log("No more masters, bring down vlan slave %s" % interface_name(slave)) - pif = slave - else: - # Stop here if this PIF has attached VLAN masters. - vlan_masters = get_vlan_masters_of_pif(pif) - log("VLAN masters of %s - %s" % (rec['device'], [interface_name(m) for m in vlan_masters])) - for m in vlan_masters: - if db.get_pif_record(m)['currently_attached']: - log("Leaving %s up due to currently attached VLAN master %s" % (interface, interface_name(m))) - return - - # pif is now either a bond or a physical device which needs to be - # brought down. pif might have changed so re-check all its attributes. - rec = db.get_pif_record(pif) - interface = interface_name(pif) - bridge = bridge_name(pif) - ipdev = ipdev_name(pif) - - - bond_slaves = get_bond_slaves_of_pif(pif) - log("bond slaves of %s - %s" % (rec['device'], [interface_name(s) for s in bond_slaves])) - for slave in bond_slaves: - slave_interface = interface_name(slave) - log("bring down bond slave %s" % slave_interface) - argv += interface_deconfigure_commands(slave_interface) - down_netdev(slave_interface) - - argv += interface_deconfigure_commands(ipdev) - down_netdev(ipdev) - - argv += ['--del-match', 'bridge.%s.*' % datapath_name(pif)] - argv += ['--del-match', 'bonding.%s.[!0-9]*' % interface] - 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) - 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 - - session = None - pif_uuid = None - pif = None - - force_interface = None - force_management = False - - if argv is None: - argv = sys.argv - - try: - try: - shortops = "h" - longops = [ "output-directory=", - "pif=", "pif-uuid=", - "session=", - "force=", - "force-interface=", - "management", - "test-mode", - "device=", "mode=", "ip=", "netmask=", "gateway=", - "help" ] - arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops) - except getopt.GetoptError, msg: - raise Usage(msg) - - force_rewrite_config = {} - - for o,a in arglist: - if o == "--output-directory": - output_directory = a - elif o == "--pif": - pif = a - elif o == "--pif-uuid": - pif_uuid = a - elif o == "--session": - session = a - elif o == "--force-interface" or o == "--force": - force_interface = a - elif o == "--management": - force_management = True - elif o in ["--device", "--mode", "--ip", "--netmask", "--gateway"]: - force_rewrite_config[o[2:]] = a - elif o == "-h" or o == "--help": - print __doc__ % {'command-name': os.path.basename(argv[0])} - return 0 - - if not debug_mode(): - syslog.openlog(os.path.basename(argv[0])) - log("Called as " + str.join(" ", argv)) - if len(args) < 1: - raise Usage("Required option not present") - if len(args) > 1: - raise Usage("Too many arguments") - - action = args[0] - # backwards compatibility - if action == "rewrite-configuration": action = "rewrite" - - if output_directory and ( session or pif ): - raise Usage("--session/--pif cannot be used with --output-directory") - if ( session or pif ) and pif_uuid: - raise Usage("--session/--pif and --pif-uuid are mutually exclusive.") - if ( session and not pif ) or ( not session and pif ): - raise Usage("--session and --pif must be used together.") - if force_interface and ( session or pif or pif_uuid ): - raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid") - if len(force_rewrite_config) and not (force_interface and action == "rewrite"): - raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway") - - global db - if force_interface: - log("Force interface %s %s" % (force_interface, action)) - - if action == "rewrite": - action_force_rewrite(force_interface, force_rewrite_config) - else: - db = DatabaseCache(cache_file=dbcache_file) - host = db.extras['host'] - pif = db.get_pif_by_bridge(host, force_interface) - management_pif = db.get_management_pif(host) + # Yes, everything checks out the way we want. Nothing to do. + return + else: + log("No network device %s" % device) - if action == "up": - action_up(pif) - elif action == "down": - action_down(pif) - else: - raise Usage("Unknown action %s" % action) - else: - db = DatabaseCache(session_ref=session) + # 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 - if pif_uuid: - pif = db.get_pif_by_uuid(pif_uuid) - - if not pif: - raise Usage("No PIF given") + # 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)) - if force_management: - # pif is going to be the management pif - management_pif = pif - else: - # pif is not going to be the management pif. - # Search DB cache for pif on same host with management=true - pifrec = db.get_pif_record(pif) - host = pifrec['host'] - management_pif = db.get_management_pif(host) + # Rename 'cur_device' to 'device'. + rename_netdev(cur_device, device) - log_pif_action(action, pif) +# +# IP Network Devices -- network devices with IP configuration +# - if not check_allowed(pif): - return 0 +def ifdown(netdev): + """Bring down a network interface""" + if not netdev_exists(netdev): + log("ifdown: device %s does not exist, ignoring" % netdev) + return + if not os.path.exists("%s/etc/sysconfig/network-scripts/ifcfg-%s" % (root_prefix(), netdev)): + log("ifdown: device %s exists but ifcfg-%s does not" % (netdev,netdev)) + run_command(["/sbin/ifconfig", netdev, 'down']) + return + run_command(["/sbin/ifdown", netdev]) + +def ifup(netdev): + """Bring up a network interface""" + if not os.path.exists(root_prefix() + "/etc/sysconfig/network-scripts/ifcfg-%s" % netdev): + raise Error("ifup: device %s exists but ifcfg-%s does not" % (netdev,netdev)) + d = os.getenv("DHCLIENTARGS","") + if os.path.exists("/etc/firstboot.d/data/firstboot_in_progress"): + os.putenv("DHCLIENTARGS", d + " -T 240 " ) + run_command(["/sbin/ifup", netdev]) + os.putenv("DHCLIENTARGS", d ) - if action == "up": - action_up(pif) - elif action == "down": - action_down(pif) - elif action == "rewrite": - 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 - print >>sys.stderr, "For help use --help." - return 2 - except Error, err: - log(err.msg) - 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] +def pif_rename_physical_devices(pif): + if pif_is_tunnel(pif): + return - 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 pif_is_vlan(pif): + pif = pif_get_vlan_slave(pif) - 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 pif_is_bond(pif): + pifs = pif_get_bond_slaves(pif) + else: + pifs = [pif] - if settings: - f.write("ETHTOOL_OPTS=\"%s\"\n" % settings) - if offload: - f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % offload) + for pif in pifs: + netdev_remap_name(pif) -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) +# +# IP device configuration +# -def configure_static_routes(interface, oc, f): +def ipdev_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 @@ -1377,15 +241,14 @@ def configure_static_routes(interface, oc, f): 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 + # The key is present - extract comma separates entries lines = oc['static-routes'].split(',') else: # The key is not present, i.e. there are no static routes lines = [] - child = ConfigurationFile(fname) + child = ConfigurationFile("%s/etc/sysconfig/network-scripts/route-%s" % (root_prefix(), interface)) child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \ (os.path.basename(child.path()), os.path.basename(sys.argv[0]))) @@ -1393,57 +256,34 @@ def configure_static_routes(interface, oc, f): 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. +def ipdev_open_ifcfg(pif): + ipdev = pif_ipdev_name(pif) + + log("Writing network configuration for %s" % ipdev) + + f = ConfigurationFile("%s/etc/sysconfig/network-scripts/ifcfg-%s" % (root_prefix(), ipdev)) - 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("DEVICE=%s\n" % ipdev) 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) + f.write("NOZEROCONF=yes\n") return f -def configure_network(pif, f): +def ipdev_configure_network(pif, dp): """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 + 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 @@ -1451,41 +291,31 @@ def configure_network(pif, f): params: pif: Opaque_ref of pif - f : ConfigurationFile(/path/to/ifcfg) to which we append network configuration + dp: Datapath object """ - - pifrec = db.get_pif_record(pif) - host = pifrec['host'] + + pifrec = db().get_pif_record(pif) 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 + nwrec = db().get_network_record(nw) - 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) + ipdev = pif_ipdev_name(pif) - + f = ipdev_open_ifcfg(pif) + + mode = pifrec['ip_configuration_mode'] + log("Configuring %s using %s configuration" % (ipdev, mode)) + + oc = None 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)) + dp.configure_ipdev(f) 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("BOOTPROTO=none\n") f.write("NETMASK=%(netmask)s\n" % pifrec) f.write("IPADDR=%(IP)s\n" % pifrec) f.write("GATEWAY=%(gateway)s\n" % pifrec) @@ -1494,209 +324,405 @@ def configure_network(pif, f): else: raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode']) + if nwrec.has_key('other_config'): + settings,offload = ethtool_settings(nwrec['other_config']) + if len(settings): + f.write("ETHTOOL_OPTS=\"%s\"\n" % str.join(" ", settings)) + if len(offload): + f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload)) + + ipdev_configure_static_routes(ipdev, nwrec['other_config'], f) + + mtu = mtu_setting(nw, "Network", nwrec['other_config']) + if mtu: + f.write("MTU=%s\n" % mtu) + + 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. + # There can be only one DNSDEV and 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 + # Work out which pif on this host should be the DNSDEV 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 ] + # 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 = [p for p in db().get_all_pifs() if not p in pif_get_bond_masters(pif)] + + # now prune out bond slaves as they are not connected to the IP + # stack and so cannot be used as gateway or DNS devices. + pifs_on_host = [ p for p in pifs_on_host if len(pif_get_bond_masters(p)) == 0] - 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 + peerdns_pif = None + defaultroute_pif = None for __pif in pifs_on_host: - __pifrec = db.get_pif_record(__pif) + __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'])) + (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 + (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. - """ + is_dnsdev = peerdns_pif == pif + is_gatewaydev = defaultroute_pif == pif + + if is_dnsdev or is_gatewaydev: + fnetwork = ConfigurationFile(root_prefix() + "/etc/sysconfig/network") + for line in fnetwork.readlines(): + if is_dnsdev and line.lstrip().startswith('DNSDEV='): + fnetwork.write('DNSDEV=%s\n' % ipdev) + is_dnsdev = False + elif is_gatewaydev and line.lstrip().startswith('GATEWAYDEV='): + fnetwork.write('GATEWAYDEV=%s\n' % ipdev) + is_gatewaydev = False + else: + fnetwork.write(line) - pifrec = db.get_pif_record(pif) + if is_dnsdev: + fnetwork.write('DNSDEV=%s\n' % ipdev) + if is_gatewaydev: + fnetwork.write('GATEWAYDEV=%s\n' % ipdev) - f = open_pif_ifcfg(pif) - - f.write("TYPE=Ethernet\n") - f.write("HWADDR=%(MAC)s\n" % pifrec) + fnetwork.close() + f.attach_child(fnetwork) return f -def configure_bond_interface(pif): - """Write the configuration for a bond interface. +# +# Toplevel actions +# - Writes the configuration file for the bond interface described by - the pif object. Handles writing the configuration for the slave - interfaces. +def action_up(pif, force): + pifrec = db().get_pif_record(pif) - Returns the open file handle for the bond interface configuration - file. - """ + ipdev = pif_ipdev_name(pif) + dp = DatapathFactory()(pif) - 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 + log("action_up: %s" % ipdev) -def configure_vlan_interface(pif): - """Write the configuration for a VLAN interface. + f = ipdev_configure_network(pif, dp) - Writes the configuration file for the VLAN interface described by - the pif object. Handles writing the configuration for the master - interface if necessary. + dp.preconfigure(f) - Returns the open file handle for the VLAN interface configuration - file. - """ + f.close() - slave = configure_pif(get_vlan_slave_of_pif(pif)) - slave.close() + pif_rename_physical_devices(pif) - f = open_pif_ifcfg(pif) - f.write("VLAN=yes\n") - f.attach_child(slave) - - return f + # if we are not forcing the interface up then attempt to tear down + # any existing devices which might interfere with brinign this one + # up. + if not force: + ifdown(ipdev) + + dp.bring_down_existing() -def configure_pif(pif): - """Write the configuration for a PIF object. + try: + f.apply() - Writes the configuration file the PIF and all dependent - interfaces (bond slaves and VLAN masters etc). + dp.configure() - Returns the open file handle for the interface configuration file. - """ + ifup(ipdev) + + dp.post() + + # Update /etc/issue (which contains the IP address of the management interface) + os.system(root_prefix() + "/sbin/update-issue") + + f.commit() + except Error, e: + log("failed to apply changes: %s" % e.msg) + f.revert() + raise + +def action_down(pif): + ipdev = pif_ipdev_name(pif) + dp = DatapathFactory()(pif) + + log("action_down: %s" % ipdev) + + ifdown(ipdev) + + dp.bring_down() + +def action_rewrite(): + DatapathFactory().rewrite() + +# This is useful for reconfiguring the mgmt interface after having lost connectivity to the pool master +def action_force_rewrite(bridge, config): + def getUUID(): + import subprocess + uuid,_ = subprocess.Popen(['uuidgen'], stdout = subprocess.PIPE).communicate() + return uuid.strip() + + # Notes: + # 1. that this assumes the interface is bridged + # 2. If --gateway is given it will make that the default gateway for the host + + # extract the configuration + try: + mode = config['mode'] + mac = config['mac'] + interface = config['device'] + except: + raise Usage("Please supply --mode, --mac and --device") - pifrec = db.get_pif_record(pif) + if mode == 'static': + try: + netmask = config['netmask'] + ip = config['ip'] + except: + raise Usage("Please supply --netmask and --ip") + try: + gateway = config['gateway'] + except: + gateway = None + elif mode != 'dhcp': + raise Usage("--mode must be either static or dhcp") - if pifrec['VLAN'] != '-1': - f = configure_vlan_interface(pif) - elif len(pifrec['bond_master_of']) != 0: - f = configure_bond_interface(pif) + if config.has_key('vlan'): + is_vlan = True + vlan_slave, vlan_vid = config['vlan'].split('.') else: - f = configure_physical_interface(pif) + is_vlan = False + + if is_vlan: + raise Error("Force rewrite of VLAN not implemented") + + log("Configuring %s using %s configuration" % (bridge, mode)) + + f = ConfigurationFile(root_prefix() + dbcache_file) + + pif_uuid = getUUID() + network_uuid = getUUID() + + f.write('\n') + f.write('\n') + f.write('\t\n' % pif_uuid) + f.write('\t\tOpaqueRef:%s\n' % network_uuid) + f.write('\t\tTrue\n') + f.write('\t\t%sPif\n' % interface) + f.write('\t\tOpaqueRef:NULL\n') + f.write('\t\t\n') + f.write('\t\t\n') + f.write('\t\tOpaqueRef:NULL\n') + f.write('\t\t-1\n') + f.write('\t\t\n') + f.write('\t\t\n') + f.write('\t\t%s\n' % interface) + f.write('\t\t%s\n' % mac) + f.write('\t\t\n') + if mode == 'dhcp': + f.write('\t\tDHCP\n') + f.write('\t\t\n') + f.write('\t\t\n') + f.write('\t\t\n') + f.write('\t\t\n') + elif mode == 'static': + f.write('\t\tStatic\n') + f.write('\t\t%s\n' % ip) + f.write('\t\t%s\n' % netmask) + if gateway is not None: + f.write('\t\t%s\n' % gateway) + f.write('\t\t\n') + else: + raise Error("Unknown mode %s" % mode) + f.write('\t\n') + + f.write('\t\n' % network_uuid) + f.write('\t\tInitialManagementNetwork\n') + f.write('\t\t\n') + f.write('\t\t\tOpaqueRef:%s\n' % pif_uuid) + f.write('\t\t\n') + f.write('\t\t%s\n' % bridge) + f.write('\t\t\n') + f.write('\t\n') + f.write('\n') - bridge = bridge_name(pif) - if bridge: - f.write("BRIDGE=%s\n" % bridge) + f.close() - return f + try: + f.apply() + f.commit() + except Error, e: + log("failed to apply changes: %s" % e.msg) + f.revert() + raise + +def main(argv=None): + global management_pif + + session = None + pif_uuid = None + pif = None + + force_interface = None + force_management = False + + if argv is None: + argv = sys.argv + + try: + try: + shortops = "h" + longops = [ "pif=", "pif-uuid=", + "session=", + "force=", + "force-interface=", + "management", + "mac=", "device=", "mode=", "ip=", "netmask=", "gateway=", + "root-prefix=", + "no-syslog", + "help" ] + arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops) + except getopt.GetoptError, msg: + raise Usage(msg) + + force_rewrite_config = {} + + for o,a in arglist: + if o == "--pif": + pif = a + elif o == "--pif-uuid": + pif_uuid = a + elif o == "--session": + session = a + elif o == "--force-interface" or o == "--force": + force_interface = a + elif o == "--management": + force_management = True + elif o in ["--mac", "--device", "--mode", "--ip", "--netmask", "--gateway"]: + force_rewrite_config[o[2:]] = a + elif o == "--root-prefix": + set_root_prefix(a) + elif o == "--no-syslog": + set_log_destination("stderr") + elif o == "-h" or o == "--help": + print __doc__ % {'command-name': os.path.basename(argv[0])} + return 0 + + if get_log_destination() == "syslog": + syslog.openlog(os.path.basename(argv[0])) + log("Called as " + str.join(" ", argv)) + + if len(args) < 1: + raise Usage("Required option not present") + if len(args) > 1: + raise Usage("Too many arguments") + + action = args[0] + + if not action in ["up", "down", "rewrite", "rewrite-configuration"]: + raise Usage("Unknown action \"%s\"" % action) + + # backwards compatibility + if action == "rewrite-configuration": action = "rewrite" + + if ( session or pif ) and pif_uuid: + raise Usage("--session/--pif and --pif-uuid are mutually exclusive.") + if ( session and not pif ) or ( not session and pif ): + raise Usage("--session and --pif must be used together.") + if force_interface and ( session or pif or pif_uuid ): + raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid") + if len(force_rewrite_config) and not (force_interface and action == "rewrite"): + raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway") + if (action == "rewrite") and (pif or pif_uuid ): + raise Usage("rewrite action does not take --pif or --pif-uuid") + + global db + if force_interface: + log("Force interface %s %s" % (force_interface, action)) + + if action == "rewrite": + action_force_rewrite(force_interface, force_rewrite_config) + elif action in ["up", "down"]: + db_init_from_cache(dbcache_file) + pif = db().get_pif_by_bridge(force_interface) + management_pif = db().get_management_pif() + + if action == "up": + action_up(pif, True) + elif action == "down": + action_down(pif) + else: + raise Error("Unknown action %s" % action) + else: + db_init_from_xenapi(session) + + if pif_uuid: + pif = db().get_pif_by_uuid(pif_uuid) + + if action == "rewrite": + action_rewrite() + else: + if not pif: + raise Usage("No PIF given") + + if force_management: + # pif is going to be the management pif + management_pif = pif + else: + # pif is not going to be the management pif. + # Search DB cache for pif on same host with management=true + pifrec = db().get_pif_record(pif) + management_pif = db().get_management_pif() + + log_pif_action(action, pif) + + if not check_allowed(pif): + return 0 + + if action == "up": + action_up(pif, False) + elif action == "down": + action_down(pif) + else: + raise Error("Unknown action %s" % action) + + # Save cache. + db().save(dbcache_file) + + except Usage, err: + print >>sys.stderr, err.msg + print >>sys.stderr, "For help use --help." + return 2 + except Error, err: + log(err.msg) + return 1 + + return 0 -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: @@ -1707,7 +733,6 @@ if __name__ == "__main__": for exline in err: log(exline) - if not debug_mode(): - syslog.closelog() - + syslog.closelog() + sys.exit(rc)