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).
40 # 1. Every pif belongs to exactly one network
41 # 2. Every network has zero or one pifs
42 # 3. A network may have an associated bridge, allowing vifs to be attached
43 # 4. A network may be bridgeless (there's no point having a bridge over a storage pif)
45 from InterfaceReconfigure import *
47 import os, sys, getopt
55 dbcache_file = "/var/xapi/network.dbcache"
61 def log_pif_action(action, pif):
62 pifrec = db().get_pif_record(pif)
64 rec['uuid'] = pifrec['uuid']
65 rec['ip_configuration_mode'] = pifrec['ip_configuration_mode']
66 rec['action'] = action
67 rec['pif_netdev_name'] = pif_netdev_name(pif)
68 rec['message'] = "Bring %(action)s PIF %(uuid)s" % rec
69 log("%(message)s: %(pif_netdev_name)s configured as %(ip_configuration_mode)s" % rec)
75 class Usage(Exception):
76 def __init__(self, msg):
77 Exception.__init__(self)
81 # Boot from Network filesystem or device.
84 def check_allowed(pif):
85 """Determine whether interface-reconfigure should be manipulating this PIF.
87 Used to prevent system PIFs (such as network root disk) from being interfered with.
90 pifrec = db().get_pif_record(pif)
92 f = open(root_prefix() + "/proc/ardence")
93 macline = filter(lambda x: x.startswith("HWaddr:"), f.readlines())
96 p = re.compile(".*\s%(MAC)s\s.*" % pifrec, re.IGNORECASE)
97 if p.match(macline[0]):
98 log("Skipping PVS device %(device)s (%(MAC)s)" % pifrec)
105 # Bare Network Devices -- network devices without IP configuration
108 def netdev_remap_name(pif, already_renamed=[]):
109 """Check whether 'pif' exists and has the correct MAC.
110 If not, try to find a device with the correct MAC and rename it.
111 'already_renamed' is used to avoid infinite recursion.
117 file = open(name, 'r')
118 return file.readline().rstrip('\n')
123 def get_netdev_mac(device):
125 return read1("%s/sys/class/net/%s/address" % (root_prefix(), device))
127 # Probably no such device.
130 def get_netdev_tx_queue_len(device):
132 return int(read1("%s/sys/class/net/%s/tx_queue_len" % (root_prefix(), device)))
134 # Probably no such device.
137 def get_netdev_by_mac(mac):
138 for device in os.listdir(root_prefix() + "/sys/class/net"):
139 dev_mac = get_netdev_mac(device)
140 if (dev_mac and mac.lower() == dev_mac.lower() and
141 get_netdev_tx_queue_len(device)):
145 def rename_netdev(old_name, new_name):
146 log("Changing the name of %s to %s" % (old_name, new_name))
147 run_command(['/sbin/ifconfig', old_name, 'down'])
148 if not run_command(['/sbin/ip', 'link', 'set', old_name, 'name', new_name]):
149 raise Error("Could not rename %s to %s" % (old_name, new_name))
151 pifrec = db().get_pif_record(pif)
152 device = pifrec['device']
155 # Is there a network device named 'device' at all?
156 device_exists = netdev_exists(device)
158 # Yes. Does it have MAC 'mac'?
159 found_mac = get_netdev_mac(device)
160 if found_mac and mac.lower() == found_mac.lower():
161 # Yes, everything checks out the way we want. Nothing to do.
164 log("No network device %s" % device)
166 # What device has MAC 'mac'?
167 cur_device = get_netdev_by_mac(mac)
169 log("No network device has MAC %s" % mac)
172 # First rename 'device', if it exists, to get it out of the way
173 # for 'cur_device' to replace it.
175 rename_netdev(device, "dev%d" % random.getrandbits(24))
177 # Rename 'cur_device' to 'device'.
178 rename_netdev(cur_device, device)
181 # IP Network Devices -- network devices with IP configuration
185 """Bring down a network interface"""
186 if not netdev_exists(netdev):
187 log("ifdown: device %s does not exist, ignoring" % netdev)
189 if not os.path.exists("%s/etc/sysconfig/network-scripts/ifcfg-%s" % (root_prefix(), netdev)):
190 log("ifdown: device %s exists but ifcfg-%s does not" % (netdev,netdev))
191 run_command(["/sbin/ifconfig", netdev, 'down'])
193 run_command(["/sbin/ifdown", netdev])
196 """Bring up a network interface"""
197 if not os.path.exists(root_prefix() + "/etc/sysconfig/network-scripts/ifcfg-%s" % netdev):
198 raise Error("ifup: device %s exists but ifcfg-%s does not" % (netdev,netdev))
199 run_command(["/sbin/ifup", netdev])
205 def pif_rename_physical_devices(pif):
208 pif = pif_get_vlan_slave(pif)
211 pifs = pif_get_bond_slaves(pif)
216 netdev_remap_name(pif)
219 # IP device configuration
222 def ipdev_configure_static_routes(interface, oc, f):
223 """Open a route-<interface> file for static routes.
225 Opens the static routes configuration file for interface and writes one
226 line for each route specified in the network's other config "static-routes" value.
228 interface ( RO): xenbr1
229 other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
231 Then route-xenbr1 should be
232 172.16.0.0/15 via 192.168.0.3 dev xenbr1
233 172.18.0.0/16 via 192.168.0.4 dev xenbr1
235 if oc.has_key('static-routes'):
236 # The key is present - extract comma seperates entries
237 lines = oc['static-routes'].split(',')
239 # The key is not present, i.e. there are no static routes
242 child = ConfigurationFile("%s/etc/sysconfig/network-scripts/route-%s" % (root_prefix(), interface))
243 child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
244 (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
248 network, masklen, gateway = l.split('/')
249 child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
251 f.attach_child(child)
254 except ValueError, e:
255 log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
257 def ipdev_open_ifcfg(pif):
258 ipdev = pif_ipdev_name(pif)
260 log("Writing network configuration for %s" % ipdev)
262 f = ConfigurationFile("%s/etc/sysconfig/network-scripts/ifcfg-%s" % (root_prefix(), ipdev))
264 f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
265 (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
266 f.write("XEMANAGED=yes\n")
267 f.write("DEVICE=%s\n" % ipdev)
268 f.write("ONBOOT=no\n")
272 def ipdev_configure_network(pif, dp):
273 """Write the configuration file for a network.
275 Writes configuration derived from the network object into the relevant
276 ifcfg file. The configuration file is passed in, but if the network is
277 bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
279 This routine may also write ifcfg files of the networks corresponding to other PIFs
280 in order to maintain consistency.
283 pif: Opaque_ref of pif
287 pifrec = db().get_pif_record(pif)
288 nwrec = db().get_network_record(pifrec['network'])
290 ipdev = pif_ipdev_name(pif)
292 f = ipdev_open_ifcfg(pif)
294 mode = pifrec['ip_configuration_mode']
295 log("Configuring %s using %s configuration" % (ipdev, mode))
298 if pifrec.has_key('other_config'):
299 oc = pifrec['other_config']
301 dp.configure_ipdev(f)
303 if pifrec['ip_configuration_mode'] == "DHCP":
304 f.write("BOOTPROTO=dhcp\n")
305 f.write("PERSISTENT_DHCLIENT=yes\n")
306 elif pifrec['ip_configuration_mode'] == "Static":
307 f.write("BOOTPROTO=none\n")
308 f.write("NETMASK=%(netmask)s\n" % pifrec)
309 f.write("IPADDR=%(IP)s\n" % pifrec)
310 f.write("GATEWAY=%(gateway)s\n" % pifrec)
311 elif pifrec['ip_configuration_mode'] == "None":
312 f.write("BOOTPROTO=none\n")
314 raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
316 if nwrec.has_key('other_config'):
317 settings,offload = ethtool_settings(nwrec['other_config'])
319 f.write("ETHTOOL_OPTS=\"%s\"\n" % str.join(" ", settings))
321 f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload))
323 mtu = mtu_setting(nwrec['other_config'])
325 f.write("MTU=%s\n" % mtu)
327 ipdev_configure_static_routes(ipdev, nwrec['other_config'], f)
329 if pifrec.has_key('DNS') and pifrec['DNS'] != "":
330 ServerList = pifrec['DNS'].split(",")
331 for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
332 if oc and oc.has_key('domain'):
333 f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
335 # There can be only one DNSDEV and one GATEWAYDEV in /etc/sysconfig/network.
337 # The peerdns pif will be the one with
338 # pif::other-config:peerdns=true, or the mgmt pif if none have
341 # The gateway pif will be the one with
342 # pif::other-config:defaultroute=true, or the mgmt pif if none
345 # Work out which pif on this host should be the DNSDEV and which
346 # should be the GATEWAYDEV
348 # Note: we prune out the bond master pif (if it exists). This is
349 # because when we are called to bring up an interface with a bond
350 # master, it is implicit that we should bring down that master.
352 pifs_on_host = [p for p in db().get_all_pifs() if not p in pif_get_bond_masters(pif)]
354 # loop through all the pifs on this host looking for one with
355 # other-config:peerdns = true, and one with
356 # other-config:default-route=true
358 defaultroute_pif = None
359 for __pif in pifs_on_host:
360 __pifrec = db().get_pif_record(__pif)
361 __oc = __pifrec['other_config']
362 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
363 if peerdns_pif == None:
366 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
367 (db().get_pif_record(peerdns_pif)['device'], __pifrec['device']))
368 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
369 if defaultroute_pif == None:
370 defaultroute_pif = __pif
372 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
373 (db().get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
375 # If no pif is explicitly specified then use the mgmt pif for
376 # peerdns/defaultroute.
377 if peerdns_pif == None:
378 peerdns_pif = management_pif
379 if defaultroute_pif == None:
380 defaultroute_pif = management_pif
382 is_dnsdev = peerdns_pif == pif
383 is_gatewaydev = defaultroute_pif == pif
385 if is_dnsdev or is_gatewaydev:
386 fnetwork = ConfigurationFile(root_prefix() + "/etc/sysconfig/network")
387 for line in fnetwork.readlines():
388 if is_dnsdev and line.lstrip().startswith('DNSDEV='):
389 fnetwork.write('DNSDEV=%s\n' % ipdev)
391 elif is_gatewaydev and line.lstrip().startswith('GATEWAYDEV='):
392 fnetwork.write('GATEWAYDEV=%s\n' % ipdev)
393 is_gatewaydev = False
398 fnetwork.write('DNSDEV=%s\n' % ipdev)
400 fnetwork.write('GATEWAYDEV=%s\n' % ipdev)
403 f.attach_child(fnetwork)
411 def action_up(pif, force):
412 pifrec = db().get_pif_record(pif)
414 ipdev = pif_ipdev_name(pif)
415 dp = DatapathFactory(pif)
417 log("action_up: %s" % ipdev)
419 f = ipdev_configure_network(pif, dp)
425 pif_rename_physical_devices(pif)
427 # if we are not forcing the interface up then attempt to tear down
428 # any existing devices which might interfere with brinign this one
433 dp.bring_down_existing()
444 # Update /etc/issue (which contains the IP address of the management interface)
445 os.system(root_prefix() + "/sbin/update-issue")
449 log("failed to apply changes: %s" % e.msg)
453 def action_down(pif):
454 ipdev = pif_ipdev_name(pif)
455 dp = DatapathFactory(pif)
457 log("action_down: %s" % ipdev)
463 # This is useful for reconfiguring the mgmt interface after having lost connectivity to the pool master
464 def action_force_rewrite(bridge, config):
467 uuid,_ = subprocess.Popen(['uuidgen'], stdout = subprocess.PIPE).communicate()
471 # 1. that this assumes the interface is bridged
472 # 2. If --gateway is given it will make that the default gateway for the host
474 # extract the configuration
476 mode = config['mode']
478 interface = config['device']
480 raise Usage("Please supply --mode, --mac and --device")
484 netmask = config['netmask']
487 raise Usage("Please supply --netmask and --ip")
489 gateway = config['gateway']
493 raise Usage("--mode must be either static or dhcp")
495 if config.has_key('vlan'):
497 vlan_slave, vlan_vid = config['vlan'].split('.')
502 raise Error("Force rewrite of VLAN not implemented")
504 log("Configuring %s using %s configuration" % (bridge, mode))
506 f = ConfigurationFile(root_prefix() + dbcache_file)
509 network_uuid = getUUID()
511 f.write('<?xml version="1.0" ?>\n')
512 f.write('<xenserver-network-configuration>\n')
513 f.write('\t<pif ref="OpaqueRef:%s">\n' % pif_uuid)
514 f.write('\t\t<network>OpaqueRef:%s</network>\n' % network_uuid)
515 f.write('\t\t<management>True</management>\n')
516 f.write('\t\t<uuid>%sPif</uuid>\n' % interface)
517 f.write('\t\t<bond_slave_of>OpaqueRef:NULL</bond_slave_of>\n')
518 f.write('\t\t<bond_master_of/>\n')
519 f.write('\t\t<VLAN_slave_of/>\n')
520 f.write('\t\t<VLAN_master_of>OpaqueRef:NULL</VLAN_master_of>\n')
521 f.write('\t\t<VLAN>-1</VLAN>\n')
522 f.write('\t\t<device>%s</device>\n' % interface)
523 f.write('\t\t<MAC>%s</MAC>\n' % mac)
524 f.write('\t\t<other_config/>\n')
526 f.write('\t\t<ip_configuration_mode>DHCP</ip_configuration_mode>\n')
527 f.write('\t\t<IP></IP>\n')
528 f.write('\t\t<netmask></netmask>\n')
529 f.write('\t\t<gateway></gateway>\n')
530 f.write('\t\t<DNS></DNS>\n')
531 elif mode == 'static':
532 f.write('\t\t<ip_configuration_mode>Static</ip_configuration_mode>\n')
533 f.write('\t\t<IP>%s</IP>\n' % ip)
534 f.write('\t\t<netmask>%s</netmask>\n' % netmask)
535 if gateway is not None:
536 f.write('\t\t<gateway>%s</gateway>\n' % gateway)
537 f.write('\t\t<DNS></DNS>\n')
539 raise Error("Unknown mode %s" % mode)
540 f.write('\t</pif>\n')
542 f.write('\t<network ref="OpaqueRef:%s">\n' % network_uuid)
543 f.write('\t\t<uuid>InitialManagementNetwork</uuid>\n')
544 f.write('\t\t<PIFs>\n')
545 f.write('\t\t\t<PIF>OpaqueRef:%s</PIF>\n' % pif_uuid)
546 f.write('\t\t</PIFs>\n')
547 f.write('\t\t<bridge>%s</bridge>\n' % bridge)
548 f.write('\t\t<other_config/>\n')
549 f.write('\t</network>\n')
550 f.write('</xenserver-network-configuration>\n')
558 log("failed to apply changes: %s" % e.msg)
563 global management_pif
569 force_interface = None
570 force_management = False
578 longops = [ "pif=", "pif-uuid=",
583 "mac=", "device=", "mode=", "ip=", "netmask=", "gateway=",
586 arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
587 except getopt.GetoptError, msg:
590 force_rewrite_config = {}
595 elif o == "--pif-uuid":
597 elif o == "--session":
599 elif o == "--force-interface" or o == "--force":
601 elif o == "--management":
602 force_management = True
603 elif o in ["--mac", "--device", "--mode", "--ip", "--netmask", "--gateway"]:
604 force_rewrite_config[o[2:]] = a
605 elif o == "--root-prefix":
607 elif o == "-h" or o == "--help":
608 print __doc__ % {'command-name': os.path.basename(argv[0])}
611 syslog.openlog(os.path.basename(argv[0]))
612 log("Called as " + str.join(" ", argv))
615 raise Usage("Required option <action> not present")
617 raise Usage("Too many arguments")
621 if not action in ["up", "down", "rewrite", "rewrite-configuration"]:
622 raise Usage("Unknown action \"%s\"" % action)
624 # backwards compatibility
625 if action == "rewrite-configuration": action = "rewrite"
627 if ( session or pif ) and pif_uuid:
628 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
629 if ( session and not pif ) or ( not session and pif ):
630 raise Usage("--session and --pif must be used together.")
631 if force_interface and ( session or pif or pif_uuid ):
632 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
633 if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
634 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
635 if (action == "rewrite") and (pif or pif_uuid ):
636 raise Usage("rewrite action does not take --pif or --pif-uuid")
640 log("Force interface %s %s" % (force_interface, action))
642 if action == "rewrite":
643 action_force_rewrite(force_interface, force_rewrite_config)
644 elif action in ["up", "down"]:
645 db_init_from_cache(dbcache_file)
646 pif = db().get_pif_by_bridge(force_interface)
647 management_pif = db().get_management_pif()
651 elif action == "down":
654 raise Error("Unknown action %s" % action)
656 db_init_from_xenapi(session)
659 pif = db().get_pif_by_uuid(pif_uuid)
661 if action == "rewrite":
665 raise Usage("No PIF given")
668 # pif is going to be the management pif
671 # pif is not going to be the management pif.
672 # Search DB cache for pif on same host with management=true
673 pifrec = db().get_pif_record(pif)
674 management_pif = db().get_management_pif()
676 log_pif_action(action, pif)
678 if not check_allowed(pif):
682 action_up(pif, False)
683 elif action == "down":
686 raise Error("Unknown action %s" % action)
689 db().save(dbcache_file)
692 print >>sys.stderr, err.msg
693 print >>sys.stderr, "For help use --help."
701 if __name__ == "__main__":
707 err = traceback.format_exception(*ex)