mirror of
https://github.com/openvswitch/ovs
synced 2025-10-25 15:07:05 +00:00
1573 lines
52 KiB
Plaintext
1573 lines
52 KiB
Plaintext
|
|
#!/usr/bin/python
|
|||
|
|
#
|
|||
|
|
# Copyright (c) Citrix Systems 2008. All rights reserved.
|
|||
|
|
# Copyright (c) 2009 Nicira Networks.
|
|||
|
|
#
|
|||
|
|
"""Usage:
|
|||
|
|
|
|||
|
|
%(command-name)s --session <SESSION-REF> --pif <PIF-REF> [up|down|rewrite]
|
|||
|
|
%(command-name)s --force <BRIDGE> [up|down|rewrite <CONFIG>]
|
|||
|
|
%(command-name)s --force all down
|
|||
|
|
|
|||
|
|
where,
|
|||
|
|
<CONFIG> = --device=<INTERFACE> --mode=dhcp
|
|||
|
|
<CONFIG> = --device=<INTERFACE> --mode=static --ip=<IPADDR> --netmask=<NM> [--gateway=<GW>]
|
|||
|
|
|
|||
|
|
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.
|
|||
|
|
|
|||
|
|
<ACTION> is either "up" or "down" or "rewrite"
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
#
|
|||
|
|
# Undocumented parameters for test & dev:
|
|||
|
|
#
|
|||
|
|
# --output-directory=<DIR> Write configuration to <DIR>. 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.
|
|||
|
|
|
|||
|
|
import XenAPI
|
|||
|
|
import os, sys, getopt, time, signal
|
|||
|
|
import syslog
|
|||
|
|
import traceback
|
|||
|
|
import time
|
|||
|
|
import re
|
|||
|
|
import pickle
|
|||
|
|
|
|||
|
|
output_directory = None
|
|||
|
|
|
|||
|
|
db = None
|
|||
|
|
management_pif = None
|
|||
|
|
|
|||
|
|
dbcache_file = "/etc/vswitch.dbcache"
|
|||
|
|
vswitch_config_dir = "/etc/openvswitch"
|
|||
|
|
|
|||
|
|
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
|
|||
|
|
|
|||
|
|
class ConfigurationFile(object):
|
|||
|
|
"""Write a file, tracking old and new versions.
|
|||
|
|
|
|||
|
|
Supports writing a new version of a file and applying and
|
|||
|
|
reverting those changes.
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
__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)
|
|||
|
|
try:
|
|||
|
|
f = open("/proc/ardence")
|
|||
|
|
macline = filter(lambda x: x.startswith("HWaddr:"), f.readlines())
|
|||
|
|
f.close()
|
|||
|
|
if len(macline) == 1:
|
|||
|
|
p = re.compile(".*\s%(MAC)s\s.*" % pifrec, re.IGNORECASE)
|
|||
|
|
if p.match(macline[0]):
|
|||
|
|
log("Skipping PVS device %(device)s (%(MAC)s)" % pifrec)
|
|||
|
|
return False
|
|||
|
|
except IOError:
|
|||
|
|
pass
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
def interface_exists(i):
|
|||
|
|
return os.path.exists("/sys/class/net/" + i)
|
|||
|
|
|
|||
|
|
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()
|
|||
|
|
|
|||
|
|
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 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 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:
|
|||
|
|
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 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.
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
pifrec = db.get_pif_record(pif)
|
|||
|
|
|
|||
|
|
if pifrec['VLAN'] != '-1':
|
|||
|
|
return physdev_names(get_vlan_slave_of_pif(pif))
|
|||
|
|
elif len(pifrec['bond_master_of']) != 0:
|
|||
|
|
physdevs = []
|
|||
|
|
for slave in get_bond_slaves_of_pif(pif):
|
|||
|
|
physdevs += physdev_names(slave)
|
|||
|
|
return physdevs
|
|||
|
|
else:
|
|||
|
|
return [pifrec['device']]
|
|||
|
|
|
|||
|
|
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")
|
|||
|
|
|
|||
|
|
return vlanrec['tagged_PIF']
|
|||
|
|
|
|||
|
|
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 down_netdev(interface, deconfigure=True):
|
|||
|
|
if not interface_exists(interface):
|
|||
|
|
log("down_netdev: interface %s does not exist, ignoring" % interface)
|
|||
|
|
return
|
|||
|
|
argv = ["/sbin/ifconfig", interface, 'down']
|
|||
|
|
if deconfigure:
|
|||
|
|
argv += ['0.0.0.0']
|
|||
|
|
|
|||
|
|
# Kill dhclient.
|
|||
|
|
pidfile_name = '/var/run/dhclient-%s.pid' % interface
|
|||
|
|
pidfile = None
|
|||
|
|
try:
|
|||
|
|
pidfile = open(pidfile_name, 'r')
|
|||
|
|
os.kill(int(pidfile.readline()), signal.SIGTERM)
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
if pidfile != None:
|
|||
|
|
pidfile.close()
|
|||
|
|
|
|||
|
|
# Remove dhclient pidfile.
|
|||
|
|
try:
|
|||
|
|
os.remove(pidfile_name)
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
run_command(argv)
|
|||
|
|
|
|||
|
|
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 ethtool_settings(oc):
|
|||
|
|
# Options for "ethtool -s"
|
|||
|
|
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)
|
|||
|
|
|
|||
|
|
# Options for "ethtool -K"
|
|||
|
|
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))
|
|||
|
|
|
|||
|
|
return settings, offload
|
|||
|
|
|
|||
|
|
def configure_netdev(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)
|
|||
|
|
|
|||
|
|
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'])
|
|||
|
|
|
|||
|
|
oc = {}
|
|||
|
|
if pifrec.has_key('other_config'):
|
|||
|
|
oc = pifrec['other_config']
|
|||
|
|
if oc.has_key('mtu'):
|
|||
|
|
int(oc['mtu']) # Check that the value is an integer
|
|||
|
|
ifconfig_argv += ['mtu', oc['mtu']]
|
|||
|
|
|
|||
|
|
run_command(ifconfig_argv)
|
|||
|
|
|
|||
|
|
(peerdns_pif, defaultroute_pif) = find_distinguished_pifs(pif)
|
|||
|
|
|
|||
|
|
if peerdns_pif == pif:
|
|||
|
|
f = ConfigurationFile('resolv.conf', "/etc")
|
|||
|
|
if oc.has_key('domain'):
|
|||
|
|
f.write("search %s\n" % oc['domain'])
|
|||
|
|
for dns in pifrec['DNS'].split(","):
|
|||
|
|
f.write("nameserver %s\n" % dns)
|
|||
|
|
f.close()
|
|||
|
|
f.apply()
|
|||
|
|
f.commit()
|
|||
|
|
|
|||
|
|
if defaultroute_pif == pif and gateway != '':
|
|||
|
|
run_command(['/sbin/ip', 'route', 'replace', 'default',
|
|||
|
|
'via', gateway, 'dev', ipdev])
|
|||
|
|
|
|||
|
|
if oc.has_key('static-routes'):
|
|||
|
|
for line in oc['static-routes'].split(','):
|
|||
|
|
network, masklen, gateway = line.split('/')
|
|||
|
|
run_command(['/sbin/ip', 'route', 'add',
|
|||
|
|
'%s/%s' % (netmask, masklen), 'via', gateway,
|
|||
|
|
'dev', ipdev])
|
|||
|
|
|
|||
|
|
settings, offload = ethtool_settings(oc)
|
|||
|
|
if settings:
|
|||
|
|
run_command(['/sbin/ethtool', '-s', ipdev] + settings)
|
|||
|
|
if offload:
|
|||
|
|
run_command(['/sbin/ethtool', '-K', ipdev] + offload)
|
|||
|
|
|
|||
|
|
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 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)
|
|||
|
|
physdevs = physdev_names(pif)
|
|||
|
|
|
|||
|
|
argv = ['--del-match=bonding.%s.[!0-9]*' % interface]
|
|||
|
|
argv += ["--add=bonding.%s.slave=%s" % (interface, slave)
|
|||
|
|
for slave in physdevs]
|
|||
|
|
|
|||
|
|
# 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)
|
|||
|
|
physdevs = 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
|
|||
|
|
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)
|
|||
|
|
for physdev in physdevs:
|
|||
|
|
down_netdev(physdev)
|
|||
|
|
|
|||
|
|
# Remove all keys related to pif and any bond masters linked to PIF.
|
|||
|
|
del_ports = [ipdev] + physdevs + bond_masters
|
|||
|
|
if vlan_slave and bond_master:
|
|||
|
|
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 += physdevs
|
|||
|
|
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 + physdevs + 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']]
|
|||
|
|
|
|||
|
|
# 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)
|
|||
|
|
|
|||
|
|
# Configure network devices.
|
|||
|
|
configure_netdev(pif)
|
|||
|
|
|
|||
|
|
# Bring up VLAN slave and bond slaves.
|
|||
|
|
if vlan_slave:
|
|||
|
|
up_netdev(ipdev_name(vlan_slave))
|
|||
|
|
for physdev in physdevs:
|
|||
|
|
up_netdev(physdev)
|
|||
|
|
|
|||
|
|
# Update /etc/issue (which contains the IP address of the management interface)
|
|||
|
|
os.system("/sbin/update-issue")
|
|||
|
|
|
|||
|
|
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 <action> 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)
|
|||
|
|
|
|||
|
|
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)
|
|||
|
|
|
|||
|
|
if pif_uuid:
|
|||
|
|
pif = db.get_pif_by_uuid(pif_uuid)
|
|||
|
|
|
|||
|
|
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)
|
|||
|
|
host = pifrec['host']
|
|||
|
|
management_pif = db.get_management_pif(host)
|
|||
|
|
|
|||
|
|
log_pif_action(action, pif)
|
|||
|
|
|
|||
|
|
if not check_allowed(pif):
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
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]
|
|||
|
|
|
|||
|
|
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
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
rc = 1
|
|||
|
|
try:
|
|||
|
|
rc = main()
|
|||
|
|
except:
|
|||
|
|
ex = sys.exc_info()
|
|||
|
|
err = traceback.format_exception(*ex)
|
|||
|
|
for exline in err:
|
|||
|
|
log(exline)
|
|||
|
|
|
|||
|
|
if not debug_mode():
|
|||
|
|
syslog.closelog()
|
|||
|
|
|
|||
|
|
sys.exit(rc)
|