xenserver: Whitelist specific XAPI fields to pickle in interface-reconfigure.
[sliver-openvswitch.git] / xenserver / opt_xensource_libexec_interface-reconfigure
1 #!/usr/bin/python
2 #
3 # Copyright (c) Citrix Systems 2008. All rights reserved.
4 # Copyright (c) 2009 Nicira Networks.
5 #
6 """Usage:
7
8     %(command-name)s --session <SESSION-REF> --pif <PIF-REF> [up|down|rewrite]
9     %(command-name)s --force <BRIDGE> [up|down|rewrite <CONFIG>]
10     %(command-name)s --force all down
11
12     where,
13           <CONFIG> = --device=<INTERFACE> --mode=dhcp
14           <CONFIG> = --device=<INTERFACE> --mode=static --ip=<IPADDR> --netmask=<NM> [--gateway=<GW>]
15
16   Options:
17     --session           A session reference to use to access the xapi DB
18     --pif               A PIF reference.
19     --force-interface   An interface name. Mutually exclusive with --session/--pif.
20
21   Either both --session and --pif  or just --pif-uuid.
22   
23   <ACTION> is either "up" or "down" or "rewrite"
24 """
25
26 #
27 # Undocumented parameters for test & dev:
28 #
29 #  --output-directory=<DIR>     Write configuration to <DIR>. Also disables actually
30 #                               raising/lowering the interfaces
31 #  --pif-uuid                   A PIF UUID, use instead of --session/--pif.
32 #
33 #
34 #
35 # Notes:
36 # 1. Every pif belongs to exactly one network
37 # 2. Every network has zero or one pifs
38 # 3. A network may have an associated bridge, allowing vifs to be attached
39 # 4. A network may be bridgeless (there's no point having a bridge over a storage pif)
40
41 # XXX: --force-interface=all down
42
43 # XXX: --force-interface rewrite
44
45 # XXX: Sometimes this leaves "orphaned" datapaths, e.g. a datapath whose
46 #      only port is the local port.  Should delete those.
47
48 # XXX: This can leave crud in ovs-vswitchd.conf in this scenario:
49 #      - Create bond in XenCenter.
50 #      - Create VLAN on bond in XenCenter.
51 #      - Attempt to delete bond in XenCenter (this will fail because there
52 #        is a VLAN on the bond, although the error may not be reported
53 #        until the next step)
54 #      - Delete VLAN in XenCenter.
55 #      - Delete bond in XenCenter.
56 # At this point there will still be some configuration data for the bond
57 # or the VLAN in ovs-vswitchd.conf.
58
59 import XenAPI
60 import os, sys, getopt, time, signal
61 import syslog
62 import traceback
63 import time
64 import re
65 import pickle
66
67 output_directory = None
68
69 db = None
70 management_pif = None
71
72 dbcache_file = "/etc/ovs-vswitch.dbcache"
73 vswitch_config_dir = "/etc/openvswitch"
74
75 class Usage(Exception):
76     def __init__(self, msg):
77         Exception.__init__(self)
78         self.msg = msg
79
80 class Error(Exception):
81     def __init__(self, msg):
82         Exception.__init__(self)
83         self.msg = msg
84
85 class ConfigurationFile(object):
86     """Write a file, tracking old and new versions.
87
88     Supports writing a new version of a file and applying and
89     reverting those changes.
90     """
91
92     __STATE = {"OPEN":"OPEN",
93                "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
94                "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
95     
96     def __init__(self, fname, path="/etc/sysconfig/network-scripts"):
97
98         self.__state = self.__STATE['OPEN']
99         self.__fname = fname
100         self.__children = []
101         
102         if debug_mode():
103             dirname = output_directory
104         else:
105             dirname = path
106             
107         self.__path    = os.path.join(dirname, fname)
108         self.__oldpath = os.path.join(dirname, "." + fname + ".xapi-old")
109         self.__newpath = os.path.join(dirname, "." + fname + ".xapi-new")
110         self.__unlink = False
111         
112         self.__f = open(self.__newpath, "w")
113
114     def attach_child(self, child):
115         self.__children.append(child)
116
117     def path(self):
118         return self.__path
119
120     def readlines(self):
121         try:
122             return open(self.path()).readlines()
123         except:
124             return ""
125         
126     def write(self, args):
127         if self.__state != self.__STATE['OPEN']:
128             raise Error("Attempt to write to file in state %s" % self.__state)
129         self.__f.write(args)
130
131     def unlink(self):
132         if self.__state != self.__STATE['OPEN']:
133             raise Error("Attempt to unlink file in state %s" % self.__state)
134         self.__unlink = True
135         self.__f.close()
136         self.__state = self.__STATE['NOT-APPLIED']
137     
138     def close(self):
139         if self.__state != self.__STATE['OPEN']:
140             raise Error("Attempt to close file in state %s" % self.__state)
141         
142         self.__f.close()
143         self.__state = self.__STATE['NOT-APPLIED']
144
145     def changed(self):
146         if self.__state != self.__STATE['NOT-APPLIED']:
147             raise Error("Attempt to compare file in state %s" % self.__state)
148
149         return True
150
151     def apply(self):
152         if self.__state != self.__STATE['NOT-APPLIED']:
153             raise Error("Attempt to apply configuration from state %s" % self.__state)
154
155         for child in self.__children:
156             child.apply()
157
158         log("Applying changes to %s configuration" % self.__fname)
159
160         # Remove previous backup.
161         if os.access(self.__oldpath, os.F_OK):
162             os.unlink(self.__oldpath)
163
164         # Save current configuration.
165         if os.access(self.__path, os.F_OK):
166             os.link(self.__path, self.__oldpath)
167             os.unlink(self.__path)
168
169         # Apply new configuration.
170         assert(os.path.exists(self.__newpath))
171         if not self.__unlink:
172             os.link(self.__newpath, self.__path)
173         else:
174             pass # implicit unlink of original file 
175
176         # Remove temporary file.
177         os.unlink(self.__newpath)
178
179         self.__state = self.__STATE['APPLIED']
180
181     def revert(self):
182         if self.__state != self.__STATE['APPLIED']:
183             raise Error("Attempt to revert configuration from state %s" % self.__state)
184
185         for child in self.__children:
186             child.revert()
187
188         log("Reverting changes to %s configuration" % self.__fname)
189
190         # Remove existing new configuration
191         if os.access(self.__newpath, os.F_OK):
192             os.unlink(self.__newpath)
193
194         # Revert new configuration.
195         if os.access(self.__path, os.F_OK):
196             os.link(self.__path, self.__newpath)
197             os.unlink(self.__path)
198
199         # Revert to old configuration.
200         if os.access(self.__oldpath, os.F_OK):
201             os.link(self.__oldpath, self.__path)
202             os.unlink(self.__oldpath)
203
204         # Leave .*.xapi-new as an aid to debugging.
205         
206         self.__state = self.__STATE['REVERTED']
207     
208     def commit(self):
209         if self.__state != self.__STATE['APPLIED']:
210             raise Error("Attempt to commit configuration from state %s" % self.__state)
211
212         for child in self.__children:
213             child.commit()
214
215         log("Committing changes to %s configuration" % self.__fname)
216         
217         if os.access(self.__oldpath, os.F_OK):
218             os.unlink(self.__oldpath)
219         if os.access(self.__newpath, os.F_OK):
220             os.unlink(self.__newpath)
221
222         self.__state = self.__STATE['COMMITTED']
223
224 def debug_mode():
225     return output_directory is not None
226
227 def log(s):
228     if debug_mode():
229         print >>sys.stderr, s
230     else:
231         syslog.syslog(s)
232
233 def check_allowed(pif):
234     pifrec = db.get_pif_record(pif)
235     try:
236         f = open("/proc/ardence")
237         macline = filter(lambda x: x.startswith("HWaddr:"), f.readlines())
238         f.close()
239         if len(macline) == 1:
240             p = re.compile(".*\s%(MAC)s\s.*" % pifrec, re.IGNORECASE)
241             if p.match(macline[0]):
242                 log("Skipping PVS device %(device)s (%(MAC)s)" % pifrec)
243                 return False
244     except IOError:
245         pass
246     return True
247
248 def interface_exists(i):
249     return os.path.exists("/sys/class/net/" + i)
250
251 ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
252
253 PIF_ATTRS = [ 'uuid',
254               'management',
255               'network',
256               'device',
257               'bond_master_of',
258               'bond_slave_of',
259               'VLAN',
260               'VLAN_master_of',
261               'VLAN_slave_of',
262               'ip_configuration_mode',
263               'IP',
264               'netmask',
265               'gateway',
266               'DNS',
267               'MAC'
268             ]
269
270 PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
271                         [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
272                         ETHTOOL_OTHERCONFIG_ATTRS
273
274 VLAN_ATTRS = [ 'uuid',
275                'tagged_PIF',
276                'untagged_PIF'
277              ]
278     
279 BOND_ATTRS = [ 'uuid',
280                'master',
281                'slaves'
282              ]
283
284 NETWORK_ATTRS = [ 'uuid',
285                   'PIFs',
286                   'bridge'
287                 ]
288
289 NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + ETHTOOL_OTHERCONFIG_ATTRS
290
291
292 class DatabaseCache(object):
293     def __read_xensource_inventory(self):
294         filename = "/etc/xensource-inventory"
295         f = open(filename, "r")
296         lines = [x.strip("\n") for x in f.readlines()]
297         f.close()
298
299         defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
300         defs = [ (a, b.strip("'")) for (a,b) in defs ]
301
302         return dict(defs)
303
304
305     def __pif_on_host(self,pif):
306         return self.__pifs.has_key(pif)
307
308
309     def __get_pif_records_from_xapi(self, session, host):
310         self.__pifs = {}
311         for (p,rec) in session.xenapi.PIF.get_all_records().items():
312             if rec['host'] != host:
313                 continue
314             self.__pifs[p] = {}
315             for f in PIF_ATTRS:
316                 self.__pifs[p][f] = rec[f]
317             self.__pifs[p]['other_config'] = {}
318             for f in PIF_OTHERCONFIG_ATTRS:
319                 if not rec['other_config'].has_key(f): continue
320                 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
321
322     def __get_vlan_records_from_xapi(self, session):
323         self.__vlans = {}
324         for v in session.xenapi.VLAN.get_all():
325             rec = session.xenapi.VLAN.get_record(v)
326             if not self.__pif_on_host(rec['untagged_PIF']):
327                 continue
328             self.__vlans[v] = {}
329             for f in VLAN_ATTRS:
330                 self.__vlans[v][f] = rec[f]
331
332     def __get_bond_records_from_xapi(self, session):
333         self.__bonds = {}
334         for b in session.xenapi.Bond.get_all():
335             rec = session.xenapi.Bond.get_record(b)
336             if not self.__pif_on_host(rec['master']):
337                 continue
338             self.__bonds[b] = {}
339             for f in BOND_ATTRS:
340                 self.__bonds[b][f] = rec[f]
341
342     def __get_network_records_from_xapi(self, session):
343         self.__networks = {}
344         for n in session.xenapi.network.get_all():
345             rec = session.xenapi.network.get_record(n)
346             self.__networks[n] = {}
347             for f in NETWORK_ATTRS:
348                 self.__networks[n][f] = rec[f]
349             self.__networks[n]['other_config'] = {}
350             for f in NETWORK_OTHERCONFIG_ATTRS:
351                 if not rec['other_config'].has_key(f): continue
352                 self.__networks[n]['other_config'][f] = rec['other_config'][f]
353                 
354     def __init__(self, session_ref=None, cache_file=None):
355         if session_ref and cache_file:
356             raise Error("can't specify session reference and cache file")
357         if cache_file == None:
358             session = XenAPI.xapi_local()
359
360             if not session_ref:
361                 log("No session ref given on command line, logging in.")
362                 session.xenapi.login_with_password("root", "")
363             else:
364                 session._session = session_ref
365
366             try:
367                 
368                 inventory = self.__read_xensource_inventory()
369                 assert(inventory.has_key('INSTALLATION_UUID'))
370                 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
371                 
372                 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
373                 
374                 self.__get_pif_records_from_xapi(session, host)
375
376                 self.__get_vlan_records_from_xapi(session)
377                 self.__get_bond_records_from_xapi(session)
378                 self.__get_network_records_from_xapi(session)
379             finally:
380                 if not session_ref:
381                     session.xenapi.session.logout()
382         else:
383             log("Loading xapi database cache from %s" % cache_file)
384             f = open(cache_file, 'r')
385             members = pickle.load(f)
386             f.close()
387
388             self.__vlans = members['vlans']
389             self.__bonds = members['bonds']
390             self.__pifs = members['pifs']
391             self.__networks = members['networks']
392
393     def save(self, cache_file):
394         f = open(cache_file, 'w')
395         pickle.dump({'vlans': self.__vlans,
396                      'bonds': self.__bonds,
397                      'pifs': self.__pifs,
398                      'networks': self.__networks}, f)
399         f.close()
400
401     def get_pif_by_uuid(self, uuid):
402         pifs = map(lambda (ref,rec): ref,
403                   filter(lambda (ref,rec): uuid == rec['uuid'],
404                          self.__pifs.items()))
405         if len(pifs) == 0:
406             raise Error("Unknown PIF \"%s\"" % uuid)
407         elif len(pifs) > 1:
408             raise Error("Non-unique PIF \"%s\"" % uuid)
409
410         return pifs[0]
411
412     def get_pifs_by_device(self, device):
413         return map(lambda (ref,rec): ref,
414                    filter(lambda (ref,rec): rec['device'] == device,
415                           self.__pifs.items()))
416
417     def get_pif_by_bridge(self, bridge):
418         networks = map(lambda (ref,rec): ref,
419                        filter(lambda (ref,rec): rec['bridge'] == bridge,
420                               self.__networks.items()))
421         if len(networks) == 0:
422             raise Error("No matching network \"%s\"")
423
424         answer = None
425         for network in networks:
426             nwrec = self.get_network_record(network)
427             for pif in nwrec['PIFs']:
428                 pifrec = self.get_pif_record(pif)
429                 if answer:
430                     raise Error("Multiple PIFs on host for network %s" % (bridge))
431                 answer = pif
432         if not answer:
433             raise Error("No PIF on host for network %s" % (bridge))
434         return answer
435
436     def get_pif_record(self, pif):
437         if self.__pifs.has_key(pif):
438             return self.__pifs[pif]
439         raise Error("Unknown PIF \"%s\"" % pif)
440     def get_all_pifs(self):
441         return self.__pifs
442     def pif_exists(self, pif):
443         return self.__pifs.has_key(pif)
444     
445     def get_management_pif(self):
446         """ Returns the management pif on host
447         """
448         all = self.get_all_pifs()
449         for pif in all: 
450             pifrec = self.get_pif_record(pif)
451             if pifrec['management']: return pif
452         return None
453
454     def get_network_record(self, network):
455         if self.__networks.has_key(network):
456             return self.__networks[network]
457         raise Error("Unknown network \"%s\"" % network)
458     def get_all_networks(self):
459         return self.__networks
460
461     def get_bond_record(self, bond):
462         if self.__bonds.has_key(bond):
463             return self.__bonds[bond]
464         else:
465             return None
466         
467     def get_vlan_record(self, vlan):
468         if self.__vlans.has_key(vlan):
469             return self.__vlans[vlan]
470         else:
471             return None
472             
473 def bridge_name(pif):
474     """Return the bridge name associated with pif, or None if network is bridgeless"""
475     pifrec = db.get_pif_record(pif)
476     nwrec = db.get_network_record(pifrec['network'])
477
478     if nwrec['bridge']:
479         # TODO: sanity check that nwrec['bridgeless'] != 'true'
480         return nwrec['bridge']
481     else:
482         # TODO: sanity check that nwrec['bridgeless'] == 'true'
483         return None
484
485 def interface_name(pif):
486     """Construct an interface name from the given PIF record."""
487
488     pifrec = db.get_pif_record(pif)
489
490     if pifrec['VLAN'] == '-1':
491         return pifrec['device']
492     else:
493         return "%(device)s.%(VLAN)s" % pifrec
494
495 def datapath_name(pif):
496     """Return the OpenFlow datapath name associated with pif.
497 For a non-VLAN PIF, the datapath name is the bridge name.
498 For a VLAN PIF, the datapath name is the bridge name for the PIF's VLAN slave.
499 (xapi will create a datapath named with the bridge name even though we won't
500 use it.)
501 """
502
503     pifrec = db.get_pif_record(pif)
504
505     if pifrec['VLAN'] == '-1':
506         return bridge_name(pif)
507     else:
508         return bridge_name(get_vlan_slave_of_pif(pif))
509
510 def ipdev_name(pif):
511     """Return the the name of the network device that carries the
512 IP configuration (if any) associated with pif.
513 The ipdev name is the same as the bridge name.
514 """
515
516     pifrec = db.get_pif_record(pif)
517     return bridge_name(pif)
518
519 def physdev_names(pif):
520     """Return the name(s) of the physical network device(s) associated with pif.
521 For a VLAN PIF, the physical devices are the VLAN slave's physical devices.
522 For a bond master PIF, the physical devices are the bond slaves.
523 For a non-VLAN, non-bond master PIF, the physical device is the PIF itself.
524 """
525
526     pifrec = db.get_pif_record(pif)
527
528     if pifrec['VLAN'] != '-1':
529         return physdev_names(get_vlan_slave_of_pif(pif))
530     elif len(pifrec['bond_master_of']) != 0:
531         physdevs = []
532         for slave in get_bond_slaves_of_pif(pif):
533             physdevs += physdev_names(slave)
534         return physdevs
535     else:
536         return [pifrec['device']]
537
538 def log_pif_action(action, pif):
539     pifrec = db.get_pif_record(pif)
540     pifrec['action'] = action
541     pifrec['interface-name'] = interface_name(pif)
542     if action == "rewrite":
543         pifrec['message'] = "Rewrite PIF %(uuid)s configuration" % pifrec
544     else:
545         pifrec['message'] = "Bring %(action)s PIF %(uuid)s" % pifrec
546     log("%(message)s: %(interface-name)s configured as %(ip_configuration_mode)s" % pifrec)
547
548 def get_bond_masters_of_pif(pif):
549     """Returns a list of PIFs which are bond masters of this PIF"""
550
551     pifrec = db.get_pif_record(pif)
552
553     bso = pifrec['bond_slave_of']
554
555     # bond-slave-of is currently a single reference but in principle a
556     # PIF could be a member of several bonds which are not
557     # concurrently attached. Be robust to this possibility.
558     if not bso or bso == "OpaqueRef:NULL":
559         bso = []
560     elif not type(bso) == list:
561         bso = [bso]
562
563     bondrecs = [db.get_bond_record(bond) for bond in bso]
564     bondrecs = [rec for rec in bondrecs if rec]
565
566     return [bond['master'] for bond in bondrecs]
567
568 def get_bond_slaves_of_pif(pif):
569     """Returns a list of PIFs which make up the given bonded pif."""
570     
571     pifrec = db.get_pif_record(pif)
572
573     bmo = pifrec['bond_master_of']
574     if len(bmo) > 1:
575         raise Error("Bond-master-of contains too many elements")
576     
577     if len(bmo) == 0:
578         return []
579     
580     bondrec = db.get_bond_record(bmo[0])
581     if not bondrec:
582         raise Error("No bond record for bond master PIF")
583
584     return bondrec['slaves']
585
586 def get_vlan_slave_of_pif(pif):
587     """Find the PIF which is the VLAN slave of pif.
588
589 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
590     
591     pifrec = db.get_pif_record(pif)
592
593     vlan = pifrec['VLAN_master_of']
594     if not vlan or vlan == "OpaqueRef:NULL":
595         raise Error("PIF is not a VLAN master")
596
597     vlanrec = db.get_vlan_record(vlan)
598     if not vlanrec:
599         raise Error("No VLAN record found for PIF")
600
601     return vlanrec['tagged_PIF']
602
603 def get_vlan_masters_of_pif(pif):
604     """Returns a list of PIFs which are VLANs on top of the given pif."""
605     
606     pifrec = db.get_pif_record(pif)
607     vlans = [db.get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
608     return [v['untagged_PIF'] for v in vlans if v and db.pif_exists(v['untagged_PIF'])]
609
610 def interface_deconfigure_commands(interface):
611     # The use of [!0-9] keeps an interface of 'eth0' from matching
612     # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
613     # interfaces.
614     return ['--del-match=bridge.*.port=%s' % interface,
615             '--del-match=bonding.%s.[!0-9]*' % interface,
616             '--del-match=bonding.*.slave=%s' % interface,
617             '--del-match=vlan.%s.[!0-9]*' % interface,
618             '--del-match=port.%s.[!0-9]*' % interface,
619             '--del-match=iface.%s.[!0-9]*' % interface]
620
621 def run_command(command):
622     log("Running command: " + ' '.join(command))
623     if os.spawnl(os.P_WAIT, command[0], *command) != 0:
624         log("Command failed: " + ' '.join(command))
625         return False
626     return True
627
628 def down_netdev(interface, deconfigure=True):
629     if not interface_exists(interface):
630         log("down_netdev: interface %s does not exist, ignoring" % interface)
631         return
632     argv = ["/sbin/ifconfig", interface, 'down']
633     if deconfigure:
634         argv += ['0.0.0.0']
635
636         # Kill dhclient.
637         pidfile_name = '/var/run/dhclient-%s.pid' % interface
638         pidfile = None
639         try:
640             pidfile = open(pidfile_name, 'r')
641             os.kill(int(pidfile.readline()), signal.SIGTERM)
642         except:
643             pass
644         if pidfile != None:
645             pidfile.close()
646
647         # Remove dhclient pidfile.
648         try:
649             os.remove(pidfile_name)
650         except:
651             pass
652     run_command(argv)
653
654 def up_netdev(interface):
655     run_command(["/sbin/ifconfig", interface, 'up'])
656
657 def find_distinguished_pifs(pif):
658     """Returns the PIFs on host that own DNS and the default route.
659 The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
660 The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
661
662 Note: we prune out the bond master pif (if it exists).
663 This is because when we are called to bring up an interface with a bond master, it is implicit that
664 we should bring down that master."""
665
666     pifrec = db.get_pif_record(pif)
667
668     pifs = [ __pif for __pif in db.get_all_pifs() if
669                      (not  __pif in get_bond_masters_of_pif(pif)) ]
670
671     peerdns_pif = None
672     defaultroute_pif = None
673     
674     # loop through all the pifs on this host looking for one with
675     #   other-config:peerdns = true, and one with
676     #   other-config:default-route=true
677     for __pif in pifs:
678         __pifrec = db.get_pif_record(__pif)
679         __oc = __pifrec['other_config']
680         if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
681             if peerdns_pif == None:
682                 peerdns_pif = __pif
683             else:
684                 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
685                         (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
686         if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
687             if defaultroute_pif == None:
688                 defaultroute_pif = __pif
689             else:
690                 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
691                         (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
692     
693     # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
694     if peerdns_pif == None:
695         peerdns_pif = management_pif
696     if defaultroute_pif == None:
697         defaultroute_pif = management_pif
698
699     return peerdns_pif, defaultroute_pif
700
701 def ethtool_settings(oc):
702     # Options for "ethtool -s"
703     settings = []
704     if oc.has_key('ethtool-speed'):
705         val = oc['ethtool-speed']
706         if val in ["10", "100", "1000"]:
707             settings += ['speed', val]
708         else:
709             log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
710     if oc.has_key('ethtool-duplex'):
711         val = oc['ethtool-duplex']
712         if val in ["10", "100", "1000"]:
713             settings += ['duplex', 'val']
714         else:
715             log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
716     if oc.has_key('ethtool-autoneg'):
717         val = oc['ethtool-autoneg']
718         if val in ["true", "on"]:
719             settings += ['autoneg', 'on']
720         elif val in ["false", "off"]:
721             settings += ['autoneg', 'off']
722         else:
723             log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
724
725     # Options for "ethtool -K"
726     offload = []
727     for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
728         if oc.has_key("ethtool-" + opt):
729             val = oc["ethtool-" + opt]
730             if val in ["true", "on"]:
731                 offload += [opt, 'on']
732             elif val in ["false", "off"]:
733                 offload += [opt, 'off']
734             else:
735                 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
736
737     return settings, offload
738
739 def configure_netdev(pif):
740     pifrec = db.get_pif_record(pif)
741     datapath = datapath_name(pif)
742     ipdev = ipdev_name(pif)
743
744     nw = pifrec['network']
745     nwrec = db.get_network_record(nw)
746
747     ifconfig_argv = ['/sbin/ifconfig', ipdev, 'up']
748     gateway = ''
749     if pifrec['ip_configuration_mode'] == "DHCP":
750         pass
751     elif pifrec['ip_configuration_mode'] == "Static":
752         ifconfig_argv += [pifrec['IP']]
753         ifconfig_argv += ['netmask', pifrec['netmask']]
754         gateway = pifrec['gateway']
755     elif pifrec['ip_configuration_mode'] == "None":
756         # Nothing to do.
757         pass
758     else:
759         raise Error("Unknown IP-configuration-mode %s" % pifrec['ip_configuration_mode'])
760
761     oc = {}
762     if pifrec.has_key('other_config'):
763         oc = pifrec['other_config']
764         if oc.has_key('mtu'):
765             int(oc['mtu'])      # Check that the value is an integer
766             ifconfig_argv += ['mtu', oc['mtu']]
767
768     run_command(ifconfig_argv)
769     
770     (peerdns_pif, defaultroute_pif) = find_distinguished_pifs(pif)
771
772     if peerdns_pif == pif:
773         f = ConfigurationFile('resolv.conf', "/etc")
774         if oc.has_key('domain'):
775             f.write("search %s\n" % oc['domain'])
776         for dns in pifrec['DNS'].split(","): 
777             f.write("nameserver %s\n" % dns)
778         f.close()
779         f.apply()
780         f.commit()
781
782     if defaultroute_pif == pif and gateway != '':
783         run_command(['/sbin/ip', 'route', 'replace', 'default',
784                      'via', gateway, 'dev', ipdev])
785     
786     if oc.has_key('static-routes'):
787         for line in oc['static-routes'].split(','):
788             network, masklen, gateway = line.split('/')
789             run_command(['/sbin/ip', 'route', 'add',
790                          '%s/%s' % (netmask, masklen), 'via', gateway,
791                          'dev', ipdev])
792
793     settings, offload = ethtool_settings(oc)
794     if settings:
795         run_command(['/sbin/ethtool', '-s', ipdev] + settings)
796     if offload:
797         run_command(['/sbin/ethtool', '-K', ipdev] + offload)
798
799     if pifrec['ip_configuration_mode'] == "DHCP":
800         print
801         print "Determining IP information for %s..." % ipdev,
802         argv = ['/sbin/dhclient', '-q',
803                 '-lf', '/var/lib/dhclient/dhclient-%s.leases' % ipdev,
804                 '-pf', '/var/run/dhclient-%s.pid' % ipdev,
805                 ipdev]
806         if run_command(argv):
807             print 'done.'
808         else:
809             print 'failed.'
810
811 def modify_config(commands):
812     run_command(['/root/vswitch/bin/ovs-cfg-mod', '-vANY:console:emer',
813                  '-F', '/etc/ovs-vswitchd.conf']
814                 + commands + ['-c'])
815     run_command(['/sbin/service', 'vswitch', 'reload'])
816
817 def is_bond_pif(pif):
818     pifrec = db.get_pif_record(pif)
819     return len(pifrec['bond_master_of']) != 0
820
821 def configure_bond(pif):
822     pifrec = db.get_pif_record(pif)
823     interface = interface_name(pif)
824     ipdev = ipdev_name(pif)
825     datapath = datapath_name(pif)
826     physdevs = physdev_names(pif)
827
828     argv = ['--del-match=bonding.%s.[!0-9]*' % interface]
829     argv += ["--add=bonding.%s.slave=%s" % (interface, slave)
830              for slave in physdevs]
831
832     # Bonding options.
833     bond_options = { 
834         "mode":   "balance-slb",
835         "miimon": "100",
836         "downdelay": "200",
837         "updelay": "31000",
838         "use_carrier": "1",
839         }
840     # override defaults with values from other-config whose keys
841     # being with "bond-"
842     oc = pifrec['other_config']
843     overrides = filter(lambda (key,val):
844                            key.startswith("bond-"), oc.items())
845     overrides = map(lambda (key,val): (key[5:], val), overrides)
846     bond_options.update(overrides)
847     for (name,val) in bond_options.items():
848         argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)]
849     return argv
850
851 def action_up(pif):
852     pifrec = db.get_pif_record(pif)
853
854     bridge = bridge_name(pif)
855     interface = interface_name(pif)
856     ipdev = ipdev_name(pif)
857     datapath = datapath_name(pif)
858     physdevs = physdev_names(pif)
859     vlan_slave = None
860     if pifrec['VLAN'] != '-1':
861         vlan_slave = get_vlan_slave_of_pif(pif)
862     if vlan_slave and is_bond_pif(vlan_slave):
863         bond_master = vlan_slave
864     elif is_bond_pif(pif):
865         bond_master = pif
866     else:
867         bond_master = None
868     if bond_master:
869         bond_slaves = get_bond_slaves_of_pif(bond_master)
870     else:
871         bond_slaves = []
872     bond_masters = get_bond_masters_of_pif(pif)
873
874     # Support "rpm -e vswitch" gracefully by keeping Centos configuration
875     # files up-to-date, even though we don't use them or need them.
876     f = configure_pif(pif)
877     mode = pifrec['ip_configuration_mode']
878     if bridge:
879         log("Configuring %s using %s configuration" % (bridge, mode))
880         br = open_network_ifcfg(pif)
881         configure_network(pif, br)
882         br.close()
883         f.attach_child(br)
884     else:
885         log("Configuring %s using %s configuration" % (interface, mode))
886         configure_network(pif, f)
887     f.close()
888     for master in bond_masters:
889         master_bridge = bridge_name(master)
890         removed = unconfigure_pif(master)
891         f.attach_child(removed)
892         if master_bridge:
893             removed = open_network_ifcfg(master)
894             log("Unlinking stale file %s" % removed.path())
895             removed.unlink()
896             f.attach_child(removed)
897
898     # /etc/xensource/scripts/vif needs to know where to add VIFs.
899     if vlan_slave:
900         if not os.path.exists(vswitch_config_dir):
901             os.mkdir(vswitch_config_dir)
902         br = ConfigurationFile("br-%s" % bridge, vswitch_config_dir)
903         br.write("VLAN_SLAVE=%s\n" % datapath)
904         br.write("VLAN_VID=%s\n" % pifrec['VLAN'])
905         br.close()
906         f.attach_child(br)
907
908     # Update all configuration files (both ours and Centos's).
909     f.apply()
910     f.commit()
911
912     # "ifconfig down" the network device and delete its IP address, etc.
913     down_netdev(ipdev)
914     for physdev in physdevs:
915         down_netdev(physdev)
916
917     # If we are bringing up a bond, remove IP addresses from the
918     # slaves (because we are implicitly being asked to take them down).
919     # 
920     # Conversely, if we are bringing up an interface that has bond
921     # masters, remove IP addresses from the bond master (because we
922     # are implicitly being asked to take it down).
923     for bond_pif in bond_slaves + bond_masters:
924         run_command(["/sbin/ifconfig", ipdev_name(bond_pif), '0.0.0.0']) 
925
926     # Remove all keys related to pif and any bond masters linked to PIF.
927     del_ports = [ipdev] + physdevs + bond_masters
928     if vlan_slave and bond_master:
929         del_ports += [interface_name(bond_master)]
930     
931     # What ports do we need to add to the datapath?
932     #
933     # We definitely need the ipdev, and ordinarily we want the
934     # physical devices too, but for bonds we need the bond as bridge
935     # port.
936     add_ports = [ipdev, datapath]
937     if not bond_master:
938         add_ports += physdevs
939     else:
940         add_ports += [interface_name(bond_master)]
941
942     # What ports do we need to delete?
943     #
944     #  - All the ports that we add, to avoid duplication and to drop
945     #    them from another datapath in case they're misassigned.
946     #    
947     #  - The physical devices, since they will either be in add_ports
948     #    or added to the bonding device (see below).
949     #
950     #  - The bond masters for pif.  (Ordinarily pif shouldn't have any
951     #    bond masters.  If it does then interface-reconfigure is
952     #    implicitly being asked to take them down.)
953     del_ports = add_ports + physdevs + bond_masters
954
955     # What networks does this datapath carry?
956     #
957     # - The network corresponding to the datapath's PIF.
958     #
959     # - The networks corresponding to any VLANs attached to the
960     #   datapath's PIF.
961     network_uuids = []
962     for nwpif in db.get_pifs_by_device({'device': pifrec['device']}):
963         net = db.get_pif_record(nwpif)['network']
964         network_uuids += [db.get_network_record(net)['uuid']]
965
966     # Bring up bond slaves early, because ovs-vswitchd initially
967     # enables or disables bond slaves based on whether carrier is
968     # detected when they are added, and a network device that is down
969     # always reports "no carrier".
970     bond_slave_physdevs = []
971     for slave in bond_slaves:
972         bond_slave_physdevs += physdev_names(slave)
973     for slave_physdev in bond_slave_physdevs:
974         up_netdev(slave_physdev)
975
976     # Now modify the ovs-vswitchd config file.
977     argv = []
978     for port in set(del_ports):
979         argv += interface_deconfigure_commands(port)
980     for port in set(add_ports):
981         argv += ['--add=bridge.%s.port=%s' % (datapath, port)]
982     if vlan_slave:
983         argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])]
984         argv += ['--add=iface.%s.internal=true' % (ipdev)]
985
986         # xapi creates a bridge by the name of the ipdev and requires
987         # that the IP address will be on it.  We need to delete this
988         # bridge because we need that device to be a member of our
989         # datapath.
990         argv += ['--del-match=bridge.%s.[!0-9]*' % ipdev]
991
992         # xapi insists that its attempts to create the bridge succeed,
993         # so force that to happen.
994         argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)]
995     else:
996         try:
997             os.unlink("%s/br-%s" % (vswitch_config_dir, bridge))
998         except OSError:
999             pass
1000     argv += ['--del-match=bridge.%s.xs-network-uuids=*' % datapath]
1001     argv += ['--add=bridge.%s.xs-network-uuids=%s' % (datapath, uuid)
1002              for uuid in set(network_uuids)]
1003     if bond_master:
1004         argv += configure_bond(bond_master)
1005     modify_config(argv)
1006
1007     # Configure network devices.
1008     configure_netdev(pif)
1009
1010     # Bring up VLAN slave, plus physical devices other than bond
1011     # slaves (which we brought up earlier).
1012     if vlan_slave:
1013         up_netdev(ipdev_name(vlan_slave))
1014     for physdev in set(physdevs) - set(bond_slave_physdevs):
1015         up_netdev(physdev)
1016
1017     # Update /etc/issue (which contains the IP address of the management interface)
1018     os.system("/sbin/update-issue")
1019
1020     if bond_slaves:
1021         # There seems to be a race somewhere: without this sleep, using
1022         # XenCenter to create a bond that becomes the management interface
1023         # fails with "The underlying connection was closed: A connection that
1024         # was expected to be kept alive was closed by the server." on every
1025         # second or third try, even though /var/log/messages doesn't show
1026         # anything unusual.
1027         #
1028         # The race is probably present even without vswitch, but bringing up a
1029         # bond without vswitch involves a built-in pause of 10 seconds or more
1030         # to wait for the bond to transition from learning to forwarding state.
1031         time.sleep(5)
1032         
1033 def action_down(pif):
1034     rec = db.get_pif_record(pif)    
1035     interface = interface_name(pif)
1036     bridge = bridge_name(pif)
1037     ipdev = ipdev_name(pif)
1038
1039     # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1040     # files up-to-date, even though we don't use them or need them.
1041     f = unconfigure_pif(pif)
1042     if bridge:
1043         br = open_network_ifcfg(pif)
1044         log("Unlinking stale file %s" % br.path())
1045         br.unlink()
1046         f.attach_child(br)
1047     try:
1048         f.apply()
1049         f.commit()
1050     except Error, e:
1051         log("action_down failed to apply changes: %s" % e.msg)
1052         f.revert()
1053         raise
1054
1055     argv = []
1056     if rec['VLAN'] != '-1':
1057         # Get rid of the VLAN device itself.
1058         down_netdev(ipdev)
1059         argv += interface_deconfigure_commands(ipdev)
1060
1061         # If the VLAN's slave is attached, stop here.
1062         slave = get_vlan_slave_of_pif(pif)
1063         if db.get_pif_record(slave)['currently_attached']:
1064             log("VLAN slave is currently attached")
1065             modify_config(argv)
1066             return
1067         
1068         # If the VLAN's slave has other VLANs that are attached, stop here.
1069         masters = get_vlan_masters_of_pif(slave)
1070         for m in masters:
1071             if m != pif and db.get_pif_record(m)['currently_attached']:
1072                 log("VLAN slave has other master %s" % interface_naem(m))
1073                 modify_config(argv)
1074                 return
1075
1076         # Otherwise, take down the VLAN's slave too.
1077         log("No more masters, bring down vlan slave %s" % interface_name(slave))
1078         pif = slave
1079     else:
1080         # Stop here if this PIF has attached VLAN masters.
1081         vlan_masters = get_vlan_masters_of_pif(pif)
1082         log("VLAN masters of %s - %s" % (rec['device'], [interface_name(m) for m in vlan_masters]))
1083         for m in vlan_masters:
1084             if db.get_pif_record(m)['currently_attached']:
1085                 log("Leaving %s up due to currently attached VLAN master %s" % (interface, interface_name(m)))
1086                 return
1087
1088     # pif is now either a bond or a physical device which needs to be
1089     # brought down.  pif might have changed so re-check all its attributes.
1090     rec = db.get_pif_record(pif)
1091     interface = interface_name(pif)
1092     bridge = bridge_name(pif)
1093     ipdev = ipdev_name(pif)
1094
1095
1096     bond_slaves = get_bond_slaves_of_pif(pif)
1097     log("bond slaves of %s - %s" % (rec['device'], [interface_name(s) for s in bond_slaves]))
1098     for slave in bond_slaves:
1099         slave_interface = interface_name(slave)
1100         log("bring down bond slave %s" % slave_interface)
1101         argv += interface_deconfigure_commands(slave_interface)
1102         down_netdev(slave_interface)
1103
1104     argv += interface_deconfigure_commands(ipdev)
1105     down_netdev(ipdev)
1106
1107     argv += ['--del-match', 'bridge.%s.*' % datapath_name(pif)]
1108     argv += ['--del-match', 'bonding.%s.[!0-9]*' % interface]
1109     modify_config(argv)
1110
1111 def action_rewrite(pif):
1112     # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1113     # files up-to-date, even though we don't use them or need them.
1114     pifrec = db.get_pif_record(pif)
1115     f = configure_pif(pif)
1116     interface = interface_name(pif)
1117     bridge = bridge_name(pif)
1118     mode = pifrec['ip_configuration_mode']
1119     if bridge:
1120         log("Configuring %s using %s configuration" % (bridge, mode))
1121         br = open_network_ifcfg(pif)
1122         configure_network(pif, br)
1123         br.close()
1124         f.attach_child(br)
1125     else:
1126         log("Configuring %s using %s configuration" % (interface, mode))
1127         configure_network(pif, f)
1128     f.close()
1129     try:
1130         f.apply()
1131         f.commit()
1132     except Error, e:
1133         log("failed to apply changes: %s" % e.msg)
1134         f.revert()
1135         raise
1136
1137     # We have no code of our own to run here.
1138     pass
1139
1140 def main(argv=None):
1141     global output_directory, management_pif
1142     
1143     session = None
1144     pif_uuid = None
1145     pif = None
1146
1147     force_interface = None
1148     force_management = False
1149     
1150     if argv is None:
1151         argv = sys.argv
1152
1153     try:
1154         try:
1155             shortops = "h"
1156             longops = [ "output-directory=",
1157                         "pif=", "pif-uuid=",
1158                         "session=",
1159                         "force=",
1160                         "force-interface=",
1161                         "management",
1162                         "test-mode",
1163                         "device=", "mode=", "ip=", "netmask=", "gateway=",
1164                         "help" ]
1165             arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops)
1166         except getopt.GetoptError, msg:
1167             raise Usage(msg)
1168
1169         force_rewrite_config = {}
1170         
1171         for o,a in arglist:
1172             if o == "--output-directory":
1173                 output_directory = a
1174             elif o == "--pif":
1175                 pif = a
1176             elif o == "--pif-uuid":
1177                 pif_uuid = a
1178             elif o == "--session":
1179                 session = a
1180             elif o == "--force-interface" or o == "--force":
1181                 force_interface = a
1182             elif o == "--management":
1183                 force_management = True
1184             elif o in ["--device", "--mode", "--ip", "--netmask", "--gateway"]:
1185                 force_rewrite_config[o[2:]] = a
1186             elif o == "-h" or o == "--help":
1187                 print __doc__ % {'command-name': os.path.basename(argv[0])}
1188                 return 0
1189
1190         if not debug_mode():
1191             syslog.openlog(os.path.basename(argv[0]))
1192             log("Called as " + str.join(" ", argv))
1193         if len(args) < 1:
1194             raise Usage("Required option <action> not present")
1195         if len(args) > 1:
1196             raise Usage("Too many arguments")
1197
1198         action = args[0]
1199         # backwards compatibility
1200         if action == "rewrite-configuration": action = "rewrite"
1201         
1202         if output_directory and ( session or pif ):
1203             raise Usage("--session/--pif cannot be used with --output-directory")
1204         if ( session or pif ) and pif_uuid:
1205             raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
1206         if ( session and not pif ) or ( not session and pif ):
1207             raise Usage("--session and --pif must be used together.")
1208         if force_interface and ( session or pif or pif_uuid ):
1209             raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
1210         if len(force_rewrite_config) and not (force_interface and action == "rewrite"):
1211             raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
1212
1213         global db
1214         if force_interface:
1215             log("Force interface %s %s" % (force_interface, action))
1216
1217             if action == "rewrite":
1218                 action_force_rewrite(force_interface, force_rewrite_config)
1219             else:
1220                 db = DatabaseCache(cache_file=dbcache_file)
1221                 pif = db.get_pif_by_bridge(force_interface)
1222                 management_pif = db.get_management_pif()
1223
1224                 if action == "up":
1225                     action_up(pif)
1226                 elif action == "down":
1227                     action_down(pif)
1228                 else:
1229                     raise Usage("Unknown action %s"  % action)
1230         else:
1231             db = DatabaseCache(session_ref=session)
1232
1233             if pif_uuid:
1234                 pif = db.get_pif_by_uuid(pif_uuid)
1235         
1236             if not pif:
1237                 raise Usage("No PIF given")
1238
1239             if force_management:
1240                 # pif is going to be the management pif 
1241                 management_pif = pif
1242             else:
1243                 # pif is not going to be the management pif.
1244                 # Search DB cache for pif on same host with management=true
1245                 pifrec = db.get_pif_record(pif)
1246                 management_pif = db.get_management_pif()
1247
1248             log_pif_action(action, pif)
1249
1250             if not check_allowed(pif):
1251                 return 0
1252
1253             if action == "up":
1254                 action_up(pif)
1255             elif action == "down":
1256                 action_down(pif)
1257             elif action == "rewrite":
1258                 action_rewrite(pif)
1259             else:
1260                 raise Usage("Unknown action %s"  % action)
1261
1262             # Save cache.
1263             pifrec = db.get_pif_record(pif)
1264             db.save(dbcache_file)
1265         
1266     except Usage, err:
1267         print >>sys.stderr, err.msg
1268         print >>sys.stderr, "For help use --help."
1269         return 2
1270     except Error, err:
1271         log(err.msg)
1272         return 1
1273     
1274     return 0
1275 \f
1276 # The following code allows interface-reconfigure to keep Centos
1277 # network configuration files up-to-date, even though the vswitch
1278 # never uses them.  In turn, that means that "rpm -e vswitch" does not
1279 # have to update any configuration files.
1280
1281 def configure_ethtool(oc, f):
1282     # Options for "ethtool -s"
1283     settings = None
1284     setting_opts = ["autoneg", "speed", "duplex"]
1285     # Options for "ethtool -K"
1286     offload = None
1287     offload_opts = ["rx", "tx", "sg", "tso", "ufo", "gso"]
1288     
1289     for opt in [opt for opt in setting_opts + offload_opts if oc.has_key("ethtool-" + opt)]:
1290         val = oc["ethtool-" + opt]
1291
1292         if opt in ["speed"]:
1293             if val in ["10", "100", "1000"]:
1294                 val = "speed " + val
1295             else:
1296                 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
1297                 val = None
1298         elif opt in ["duplex"]:
1299             if val in ["half", "full"]:
1300                 val = "duplex " + val
1301             else:
1302                 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
1303                 val = None
1304         elif opt in ["autoneg"] + offload_opts:
1305             if val in ["true", "on"]:
1306                 val = opt + " on"
1307             elif val in ["false", "off"]:
1308                 val = opt + " off"
1309             else:
1310                 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
1311                 val = None
1312
1313         if opt in setting_opts:
1314             if val and settings:
1315                 settings = settings + " " + val
1316             else:
1317                 settings = val
1318         elif opt in offload_opts:
1319             if val and offload:
1320                 offload = offload + " " + val
1321             else:
1322                 offload = val
1323
1324     if settings:
1325         f.write("ETHTOOL_OPTS=\"%s\"\n" % settings)
1326     if offload:
1327         f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % offload)
1328
1329 def configure_mtu(oc, f):
1330     if not oc.has_key('mtu'):
1331         return
1332     
1333     try:
1334         mtu = int(oc['mtu'])
1335         f.write("MTU=%d\n" % mtu)
1336     except ValueError, x:
1337         log("Invalid value for mtu = %s" % mtu)
1338
1339 def configure_static_routes(interface, oc, f):
1340     """Open a route-<interface> file for static routes.
1341     
1342     Opens the static routes configuration file for interface and writes one
1343     line for each route specified in the network's other config "static-routes" value.
1344     E.g. if
1345            interface ( RO): xenbr1
1346            other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
1347
1348     Then route-xenbr1 should be
1349           172.16.0.0/15 via 192.168.0.3 dev xenbr1
1350           172.18.0.0/16 via 192.168.0.4 dev xenbr1
1351     """
1352     fname = "route-%s" % interface
1353     if oc.has_key('static-routes'):
1354         # The key is present - extract comma seperates entries
1355         lines = oc['static-routes'].split(',')
1356     else:
1357         # The key is not present, i.e. there are no static routes
1358         lines = []
1359
1360     child = ConfigurationFile(fname)
1361     child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1362             (os.path.basename(child.path()), os.path.basename(sys.argv[0])))
1363
1364     try:
1365         for l in lines:
1366             network, masklen, gateway = l.split('/')
1367             child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface))
1368             
1369         f.attach_child(child)
1370         child.close()
1371
1372     except ValueError, e:
1373         log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e))
1374
1375 def __open_ifcfg(interface):
1376     """Open a network interface configuration file.
1377
1378     Opens the configuration file for interface, writes a header and
1379     common options and returns the file object.
1380     """
1381     fname = "ifcfg-%s" % interface
1382     f = ConfigurationFile(fname)
1383     
1384     f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1385             (os.path.basename(f.path()), os.path.basename(sys.argv[0])))
1386     f.write("XEMANAGED=yes\n")
1387     f.write("DEVICE=%s\n" % interface)
1388     f.write("ONBOOT=no\n")
1389     
1390     return f
1391
1392 def open_network_ifcfg(pif):    
1393     bridge = bridge_name(pif)
1394     interface = interface_name(pif)
1395     if bridge:
1396         return __open_ifcfg(bridge)
1397     else:
1398         return __open_ifcfg(interface)
1399
1400
1401 def open_pif_ifcfg(pif):
1402     pifrec = db.get_pif_record(pif)
1403     
1404     log("Configuring %s (%s)" % (interface_name(pif), pifrec['MAC']))
1405     
1406     f = __open_ifcfg(interface_name(pif))
1407
1408     if pifrec.has_key('other_config'):
1409         configure_ethtool(pifrec['other_config'], f)
1410         configure_mtu(pifrec['other_config'], f)
1411
1412     return f
1413
1414 def configure_network(pif, f):
1415     """Write the configuration file for a network.
1416
1417     Writes configuration derived from the network object into the relevant 
1418     ifcfg file.  The configuration file is passed in, but if the network is 
1419     bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
1420
1421     This routine may also write ifcfg files of the networks corresponding to other PIFs
1422     in order to maintain consistency.
1423
1424     params:
1425         pif:  Opaque_ref of pif
1426         f :   ConfigurationFile(/path/to/ifcfg) to which we append network configuration
1427     """
1428     
1429     pifrec = db.get_pif_record(pif)
1430     nw = pifrec['network']
1431     nwrec = db.get_network_record(nw)
1432     oc = None
1433     bridge = bridge_name(pif)
1434     interface = interface_name(pif)
1435     if bridge:
1436         device = bridge
1437     else:
1438         device = interface
1439
1440     if nwrec.has_key('other_config'):
1441         configure_ethtool(nwrec['other_config'], f)
1442         configure_mtu(nwrec['other_config'], f)
1443         configure_static_routes(device, nwrec['other_config'], f)
1444
1445     
1446     if pifrec.has_key('other_config'):
1447         oc = pifrec['other_config']
1448
1449     if device == bridge:
1450         f.write("TYPE=Bridge\n")
1451         f.write("DELAY=0\n")
1452         f.write("STP=off\n")
1453         f.write("PIFDEV=%s\n" % interface_name(pif))
1454
1455     if pifrec['ip_configuration_mode'] == "DHCP":
1456         f.write("BOOTPROTO=dhcp\n")
1457         f.write("PERSISTENT_DHCLIENT=yes\n")
1458     elif pifrec['ip_configuration_mode'] == "Static":
1459         f.write("BOOTPROTO=none\n") 
1460         f.write("NETMASK=%(netmask)s\n" % pifrec)
1461         f.write("IPADDR=%(IP)s\n" % pifrec)
1462         f.write("GATEWAY=%(gateway)s\n" % pifrec)
1463     elif pifrec['ip_configuration_mode'] == "None":
1464         f.write("BOOTPROTO=none\n")
1465     else:
1466         raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode'])
1467
1468     if pifrec.has_key('DNS') and pifrec['DNS'] != "":
1469         ServerList = pifrec['DNS'].split(",")
1470         for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i]))
1471     if oc and oc.has_key('domain'):
1472         f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' '))
1473
1474     # We only allow one ifcfg-xenbr* to have PEERDNS=yes and there can be only one GATEWAYDEV in /etc/sysconfig/network.
1475     # The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
1476     # The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
1477
1478     # Work out which pif on this host should be the one with PEERDNS=yes and which should be the GATEWAYDEV
1479     #
1480     # Note: we prune out the bond master pif (if it exists).
1481     # This is because when we are called to bring up an interface with a bond master, it is implicit that
1482     # we should bring down that master.
1483     pifs_on_host = [ __pif for __pif in db.get_all_pifs() if
1484                      not __pif in get_bond_masters_of_pif(pif) ]
1485     other_pifs_on_host = [ __pif for __pif in pifs_on_host if __pif != pif ]
1486
1487     peerdns_pif = None
1488     defaultroute_pif = None
1489     
1490     # loop through all the pifs on this host looking for one with
1491     #   other-config:peerdns = true, and one with
1492     #   other-config:default-route=true
1493     for __pif in pifs_on_host:
1494         __pifrec = db.get_pif_record(__pif)
1495         __oc = __pifrec['other_config']
1496         if __oc.has_key('peerdns') and __oc['peerdns'] == 'true':
1497             if peerdns_pif == None:
1498                 peerdns_pif = __pif
1499             else:
1500                 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
1501                         (db.get_pif_record(peerdns_pif)['device'], __pifrec['device']))
1502         if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true':
1503             if defaultroute_pif == None:
1504                 defaultroute_pif = __pif
1505             else:
1506                 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
1507                         (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device']))
1508     
1509     # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
1510     if peerdns_pif == None:
1511         peerdns_pif = management_pif
1512     if defaultroute_pif == None:
1513         defaultroute_pif = management_pif
1514         
1515     # Update all the other network's ifcfg files and ensure consistency
1516     for __pif in other_pifs_on_host:
1517         __f = open_network_ifcfg(__pif)
1518         peerdns_line_wanted = 'PEERDNS=%s\n' % ((__pif == peerdns_pif) and 'yes' or 'no')
1519         lines =  __f.readlines()
1520
1521         if not peerdns_line_wanted in lines:
1522             # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting
1523             for line in lines:
1524                 if not line.lstrip().startswith('PEERDNS'):
1525                     __f.write(line)
1526             log("Setting %s in %s" % (peerdns_line_wanted.strip(), __f.path()))
1527             __f.write(peerdns_line_wanted)
1528             __f.close()
1529             f.attach_child(__f)
1530
1531         else:
1532             # There is no need to change this ifcfg file.  So don't attach_child.
1533             pass
1534         
1535     # ... and for this pif too
1536     f.write('PEERDNS=%s\n' % ((pif == peerdns_pif) and 'yes' or 'no'))
1537     
1538     # Update gatewaydev
1539     fnetwork = ConfigurationFile("network", "/etc/sysconfig")
1540     for line in fnetwork.readlines():
1541         if line.lstrip().startswith('GATEWAY') :
1542             continue
1543         fnetwork.write(line)
1544     if defaultroute_pif:
1545         gatewaydev = bridge_name(defaultroute_pif)
1546         if not gatewaydev:
1547             gatewaydev = interface_name(defaultroute_pif)
1548         fnetwork.write('GATEWAYDEV=%s\n' % gatewaydev)
1549     fnetwork.close()
1550     f.attach_child(fnetwork)
1551
1552     return
1553
1554
1555 def configure_physical_interface(pif):
1556     """Write the configuration for a physical interface.
1557
1558     Writes the configuration file for the physical interface described by
1559     the pif object.
1560
1561     Returns the open file handle for the interface configuration file.
1562     """
1563
1564     pifrec = db.get_pif_record(pif)
1565
1566     f = open_pif_ifcfg(pif)
1567     
1568     f.write("TYPE=Ethernet\n")
1569     f.write("HWADDR=%(MAC)s\n" % pifrec)
1570
1571     return f
1572
1573 def configure_bond_interface(pif):
1574     """Write the configuration for a bond interface.
1575
1576     Writes the configuration file for the bond interface described by
1577     the pif object. Handles writing the configuration for the slave
1578     interfaces.
1579
1580     Returns the open file handle for the bond interface configuration
1581     file.
1582     """
1583
1584     pifrec = db.get_pif_record(pif)
1585     oc = pifrec['other_config']
1586     f = open_pif_ifcfg(pif)
1587
1588     if pifrec['MAC'] != "":
1589         f.write("MACADDR=%s\n" % pifrec['MAC'])
1590
1591     for slave in get_bond_slaves_of_pif(pif):
1592         s = configure_physical_interface(slave)
1593         s.write("MASTER=%(device)s\n" % pifrec)
1594         s.write("SLAVE=yes\n")
1595         s.close()
1596         f.attach_child(s)
1597
1598     # The bond option defaults
1599     bond_options = { 
1600         "mode":   "balance-slb",
1601         "miimon": "100",
1602         "downdelay": "200",
1603         "updelay": "31000",
1604         "use_carrier": "1",
1605         }
1606
1607     # override defaults with values from other-config whose keys being with "bond-"
1608     overrides = filter(lambda (key,val): key.startswith("bond-"), oc.items())
1609     overrides = map(lambda (key,val): (key[5:], val), overrides)
1610     bond_options.update(overrides)
1611
1612     # write the bond options to ifcfg-bondX
1613     f.write('BONDING_OPTS="')
1614     for (name,val) in bond_options.items():
1615         f.write("%s=%s " % (name,val))
1616     f.write('"\n')
1617     return f
1618
1619 def configure_vlan_interface(pif):
1620     """Write the configuration for a VLAN interface.
1621
1622     Writes the configuration file for the VLAN interface described by
1623     the pif object. Handles writing the configuration for the master
1624     interface if necessary.
1625
1626     Returns the open file handle for the VLAN interface configuration
1627     file.
1628     """
1629
1630     slave = configure_pif(get_vlan_slave_of_pif(pif))
1631     slave.close()
1632
1633     f = open_pif_ifcfg(pif)
1634     f.write("VLAN=yes\n")
1635     f.attach_child(slave)
1636     
1637     return f
1638
1639 def configure_pif(pif):
1640     """Write the configuration for a PIF object.
1641
1642     Writes the configuration file the PIF and all dependent
1643     interfaces (bond slaves and VLAN masters etc).
1644
1645     Returns the open file handle for the interface configuration file.
1646     """
1647
1648     pifrec = db.get_pif_record(pif)
1649
1650     if pifrec['VLAN'] != '-1':
1651         f = configure_vlan_interface(pif)
1652     elif len(pifrec['bond_master_of']) != 0:
1653         f = configure_bond_interface(pif)
1654     else:
1655         f = configure_physical_interface(pif)
1656
1657     bridge = bridge_name(pif)
1658     if bridge:
1659         f.write("BRIDGE=%s\n" % bridge)
1660
1661     return f
1662
1663 def unconfigure_pif(pif):
1664     """Clear up the files created by configure_pif"""
1665     f = open_pif_ifcfg(pif)
1666     log("Unlinking stale file %s" % f.path())
1667     f.unlink()
1668     return f
1669 \f
1670 if __name__ == "__main__":
1671     rc = 1
1672     try:
1673         rc = main()
1674     except:
1675         ex = sys.exc_info()
1676         err = traceback.format_exception(*ex)
1677         for exline in err:
1678             log(exline)
1679
1680     if not debug_mode():
1681         syslog.closelog()
1682         
1683     sys.exit(rc)