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
57 dbcache_file = "/var/xapi/network.dbcache"
63 def log_pif_action(action, pif):
64 pifrec = db().get_pif_record(pif)
66 rec['uuid'] = pifrec['uuid']
67 rec['ip_configuration_mode'] = pifrec['ip_configuration_mode']
68 rec['action'] = action
69 rec['pif_netdev_name'] = pif_netdev_name(pif)
70 rec['message'] = "Bring %(action)s PIF %(uuid)s" % rec
71 log("%(message)s: %(pif_netdev_name)s configured as %(ip_configuration_mode)s" % rec)
77 class Usage(Exception):
78 def __init__(self, msg):
79 Exception.__init__(self)
83 # Boot from Network filesystem or device.
86 def check_allowed(pif):
87 """Determine whether interface-reconfigure should be manipulating this PIF.
89 Used to prevent system PIFs (such as network root disk) from being interfered with.
92 pifrec = db().get_pif_record(pif)
94 f = open(root_prefix() + "/proc/ardence")
95 macline = filter(lambda x: x.startswith("HWaddr:"), f.readlines())
98 p = re.compile(".*\s%(MAC)s\s.*" % pifrec, re.IGNORECASE)
99 if p.match(macline[0]):
100 log("Skipping PVS device %(device)s (%(MAC)s)" % pifrec)
107 # Bare Network Devices -- network devices without IP configuration
110 def netdev_remap_name(pif, already_renamed=[]):
111 """Check whether 'pif' exists and has the correct MAC.
112 If not, try to find a device with the correct MAC and rename it.
113 'already_renamed' is used to avoid infinite recursion.
119 file = open(name, 'r')
120 return file.readline().rstrip('\n')
125 def get_netdev_mac(device):
127 return read1("%s/sys/class/net/%s/address" % (root_prefix(), device))
129 # Probably no such device.
132 def get_netdev_tx_queue_len(device):
134 return int(read1("%s/sys/class/net/%s/tx_queue_len" % (root_prefix(), device)))
136 # Probably no such device.
139 def get_netdev_by_mac(mac):
140 for device in os.listdir(root_prefix() + "/sys/class/net"):
141 dev_mac = get_netdev_mac(device)
142 if (dev_mac and mac.lower() == dev_mac.lower() and
143 get_netdev_tx_queue_len(device)):
147 def rename_netdev(old_name, new_name):
148 raise Error("Trying to rename %s to %s - This functionality has been removed" % (old_name, new_name))
149 # log("Changing the name of %s to %s" % (old_name, new_name))
150 # run_command(['/sbin/ifconfig', old_name, 'down'])
151 # if not run_command(['/sbin/ip', 'link', 'set', old_name, 'name', new_name]):
152 # raise Error("Could not rename %s to %s" % (old_name, new_name))
154 pifrec = db().get_pif_record(pif)
155 device = pifrec['device']
158 # Is there a network device named 'device' at all?
159 device_exists = netdev_exists(device)
161 # Yes. Does it have MAC 'mac'?
162 found_mac = get_netdev_mac(device)
163 if found_mac and mac.lower() == found_mac.lower():
164 # Yes, everything checks out the way we want. Nothing to do.
167 log("No network device %s" % device)
169 # What device has MAC 'mac'?
170 cur_device = get_netdev_by_mac(mac)
172 log("No network device has MAC %s" % mac)
175 # First rename 'device', if it exists, to get it out of the way
176 # for 'cur_device' to replace it.
178 rename_netdev(device, "dev%d" % random.getrandbits(24))
180 # Rename 'cur_device' to 'device'.
181 rename_netdev(cur_device, device)
184 # IP Network Devices -- network devices with IP configuration
188 """Bring down a network interface"""
189 if not netdev_exists(netdev):
190 log("ifdown: device %s does not exist, ignoring" % netdev)
192 if not os.path.exists("%s/etc/sysconfig/network-scripts/ifcfg-%s" % (root_prefix(), netdev)):
193 log("ifdown: device %s exists but ifcfg-%s does not" % (netdev,netdev))
194 run_command(["/sbin/ifconfig", netdev, 'down'])
196 run_command(["/sbin/ifdown", netdev])
199 """Bring up a network interface"""
200 if not os.path.exists(root_prefix() + "/etc/sysconfig/network-scripts/ifcfg-%s" % netdev):
201 raise Error("ifup: device %s exists but ifcfg-%s does not" % (netdev,netdev))
202 d = os.getenv("DHCLIENTARGS","")
203 if os.path.exists("/etc/firstboot.d/data/firstboot_in_progress"):
204 os.putenv("DHCLIENTARGS", d + " -T 240 " )
205 run_command(["/sbin/ifup", netdev])
206 os.putenv("DHCLIENTARGS", d )
212 def pif_rename_physical_devices(pif):
213 if pif_is_tunnel(pif):
217 pif = pif_get_vlan_slave(pif)
220 pifs = pif_get_bond_slaves(pif)
225 netdev_remap_name(pif)
228 # IP device configuration
231 def ipdev_configure_static_routes(interface, oc, f):
232 """Open a route-<interface> file for static routes.
234 Opens the static routes configuration file for interface and writes one
235 line for each route specified in the network's other config "static-routes" value.
237 interface ( RO): xenbr1
238 other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
240 Then route-xenbr1 should be
241 172.16.0.0/15 via 192.168.0.3 dev xenbr1
242 172.18.0.0/16 via 192.168.0.4 dev xenbr1
244 if oc.has_key('static-routes'):
245 # The key is present - extract comma seperates entries
246 lines = oc['static-routes'].split(',')
248 # The key is not present, i.e. there are no static routes
251 child = ConfigurationFile("%s/etc/sysconfig/network-scripts/route-%s" % (root_prefix(), interface))
252 child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
253 (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
257 network, masklen, gateway = l.split('/')
258 child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
260 f.attach_child(child)
263 except ValueError, e:
264 log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
266 def ipdev_open_ifcfg(pif):
267 ipdev = pif_ipdev_name(pif)
269 log("Writing network configuration for %s" % ipdev)
271 f = ConfigurationFile("%s/etc/sysconfig/network-scripts/ifcfg-%s" % (root_prefix(), ipdev))
273 f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
274 (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
275 f.write("XEMANAGED=yes\n")
276 f.write("DEVICE=%s\n" % ipdev)
277 f.write("ONBOOT=no\n")
278 f.write("NOZEROCONF=yes\n")
282 def ipdev_configure_network(pif, dp):
283 """Write the configuration file for a network.
285 Writes configuration derived from the network object into the relevant
286 ifcfg file. The configuration file is passed in, but if the network is
287 bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
289 This routine may also write ifcfg files of the networks corresponding to other PIFs
290 in order to maintain consistency.
293 pif: Opaque_ref of pif
297 pifrec = db().get_pif_record(pif)
298 nw = pifrec['network']
299 nwrec = db().get_network_record(nw)
301 ipdev = pif_ipdev_name(pif)
303 f = ipdev_open_ifcfg(pif)
305 mode = pifrec['ip_configuration_mode']
306 log("Configuring %s using %s configuration" % (ipdev, mode))
309 if pifrec.has_key('other_config'):
310 oc = pifrec['other_config']
312 dp.configure_ipdev(f)
314 if pifrec['ip_configuration_mode'] == "DHCP":
315 f.write("BOOTPROTO=dhcp\n")
316 f.write("PERSISTENT_DHCLIENT=yes\n")
317 elif pifrec['ip_configuration_mode'] == "Static":
318 f.write("BOOTPROTO=none\n")
319 f.write("NETMASK=%(netmask)s\n" % pifrec)
320 f.write("IPADDR=%(IP)s\n" % pifrec)
321 f.write("GATEWAY=%(gateway)s\n" % pifrec)
322 elif pifrec['ip_configuration_mode'] == "None":
323 f.write("BOOTPROTO=none\n")
325 raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
327 if nwrec.has_key('other_config'):
328 settings,offload = ethtool_settings(nwrec['other_config'])
330 f.write("ETHTOOL_OPTS=\"%s\"\n" % str.join(" ", settings))
332 f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload))
334 ipdev_configure_static_routes(ipdev, nwrec['other_config'], f)
336 mtu = mtu_setting(nw, "Network", nwrec['other_config'])
338 f.write("MTU=%s\n" % mtu)
341 if pifrec.has_key('DNS') and pifrec['DNS'] != "":
342 ServerList = pifrec['DNS'].split(",")
343 for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
344 if oc and oc.has_key('domain'):
345 f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
347 # There can be only one DNSDEV and one GATEWAYDEV in /etc/sysconfig/network.
349 # The peerdns pif will be the one with
350 # pif::other-config:peerdns=true, or the mgmt pif if none have
353 # The gateway pif will be the one with
354 # pif::other-config:defaultroute=true, or the mgmt pif if none
357 # Work out which pif on this host should be the DNSDEV and which
358 # should be the GATEWAYDEV
360 # Note: we prune out the bond master pif (if it exists). This is
361 # because when we are called to bring up an interface with a bond
362 # master, it is implicit that we should bring down that master.
364 pifs_on_host = [p for p in db().get_all_pifs() if not p in pif_get_bond_masters(pif)]
366 # now prune out bond slaves as they are not connected to the IP
367 # stack and so cannot be used as gateway or DNS devices.
368 pifs_on_host = [ p for p in pifs_on_host if len(pif_get_bond_masters(p)) == 0]
370 # loop through all the pifs on this host looking for one with
371 # other-config:peerdns = true, and one with
372 # other-config:default-route=true
374 defaultroute_pif = None
375 for __pif in pifs_on_host:
376 __pifrec = db().get_pif_record(__pif)
377 __oc = __pifrec['other_config']
378 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
379 if peerdns_pif == None:
382 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
383 (db().get_pif_record(peerdns_pif)['device'], __pifrec['device']))
384 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
385 if defaultroute_pif == None:
386 defaultroute_pif = __pif
388 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
389 (db().get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
391 # If no pif is explicitly specified then use the mgmt pif for
392 # peerdns/defaultroute.
393 if peerdns_pif == None:
394 peerdns_pif = management_pif
395 if defaultroute_pif == None:
396 defaultroute_pif = management_pif
398 is_dnsdev = peerdns_pif == pif
399 is_gatewaydev = defaultroute_pif == pif
401 if is_dnsdev or is_gatewaydev:
402 fnetwork = ConfigurationFile(root_prefix() + "/etc/sysconfig/network")
403 for line in fnetwork.readlines():
404 if is_dnsdev and line.lstrip().startswith('DNSDEV='):
405 fnetwork.write('DNSDEV=%s\n' % ipdev)
407 elif is_gatewaydev and line.lstrip().startswith('GATEWAYDEV='):
408 fnetwork.write('GATEWAYDEV=%s\n' % ipdev)
409 is_gatewaydev = False
414 fnetwork.write('DNSDEV=%s\n' % ipdev)
416 fnetwork.write('GATEWAYDEV=%s\n' % ipdev)
419 f.attach_child(fnetwork)
427 def action_up(pif, force):
428 pifrec = db().get_pif_record(pif)
430 ipdev = pif_ipdev_name(pif)
431 dp = DatapathFactory()(pif)
433 log("action_up: %s" % ipdev)
435 f = ipdev_configure_network(pif, dp)
441 pif_rename_physical_devices(pif)
443 # if we are not forcing the interface up then attempt to tear down
444 # any existing devices which might interfere with brinign this one
449 dp.bring_down_existing()
460 # Update /etc/issue (which contains the IP address of the management interface)
461 os.system(root_prefix() + "/sbin/update-issue")
465 log("failed to apply changes: %s" % e.msg)
469 def action_down(pif):
470 ipdev = pif_ipdev_name(pif)
471 dp = DatapathFactory()(pif)
473 log("action_down: %s" % ipdev)
479 def action_rewrite():
480 DatapathFactory().rewrite()
482 # This is useful for reconfiguring the mgmt interface after having lost connectivity to the pool master
483 def action_force_rewrite(bridge, config):
486 uuid,_ = subprocess.Popen(['uuidgen'], stdout = subprocess.PIPE).communicate()
490 # 1. that this assumes the interface is bridged
491 # 2. If --gateway is given it will make that the default gateway for the host
493 # extract the configuration
495 mode = config['mode']
497 interface = config['device']
499 raise Usage("Please supply --mode, --mac and --device")
503 netmask = config['netmask']
506 raise Usage("Please supply --netmask and --ip")
508 gateway = config['gateway']
512 raise Usage("--mode must be either static or dhcp")
514 if config.has_key('vlan'):
516 vlan_slave, vlan_vid = config['vlan'].split('.')
521 raise Error("Force rewrite of VLAN not implemented")
523 log("Configuring %s using %s configuration" % (bridge, mode))
525 f = ConfigurationFile(root_prefix() + dbcache_file)
528 network_uuid = getUUID()
530 f.write('<?xml version="1.0" ?>\n')
531 f.write('<xenserver-network-configuration>\n')
532 f.write('\t<pif ref="OpaqueRef:%s">\n' % pif_uuid)
533 f.write('\t\t<network>OpaqueRef:%s</network>\n' % network_uuid)
534 f.write('\t\t<management>True</management>\n')
535 f.write('\t\t<uuid>%sPif</uuid>\n' % interface)
536 f.write('\t\t<bond_slave_of>OpaqueRef:NULL</bond_slave_of>\n')
537 f.write('\t\t<bond_master_of/>\n')
538 f.write('\t\t<VLAN_slave_of/>\n')
539 f.write('\t\t<VLAN_master_of>OpaqueRef:NULL</VLAN_master_of>\n')
540 f.write('\t\t<VLAN>-1</VLAN>\n')
541 f.write('\t\t<tunnel_access_PIF_of/>\n')
542 f.write('\t\t<tunnel_transport_PIF_of/>\n')
543 f.write('\t\t<device>%s</device>\n' % interface)
544 f.write('\t\t<MAC>%s</MAC>\n' % mac)
545 f.write('\t\t<other_config/>\n')
547 f.write('\t\t<ip_configuration_mode>DHCP</ip_configuration_mode>\n')
548 f.write('\t\t<IP></IP>\n')
549 f.write('\t\t<netmask></netmask>\n')
550 f.write('\t\t<gateway></gateway>\n')
551 f.write('\t\t<DNS></DNS>\n')
552 elif mode == 'static':
553 f.write('\t\t<ip_configuration_mode>Static</ip_configuration_mode>\n')
554 f.write('\t\t<IP>%s</IP>\n' % ip)
555 f.write('\t\t<netmask>%s</netmask>\n' % netmask)
556 if gateway is not None:
557 f.write('\t\t<gateway>%s</gateway>\n' % gateway)
558 f.write('\t\t<DNS></DNS>\n')
560 raise Error("Unknown mode %s" % mode)
561 f.write('\t</pif>\n')
563 f.write('\t<network ref="OpaqueRef:%s">\n' % network_uuid)
564 f.write('\t\t<uuid>InitialManagementNetwork</uuid>\n')
565 f.write('\t\t<PIFs>\n')
566 f.write('\t\t\t<PIF>OpaqueRef:%s</PIF>\n' % pif_uuid)
567 f.write('\t\t</PIFs>\n')
568 f.write('\t\t<bridge>%s</bridge>\n' % bridge)
569 f.write('\t\t<other_config/>\n')
570 f.write('\t</network>\n')
571 f.write('</xenserver-network-configuration>\n')
579 log("failed to apply changes: %s" % e.msg)
584 global management_pif
590 force_interface = None
591 force_management = False
599 longops = [ "pif=", "pif-uuid=",
604 "mac=", "device=", "mode=", "ip=", "netmask=", "gateway=",
608 arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
609 except getopt.GetoptError, msg:
612 force_rewrite_config = {}
617 elif o == "--pif-uuid":
619 elif o == "--session":
621 elif o == "--force-interface" or o == "--force":
623 elif o == "--management":
624 force_management = True
625 elif o in ["--mac", "--device", "--mode", "--ip", "--netmask", "--gateway"]:
626 force_rewrite_config[o[2:]] = a
627 elif o == "--root-prefix":
629 elif o == "--no-syslog":
630 set_log_destination("stderr")
631 elif o == "-h" or o == "--help":
632 print __doc__ % {'command-name': os.path.basename(argv[0])}
635 if get_log_destination() == "syslog":
636 syslog.openlog(os.path.basename(argv[0]))
637 log("Called as " + str.join(" ", argv))
640 raise Usage("Required option <action> not present")
642 raise Usage("Too many arguments")
646 if not action in ["up", "down", "rewrite", "rewrite-configuration"]:
647 raise Usage("Unknown action \"%s\"" % action)
649 # backwards compatibility
650 if action == "rewrite-configuration": action = "rewrite"
652 if ( session or pif ) and pif_uuid:
653 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
654 if ( session and not pif ) or ( not session and pif ):
655 raise Usage("--session and --pif must be used together.")
656 if force_interface and ( session or pif or pif_uuid ):
657 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
658 if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
659 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
660 if (action == "rewrite") and (pif or pif_uuid ):
661 raise Usage("rewrite action does not take --pif or --pif-uuid")
665 log("Force interface %s %s" % (force_interface, action))
667 if action == "rewrite":
668 action_force_rewrite(force_interface, force_rewrite_config)
669 elif action in ["up", "down"]:
670 db_init_from_cache(dbcache_file)
671 pif = db().get_pif_by_bridge(force_interface)
672 management_pif = db().get_management_pif()
676 elif action == "down":
679 raise Error("Unknown action %s" % action)
681 db_init_from_xenapi(session)
684 pif = db().get_pif_by_uuid(pif_uuid)
686 if action == "rewrite":
690 raise Usage("No PIF given")
693 # pif is going to be the management pif
696 # pif is not going to be the management pif.
697 # Search DB cache for pif on same host with management=true
698 pifrec = db().get_pif_record(pif)
699 management_pif = db().get_management_pif()
701 log_pif_action(action, pif)
703 if not check_allowed(pif):
707 action_up(pif, False)
708 elif action == "down":
711 raise Error("Unknown action %s" % action)
714 db().save(dbcache_file)
717 print >>sys.stderr, err.msg
718 print >>sys.stderr, "For help use --help."
726 if __name__ == "__main__":
732 err = traceback.format_exception(*ex)