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