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