Merge branch 'next' of repo.nicira.com:/srv/git/openvswitch into next
[sliver-openvswitch.git] / xenserver / opt_xensource_libexec_InterfaceReconfigure.py
1 # Copyright (c) 2008,2009 Citrix Systems, Inc.
2 #
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU Lesser General Public License as published
5 # by the Free Software Foundation; version 2.1 only. with the special
6 # exception on linking described in file LICENSE.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 # GNU Lesser General Public License for more details.
12 #
13 import syslog
14 import os
15
16 from xml.dom.minidom import getDOMImplementation
17 from xml.dom.minidom import parse as parseXML
18
19 #
20 # Logging.
21 #
22
23 def log(s):
24     syslog.syslog(s)
25
26 #
27 # Exceptions.
28 #
29
30 class Error(Exception):
31     def __init__(self, msg):
32         Exception.__init__(self)
33         self.msg = msg
34
35 #
36 # Run external utilities
37 #
38
39 def run_command(command):
40     log("Running command: " + ' '.join(command))
41     rc = os.spawnl(os.P_WAIT, command[0], *command)
42     if rc != 0:
43         log("Command failed %d: " % rc + ' '.join(command))
44         return False
45     return True
46
47 #
48 # Configuration File Handling.
49 #
50
51 class ConfigurationFile(object):
52     """Write a file, tracking old and new versions.
53
54     Supports writing a new version of a file and applying and
55     reverting those changes.
56     """
57
58     __STATE = {"OPEN":"OPEN",
59                "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
60                "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
61
62     def __init__(self, path):
63         dirname,basename = os.path.split(path)
64
65         self.__state = self.__STATE['OPEN']
66         self.__children = []
67
68         self.__path    = os.path.join(dirname, basename)
69         self.__oldpath = os.path.join(dirname, "." + basename + ".xapi-old")
70         self.__newpath = os.path.join(dirname, "." + basename + ".xapi-new")
71
72         self.__f = open(self.__newpath, "w")
73
74     def attach_child(self, child):
75         self.__children.append(child)
76
77     def path(self):
78         return self.__path
79
80     def readlines(self):
81         try:
82             return open(self.path()).readlines()
83         except:
84             return ""
85
86     def write(self, args):
87         if self.__state != self.__STATE['OPEN']:
88             raise Error("Attempt to write to file in state %s" % self.__state)
89         self.__f.write(args)
90
91     def close(self):
92         if self.__state != self.__STATE['OPEN']:
93             raise Error("Attempt to close file in state %s" % self.__state)
94
95         self.__f.close()
96         self.__state = self.__STATE['NOT-APPLIED']
97
98     def changed(self):
99         if self.__state != self.__STATE['NOT-APPLIED']:
100             raise Error("Attempt to compare file in state %s" % self.__state)
101
102         return True
103
104     def apply(self):
105         if self.__state != self.__STATE['NOT-APPLIED']:
106             raise Error("Attempt to apply configuration from state %s" % self.__state)
107
108         for child in self.__children:
109             child.apply()
110
111         log("Applying changes to %s configuration" % self.__path)
112
113         # Remove previous backup.
114         if os.access(self.__oldpath, os.F_OK):
115             os.unlink(self.__oldpath)
116
117         # Save current configuration.
118         if os.access(self.__path, os.F_OK):
119             os.link(self.__path, self.__oldpath)
120             os.unlink(self.__path)
121
122         # Apply new configuration.
123         assert(os.path.exists(self.__newpath))
124         os.link(self.__newpath, self.__path)
125
126         # Remove temporary file.
127         os.unlink(self.__newpath)
128
129         self.__state = self.__STATE['APPLIED']
130
131     def revert(self):
132         if self.__state != self.__STATE['APPLIED']:
133             raise Error("Attempt to revert configuration from state %s" % self.__state)
134
135         for child in self.__children:
136             child.revert()
137
138         log("Reverting changes to %s configuration" % self.__path)
139
140         # Remove existing new configuration
141         if os.access(self.__newpath, os.F_OK):
142             os.unlink(self.__newpath)
143
144         # Revert new configuration.
145         if os.access(self.__path, os.F_OK):
146             os.link(self.__path, self.__newpath)
147             os.unlink(self.__path)
148
149         # Revert to old configuration.
150         if os.access(self.__oldpath, os.F_OK):
151             os.link(self.__oldpath, self.__path)
152             os.unlink(self.__oldpath)
153
154         # Leave .*.xapi-new as an aid to debugging.
155
156         self.__state = self.__STATE['REVERTED']
157
158     def commit(self):
159         if self.__state != self.__STATE['APPLIED']:
160             raise Error("Attempt to commit configuration from state %s" % self.__state)
161
162         for child in self.__children:
163             child.commit()
164
165         log("Committing changes to %s configuration" % self.__path)
166
167         if os.access(self.__oldpath, os.F_OK):
168             os.unlink(self.__oldpath)
169         if os.access(self.__newpath, os.F_OK):
170             os.unlink(self.__newpath)
171
172         self.__state = self.__STATE['COMMITTED']
173
174 #
175 # Helper functions for encoding/decoding database attributes to/from XML.
176 #
177
178 def _str_to_xml(xml, parent, tag, val):
179     e = xml.createElement(tag)
180     parent.appendChild(e)
181     v = xml.createTextNode(val)
182     e.appendChild(v)
183 def _str_from_xml(n):
184     def getText(nodelist):
185         rc = ""
186         for node in nodelist:
187             if node.nodeType == node.TEXT_NODE:
188                 rc = rc + node.data
189         return rc
190     return getText(n.childNodes).strip()
191
192 def _bool_to_xml(xml, parent, tag, val):
193     if val:
194         _str_to_xml(xml, parent, tag, "True")
195     else:
196         _str_to_xml(xml, parent, tag, "False")
197 def _bool_from_xml(n):
198     s = _str_from_xml(n)
199     if s == "True":
200         return True
201     elif s == "False":
202         return False
203     else:
204         raise Error("Unknown boolean value %s" % s)
205
206 def _strlist_to_xml(xml, parent, ltag, itag, val):
207     e = xml.createElement(ltag)
208     parent.appendChild(e)
209     for v in val:
210         c = xml.createElement(itag)
211         e.appendChild(c)
212         cv = xml.createTextNode(v)
213         c.appendChild(cv)
214 def _strlist_from_xml(n, ltag, itag):
215     ret = []
216     for n in n.childNodes:
217         if n.nodeName == itag:
218             ret.append(_str_from_xml(n))
219     return ret
220
221 def _otherconfig_to_xml(xml, parent, val, attrs):
222     otherconfig = xml.createElement("other_config")
223     parent.appendChild(otherconfig)
224     for n,v in val.items():
225         if not n in attrs:
226             raise Error("Unknown other-config attribute: %s" % n)
227         _str_to_xml(xml, otherconfig, n, v)
228 def _otherconfig_from_xml(n, attrs):
229     ret = {}
230     for n in n.childNodes:
231         if n.nodeName in attrs:
232             ret[n.nodeName] = _str_from_xml(n)
233     return ret
234
235 #
236 # Definitions of the database objects (and their attributes) used by interface-reconfigure.
237 #
238 # Each object is defined by a dictionary mapping an attribute name in
239 # the xapi database to a tuple containing two items:
240 #  - a function which takes this attribute and encodes it as XML.
241 #  - a function which takes XML and decocdes it into a value.
242 #
243 # other-config attributes are specified as a simple array of strings
244
245 _PIF_XML_TAG = "pif"
246 _VLAN_XML_TAG = "vlan"
247 _BOND_XML_TAG = "bond"
248 _NETWORK_XML_TAG = "network"
249
250 _ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
251
252 _PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
253                         [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
254                         _ETHTOOL_OTHERCONFIG_ATTRS
255
256 _PIF_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
257                'management': (_bool_to_xml,_bool_from_xml),
258                'network': (_str_to_xml,_str_from_xml),
259                'device': (_str_to_xml,_str_from_xml),
260                'bond_master_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
261                                   lambda n: _strlist_from_xml(n, 'bond_master_of', 'slave')),
262                'bond_slave_of': (_str_to_xml,_str_from_xml),
263                'VLAN': (_str_to_xml,_str_from_xml),
264                'VLAN_master_of': (_str_to_xml,_str_from_xml),
265                'VLAN_slave_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
266                                  lambda n: _strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
267                'ip_configuration_mode': (_str_to_xml,_str_from_xml),
268                'IP': (_str_to_xml,_str_from_xml),
269                'netmask': (_str_to_xml,_str_from_xml),
270                'gateway': (_str_to_xml,_str_from_xml),
271                'DNS': (_str_to_xml,_str_from_xml),
272                'MAC': (_str_to_xml,_str_from_xml),
273                'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _PIF_OTHERCONFIG_ATTRS),
274                                 lambda n: _otherconfig_from_xml(n, _PIF_OTHERCONFIG_ATTRS)),
275
276                # Special case: We write the current value
277                # PIF.currently-attached to the cache but since it will
278                # not be valid when we come to use the cache later
279                # (i.e. after a reboot) we always read it as False.
280                'currently_attached': (_bool_to_xml, lambda n: False),
281              }
282
283 _VLAN_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
284                 'tagged_PIF': (_str_to_xml,_str_from_xml),
285                 'untagged_PIF': (_str_to_xml,_str_from_xml),
286               }
287
288 _BOND_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
289                'master': (_str_to_xml,_str_from_xml),
290                'slaves': (lambda x, p, t, v: _strlist_to_xml(x, p, 'slaves', 'slave', v),
291                           lambda n: _strlist_from_xml(n, 'slaves', 'slave')),
292               }
293
294 _NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + _ETHTOOL_OTHERCONFIG_ATTRS
295
296 _NETWORK_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
297                    'bridge': (_str_to_xml,_str_from_xml),
298                    'PIFs': (lambda x, p, t, v: _strlist_to_xml(x, p, 'PIFs', 'PIF', v),
299                             lambda n: _strlist_from_xml(n, 'PIFs', 'PIF')),
300                    'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _NETWORK_OTHERCONFIG_ATTRS),
301                                     lambda n: _otherconfig_from_xml(n, _NETWORK_OTHERCONFIG_ATTRS)),
302                  }
303
304 #
305 # Database Cache object
306 #
307
308 _db = None
309
310 def db():
311     assert(_db is not None)
312     return _db
313
314 def db_init_from_cache(cache):
315     global _db
316     assert(_db is None)
317     _db = DatabaseCache(cache_file=cache)
318     
319 def db_init_from_xenapi(session):
320     global _db 
321     assert(_db is None)
322     _db  = DatabaseCache(session_ref=session)
323     
324 class DatabaseCache(object):
325     def __read_xensource_inventory(self):
326         filename = "/etc/xensource-inventory"
327         f = open(filename, "r")
328         lines = [x.strip("\n") for x in f.readlines()]
329         f.close()
330
331         defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
332         defs = [ (a, b.strip("'")) for (a,b) in defs ]
333
334         return dict(defs)
335     def __pif_on_host(self,pif):
336         return self.__pifs.has_key(pif)
337
338     def __get_pif_records_from_xapi(self, session, host):
339         self.__pifs = {}
340         for (p,rec) in session.xenapi.PIF.get_all_records().items():
341             if rec['host'] != host:
342                 continue
343             self.__pifs[p] = {}
344             for f in _PIF_ATTRS:
345                 self.__pifs[p][f] = rec[f]
346             self.__pifs[p]['other_config'] = {}
347             for f in _PIF_OTHERCONFIG_ATTRS:
348                 if not rec['other_config'].has_key(f): continue
349                 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
350
351     def __get_vlan_records_from_xapi(self, session):
352         self.__vlans = {}
353         for v in session.xenapi.VLAN.get_all():
354             rec = session.xenapi.VLAN.get_record(v)
355             if not self.__pif_on_host(rec['untagged_PIF']):
356                 continue
357             self.__vlans[v] = {}
358             for f in _VLAN_ATTRS:
359                 self.__vlans[v][f] = rec[f]
360
361     def __get_bond_records_from_xapi(self, session):
362         self.__bonds = {}
363         for b in session.xenapi.Bond.get_all():
364             rec = session.xenapi.Bond.get_record(b)
365             if not self.__pif_on_host(rec['master']):
366                 continue
367             self.__bonds[b] = {}
368             for f in _BOND_ATTRS:
369                 self.__bonds[b][f] = rec[f]
370
371     def __get_network_records_from_xapi(self, session):
372         self.__networks = {}
373         for n in session.xenapi.network.get_all():
374             rec = session.xenapi.network.get_record(n)
375             self.__networks[n] = {}
376             for f in _NETWORK_ATTRS:
377                 if f == "PIFs":
378                     # drop PIFs on other hosts
379                     self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
380                 else:
381                     self.__networks[n][f] = rec[f]
382             self.__networks[n]['other_config'] = {}
383             for f in _NETWORK_OTHERCONFIG_ATTRS:
384                 if not rec['other_config'].has_key(f): continue
385                 self.__networks[n]['other_config'][f] = rec['other_config'][f]
386
387     def __to_xml(self, xml, parent, key, ref, rec, attrs):
388         """Encode a database object as XML"""
389         e = xml.createElement(key)
390         parent.appendChild(e)
391         if ref:
392             e.setAttribute('ref', ref)
393
394         for n,v in rec.items():
395             if attrs.has_key(n):
396                 h,_ = attrs[n]
397                 h(xml, e, n, v)
398             else:
399                 raise Error("Unknown attribute %s" % n)
400     def __from_xml(self, e, attrs):
401         """Decode a database object from XML"""
402         ref = e.attributes['ref'].value
403         rec = {}
404         for n in e.childNodes:
405             if n.nodeName in attrs:
406                 _,h = attrs[n.nodeName]
407                 rec[n.nodeName] = h(n)
408         return (ref,rec)
409
410     def __init__(self, session_ref=None, cache_file=None):
411         if session_ref and cache_file:
412             raise Error("can't specify session reference and cache file")
413         if cache_file == None:
414             import XenAPI
415             session = XenAPI.xapi_local()
416
417             if not session_ref:
418                 log("No session ref given on command line, logging in.")
419                 session.xenapi.login_with_password("root", "")
420             else:
421                 session._session = session_ref
422
423             try:
424
425                 inventory = self.__read_xensource_inventory()
426                 assert(inventory.has_key('INSTALLATION_UUID'))
427                 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
428
429                 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
430
431                 self.__get_pif_records_from_xapi(session, host)
432
433                 self.__get_vlan_records_from_xapi(session)
434                 self.__get_bond_records_from_xapi(session)
435                 self.__get_network_records_from_xapi(session)
436             finally:
437                 if not session_ref:
438                     session.xenapi.session.logout()
439         else:
440             log("Loading xapi database cache from %s" % cache_file)
441
442             xml = parseXML(cache_file)
443
444             self.__pifs = {}
445             self.__bonds = {}
446             self.__vlans = {}
447             self.__networks = {}
448
449             assert(len(xml.childNodes) == 1)
450             toplevel = xml.childNodes[0]
451
452             assert(toplevel.nodeName == "xenserver-network-configuration")
453
454             for n in toplevel.childNodes:
455                 if n.nodeName == "#text":
456                     pass
457                 elif n.nodeName == _PIF_XML_TAG:
458                     (ref,rec) = self.__from_xml(n, _PIF_ATTRS)
459                     self.__pifs[ref] = rec
460                 elif n.nodeName == _BOND_XML_TAG:
461                     (ref,rec) = self.__from_xml(n, _BOND_ATTRS)
462                     self.__bonds[ref] = rec
463                 elif n.nodeName == _VLAN_XML_TAG:
464                     (ref,rec) = self.__from_xml(n, _VLAN_ATTRS)
465                     self.__vlans[ref] = rec
466                 elif n.nodeName == _NETWORK_XML_TAG:
467                     (ref,rec) = self.__from_xml(n, _NETWORK_ATTRS)
468                     self.__networks[ref] = rec
469                 else:
470                     raise Error("Unknown XML element %s" % n.nodeName)
471
472     def save(self, cache_file):
473
474         xml = getDOMImplementation().createDocument(
475             None, "xenserver-network-configuration", None)
476         for (ref,rec) in self.__pifs.items():
477             self.__to_xml(xml, xml.documentElement, _PIF_XML_TAG, ref, rec, _PIF_ATTRS)
478         for (ref,rec) in self.__bonds.items():
479             self.__to_xml(xml, xml.documentElement, _BOND_XML_TAG, ref, rec, _BOND_ATTRS)
480         for (ref,rec) in self.__vlans.items():
481             self.__to_xml(xml, xml.documentElement, _VLAN_XML_TAG, ref, rec, _VLAN_ATTRS)
482         for (ref,rec) in self.__networks.items():
483             self.__to_xml(xml, xml.documentElement, _NETWORK_XML_TAG, ref, rec,
484                           _NETWORK_ATTRS)
485
486         f = open(cache_file, 'w')
487         f.write(xml.toprettyxml())
488         f.close()
489
490     def get_pif_by_uuid(self, uuid):
491         pifs = map(lambda (ref,rec): ref,
492                   filter(lambda (ref,rec): uuid == rec['uuid'],
493                          self.__pifs.items()))
494         if len(pifs) == 0:
495             raise Error("Unknown PIF \"%s\"" % uuid)
496         elif len(pifs) > 1:
497             raise Error("Non-unique PIF \"%s\"" % uuid)
498
499         return pifs[0]
500
501     def get_pifs_by_device(self, device):
502         return map(lambda (ref,rec): ref,
503                    filter(lambda (ref,rec): rec['device'] == device,
504                           self.__pifs.items()))
505
506     def get_pif_by_bridge(self, bridge):
507         networks = map(lambda (ref,rec): ref,
508                        filter(lambda (ref,rec): rec['bridge'] == bridge,
509                               self.__networks.items()))
510         if len(networks) == 0:
511             raise Error("No matching network \"%s\"" % bridge)
512
513         answer = None
514         for network in networks:
515             nwrec = self.get_network_record(network)
516             for pif in nwrec['PIFs']:
517                 pifrec = self.get_pif_record(pif)
518                 if answer:
519                     raise Error("Multiple PIFs on host for network %s" % (bridge))
520                 answer = pif
521         if not answer:
522             raise Error("No PIF on host for network %s" % (bridge))
523         return answer
524
525     def get_pif_record(self, pif):
526         if self.__pifs.has_key(pif):
527             return self.__pifs[pif]
528         raise Error("Unknown PIF \"%s\"" % pif)
529     def get_all_pifs(self):
530         return self.__pifs
531     def pif_exists(self, pif):
532         return self.__pifs.has_key(pif)
533
534     def get_management_pif(self):
535         """ Returns the management pif on host
536         """
537         all = self.get_all_pifs()
538         for pif in all:
539             pifrec = self.get_pif_record(pif)
540             if pifrec['management']: return pif
541         return None
542
543     def get_network_record(self, network):
544         if self.__networks.has_key(network):
545             return self.__networks[network]
546         raise Error("Unknown network \"%s\"" % network)
547
548     def get_bond_record(self, bond):
549         if self.__bonds.has_key(bond):
550             return self.__bonds[bond]
551         else:
552             return None
553
554     def get_vlan_record(self, vlan):
555         if self.__vlans.has_key(vlan):
556             return self.__vlans[vlan]
557         else:
558             return None
559
560 #
561 #
562 #
563
564 def ethtool_settings(oc):
565     settings = []
566     if oc.has_key('ethtool-speed'):
567         val = oc['ethtool-speed']
568         if val in ["10", "100", "1000"]:
569             settings += ['speed', val]
570         else:
571             log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
572     if oc.has_key('ethtool-duplex'):
573         val = oc['ethtool-duplex']
574         if val in ["10", "100", "1000"]:
575             settings += ['duplex', 'val']
576         else:
577             log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
578     if oc.has_key('ethtool-autoneg'):
579         val = oc['ethtool-autoneg']
580         if val in ["true", "on"]:
581             settings += ['autoneg', 'on']
582         elif val in ["false", "off"]:
583             settings += ['autoneg', 'off']
584         else:
585             log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
586     offload = []
587     for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
588         if oc.has_key("ethtool-" + opt):
589             val = oc["ethtool-" + opt]
590             if val in ["true", "on"]:
591                 offload += [opt, 'on']
592             elif val in ["false", "off"]:
593                 offload += [opt, 'off']
594             else:
595                 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
596     return settings,offload
597
598 def mtu_setting(oc):
599     if oc.has_key('mtu'):
600         try:
601             int(oc['mtu'])      # Check that the value is an integer
602             return oc['mtu']
603         except ValueError, x:
604             log("Invalid value for mtu = %s" % oc['mtu'])
605     return None
606
607 #
608 # IP Network Devices -- network devices with IP configuration
609 #
610 def pif_ipdev_name(pif):
611     """Return the ipdev name associated with pif"""
612     pifrec = db().get_pif_record(pif)
613     nwrec = db().get_network_record(pifrec['network'])
614
615     if nwrec['bridge']:
616         # TODO: sanity check that nwrec['bridgeless'] != 'true'
617         return nwrec['bridge']
618     else:
619         # TODO: sanity check that nwrec['bridgeless'] == 'true'
620         return pif_netdev_name(pif)
621
622 #
623 # Bare Network Devices -- network devices without IP configuration
624 #
625
626 def netdev_exists(netdev):
627     return os.path.exists("/sys/class/net/" + netdev)
628
629 def pif_netdev_name(pif):
630     """Get the netdev name for a PIF."""
631
632     pifrec = db().get_pif_record(pif)
633
634     if pif_is_vlan(pif):
635         return "%(device)s.%(VLAN)s" % pifrec
636     else:
637         return pifrec['device']
638
639 #
640 # Bonded PIFs
641 #
642 def pif_is_bond(pif):
643     pifrec = db().get_pif_record(pif)
644
645     return len(pifrec['bond_master_of']) > 0
646
647 def pif_get_bond_masters(pif):
648     """Returns a list of PIFs which are bond masters of this PIF"""
649
650     pifrec = db().get_pif_record(pif)
651
652     bso = pifrec['bond_slave_of']
653
654     # bond-slave-of is currently a single reference but in principle a
655     # PIF could be a member of several bonds which are not
656     # concurrently attached. Be robust to this possibility.
657     if not bso or bso == "OpaqueRef:NULL":
658         bso = []
659     elif not type(bso) == list:
660         bso = [bso]
661
662     bondrecs = [db().get_bond_record(bond) for bond in bso]
663     bondrecs = [rec for rec in bondrecs if rec]
664
665     return [bond['master'] for bond in bondrecs]
666
667 def pif_get_bond_slaves(pif):
668     """Returns a list of PIFs which make up the given bonded pif."""
669
670     pifrec = db().get_pif_record(pif)
671
672     bmo = pifrec['bond_master_of']
673     if len(bmo) > 1:
674         raise Error("Bond-master-of contains too many elements")
675
676     if len(bmo) == 0:
677         return []
678
679     bondrec = db().get_bond_record(bmo[0])
680     if not bondrec:
681         raise Error("No bond record for bond master PIF")
682
683     return bondrec['slaves']
684
685 #
686 # VLAN PIFs
687 #
688
689 def pif_is_vlan(pif):
690     return db().get_pif_record(pif)['VLAN'] != '-1'
691
692 def pif_get_vlan_slave(pif):
693     """Find the PIF which is the VLAN slave of pif.
694
695 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
696
697     pifrec = db().get_pif_record(pif)
698
699     vlan = pifrec['VLAN_master_of']
700     if not vlan or vlan == "OpaqueRef:NULL":
701         raise Error("PIF is not a VLAN master")
702
703     vlanrec = db().get_vlan_record(vlan)
704     if not vlanrec:
705         raise Error("No VLAN record found for PIF")
706
707     return vlanrec['tagged_PIF']
708
709 def pif_get_vlan_masters(pif):
710     """Returns a list of PIFs which are VLANs on top of the given pif."""
711
712     pifrec = db().get_pif_record(pif)
713     vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
714     return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])]
715
716 #
717 # Datapath base class
718 #
719
720 class Datapath(object):
721     """Object encapsulating the actions necessary to (de)configure the
722        datapath for a given PIF. Does not include configuration of the
723        IP address on the ipdev.
724     """
725     
726     def __init__(self, pif):
727         self._pif = pif
728
729     def configure_ipdev(self, cfg):
730         """Write ifcfg TYPE field for an IPdev, plus any type specific
731            fields to cfg
732         """
733         raise NotImplementedError        
734
735     def preconfigure(self, parent):
736         """Prepare datapath configuration for PIF, but do not actually
737            apply any changes.
738
739            Any configuration files should be attached to parent.
740         """
741         raise NotImplementedError
742     
743     def bring_down_existing(self):
744         """Tear down any existing network device configuration which
745            needs to be undone in order to bring this PIF up.
746         """
747         raise NotImplementedError
748
749     def configure(self):
750         """Apply the configuration prepared in the preconfigure stage.
751
752            Should assume any configuration files changed attached in
753            the preconfigure stage are applied and bring up the
754            necesary devices to provide the datapath for the
755            PIF.
756
757            Should not bring up the IPdev.
758         """
759         raise NotImplementedError
760     
761     def post(self):
762         """Called after the IPdev has been brought up.
763
764            Should do any final setup, including reinstating any
765            devices which were taken down in the bring_down_existing
766            hook.
767         """
768         raise NotImplementedError
769
770     def bring_down(self):
771         """Tear down and deconfigure the datapath. Should assume the
772            IPdev has already been brought down.
773         """
774         raise NotImplementedError
775         
776 def DatapathFactory(pif):
777     # XXX Need a datapath object for bridgeless PIFs
778
779     try:
780         network_conf = open("/etc/xensource/network.conf", 'r')
781         network_backend = network_conf.readline().strip()
782         network_conf.close()                
783     except Exception, e:
784         raise Error("failed to determine network backend:" + e)
785     
786     if network_backend == "bridge":
787         from InterfaceReconfigureBridge import DatapathBridge
788         return DatapathBridge(pif)
789     elif network_backend == "vswitch":
790         from InterfaceReconfigureVswitch import DatapathVswitch
791         return DatapathVswitch(pif)
792     else:
793         raise Error("unknown network backend %s" % network_backend)