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.
39 # 1. Every pif belongs to exactly one network
40 # 2. Every network has zero or one pifs
41 # 3. A network may have an associated bridge, allowing vifs to be attached
42 # 4. A network may be bridgeless (there's no point having a bridge over a storage pif)
44 from InterfaceReconfigure import *
46 import os, sys, getopt
54 dbcache_file = "/var/xapi/network.dbcache"
60 def log_pif_action(action, pif):
61 pifrec = db().get_pif_record(pif)
63 rec['uuid'] = pifrec['uuid']
64 rec['ip_configuration_mode'] = pifrec['ip_configuration_mode']
65 rec['action'] = action
66 rec['pif_netdev_name'] = pif_netdev_name(pif)
67 rec['message'] = "Bring %(action)s PIF %(uuid)s" % rec
68 log("%(message)s: %(pif_netdev_name)s configured as %(ip_configuration_mode)s" % rec)
74 class Usage(Exception):
75 def __init__(self, msg):
76 Exception.__init__(self)
80 # Boot from Network filesystem or device.
83 def check_allowed(pif):
84 """Determine whether interface-reconfigure should be manipulating this PIF.
86 Used to prevent system PIFs (such as network root disk) from being interfered with.
89 pifrec = db().get_pif_record(pif)
91 f = open("/proc/ardence")
92 macline = filter(lambda x: x.startswith("HWaddr:"), f.readlines())
95 p = re.compile(".*\s%(MAC)s\s.*" % pifrec, re.IGNORECASE)
96 if p.match(macline[0]):
97 log("Skipping PVS device %(device)s (%(MAC)s)" % pifrec)
104 # Bare Network Devices -- network devices without IP configuration
107 def netdev_remap_name(pif, already_renamed=[]):
108 """Check whether 'pif' exists and has the correct MAC.
109 If not, try to find a device with the correct MAC and rename it.
110 'already_renamed' is used to avoid infinite recursion.
116 file = open(name, 'r')
117 return file.readline().rstrip('\n')
122 def get_netdev_mac(device):
124 return read1("/sys/class/net/%s/address" % device)
126 # Probably no such device.
129 def get_netdev_tx_queue_len(device):
131 return int(read1("/sys/class/net/%s/tx_queue_len" % device))
133 # Probably no such device.
136 def get_netdev_by_mac(mac):
137 for device in os.listdir("/sys/class/net"):
138 dev_mac = get_netdev_mac(device)
139 if (dev_mac and mac.lower() == dev_mac.lower() and
140 get_netdev_tx_queue_len(device)):
144 def rename_netdev(old_name, new_name):
145 log("Changing the name of %s to %s" % (old_name, new_name))
146 run_command(['/sbin/ifconfig', old_name, 'down'])
147 if not run_command(['/sbin/ip', 'link', 'set', old_name, 'name', new_name]):
148 raise Error("Could not rename %s to %s" % (old_name, new_name))
150 pifrec = db().get_pif_record(pif)
151 device = pifrec['device']
154 # Is there a network device named 'device' at all?
155 device_exists = netdev_exists(device)
157 # Yes. Does it have MAC 'mac'?
158 found_mac = get_netdev_mac(device)
159 if found_mac and mac.lower() == found_mac.lower():
160 # Yes, everything checks out the way we want. Nothing to do.
163 log("No network device %s" % device)
165 # What device has MAC 'mac'?
166 cur_device = get_netdev_by_mac(mac)
168 log("No network device has MAC %s" % mac)
171 # First rename 'device', if it exists, to get it out of the way
172 # for 'cur_device' to replace it.
174 rename_netdev(device, "dev%d" % random.getrandbits(24))
176 # Rename 'cur_device' to 'device'.
177 rename_netdev(cur_device, device)
180 # IP Network Devices -- network devices with IP configuration
184 """Bring down a network interface"""
185 if not netdev_exists(netdev):
186 log("ifdown: device %s does not exist, ignoring" % netdev)
188 if not os.path.exists("/etc/sysconfig/network-scripts/ifcfg-%s" % netdev):
189 log("ifdown: device %s exists but ifcfg-%s does not" % (netdev,netdev))
190 run_command(["/sbin/ifconfig", netdev, 'down'])
192 run_command(["/sbin/ifdown", netdev])
195 """Bring up a network interface"""
196 if not os.path.exists("/etc/sysconfig/network-scripts/ifcfg-%s" % netdev):
197 raise Error("ifup: device %s exists but ifcfg-%s does not" % (netdev,netdev))
198 run_command(["/sbin/ifup", netdev])
204 def pif_rename_physical_devices(pif):
207 pif = pif_get_vlan_slave(pif)
210 pifs = pif_get_bond_slaves(pif)
215 netdev_remap_name(pif)
218 # IP device configuration
221 def ipdev_configure_static_routes(interface, oc, f):
222 """Open a route-<interface> file for static routes.
224 Opens the static routes configuration file for interface and writes one
225 line for each route specified in the network's other config "static-routes" value.
227 interface ( RO): xenbr1
228 other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
230 Then route-xenbr1 should be
231 172.16.0.0/15 via 192.168.0.3 dev xenbr1
232 172.18.0.0/16 via 192.168.0.4 dev xenbr1
234 if oc.has_key('static-routes'):
235 # The key is present - extract comma seperates entries
236 lines = oc['static-routes'].split(',')
238 # The key is not present, i.e. there are no static routes
241 child = ConfigurationFile("/etc/sysconfig/network-scripts/route-%s" % interface)
242 child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
243 (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
247 network, masklen, gateway = l.split('/')
248 child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
250 f.attach_child(child)
253 except ValueError, e:
254 log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
256 def ipdev_open_ifcfg(pif):
257 ipdev = pif_ipdev_name(pif)
259 log("Writing network configuration for %s" % ipdev)
261 f = ConfigurationFile("/etc/sysconfig/network-scripts/ifcfg-%s" % ipdev)
263 f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
264 (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
265 f.write("XEMANAGED=yes\n")
266 f.write("DEVICE=%s\n" % ipdev)
267 f.write("ONBOOT=no\n")
271 def ipdev_configure_network(pif, dp):
272 """Write the configuration file for a network.
274 Writes configuration derived from the network object into the relevant
275 ifcfg file. The configuration file is passed in, but if the network is
276 bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
278 This routine may also write ifcfg files of the networks corresponding to other PIFs
279 in order to maintain consistency.
282 pif: Opaque_ref of pif
286 pifrec = db().get_pif_record(pif)
287 nwrec = db().get_network_record(pifrec['network'])
289 ipdev = pif_ipdev_name(pif)
291 f = ipdev_open_ifcfg(pif)
293 mode = pifrec['ip_configuration_mode']
294 log("Configuring %s using %s configuration" % (ipdev, mode))
297 if pifrec.has_key('other_config'):
298 oc = pifrec['other_config']
300 dp.configure_ipdev(f)
302 if pifrec['ip_configuration_mode'] == "DHCP":
303 f.write("BOOTPROTO=dhcp\n")
304 f.write("PERSISTENT_DHCLIENT=yes\n")
305 elif pifrec['ip_configuration_mode'] == "Static":
306 f.write("BOOTPROTO=none\n")
307 f.write("NETMASK=%(netmask)s\n" % pifrec)
308 f.write("IPADDR=%(IP)s\n" % pifrec)
309 f.write("GATEWAY=%(gateway)s\n" % pifrec)
310 elif pifrec['ip_configuration_mode'] == "None":
311 f.write("BOOTPROTO=none\n")
313 raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
315 if nwrec.has_key('other_config'):
316 settings,offload = ethtool_settings(nwrec['other_config'])
318 f.write("ETHTOOL_OPTS=\"%s\"\n" % str.join(" ", settings))
320 f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % str.join(" ", offload))
322 mtu = mtu_setting(nwrec['other_config'])
324 f.write("MTU=%s\n" % mtu)
326 ipdev_configure_static_routes(ipdev, nwrec['other_config'], f)
328 if pifrec.has_key('DNS') and pifrec['DNS'] != "":
329 ServerList = pifrec['DNS'].split(",")
330 for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
331 if oc and oc.has_key('domain'):
332 f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
334 # There can be only one DNSDEV and one GATEWAYDEV in /etc/sysconfig/network.
336 # The peerdns pif will be the one with
337 # pif::other-config:peerdns=true, or the mgmt pif if none have
340 # The gateway pif will be the one with
341 # pif::other-config:defaultroute=true, or the mgmt pif if none
344 # Work out which pif on this host should be the DNSDEV and which
345 # should be the GATEWAYDEV
347 # Note: we prune out the bond master pif (if it exists). This is
348 # because when we are called to bring up an interface with a bond
349 # master, it is implicit that we should bring down that master.
351 pifs_on_host = [p for p in db().get_all_pifs() if not p in pif_get_bond_masters(pif)]
353 # loop through all the pifs on this host looking for one with
354 # other-config:peerdns = true, and one with
355 # other-config:default-route=true
357 defaultroute_pif = None
358 for __pif in pifs_on_host:
359 __pifrec = db().get_pif_record(__pif)
360 __oc = __pifrec['other_config']
361 if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
362 if peerdns_pif == None:
365 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
366 (db().get_pif_record(peerdns_pif)['device'], __pifrec['device']))
367 if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
368 if defaultroute_pif == None:
369 defaultroute_pif = __pif
371 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
372 (db().get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
374 # If no pif is explicitly specified then use the mgmt pif for
375 # peerdns/defaultroute.
376 if peerdns_pif == None:
377 peerdns_pif = management_pif
378 if defaultroute_pif == None:
379 defaultroute_pif = management_pif
381 is_dnsdev = peerdns_pif == pif
382 is_gatewaydev = defaultroute_pif == pif
384 if is_dnsdev or is_gatewaydev:
385 fnetwork = ConfigurationFile("/etc/sysconfig/network")
386 for line in fnetwork.readlines():
387 if is_dnsdev and line.lstrip().startswith('DNSDEV='):
388 fnetwork.write('DNSDEV=%s\n' % ipdev)
390 elif is_gatewaydev and line.lstrip().startswith('GATEWAYDEV='):
391 fnetwork.write('GATEWAYDEV=%s\n' % ipdev)
392 is_gatewaydev = False
397 fnetwork.write('DNSDEV=%s\n' % ipdev)
399 fnetwork.write('GATEWAYDEV=%s\n' % ipdev)
402 f.attach_child(fnetwork)
410 def action_up(pif, force):
411 pifrec = db().get_pif_record(pif)
413 ipdev = pif_ipdev_name(pif)
414 dp = DatapathFactory(pif)
416 log("action_up: %s" % ipdev)
418 f = ipdev_configure_network(pif, dp)
424 pif_rename_physical_devices(pif)
426 # if we are not forcing the interface up then attempt to tear down
427 # any existing devices which might interfere with brinign this one
432 dp.bring_down_existing()
443 # Update /etc/issue (which contains the IP address of the management interface)
444 os.system("/sbin/update-issue")
448 log("failed to apply changes: %s" % e.msg)
452 def action_down(pif):
453 ipdev = pif_ipdev_name(pif)
454 dp = DatapathFactory(pif)
456 log("action_down: %s" % ipdev)
462 # This is useful for reconfiguring the mgmt interface after having lost connectivity to the pool master
463 def action_force_rewrite(bridge, config):
466 uuid,_ = subprocess.Popen(['uuidgen'], stdout = subprocess.PIPE).communicate()
470 # 1. that this assumes the interface is bridged
471 # 2. If --gateway is given it will make that the default gateway for the host
473 # extract the configuration
475 mode = config['mode']
477 interface = config['device']
479 raise Usage("Please supply --mode, --mac and --device")
483 netmask = config['netmask']
486 raise Usage("Please supply --netmask and --ip")
488 gateway = config['gateway']
492 raise Usage("--mode must be either static or dhcp")
494 if config.has_key('vlan'):
496 vlan_slave, vlan_vid = config['vlan'].split('.')
501 raise Error("Force rewrite of VLAN not implemented")
503 log("Configuring %s using %s configuration" % (bridge, mode))
505 f = ConfigurationFile(dbcache_file)
508 network_uuid = getUUID()
510 f.write('<?xml version="1.0" ?>\n')
511 f.write('<xenserver-network-configuration>\n')
512 f.write('\t<pif ref="OpaqueRef:%s">\n' % pif_uuid)
513 f.write('\t\t<network>OpaqueRef:%s</network>\n' % network_uuid)
514 f.write('\t\t<management>True</management>\n')
515 f.write('\t\t<uuid>%sPif</uuid>\n' % interface)
516 f.write('\t\t<bond_slave_of>OpaqueRef:NULL</bond_slave_of>\n')
517 f.write('\t\t<bond_master_of/>\n')
518 f.write('\t\t<VLAN_slave_of/>\n')
519 f.write('\t\t<VLAN_master_of>OpaqueRef:NULL</VLAN_master_of>\n')
520 f.write('\t\t<VLAN>-1</VLAN>\n')
521 f.write('\t\t<device>%s</device>\n' % interface)
522 f.write('\t\t<MAC>%s</MAC>\n' % mac)
523 f.write('\t\t<other_config/>\n')
525 f.write('\t\t<ip_configuration_mode>DHCP</ip_configuration_mode>\n')
526 f.write('\t\t<IP></IP>\n')
527 f.write('\t\t<netmask></netmask>\n')
528 f.write('\t\t<gateway></gateway>\n')
529 f.write('\t\t<DNS></DNS>\n')
530 elif mode == 'static':
531 f.write('\t\t<ip_configuration_mode>Static</ip_configuration_mode>\n')
532 f.write('\t\t<IP>%s</IP>\n' % ip)
533 f.write('\t\t<netmask>%s</netmask>\n' % netmask)
534 if gateway is not None:
535 f.write('\t\t<gateway>%s</gateway>\n' % gateway)
536 f.write('\t\t<DNS></DNS>\n')
538 raise Error("Unknown mode %s" % mode)
539 f.write('\t</pif>\n')
541 f.write('\t<network ref="OpaqueRef:%s">\n' % network_uuid)
542 f.write('\t\t<uuid>InitialManagementNetwork</uuid>\n')
543 f.write('\t\t<PIFs>\n')
544 f.write('\t\t\t<PIF>OpaqueRef:%s</PIF>\n' % pif_uuid)
545 f.write('\t\t</PIFs>\n')
546 f.write('\t\t<bridge>%s</bridge>\n' % bridge)
547 f.write('\t\t<other_config/>\n')
548 f.write('\t</network>\n')
549 f.write('</xenserver-network-configuration>\n')
557 log("failed to apply changes: %s" % e.msg)
562 global management_pif
568 force_interface = None
569 force_management = False
577 longops = [ "pif=", "pif-uuid=",
582 "mac=", "device=", "mode=", "ip=", "netmask=", "gateway=",
584 arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
585 except getopt.GetoptError, msg:
588 force_rewrite_config = {}
593 elif o == "--pif-uuid":
595 elif o == "--session":
597 elif o == "--force-interface" or o == "--force":
599 elif o == "--management":
600 force_management = True
601 elif o in ["--mac", "--device", "--mode", "--ip", "--netmask", "--gateway"]:
602 force_rewrite_config[o[2:]] = a
603 elif o == "-h" or o == "--help":
604 print __doc__ % {'command-name': os.path.basename(argv[0])}
607 syslog.openlog(os.path.basename(argv[0]))
608 log("Called as " + str.join(" ", argv))
611 raise Usage("Required option <action> not present")
613 raise Usage("Too many arguments")
617 if not action in ["up", "down", "rewrite", "rewrite-configuration"]:
618 raise Usage("Unknown action \"%s\"" % action)
620 # backwards compatibility
621 if action == "rewrite-configuration": action = "rewrite"
623 if ( session or pif ) and pif_uuid:
624 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
625 if ( session and not pif ) or ( not session and pif ):
626 raise Usage("--session and --pif must be used together.")
627 if force_interface and ( session or pif or pif_uuid ):
628 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
629 if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
630 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
631 if (action == "rewrite") and (pif or pif_uuid ):
632 raise Usage("rewrite action does not take --pif or --pif-uuid")
636 log("Force interface %s %s" % (force_interface, action))
638 if action == "rewrite":
639 action_force_rewrite(force_interface, force_rewrite_config)
640 elif action in ["up", "down"]:
641 db_init_from_cache(dbcache_file)
642 pif = db().get_pif_by_bridge(force_interface)
643 management_pif = db().get_management_pif()
647 elif action == "down":
650 raise Error("Unknown action %s" % action)
652 db_init_from_xenapi(session)
655 pif = db().get_pif_by_uuid(pif_uuid)
657 if action == "rewrite":
661 raise Usage("No PIF given")
664 # pif is going to be the management pif
667 # pif is not going to be the management pif.
668 # Search DB cache for pif on same host with management=true
669 pifrec = db().get_pif_record(pif)
670 management_pif = db().get_management_pif()
672 log_pif_action(action, pif)
674 if not check_allowed(pif):
678 action_up(pif, False)
679 elif action == "down":
682 raise Error("Unknown action %s" % action)
685 db().save(dbcache_file)
688 print >>sys.stderr, err.msg
689 print >>sys.stderr, "For help use --help."
697 if __name__ == "__main__":
703 err = traceback.format_exception(*ex)