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