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