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