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 nwrec = db().get_network_record(pifrec['network'])
291 ipdev = pif_ipdev_name(pif)
293 f = ipdev_open_ifcfg(pif)
295 mode = pifrec['ip_configuration_mode']
296 log("Configuring %s using %s configuration" % (ipdev, mode))
299 if pifrec.has_key('other_config'):
300 oc = pifrec['other_config']
302 dp.configure_ipdev(f)
304 if pifrec['ip_configuration_mode'] == "DHCP":
305 f.write("BOOTPROTO=dhcp\n")
306 f.write("PERSISTENT_DHCLIENT=yes\n")
307 elif pifrec['ip_configuration_mode'] == "Static":
308 f.write("BOOTPROTO=none\n")
309 f.write("NETMASK=%(netmask)s\n" % pifrec)
310 f.write("IPADDR=%(IP)s\n" % pifrec)
311 f.write("GATEWAY=%(gateway)s\n" % pifrec)
312 elif pifrec['ip_configuration_mode'] == "None":
313 f.write("BOOTPROTO=none\n")
315 raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
317 if nwrec.has_key('other_config'):
318 settings,offload = ethtool_settings(nwrec['other_config'])
320 f.write("ETHTOOL_OPTS=\"%s\"\n" % str.join(" ", settings))
322 f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload))
324 mtu = mtu_setting(nwrec['other_config'])
326 f.write("MTU=%s\n" % mtu)
328 ipdev_configure_static_routes(ipdev, nwrec['other_config'], f)
330 if pifrec.has_key('DNS') and pifrec['DNS'] != "":
331 ServerList = pifrec['DNS'].split(",")
332 for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
333 if oc and oc.has_key('domain'):
334 f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
336 # There can be only one DNSDEV and one GATEWAYDEV in /etc/sysconfig/network.
338 # The peerdns pif will be the one with
339 # pif::other-config:peerdns=true, or the mgmt pif if none have
342 # The gateway pif will be the one with
343 # pif::other-config:defaultroute=true, or the mgmt pif if none
346 # Work out which pif on this host should be the DNSDEV and which
347 # should be the GATEWAYDEV
349 # Note: we prune out the bond master pif (if it exists). This is
350 # because when we are called to bring up an interface with a bond
351 # master, it is implicit that we should bring down that master.
353 pifs_on_host = [p for p in db().get_all_pifs() if not p in pif_get_bond_masters(pif)]
355 # loop through all the pifs on this host looking for one with
356 # other-config:peerdns = true, and one with
357 # other-config:default-route=true
359 defaultroute_pif = None
360 for __pif in pifs_on_host:
361 __pifrec = db().get_pif_record(__pif)
362 __oc = __pifrec['other_config']
363 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
364 if peerdns_pif == None:
367 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
368 (db().get_pif_record(peerdns_pif)['device'], __pifrec['device']))
369 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
370 if defaultroute_pif == None:
371 defaultroute_pif = __pif
373 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
374 (db().get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
376 # If no pif is explicitly specified then use the mgmt pif for
377 # peerdns/defaultroute.
378 if peerdns_pif == None:
379 peerdns_pif = management_pif
380 if defaultroute_pif == None:
381 defaultroute_pif = management_pif
383 is_dnsdev = peerdns_pif == pif
384 is_gatewaydev = defaultroute_pif == pif
386 if is_dnsdev or is_gatewaydev:
387 fnetwork = ConfigurationFile(root_prefix() + "/etc/sysconfig/network")
388 for line in fnetwork.readlines():
389 if is_dnsdev and line.lstrip().startswith('DNSDEV='):
390 fnetwork.write('DNSDEV=%s\n' % ipdev)
392 elif is_gatewaydev and line.lstrip().startswith('GATEWAYDEV='):
393 fnetwork.write('GATEWAYDEV=%s\n' % ipdev)
394 is_gatewaydev = False
399 fnetwork.write('DNSDEV=%s\n' % ipdev)
401 fnetwork.write('GATEWAYDEV=%s\n' % ipdev)
404 f.attach_child(fnetwork)
412 def action_up(pif, force):
413 pifrec = db().get_pif_record(pif)
415 ipdev = pif_ipdev_name(pif)
416 dp = DatapathFactory(pif)
418 log("action_up: %s" % ipdev)
420 f = ipdev_configure_network(pif, dp)
426 pif_rename_physical_devices(pif)
428 # if we are not forcing the interface up then attempt to tear down
429 # any existing devices which might interfere with brinign this one
434 dp.bring_down_existing()
445 # Update /etc/issue (which contains the IP address of the management interface)
446 os.system(root_prefix() + "/sbin/update-issue")
450 log("failed to apply changes: %s" % e.msg)
454 def action_down(pif):
455 ipdev = pif_ipdev_name(pif)
456 dp = DatapathFactory(pif)
458 log("action_down: %s" % ipdev)
464 # This is useful for reconfiguring the mgmt interface after having lost connectivity to the pool master
465 def action_force_rewrite(bridge, config):
468 uuid,_ = subprocess.Popen(['uuidgen'], stdout = subprocess.PIPE).communicate()
472 # 1. that this assumes the interface is bridged
473 # 2. If --gateway is given it will make that the default gateway for the host
475 # extract the configuration
477 mode = config['mode']
479 interface = config['device']
481 raise Usage("Please supply --mode, --mac and --device")
485 netmask = config['netmask']
488 raise Usage("Please supply --netmask and --ip")
490 gateway = config['gateway']
494 raise Usage("--mode must be either static or dhcp")
496 if config.has_key('vlan'):
498 vlan_slave, vlan_vid = config['vlan'].split('.')
503 raise Error("Force rewrite of VLAN not implemented")
505 log("Configuring %s using %s configuration" % (bridge, mode))
507 f = ConfigurationFile(root_prefix() + dbcache_file)
510 network_uuid = getUUID()
512 f.write('<?xml version="1.0" ?>\n')
513 f.write('<xenserver-network-configuration>\n')
514 f.write('\t<pif ref="OpaqueRef:%s">\n' % pif_uuid)
515 f.write('\t\t<network>OpaqueRef:%s</network>\n' % network_uuid)
516 f.write('\t\t<management>True</management>\n')
517 f.write('\t\t<uuid>%sPif</uuid>\n' % interface)
518 f.write('\t\t<bond_slave_of>OpaqueRef:NULL</bond_slave_of>\n')
519 f.write('\t\t<bond_master_of/>\n')
520 f.write('\t\t<VLAN_slave_of/>\n')
521 f.write('\t\t<VLAN_master_of>OpaqueRef:NULL</VLAN_master_of>\n')
522 f.write('\t\t<VLAN>-1</VLAN>\n')
523 f.write('\t\t<device>%s</device>\n' % interface)
524 f.write('\t\t<MAC>%s</MAC>\n' % mac)
525 f.write('\t\t<other_config/>\n')
527 f.write('\t\t<ip_configuration_mode>DHCP</ip_configuration_mode>\n')
528 f.write('\t\t<IP></IP>\n')
529 f.write('\t\t<netmask></netmask>\n')
530 f.write('\t\t<gateway></gateway>\n')
531 f.write('\t\t<DNS></DNS>\n')
532 elif mode == 'static':
533 f.write('\t\t<ip_configuration_mode>Static</ip_configuration_mode>\n')
534 f.write('\t\t<IP>%s</IP>\n' % ip)
535 f.write('\t\t<netmask>%s</netmask>\n' % netmask)
536 if gateway is not None:
537 f.write('\t\t<gateway>%s</gateway>\n' % gateway)
538 f.write('\t\t<DNS></DNS>\n')
540 raise Error("Unknown mode %s" % mode)
541 f.write('\t</pif>\n')
543 f.write('\t<network ref="OpaqueRef:%s">\n' % network_uuid)
544 f.write('\t\t<uuid>InitialManagementNetwork</uuid>\n')
545 f.write('\t\t<PIFs>\n')
546 f.write('\t\t\t<PIF>OpaqueRef:%s</PIF>\n' % pif_uuid)
547 f.write('\t\t</PIFs>\n')
548 f.write('\t\t<bridge>%s</bridge>\n' % bridge)
549 f.write('\t\t<other_config/>\n')
550 f.write('\t</network>\n')
551 f.write('</xenserver-network-configuration>\n')
559 log("failed to apply changes: %s" % e.msg)
564 global management_pif
570 force_interface = None
571 force_management = False
579 longops = [ "pif=", "pif-uuid=",
584 "mac=", "device=", "mode=", "ip=", "netmask=", "gateway=",
588 arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
589 except getopt.GetoptError, msg:
592 force_rewrite_config = {}
597 elif o == "--pif-uuid":
599 elif o == "--session":
601 elif o == "--force-interface" or o == "--force":
603 elif o == "--management":
604 force_management = True
605 elif o in ["--mac", "--device", "--mode", "--ip", "--netmask", "--gateway"]:
606 force_rewrite_config[o[2:]] = a
607 elif o == "--root-prefix":
609 elif o == "--no-syslog":
610 set_log_destination("stderr")
611 elif o == "-h" or o == "--help":
612 print __doc__ % {'command-name': os.path.basename(argv[0])}
615 if get_log_destination() == "syslog":
616 syslog.openlog(os.path.basename(argv[0]))
617 log("Called as " + str.join(" ", argv))
620 raise Usage("Required option <action> not present")
622 raise Usage("Too many arguments")
626 if not action in ["up", "down", "rewrite", "rewrite-configuration"]:
627 raise Usage("Unknown action \"%s\"" % action)
629 # backwards compatibility
630 if action == "rewrite-configuration": action = "rewrite"
632 if ( session or pif ) and pif_uuid:
633 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
634 if ( session and not pif ) or ( not session and pif ):
635 raise Usage("--session and --pif must be used together.")
636 if force_interface and ( session or pif or pif_uuid ):
637 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
638 if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
639 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
640 if (action == "rewrite") and (pif or pif_uuid ):
641 raise Usage("rewrite action does not take --pif or --pif-uuid")
645 log("Force interface %s %s" % (force_interface, action))
647 if action == "rewrite":
648 action_force_rewrite(force_interface, force_rewrite_config)
649 elif action in ["up", "down"]:
650 db_init_from_cache(dbcache_file)
651 pif = db().get_pif_by_bridge(force_interface)
652 management_pif = db().get_management_pif()
656 elif action == "down":
659 raise Error("Unknown action %s" % action)
661 db_init_from_xenapi(session)
664 pif = db().get_pif_by_uuid(pif_uuid)
666 if action == "rewrite":
670 raise Usage("No PIF given")
673 # pif is going to be the management pif
676 # pif is not going to be the management pif.
677 # Search DB cache for pif on same host with management=true
678 pifrec = db().get_pif_record(pif)
679 management_pif = db().get_management_pif()
681 log_pif_action(action, pif)
683 if not check_allowed(pif):
687 action_up(pif, False)
688 elif action == "down":
691 raise Error("Unknown action %s" % action)
694 db().save(dbcache_file)
697 print >>sys.stderr, err.msg
698 print >>sys.stderr, "For help use --help."
706 if __name__ == "__main__":
712 err = traceback.format_exception(*ex)