CP-1592: interface-reconfigure: Configure network device MTU from Network.MTU field
[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                    'MTU': (_str_to_xml,_str_from_xml),
322                    'PIFs': (lambda x, p, t, v: _strlist_to_xml(x, p, 'PIFs', 'PIF', v),
323                             lambda n: _strlist_from_xml(n, 'PIFs', 'PIF')),
324                    'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _NETWORK_OTHERCONFIG_ATTRS),
325                                     lambda n: _otherconfig_from_xml(n, _NETWORK_OTHERCONFIG_ATTRS)),
326                  }
327
328 #
329 # Database Cache object
330 #
331
332 _db = None
333
334 def db():
335     assert(_db is not None)
336     return _db
337
338 def db_init_from_cache(cache):
339     global _db
340     assert(_db is None)
341     _db = DatabaseCache(cache_file=cache)
342     
343 def db_init_from_xenapi(session):
344     global _db 
345     assert(_db is None)
346     _db  = DatabaseCache(session_ref=session)
347     
348 class DatabaseCache(object):
349     def __read_xensource_inventory(self):
350         filename = root_prefix() + "/etc/xensource-inventory"
351         f = open(filename, "r")
352         lines = [x.strip("\n") for x in f.readlines()]
353         f.close()
354
355         defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
356         defs = [ (a, b.strip("'")) for (a,b) in defs ]
357
358         return dict(defs)
359
360     def __pif_on_host(self,pif):
361         return self.__pifs.has_key(pif)
362
363     def __get_pif_records_from_xapi(self, session, host):
364         self.__pifs = {}
365         for (p,rec) in session.xenapi.PIF.get_all_records().items():
366             if rec['host'] != host:
367                 continue
368             self.__pifs[p] = {}
369             for f in _PIF_ATTRS:
370                 self.__pifs[p][f] = rec[f]
371             self.__pifs[p]['other_config'] = {}
372             for f in _PIF_OTHERCONFIG_ATTRS:
373                 if not rec['other_config'].has_key(f): continue
374                 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
375
376     def __get_vlan_records_from_xapi(self, session):
377         self.__vlans = {}
378         for v in session.xenapi.VLAN.get_all():
379             rec = session.xenapi.VLAN.get_record(v)
380             if not self.__pif_on_host(rec['untagged_PIF']):
381                 continue
382             self.__vlans[v] = {}
383             for f in _VLAN_ATTRS:
384                 self.__vlans[v][f] = rec[f]
385
386     def __get_bond_records_from_xapi(self, session):
387         self.__bonds = {}
388         for b in session.xenapi.Bond.get_all():
389             rec = session.xenapi.Bond.get_record(b)
390             if not self.__pif_on_host(rec['master']):
391                 continue
392             self.__bonds[b] = {}
393             for f in _BOND_ATTRS:
394                 self.__bonds[b][f] = rec[f]
395
396     def __get_network_records_from_xapi(self, session):
397         self.__networks = {}
398         for n in session.xenapi.network.get_all():
399             rec = session.xenapi.network.get_record(n)
400             self.__networks[n] = {}
401             for f in _NETWORK_ATTRS:
402                 if f == "PIFs":
403                     # drop PIFs on other hosts
404                     self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
405                 else:
406                     self.__networks[n][f] = rec[f]
407             self.__networks[n]['other_config'] = {}
408             for f in _NETWORK_OTHERCONFIG_ATTRS:
409                 if not rec['other_config'].has_key(f): continue
410                 self.__networks[n]['other_config'][f] = rec['other_config'][f]
411
412     def __to_xml(self, xml, parent, key, ref, rec, attrs):
413         """Encode a database object as XML"""
414         e = xml.createElement(key)
415         parent.appendChild(e)
416         if ref:
417             e.setAttribute('ref', ref)
418
419         for n,v in rec.items():
420             if attrs.has_key(n):
421                 h,_ = attrs[n]
422                 h(xml, e, n, v)
423             else:
424                 raise Error("Unknown attribute %s" % n)
425     def __from_xml(self, e, attrs):
426         """Decode a database object from XML"""
427         ref = e.attributes['ref'].value
428         rec = {}
429         for n in e.childNodes:
430             if n.nodeName in attrs:
431                 _,h = attrs[n.nodeName]
432                 rec[n.nodeName] = h(n)
433         return (ref,rec)
434
435     def __init__(self, session_ref=None, cache_file=None):
436         if session_ref and cache_file:
437             raise Error("can't specify session reference and cache file")
438         if cache_file == None:
439             import XenAPI
440             session = XenAPI.xapi_local()
441
442             if not session_ref:
443                 log("No session ref given on command line, logging in.")
444                 session.xenapi.login_with_password("root", "")
445             else:
446                 session._session = session_ref
447
448             try:
449
450                 inventory = self.__read_xensource_inventory()
451                 assert(inventory.has_key('INSTALLATION_UUID'))
452                 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
453
454                 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
455
456                 self.__get_pif_records_from_xapi(session, host)
457
458                 self.__get_vlan_records_from_xapi(session)
459                 self.__get_bond_records_from_xapi(session)
460                 self.__get_network_records_from_xapi(session)
461             finally:
462                 if not session_ref:
463                     session.xenapi.session.logout()
464         else:
465             log("Loading xapi database cache from %s" % cache_file)
466
467             xml = parseXML(root_prefix() + cache_file)
468
469             self.__pifs = {}
470             self.__bonds = {}
471             self.__vlans = {}
472             self.__networks = {}
473
474             assert(len(xml.childNodes) == 1)
475             toplevel = xml.childNodes[0]
476
477             assert(toplevel.nodeName == "xenserver-network-configuration")
478
479             for n in toplevel.childNodes:
480                 if n.nodeName == "#text":
481                     pass
482                 elif n.nodeName == _PIF_XML_TAG:
483                     (ref,rec) = self.__from_xml(n, _PIF_ATTRS)
484                     self.__pifs[ref] = rec
485                 elif n.nodeName == _BOND_XML_TAG:
486                     (ref,rec) = self.__from_xml(n, _BOND_ATTRS)
487                     self.__bonds[ref] = rec
488                 elif n.nodeName == _VLAN_XML_TAG:
489                     (ref,rec) = self.__from_xml(n, _VLAN_ATTRS)
490                     self.__vlans[ref] = rec
491                 elif n.nodeName == _NETWORK_XML_TAG:
492                     (ref,rec) = self.__from_xml(n, _NETWORK_ATTRS)
493                     self.__networks[ref] = rec
494                 else:
495                     raise Error("Unknown XML element %s" % n.nodeName)
496
497     def save(self, cache_file):
498
499         xml = getDOMImplementation().createDocument(
500             None, "xenserver-network-configuration", None)
501         for (ref,rec) in self.__pifs.items():
502             self.__to_xml(xml, xml.documentElement, _PIF_XML_TAG, ref, rec, _PIF_ATTRS)
503         for (ref,rec) in self.__bonds.items():
504             self.__to_xml(xml, xml.documentElement, _BOND_XML_TAG, ref, rec, _BOND_ATTRS)
505         for (ref,rec) in self.__vlans.items():
506             self.__to_xml(xml, xml.documentElement, _VLAN_XML_TAG, ref, rec, _VLAN_ATTRS)
507         for (ref,rec) in self.__networks.items():
508             self.__to_xml(xml, xml.documentElement, _NETWORK_XML_TAG, ref, rec,
509                           _NETWORK_ATTRS)
510
511         f = open(cache_file, 'w')
512         f.write(xml.toprettyxml())
513         f.close()
514
515     def get_pif_by_uuid(self, uuid):
516         pifs = map(lambda (ref,rec): ref,
517                   filter(lambda (ref,rec): uuid == rec['uuid'],
518                          self.__pifs.items()))
519         if len(pifs) == 0:
520             raise Error("Unknown PIF \"%s\"" % uuid)
521         elif len(pifs) > 1:
522             raise Error("Non-unique PIF \"%s\"" % uuid)
523
524         return pifs[0]
525
526     def get_pifs_by_device(self, device):
527         return map(lambda (ref,rec): ref,
528                    filter(lambda (ref,rec): rec['device'] == device,
529                           self.__pifs.items()))
530
531     def get_pif_by_bridge(self, bridge):
532         networks = map(lambda (ref,rec): ref,
533                        filter(lambda (ref,rec): rec['bridge'] == bridge,
534                               self.__networks.items()))
535         if len(networks) == 0:
536             raise Error("No matching network \"%s\"" % bridge)
537
538         answer = None
539         for network in networks:
540             nwrec = self.get_network_record(network)
541             for pif in nwrec['PIFs']:
542                 pifrec = self.get_pif_record(pif)
543                 if answer:
544                     raise Error("Multiple PIFs on host for network %s" % (bridge))
545                 answer = pif
546         if not answer:
547             raise Error("No PIF on host for network %s" % (bridge))
548         return answer
549
550     def get_pif_record(self, pif):
551         if self.__pifs.has_key(pif):
552             return self.__pifs[pif]
553         raise Error("Unknown PIF \"%s\"" % pif)
554     def get_all_pifs(self):
555         return self.__pifs
556     def pif_exists(self, pif):
557         return self.__pifs.has_key(pif)
558
559     def get_management_pif(self):
560         """ Returns the management pif on host
561         """
562         all = self.get_all_pifs()
563         for pif in all:
564             pifrec = self.get_pif_record(pif)
565             if pifrec['management']: return pif
566         return None
567
568     def get_network_record(self, network):
569         if self.__networks.has_key(network):
570             return self.__networks[network]
571         raise Error("Unknown network \"%s\"" % network)
572
573     def get_bond_record(self, bond):
574         if self.__bonds.has_key(bond):
575             return self.__bonds[bond]
576         else:
577             return None
578
579     def get_vlan_record(self, vlan):
580         if self.__vlans.has_key(vlan):
581             return self.__vlans[vlan]
582         else:
583             return None
584
585 #
586 #
587 #
588
589 def ethtool_settings(oc):
590     settings = []
591     if oc.has_key('ethtool-speed'):
592         val = oc['ethtool-speed']
593         if val in ["10", "100", "1000"]:
594             settings += ['speed', val]
595         else:
596             log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
597     if oc.has_key('ethtool-duplex'):
598         val = oc['ethtool-duplex']
599         if val in ["10", "100", "1000"]:
600             settings += ['duplex', 'val']
601         else:
602             log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
603     if oc.has_key('ethtool-autoneg'):
604         val = oc['ethtool-autoneg']
605         if val in ["true", "on"]:
606             settings += ['autoneg', 'on']
607         elif val in ["false", "off"]:
608             settings += ['autoneg', 'off']
609         else:
610             log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
611     offload = []
612     for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"):
613         if oc.has_key("ethtool-" + opt):
614             val = oc["ethtool-" + opt]
615             if val in ["true", "on"]:
616                 offload += [opt, 'on']
617             elif val in ["false", "off"]:
618                 offload += [opt, 'off']
619             else:
620                 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
621     return settings,offload
622
623 # By default the MTU is taken from the Network.MTU setting for VIF,
624 # PIF and Bridge. However it is possible to override this by using
625 # {VIF,PIF,Network}.other-config:mtu.
626 #
627 # type parameter is a string describing the object that the oc parameter
628 # is from. e.g. "PIF", "Network" 
629 def mtu_setting(nw, type, oc):
630     mtu = None
631
632     nwrec = db().get_network_record(nw)
633     if nwrec.has_key('MTU'):
634         mtu = nwrec['MTU']
635     else:
636         mtu = "1500"
637         
638     if oc.has_key('mtu'):
639         log("Override Network.MTU setting on bridge %s from %s.MTU is %s" % \
640             (nwrec['bridge'], type, mtu))
641         mtu = oc['mtu']
642
643     if mtu is not None:
644         try:
645             int(mtu)      # Check that the value is an integer
646             return mtu
647         except ValueError, x:
648             log("Invalid value for mtu = %s" % mtu)
649
650     return None
651
652 #
653 # IP Network Devices -- network devices with IP configuration
654 #
655 def pif_ipdev_name(pif):
656     """Return the ipdev name associated with pif"""
657     pifrec = db().get_pif_record(pif)
658     nwrec = db().get_network_record(pifrec['network'])
659
660     if nwrec['bridge']:
661         # TODO: sanity check that nwrec['bridgeless'] != 'true'
662         return nwrec['bridge']
663     else:
664         # TODO: sanity check that nwrec['bridgeless'] == 'true'
665         return pif_netdev_name(pif)
666
667 #
668 # Bare Network Devices -- network devices without IP configuration
669 #
670
671 def netdev_exists(netdev):
672     return os.path.exists(root_prefix() + "/sys/class/net/" + netdev)
673
674 def pif_netdev_name(pif):
675     """Get the netdev name for a PIF."""
676
677     pifrec = db().get_pif_record(pif)
678
679     if pif_is_vlan(pif):
680         return "%(device)s.%(VLAN)s" % pifrec
681     else:
682         return pifrec['device']
683
684 #
685 # Bridges
686 #
687
688 def pif_is_bridged(pif):
689     pifrec = db().get_pif_record(pif)
690     nwrec = db().get_network_record(pifrec['network'])
691
692     if nwrec['bridge']:
693         # TODO: sanity check that nwrec['bridgeless'] != 'true'
694         return True
695     else:
696         # TODO: sanity check that nwrec['bridgeless'] == 'true'
697         return False
698
699 def pif_bridge_name(pif):
700     """Return the bridge name of a pif.
701
702     PIF must be a bridged PIF."""
703     pifrec = db().get_pif_record(pif)
704
705     nwrec = db().get_network_record(pifrec['network'])
706
707     if nwrec['bridge']:
708         return nwrec['bridge']
709     else:
710         raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
711
712 #
713 # Bonded PIFs
714 #
715 def pif_is_bond(pif):
716     pifrec = db().get_pif_record(pif)
717
718     return len(pifrec['bond_master_of']) > 0
719
720 def pif_get_bond_masters(pif):
721     """Returns a list of PIFs which are bond masters of this PIF"""
722
723     pifrec = db().get_pif_record(pif)
724
725     bso = pifrec['bond_slave_of']
726
727     # bond-slave-of is currently a single reference but in principle a
728     # PIF could be a member of several bonds which are not
729     # concurrently attached. Be robust to this possibility.
730     if not bso or bso == "OpaqueRef:NULL":
731         bso = []
732     elif not type(bso) == list:
733         bso = [bso]
734
735     bondrecs = [db().get_bond_record(bond) for bond in bso]
736     bondrecs = [rec for rec in bondrecs if rec]
737
738     return [bond['master'] for bond in bondrecs]
739
740 def pif_get_bond_slaves(pif):
741     """Returns a list of PIFs which make up the given bonded pif."""
742
743     pifrec = db().get_pif_record(pif)
744
745     bmo = pifrec['bond_master_of']
746     if len(bmo) > 1:
747         raise Error("Bond-master-of contains too many elements")
748
749     if len(bmo) == 0:
750         return []
751
752     bondrec = db().get_bond_record(bmo[0])
753     if not bondrec:
754         raise Error("No bond record for bond master PIF")
755
756     return bondrec['slaves']
757
758 #
759 # VLAN PIFs
760 #
761
762 def pif_is_vlan(pif):
763     return db().get_pif_record(pif)['VLAN'] != '-1'
764
765 def pif_get_vlan_slave(pif):
766     """Find the PIF which is the VLAN slave of pif.
767
768 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
769
770     pifrec = db().get_pif_record(pif)
771
772     vlan = pifrec['VLAN_master_of']
773     if not vlan or vlan == "OpaqueRef:NULL":
774         raise Error("PIF is not a VLAN master")
775
776     vlanrec = db().get_vlan_record(vlan)
777     if not vlanrec:
778         raise Error("No VLAN record found for PIF")
779
780     return vlanrec['tagged_PIF']
781
782 def pif_get_vlan_masters(pif):
783     """Returns a list of PIFs which are VLANs on top of the given pif."""
784
785     pifrec = db().get_pif_record(pif)
786     vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
787     return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])]
788
789 #
790 # Datapath base class
791 #
792
793 class Datapath(object):
794     """Object encapsulating the actions necessary to (de)configure the
795        datapath for a given PIF. Does not include configuration of the
796        IP address on the ipdev.
797     """
798     
799     def __init__(self, pif):
800         self._pif = pif
801
802     def configure_ipdev(self, cfg):
803         """Write ifcfg TYPE field for an IPdev, plus any type specific
804            fields to cfg
805         """
806         raise NotImplementedError        
807
808     def preconfigure(self, parent):
809         """Prepare datapath configuration for PIF, but do not actually
810            apply any changes.
811
812            Any configuration files should be attached to parent.
813         """
814         raise NotImplementedError
815     
816     def bring_down_existing(self):
817         """Tear down any existing network device configuration which
818            needs to be undone in order to bring this PIF up.
819         """
820         raise NotImplementedError
821
822     def configure(self):
823         """Apply the configuration prepared in the preconfigure stage.
824
825            Should assume any configuration files changed attached in
826            the preconfigure stage are applied and bring up the
827            necesary devices to provide the datapath for the
828            PIF.
829
830            Should not bring up the IPdev.
831         """
832         raise NotImplementedError
833     
834     def post(self):
835         """Called after the IPdev has been brought up.
836
837            Should do any final setup, including reinstating any
838            devices which were taken down in the bring_down_existing
839            hook.
840         """
841         raise NotImplementedError
842
843     def bring_down(self):
844         """Tear down and deconfigure the datapath. Should assume the
845            IPdev has already been brought down.
846         """
847         raise NotImplementedError
848         
849 def DatapathFactory(pif):
850     # XXX Need a datapath object for bridgeless PIFs
851
852     try:
853         network_conf = open(root_prefix() + "/etc/xensource/network.conf", 'r')
854         network_backend = network_conf.readline().strip()
855         network_conf.close()                
856     except Exception, e:
857         raise Error("failed to determine network backend:" + e)
858     
859     if network_backend == "bridge":
860         from InterfaceReconfigureBridge import DatapathBridge
861         return DatapathBridge(pif)
862     elif network_backend == "vswitch":
863         from InterfaceReconfigureVswitch import DatapathVswitch
864         return DatapathVswitch(pif)
865     else:
866         raise Error("unknown network backend %s" % network_backend)