Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Branch: cr-1121
Fetching contributors…

Cannot retrieve contributors at this time

executable file 739 lines (600 sloc) 24.325 kB
#!/usr/bin/python
#
# 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 <PIF> up
%(command-name)s <PIF> down
%(command-name)s rewrite
%(command-name)s --force <BRIDGE> up
%(command-name)s --force <BRIDGE> down
%(command-name)s --force <BRIDGE> rewrite --device=<INTERFACE> --mac=<MAC-ADDRESS> <CONFIG>
where <PIF> is one of:
--session <SESSION-REF> --pif <PIF-REF>
--pif-uuid <PIF-UUID>
and <CONFIG> is one of:
--mode=dhcp
--mode=static --ip=<IPADDR> --netmask=<NM> [--gateway=<GW>]
Options:
--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.
"""
# 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)
from InterfaceReconfigure import *
import os, sys, getopt
import syslog
import traceback
import re
import random
import syslog
management_pif = None
dbcache_file = "@VARDIR@/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
#
# Boot from Network filesystem or device.
#
def check_allowed(pif):
"""Determine whether interface-reconfigure should be manipulating this PIF.
Used to prevent system PIFs (such as network root disk) from being interfered with.
"""
pifrec = db().get_pif_record(pif)
try:
f = open(root_prefix() + "/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
#
# Bare Network Devices -- network devices without IP configuration
#
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 read1(name):
file = None
try:
file = open(name, 'r')
return file.readline().rstrip('\n')
finally:
if file != None:
file.close()
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 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
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 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))
pifrec = db().get_pif_record(pif)
device = pifrec['device']
mac = pifrec['MAC']
# Is there a network device named 'device' at all?
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)
#
# IP Network Devices -- network devices with IP configuration
#
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 )
#
#
#
def pif_rename_physical_devices(pif):
if pif_is_tunnel(pif):
return
if pif_is_vlan(pif):
pif = pif_get_vlan_slave(pif)
if pif_is_bond(pif):
pifs = pif_get_bond_slaves(pif)
else:
pifs = [pif]
for pif in pifs:
netdev_remap_name(pif)
#
# IP device configuration
#
def ipdev_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
"""
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("%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])))
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 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))
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" % ipdev)
f.write("ONBOOT=no\n")
f.write("NOZEROCONF=yes\n")
return 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
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
dp: Datapath object
"""
pifrec = db().get_pif_record(pif)
nw = pifrec['network']
nwrec = db().get_network_record(nw)
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']
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("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 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(',', ' '))
# 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 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 = [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]
# 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)
__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
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)
if is_dnsdev:
fnetwork.write('DNSDEV=%s\n' % ipdev)
if is_gatewaydev:
fnetwork.write('GATEWAYDEV=%s\n' % ipdev)
fnetwork.close()
f.attach_child(fnetwork)
return f
#
# Toplevel actions
#
def action_up(pif, force):
pifrec = db().get_pif_record(pif)
ipdev = pif_ipdev_name(pif)
dp = DatapathFactory()(pif)
log("action_up: %s" % ipdev)
f = ipdev_configure_network(pif, dp)
dp.preconfigure(f)
f.close()
pif_rename_physical_devices(pif)
# 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()
try:
f.apply()
dp.configure()
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")
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 config.has_key('vlan'):
is_vlan = True
vlan_slave, vlan_vid = config['vlan'].split('.')
else:
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('<?xml version="1.0" ?>\n')
f.write('<xenserver-network-configuration>\n')
f.write('\t<pif ref="OpaqueRef:%s">\n' % pif_uuid)
f.write('\t\t<network>OpaqueRef:%s</network>\n' % network_uuid)
f.write('\t\t<management>True</management>\n')
f.write('\t\t<uuid>%sPif</uuid>\n' % interface)
f.write('\t\t<bond_slave_of>OpaqueRef:NULL</bond_slave_of>\n')
f.write('\t\t<bond_master_of/>\n')
f.write('\t\t<VLAN_slave_of/>\n')
f.write('\t\t<VLAN_master_of>OpaqueRef:NULL</VLAN_master_of>\n')
f.write('\t\t<VLAN>-1</VLAN>\n')
f.write('\t\t<tunnel_access_PIF_of/>\n')
f.write('\t\t<tunnel_transport_PIF_of/>\n')
f.write('\t\t<device>%s</device>\n' % interface)
f.write('\t\t<MAC>%s</MAC>\n' % mac)
f.write('\t\t<other_config/>\n')
if mode == 'dhcp':
f.write('\t\t<ip_configuration_mode>DHCP</ip_configuration_mode>\n')
f.write('\t\t<IP></IP>\n')
f.write('\t\t<netmask></netmask>\n')
f.write('\t\t<gateway></gateway>\n')
f.write('\t\t<DNS></DNS>\n')
elif mode == 'static':
f.write('\t\t<ip_configuration_mode>Static</ip_configuration_mode>\n')
f.write('\t\t<IP>%s</IP>\n' % ip)
f.write('\t\t<netmask>%s</netmask>\n' % netmask)
if gateway is not None:
f.write('\t\t<gateway>%s</gateway>\n' % gateway)
f.write('\t\t<DNS></DNS>\n')
else:
raise Error("Unknown mode %s" % mode)
f.write('\t</pif>\n')
f.write('\t<network ref="OpaqueRef:%s">\n' % network_uuid)
f.write('\t\t<uuid>InitialManagementNetwork</uuid>\n')
f.write('\t\t<PIFs>\n')
f.write('\t\t\t<PIF>OpaqueRef:%s</PIF>\n' % pif_uuid)
f.write('\t\t</PIFs>\n')
f.write('\t\t<bridge>%s</bridge>\n' % bridge)
f.write('\t\t<other_config/>\n')
f.write('\t</network>\n')
f.write('</xenserver-network-configuration>\n')
f.close()
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 <action> 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
if __name__ == "__main__":
rc = 1
try:
rc = main()
except:
ex = sys.exc_info()
err = traceback.format_exception(*ex)
for exline in err:
log(exline)
syslog.closelog()
sys.exit(rc)
Jump to Line
Something went wrong with that request. Please try again.