3 # Copyright (c) 2008,2009 Citrix Systems, Inc.
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as published
7 # by the Free Software Foundation; version 2.1 only. with the special
8 # exception on linking described in file LICENSE.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Lesser General Public License for more details.
17 %(command-name)s <PIF> up
18 %(command-name)s <PIF> down
19 %(command-name)s rewrite
20 %(command-name)s --force <BRIDGE> up
21 %(command-name)s --force <BRIDGE> down
22 %(command-name)s --force <BRIDGE> rewrite --device=<INTERFACE> --mac=<MAC-ADDRESS> <CONFIG>
24 where <PIF> is one of:
25 --session <SESSION-REF> --pif <PIF-REF>
27 and <CONFIG> is one of:
29 --mode=static --ip=<IPADDR> --netmask=<NM> [--gateway=<GW>]
32 --session A session reference to use to access the xapi DB
33 --pif A PIF reference within the session.
34 --pif-uuid The UUID of a PIF.
35 --force An interface name.
36 --root-prefix=DIR Use DIR as alternate root directory (for testing).
37 --no-syslog Write log messages to stderr instead of system log.
41 # 1. Every pif belongs to exactly one network
42 # 2. Every network has zero or one pifs
43 # 3. A network may have an associated bridge, allowing vifs to be attached
44 # 4. A network may be bridgeless (there's no point having a bridge over a storage pif)
46 from InterfaceReconfigure import *
48 import os, sys, getopt
56 dbcache_file = "/var/xapi/network.dbcache"
62 def log_pif_action(action, pif):
63 pifrec = db().get_pif_record(pif)
65 rec['uuid'] = pifrec['uuid']
66 rec['ip_configuration_mode'] = pifrec['ip_configuration_mode']
67 rec['action'] = action
68 rec['pif_netdev_name'] = pif_netdev_name(pif)
69 rec['message'] = "Bring %(action)s PIF %(uuid)s" % rec
70 log("%(message)s: %(pif_netdev_name)s configured as %(ip_configuration_mode)s" % rec)
76 class Usage(Exception):
77 def __init__(self, msg):
78 Exception.__init__(self)
82 # Boot from Network filesystem or device.
85 def check_allowed(pif):
86 """Determine whether interface-reconfigure should be manipulating this PIF.
88 Used to prevent system PIFs (such as network root disk) from being interfered with.
91 pifrec = db().get_pif_record(pif)
93 f = open(root_prefix() + "/proc/ardence")
94 macline = filter(lambda x: x.startswith("HWaddr:"), f.readlines())
97 p = re.compile(".*\s%(MAC)s\s.*" % pifrec, re.IGNORECASE)
98 if p.match(macline[0]):
99 log("Skipping PVS device %(device)s (%(MAC)s)" % pifrec)
106 # Bare Network Devices -- network devices without IP configuration
109 def netdev_remap_name(pif, already_renamed=[]):
110 """Check whether 'pif' exists and has the correct MAC.
111 If not, try to find a device with the correct MAC and rename it.
112 'already_renamed' is used to avoid infinite recursion.
118 file = open(name, 'r')
119 return file.readline().rstrip('\n')
124 def get_netdev_mac(device):
126 return read1("%s/sys/class/net/%s/address" % (root_prefix(), device))
128 # Probably no such device.
131 def get_netdev_tx_queue_len(device):
133 return int(read1("%s/sys/class/net/%s/tx_queue_len" % (root_prefix(), device)))
135 # Probably no such device.
138 def get_netdev_by_mac(mac):
139 for device in os.listdir(root_prefix() + "/sys/class/net"):
140 dev_mac = get_netdev_mac(device)
141 if (dev_mac and mac.lower() == dev_mac.lower() and
142 get_netdev_tx_queue_len(device)):
146 def rename_netdev(old_name, new_name):
147 log("Changing the name of %s to %s" % (old_name, new_name))
148 run_command(['/sbin/ifconfig', old_name, 'down'])
149 if not run_command(['/sbin/ip', 'link', 'set', old_name, 'name', new_name]):
150 raise Error("Could not rename %s to %s" % (old_name, new_name))
152 pifrec = db().get_pif_record(pif)
153 device = pifrec['device']
156 # Is there a network device named 'device' at all?
157 device_exists = netdev_exists(device)
159 # Yes. Does it have MAC 'mac'?
160 found_mac = get_netdev_mac(device)
161 if found_mac and mac.lower() == found_mac.lower():
162 # Yes, everything checks out the way we want. Nothing to do.
165 log("No network device %s" % device)
167 # What device has MAC 'mac'?
168 cur_device = get_netdev_by_mac(mac)
170 log("No network device has MAC %s" % mac)
173 # First rename 'device', if it exists, to get it out of the way
174 # for 'cur_device' to replace it.
176 rename_netdev(device, "dev%d" % random.getrandbits(24))
178 # Rename 'cur_device' to 'device'.
179 rename_netdev(cur_device, device)
182 # IP Network Devices -- network devices with IP configuration
186 """Bring down a network interface"""
187 if not netdev_exists(netdev):
188 log("ifdown: device %s does not exist, ignoring" % netdev)
190 if not os.path.exists("%s/etc/sysconfig/network-scripts/ifcfg-%s" % (root_prefix(), netdev)):
191 log("ifdown: device %s exists but ifcfg-%s does not" % (netdev,netdev))
192 run_command(["/sbin/ifconfig", netdev, 'down'])
194 run_command(["/sbin/ifdown", netdev])
197 """Bring up a network interface"""
198 if not os.path.exists(root_prefix() + "/etc/sysconfig/network-scripts/ifcfg-%s" % netdev):
199 raise Error("ifup: device %s exists but ifcfg-%s does not" % (netdev,netdev))
200 run_command(["/sbin/ifup", netdev])
206 def pif_rename_physical_devices(pif):
207 if pif_is_tunnel(pif):
211 pif = pif_get_vlan_slave(pif)
214 pifs = pif_get_bond_slaves(pif)
219 netdev_remap_name(pif)
222 # IP device configuration
225 def ipdev_configure_static_routes(interface, oc, f):
226 """Open a route-<interface> file for static routes.
228 Opens the static routes configuration file for interface and writes one
229 line for each route specified in the network's other config "static-routes" value.
231 interface ( RO): xenbr1
232 other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
234 Then route-xenbr1 should be
235 172.16.0.0/15 via 192.168.0.3 dev xenbr1
236 172.18.0.0/16 via 192.168.0.4 dev xenbr1
238 if oc.has_key('static-routes'):
239 # The key is present - extract comma seperates entries
240 lines = oc['static-routes'].split(',')
242 # The key is not present, i.e. there are no static routes
245 child = ConfigurationFile("%s/etc/sysconfig/network-scripts/route-%s" % (root_prefix(), interface))
246 child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
247 (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
251 network, masklen, gateway = l.split('/')
252 child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
254 f.attach_child(child)
257 except ValueError, e:
258 log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
260 def ipdev_open_ifcfg(pif):
261 ipdev = pif_ipdev_name(pif)
263 log("Writing network configuration for %s" % ipdev)
265 f = ConfigurationFile("%s/etc/sysconfig/network-scripts/ifcfg-%s" % (root_prefix(), ipdev))
267 f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
268 (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
269 f.write("XEMANAGED=yes\n")
270 f.write("DEVICE=%s\n" % ipdev)
271 f.write("ONBOOT=no\n")
275 def ipdev_configure_network(pif, dp):
276 """Write the configuration file for a network.
278 Writes configuration derived from the network object into the relevant
279 ifcfg file. The configuration file is passed in, but if the network is
280 bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
282 This routine may also write ifcfg files of the networks corresponding to other PIFs
283 in order to maintain consistency.
286 pif: Opaque_ref of pif
290 pifrec = db().get_pif_record(pif)
291 nw = pifrec['network']
292 nwrec = db().get_network_record(nw)
294 ipdev = pif_ipdev_name(pif)
296 f = ipdev_open_ifcfg(pif)
298 mode = pifrec['ip_configuration_mode']
299 log("Configuring %s using %s configuration" % (ipdev, mode))
302 if pifrec.has_key('other_config'):
303 oc = pifrec['other_config']
305 dp.configure_ipdev(f)
307 if pifrec['ip_configuration_mode'] == "DHCP":
308 f.write("BOOTPROTO=dhcp\n")
309 f.write("PERSISTENT_DHCLIENT=yes\n")
310 elif pifrec['ip_configuration_mode'] == "Static":
311 f.write("BOOTPROTO=none\n")
312 f.write("NETMASK=%(netmask)s\n" % pifrec)
313 f.write("IPADDR=%(IP)s\n" % pifrec)
314 f.write("GATEWAY=%(gateway)s\n" % pifrec)
315 elif pifrec['ip_configuration_mode'] == "None":
316 f.write("BOOTPROTO=none\n")
318 raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
320 if nwrec.has_key('other_config'):
321 settings,offload = ethtool_settings(nwrec['other_config'])
323 f.write("ETHTOOL_OPTS=\"%s\"\n" % str.join(" ", settings))
325 f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload))
327 ipdev_configure_static_routes(ipdev, nwrec['other_config'], f)
329 mtu = mtu_setting(nw, "Network", nwrec['other_config'])
331 f.write("MTU=%s\n" % mtu)
334 if pifrec.has_key('DNS') and pifrec['DNS'] != "":
335 ServerList = pifrec['DNS'].split(",")
336 for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
337 if oc and oc.has_key('domain'):
338 f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
340 # There can be only one DNSDEV and one GATEWAYDEV in /etc/sysconfig/network.
342 # The peerdns pif will be the one with
343 # pif::other-config:peerdns=true, or the mgmt pif if none have
346 # The gateway pif will be the one with
347 # pif::other-config:defaultroute=true, or the mgmt pif if none
350 # Work out which pif on this host should be the DNSDEV and which
351 # should be the GATEWAYDEV
353 # Note: we prune out the bond master pif (if it exists). This is
354 # because when we are called to bring up an interface with a bond
355 # master, it is implicit that we should bring down that master.
357 pifs_on_host = [p for p in db().get_all_pifs() if not p in pif_get_bond_masters(pif)]
359 # loop through all the pifs on this host looking for one with
360 # other-config:peerdns = true, and one with
361 # other-config:default-route=true
363 defaultroute_pif = None
364 for __pif in pifs_on_host:
365 __pifrec = db().get_pif_record(__pif)
366 __oc = __pifrec['other_config']
367 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
368 if peerdns_pif == None:
371 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
372 (db().get_pif_record(peerdns_pif)['device'], __pifrec['device']))
373 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
374 if defaultroute_pif == None:
375 defaultroute_pif = __pif
377 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
378 (db().get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
380 # If no pif is explicitly specified then use the mgmt pif for
381 # peerdns/defaultroute.
382 if peerdns_pif == None:
383 peerdns_pif = management_pif
384 if defaultroute_pif == None:
385 defaultroute_pif = management_pif
387 is_dnsdev = peerdns_pif == pif
388 is_gatewaydev = defaultroute_pif == pif
390 if is_dnsdev or is_gatewaydev:
391 fnetwork = ConfigurationFile(root_prefix() + "/etc/sysconfig/network")
392 for line in fnetwork.readlines():
393 if is_dnsdev and line.lstrip().startswith('DNSDEV='):
394 fnetwork.write('DNSDEV=%s\n' % ipdev)
396 elif is_gatewaydev and line.lstrip().startswith('GATEWAYDEV='):
397 fnetwork.write('GATEWAYDEV=%s\n' % ipdev)
398 is_gatewaydev = False
403 fnetwork.write('DNSDEV=%s\n' % ipdev)
405 fnetwork.write('GATEWAYDEV=%s\n' % ipdev)
408 f.attach_child(fnetwork)
416 def action_up(pif, force):
417 pifrec = db().get_pif_record(pif)
419 ipdev = pif_ipdev_name(pif)
420 dp = DatapathFactory()(pif)
422 log("action_up: %s" % ipdev)
424 f = ipdev_configure_network(pif, dp)
430 pif_rename_physical_devices(pif)
432 # if we are not forcing the interface up then attempt to tear down
433 # any existing devices which might interfere with brinign this one
438 dp.bring_down_existing()
449 # Update /etc/issue (which contains the IP address of the management interface)
450 os.system(root_prefix() + "/sbin/update-issue")
454 log("failed to apply changes: %s" % e.msg)
458 def action_down(pif):
459 ipdev = pif_ipdev_name(pif)
460 dp = DatapathFactory()(pif)
462 log("action_down: %s" % ipdev)
468 def action_rewrite():
469 DatapathFactory().rewrite()
471 # This is useful for reconfiguring the mgmt interface after having lost connectivity to the pool master
472 def action_force_rewrite(bridge, config):
475 uuid,_ = subprocess.Popen(['uuidgen'], stdout = subprocess.PIPE).communicate()
479 # 1. that this assumes the interface is bridged
480 # 2. If --gateway is given it will make that the default gateway for the host
482 # extract the configuration
484 mode = config['mode']
486 interface = config['device']
488 raise Usage("Please supply --mode, --mac and --device")
492 netmask = config['netmask']
495 raise Usage("Please supply --netmask and --ip")
497 gateway = config['gateway']
501 raise Usage("--mode must be either static or dhcp")
503 if config.has_key('vlan'):
505 vlan_slave, vlan_vid = config['vlan'].split('.')
510 raise Error("Force rewrite of VLAN not implemented")
512 log("Configuring %s using %s configuration" % (bridge, mode))
514 f = ConfigurationFile(root_prefix() + dbcache_file)
517 network_uuid = getUUID()
519 f.write('<?xml version="1.0" ?>\n')
520 f.write('<xenserver-network-configuration>\n')
521 f.write('\t<pif ref="OpaqueRef:%s">\n' % pif_uuid)
522 f.write('\t\t<network>OpaqueRef:%s</network>\n' % network_uuid)
523 f.write('\t\t<management>True</management>\n')
524 f.write('\t\t<uuid>%sPif</uuid>\n' % interface)
525 f.write('\t\t<bond_slave_of>OpaqueRef:NULL</bond_slave_of>\n')
526 f.write('\t\t<bond_master_of/>\n')
527 f.write('\t\t<VLAN_slave_of/>\n')
528 f.write('\t\t<VLAN_master_of>OpaqueRef:NULL</VLAN_master_of>\n')
529 f.write('\t\t<VLAN>-1</VLAN>\n')
530 f.write('\t\t<tunnel_access_PIF_of/>\n')
531 f.write('\t\t<tunnel_transport_PIF_of/>\n')
532 f.write('\t\t<device>%s</device>\n' % interface)
533 f.write('\t\t<MAC>%s</MAC>\n' % mac)
534 f.write('\t\t<other_config/>\n')
536 f.write('\t\t<ip_configuration_mode>DHCP</ip_configuration_mode>\n')
537 f.write('\t\t<IP></IP>\n')
538 f.write('\t\t<netmask></netmask>\n')
539 f.write('\t\t<gateway></gateway>\n')
540 f.write('\t\t<DNS></DNS>\n')
541 elif mode == 'static':
542 f.write('\t\t<ip_configuration_mode>Static</ip_configuration_mode>\n')
543 f.write('\t\t<IP>%s</IP>\n' % ip)
544 f.write('\t\t<netmask>%s</netmask>\n' % netmask)
545 if gateway is not None:
546 f.write('\t\t<gateway>%s</gateway>\n' % gateway)
547 f.write('\t\t<DNS></DNS>\n')
549 raise Error("Unknown mode %s" % mode)
550 f.write('\t</pif>\n')
552 f.write('\t<network ref="OpaqueRef:%s">\n' % network_uuid)
553 f.write('\t\t<uuid>InitialManagementNetwork</uuid>\n')
554 f.write('\t\t<PIFs>\n')
555 f.write('\t\t\t<PIF>OpaqueRef:%s</PIF>\n' % pif_uuid)
556 f.write('\t\t</PIFs>\n')
557 f.write('\t\t<bridge>%s</bridge>\n' % bridge)
558 f.write('\t\t<other_config/>\n')
559 f.write('\t</network>\n')
560 f.write('</xenserver-network-configuration>\n')
568 log("failed to apply changes: %s" % e.msg)
573 global management_pif
579 force_interface = None
580 force_management = False
588 longops = [ "pif=", "pif-uuid=",
593 "mac=", "device=", "mode=", "ip=", "netmask=", "gateway=",
597 arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
598 except getopt.GetoptError, msg:
601 force_rewrite_config = {}
606 elif o == "--pif-uuid":
608 elif o == "--session":
610 elif o == "--force-interface" or o == "--force":
612 elif o == "--management":
613 force_management = True
614 elif o in ["--mac", "--device", "--mode", "--ip", "--netmask", "--gateway"]:
615 force_rewrite_config[o[2:]] = a
616 elif o == "--root-prefix":
618 elif o == "--no-syslog":
619 set_log_destination("stderr")
620 elif o == "-h" or o == "--help":
621 print __doc__ % {'command-name': os.path.basename(argv[0])}
624 if get_log_destination() == "syslog":
625 syslog.openlog(os.path.basename(argv[0]))
626 log("Called as " + str.join(" ", argv))
629 raise Usage("Required option <action> not present")
631 raise Usage("Too many arguments")
635 if not action in ["up", "down", "rewrite", "rewrite-configuration"]:
636 raise Usage("Unknown action \"%s\"" % action)
638 # backwards compatibility
639 if action == "rewrite-configuration": action = "rewrite"
641 if ( session or pif ) and pif_uuid:
642 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
643 if ( session and not pif ) or ( not session and pif ):
644 raise Usage("--session and --pif must be used together.")
645 if force_interface and ( session or pif or pif_uuid ):
646 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
647 if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
648 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
649 if (action == "rewrite") and (pif or pif_uuid ):
650 raise Usage("rewrite action does not take --pif or --pif-uuid")
654 log("Force interface %s %s" % (force_interface, action))
656 if action == "rewrite":
657 action_force_rewrite(force_interface, force_rewrite_config)
658 elif action in ["up", "down"]:
659 db_init_from_cache(dbcache_file)
660 pif = db().get_pif_by_bridge(force_interface)
661 management_pif = db().get_management_pif()
665 elif action == "down":
668 raise Error("Unknown action %s" % action)
670 db_init_from_xenapi(session)
673 pif = db().get_pif_by_uuid(pif_uuid)
675 if action == "rewrite":
679 raise Usage("No PIF given")
682 # pif is going to be the management pif
685 # pif is not going to be the management pif.
686 # Search DB cache for pif on same host with management=true
687 pifrec = db().get_pif_record(pif)
688 management_pif = db().get_management_pif()
690 log_pif_action(action, pif)
692 if not check_allowed(pif):
696 action_up(pif, False)
697 elif action == "down":
700 raise Error("Unknown action %s" % action)
703 db().save(dbcache_file)
706 print >>sys.stderr, err.msg
707 print >>sys.stderr, "For help use --help."
715 if __name__ == "__main__":
721 err = traceback.format_exception(*ex)