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):
209 pif = pif_get_vlan_slave(pif)
212 pifs = pif_get_bond_slaves(pif)
217 netdev_remap_name(pif)
220 # IP device configuration
223 def ipdev_configure_static_routes(interface, oc, f):
224 """Open a route-<interface> file for static routes.
226 Opens the static routes configuration file for interface and writes one
227 line for each route specified in the network's other config "static-routes" value.
229 interface ( RO): xenbr1
230 other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
232 Then route-xenbr1 should be
233 172.16.0.0/15 via 192.168.0.3 dev xenbr1
234 172.18.0.0/16 via 192.168.0.4 dev xenbr1
236 if oc.has_key('static-routes'):
237 # The key is present - extract comma seperates entries
238 lines = oc['static-routes'].split(',')
240 # The key is not present, i.e. there are no static routes
243 child = ConfigurationFile("%s/etc/sysconfig/network-scripts/route-%s" % (root_prefix(), interface))
244 child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
245 (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
249 network, masklen, gateway = l.split('/')
250 child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
252 f.attach_child(child)
255 except ValueError, e:
256 log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
258 def ipdev_open_ifcfg(pif):
259 ipdev = pif_ipdev_name(pif)
261 log("Writing network configuration for %s" % ipdev)
263 f = ConfigurationFile("%s/etc/sysconfig/network-scripts/ifcfg-%s" % (root_prefix(), ipdev))
265 f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
266 (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
267 f.write("XEMANAGED=yes\n")
268 f.write("DEVICE=%s\n" % ipdev)
269 f.write("ONBOOT=no\n")
273 def ipdev_configure_network(pif, dp):
274 """Write the configuration file for a network.
276 Writes configuration derived from the network object into the relevant
277 ifcfg file. The configuration file is passed in, but if the network is
278 bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
280 This routine may also write ifcfg files of the networks corresponding to other PIFs
281 in order to maintain consistency.
284 pif: Opaque_ref of pif
288 pifrec = db().get_pif_record(pif)
289 nw = pifrec['network']
290 nwrec = db().get_network_record(nw)
292 ipdev = pif_ipdev_name(pif)
294 f = ipdev_open_ifcfg(pif)
296 mode = pifrec['ip_configuration_mode']
297 log("Configuring %s using %s configuration" % (ipdev, mode))
300 if pifrec.has_key('other_config'):
301 oc = pifrec['other_config']
303 dp.configure_ipdev(f)
305 if pifrec['ip_configuration_mode'] == "DHCP":
306 f.write("BOOTPROTO=dhcp\n")
307 f.write("PERSISTENT_DHCLIENT=yes\n")
308 elif pifrec['ip_configuration_mode'] == "Static":
309 f.write("BOOTPROTO=none\n")
310 f.write("NETMASK=%(netmask)s\n" % pifrec)
311 f.write("IPADDR=%(IP)s\n" % pifrec)
312 f.write("GATEWAY=%(gateway)s\n" % pifrec)
313 elif pifrec['ip_configuration_mode'] == "None":
314 f.write("BOOTPROTO=none\n")
316 raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
318 if nwrec.has_key('other_config'):
319 settings,offload = ethtool_settings(nwrec['other_config'])
321 f.write("ETHTOOL_OPTS=\"%s\"\n" % str.join(" ", settings))
323 f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload))
325 ipdev_configure_static_routes(ipdev, nwrec['other_config'], f)
327 mtu = mtu_setting(nw, "Network", nwrec['other_config'])
329 f.write("MTU=%s\n" % mtu)
332 if pifrec.has_key('DNS') and pifrec['DNS'] != "":
333 ServerList = pifrec['DNS'].split(",")
334 for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
335 if oc and oc.has_key('domain'):
336 f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
338 # There can be only one DNSDEV and one GATEWAYDEV in /etc/sysconfig/network.
340 # The peerdns pif will be the one with
341 # pif::other-config:peerdns=true, or the mgmt pif if none have
344 # The gateway pif will be the one with
345 # pif::other-config:defaultroute=true, or the mgmt pif if none
348 # Work out which pif on this host should be the DNSDEV and which
349 # should be the GATEWAYDEV
351 # Note: we prune out the bond master pif (if it exists). This is
352 # because when we are called to bring up an interface with a bond
353 # master, it is implicit that we should bring down that master.
355 pifs_on_host = [p for p in db().get_all_pifs() if not p in pif_get_bond_masters(pif)]
357 # loop through all the pifs on this host looking for one with
358 # other-config:peerdns = true, and one with
359 # other-config:default-route=true
361 defaultroute_pif = None
362 for __pif in pifs_on_host:
363 __pifrec = db().get_pif_record(__pif)
364 __oc = __pifrec['other_config']
365 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
366 if peerdns_pif == None:
369 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
370 (db().get_pif_record(peerdns_pif)['device'], __pifrec['device']))
371 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
372 if defaultroute_pif == None:
373 defaultroute_pif = __pif
375 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
376 (db().get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
378 # If no pif is explicitly specified then use the mgmt pif for
379 # peerdns/defaultroute.
380 if peerdns_pif == None:
381 peerdns_pif = management_pif
382 if defaultroute_pif == None:
383 defaultroute_pif = management_pif
385 is_dnsdev = peerdns_pif == pif
386 is_gatewaydev = defaultroute_pif == pif
388 if is_dnsdev or is_gatewaydev:
389 fnetwork = ConfigurationFile(root_prefix() + "/etc/sysconfig/network")
390 for line in fnetwork.readlines():
391 if is_dnsdev and line.lstrip().startswith('DNSDEV='):
392 fnetwork.write('DNSDEV=%s\n' % ipdev)
394 elif is_gatewaydev and line.lstrip().startswith('GATEWAYDEV='):
395 fnetwork.write('GATEWAYDEV=%s\n' % ipdev)
396 is_gatewaydev = False
401 fnetwork.write('DNSDEV=%s\n' % ipdev)
403 fnetwork.write('GATEWAYDEV=%s\n' % ipdev)
406 f.attach_child(fnetwork)
414 def action_up(pif, force):
415 pifrec = db().get_pif_record(pif)
417 ipdev = pif_ipdev_name(pif)
418 dp = DatapathFactory()(pif)
420 log("action_up: %s" % ipdev)
422 f = ipdev_configure_network(pif, dp)
428 pif_rename_physical_devices(pif)
430 # if we are not forcing the interface up then attempt to tear down
431 # any existing devices which might interfere with brinign this one
436 dp.bring_down_existing()
447 # Update /etc/issue (which contains the IP address of the management interface)
448 os.system(root_prefix() + "/sbin/update-issue")
452 log("failed to apply changes: %s" % e.msg)
456 def action_down(pif):
457 ipdev = pif_ipdev_name(pif)
458 dp = DatapathFactory()(pif)
460 log("action_down: %s" % ipdev)
466 def action_rewrite():
467 DatapathFactory().rewrite()
469 # This is useful for reconfiguring the mgmt interface after having lost connectivity to the pool master
470 def action_force_rewrite(bridge, config):
473 uuid,_ = subprocess.Popen(['uuidgen'], stdout = subprocess.PIPE).communicate()
477 # 1. that this assumes the interface is bridged
478 # 2. If --gateway is given it will make that the default gateway for the host
480 # extract the configuration
482 mode = config['mode']
484 interface = config['device']
486 raise Usage("Please supply --mode, --mac and --device")
490 netmask = config['netmask']
493 raise Usage("Please supply --netmask and --ip")
495 gateway = config['gateway']
499 raise Usage("--mode must be either static or dhcp")
501 if config.has_key('vlan'):
503 vlan_slave, vlan_vid = config['vlan'].split('.')
508 raise Error("Force rewrite of VLAN not implemented")
510 log("Configuring %s using %s configuration" % (bridge, mode))
512 f = ConfigurationFile(root_prefix() + dbcache_file)
515 network_uuid = getUUID()
517 f.write('<?xml version="1.0" ?>\n')
518 f.write('<xenserver-network-configuration>\n')
519 f.write('\t<pif ref="OpaqueRef:%s">\n' % pif_uuid)
520 f.write('\t\t<network>OpaqueRef:%s</network>\n' % network_uuid)
521 f.write('\t\t<management>True</management>\n')
522 f.write('\t\t<uuid>%sPif</uuid>\n' % interface)
523 f.write('\t\t<bond_slave_of>OpaqueRef:NULL</bond_slave_of>\n')
524 f.write('\t\t<bond_master_of/>\n')
525 f.write('\t\t<VLAN_slave_of/>\n')
526 f.write('\t\t<VLAN_master_of>OpaqueRef:NULL</VLAN_master_of>\n')
527 f.write('\t\t<VLAN>-1</VLAN>\n')
528 f.write('\t\t<device>%s</device>\n' % interface)
529 f.write('\t\t<MAC>%s</MAC>\n' % mac)
530 f.write('\t\t<other_config/>\n')
532 f.write('\t\t<ip_configuration_mode>DHCP</ip_configuration_mode>\n')
533 f.write('\t\t<IP></IP>\n')
534 f.write('\t\t<netmask></netmask>\n')
535 f.write('\t\t<gateway></gateway>\n')
536 f.write('\t\t<DNS></DNS>\n')
537 elif mode == 'static':
538 f.write('\t\t<ip_configuration_mode>Static</ip_configuration_mode>\n')
539 f.write('\t\t<IP>%s</IP>\n' % ip)
540 f.write('\t\t<netmask>%s</netmask>\n' % netmask)
541 if gateway is not None:
542 f.write('\t\t<gateway>%s</gateway>\n' % gateway)
543 f.write('\t\t<DNS></DNS>\n')
545 raise Error("Unknown mode %s" % mode)
546 f.write('\t</pif>\n')
548 f.write('\t<network ref="OpaqueRef:%s">\n' % network_uuid)
549 f.write('\t\t<uuid>InitialManagementNetwork</uuid>\n')
550 f.write('\t\t<PIFs>\n')
551 f.write('\t\t\t<PIF>OpaqueRef:%s</PIF>\n' % pif_uuid)
552 f.write('\t\t</PIFs>\n')
553 f.write('\t\t<bridge>%s</bridge>\n' % bridge)
554 f.write('\t\t<other_config/>\n')
555 f.write('\t</network>\n')
556 f.write('</xenserver-network-configuration>\n')
564 log("failed to apply changes: %s" % e.msg)
569 global management_pif
575 force_interface = None
576 force_management = False
584 longops = [ "pif=", "pif-uuid=",
589 "mac=", "device=", "mode=", "ip=", "netmask=", "gateway=",
593 arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
594 except getopt.GetoptError, msg:
597 force_rewrite_config = {}
602 elif o == "--pif-uuid":
604 elif o == "--session":
606 elif o == "--force-interface" or o == "--force":
608 elif o == "--management":
609 force_management = True
610 elif o in ["--mac", "--device", "--mode", "--ip", "--netmask", "--gateway"]:
611 force_rewrite_config[o[2:]] = a
612 elif o == "--root-prefix":
614 elif o == "--no-syslog":
615 set_log_destination("stderr")
616 elif o == "-h" or o == "--help":
617 print __doc__ % {'command-name': os.path.basename(argv[0])}
620 if get_log_destination() == "syslog":
621 syslog.openlog(os.path.basename(argv[0]))
622 log("Called as " + str.join(" ", argv))
625 raise Usage("Required option <action> not present")
627 raise Usage("Too many arguments")
631 if not action in ["up", "down", "rewrite", "rewrite-configuration"]:
632 raise Usage("Unknown action \"%s\"" % action)
634 # backwards compatibility
635 if action == "rewrite-configuration": action = "rewrite"
637 if ( session or pif ) and pif_uuid:
638 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
639 if ( session and not pif ) or ( not session and pif ):
640 raise Usage("--session and --pif must be used together.")
641 if force_interface and ( session or pif or pif_uuid ):
642 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
643 if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
644 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
645 if (action == "rewrite") and (pif or pif_uuid ):
646 raise Usage("rewrite action does not take --pif or --pif-uuid")
650 log("Force interface %s %s" % (force_interface, action))
652 if action == "rewrite":
653 action_force_rewrite(force_interface, force_rewrite_config)
654 elif action in ["up", "down"]:
655 db_init_from_cache(dbcache_file)
656 pif = db().get_pif_by_bridge(force_interface)
657 management_pif = db().get_management_pif()
661 elif action == "down":
664 raise Error("Unknown action %s" % action)
666 db_init_from_xenapi(session)
669 pif = db().get_pif_by_uuid(pif_uuid)
671 if action == "rewrite":
675 raise Usage("No PIF given")
678 # pif is going to be the management pif
681 # pif is not going to be the management pif.
682 # Search DB cache for pif on same host with management=true
683 pifrec = db().get_pif_record(pif)
684 management_pif = db().get_management_pif()
686 log_pif_action(action, pif)
688 if not check_allowed(pif):
692 action_up(pif, False)
693 elif action == "down":
696 raise Error("Unknown action %s" % action)
699 db().save(dbcache_file)
702 print >>sys.stderr, err.msg
703 print >>sys.stderr, "For help use --help."
711 if __name__ == "__main__":
717 err = traceback.format_exception(*ex)