xenserver: Clean up /usr/sbin/brctl dangling symlink.
[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 _map_to_xml(xml, parent, tag, val, attrs):
245     e = xml.createElement(tag)
246     parent.appendChild(e)
247     for n,v in val.items():
248         if n in attrs:
249             _str_to_xml(xml, e, n, v)
250         else:
251             log("Unknown other-config attribute: %s" % n)
252
253 def _map_from_xml(n, attrs):
254     ret = {}
255     for n in n.childNodes:
256         if n.nodeName in attrs:
257             ret[n.nodeName] = _str_from_xml(n)
258     return ret
259
260 def _otherconfig_to_xml(xml, parent, val, attrs):
261     return _map_to_xml(xml, parent, "other_config", val, attrs)
262 def _otherconfig_from_xml(n, attrs):
263     return _map_from_xml(n, attrs)
264
265 #
266 # Definitions of the database objects (and their attributes) used by interface-reconfigure.
267 #
268 # Each object is defined by a dictionary mapping an attribute name in
269 # the xapi database to a tuple containing two items:
270 #  - a function which takes this attribute and encodes it as XML.
271 #  - a function which takes XML and decocdes it into a value.
272 #
273 # other-config attributes are specified as a simple array of strings
274
275 _PIF_XML_TAG = "pif"
276 _VLAN_XML_TAG = "vlan"
277 _TUNNEL_XML_TAG = "tunnel"
278 _BOND_XML_TAG = "bond"
279 _NETWORK_XML_TAG = "network"
280 _POOL_XML_TAG = "pool"
281
282 _ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso', 'gro', 'lro' ]
283
284 _PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
285                         [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay',
286                                 'updelay', 'use_carrier', 'hashing-algorithm' ] + \
287                         _ETHTOOL_OTHERCONFIG_ATTRS
288
289 _PIF_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
290                'management': (_bool_to_xml,_bool_from_xml),
291                'network': (_str_to_xml,_str_from_xml),
292                'device': (_str_to_xml,_str_from_xml),
293                'bond_master_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'bond_master_of', 'slave', v),
294                                   lambda n: _strlist_from_xml(n, 'bond_master_of', 'slave')),
295                'bond_slave_of': (_str_to_xml,_str_from_xml),
296                'VLAN': (_str_to_xml,_str_from_xml),
297                'VLAN_master_of': (_str_to_xml,_str_from_xml),
298                'VLAN_slave_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v),
299                                  lambda n: _strlist_from_xml(n, 'VLAN_slave_Of', 'master')),
300                'tunnel_access_PIF_of': (lambda x, p, t, v: _strlist_to_xml(x, p, 'tunnel_access_PIF_of', 'pif', v),
301                                         lambda n: _strlist_from_xml(n, 'tunnel_access_PIF_of', 'pif')),
302                'tunnel_transport_PIF_of':  (lambda x, p, t, v: _strlist_to_xml(x, p, 'tunnel_transport_PIF_of', 'pif', v),
303                                             lambda n: _strlist_from_xml(n, 'tunnel_transport_PIF_of', 'pif')),
304                'ip_configuration_mode': (_str_to_xml,_str_from_xml),
305                'IP': (_str_to_xml,_str_from_xml),
306                'netmask': (_str_to_xml,_str_from_xml),
307                'gateway': (_str_to_xml,_str_from_xml),
308                'DNS': (_str_to_xml,_str_from_xml),
309                'MAC': (_str_to_xml,_str_from_xml),
310                'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _PIF_OTHERCONFIG_ATTRS),
311                                 lambda n: _otherconfig_from_xml(n, _PIF_OTHERCONFIG_ATTRS)),
312
313                # Special case: We write the current value
314                # PIF.currently-attached to the cache but since it will
315                # not be valid when we come to use the cache later
316                # (i.e. after a reboot) we always read it as False.
317                'currently_attached': (_bool_to_xml, lambda n: False),
318              }
319
320 _VLAN_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
321                 'tagged_PIF': (_str_to_xml,_str_from_xml),
322                 'untagged_PIF': (_str_to_xml,_str_from_xml),
323               }
324
325 _TUNNEL_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
326                   'access_PIF': (_str_to_xml,_str_from_xml),
327                   'transport_PIF': (_str_to_xml,_str_from_xml),
328                 }
329 _BOND_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
330                'master': (_str_to_xml,_str_from_xml),
331                'slaves': (lambda x, p, t, v: _strlist_to_xml(x, p, 'slaves', 'slave', v),
332                           lambda n: _strlist_from_xml(n, 'slaves', 'slave')),
333               }
334
335 _NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes', 'vswitch-controller-fail-mode' ] + _ETHTOOL_OTHERCONFIG_ATTRS
336
337 _NETWORK_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
338                    'bridge': (_str_to_xml,_str_from_xml),
339                    'MTU': (_str_to_xml,_str_from_xml),
340                    'PIFs': (lambda x, p, t, v: _strlist_to_xml(x, p, 'PIFs', 'PIF', v),
341                             lambda n: _strlist_from_xml(n, 'PIFs', 'PIF')),
342                    'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _NETWORK_OTHERCONFIG_ATTRS),
343                                     lambda n: _otherconfig_from_xml(n, _NETWORK_OTHERCONFIG_ATTRS)),
344                  }
345
346 _POOL_OTHERCONFIG_ATTRS = ['vswitch-controller-fail-mode']
347
348 _POOL_ATTRS = { 'other_config': (lambda x, p, t, v: _otherconfig_to_xml(x, p, v, _POOL_OTHERCONFIG_ATTRS),
349                                  lambda n: _otherconfig_from_xml(n, _POOL_OTHERCONFIG_ATTRS)),
350               }
351
352 #
353 # Database Cache object
354 #
355
356 _db = None
357
358 def db():
359     assert(_db is not None)
360     return _db
361
362 def db_init_from_cache(cache):
363     global _db
364     assert(_db is None)
365     _db = DatabaseCache(cache_file=cache)
366     
367 def db_init_from_xenapi(session):
368     global _db 
369     assert(_db is None)
370     _db  = DatabaseCache(session_ref=session)
371     
372 class DatabaseCache(object):
373     def __read_xensource_inventory(self):
374         filename = root_prefix() + "/etc/xensource-inventory"
375         f = open(filename, "r")
376         lines = [x.strip("\n") for x in f.readlines()]
377         f.close()
378
379         defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ]
380         defs = [ (a, b.strip("'")) for (a,b) in defs ]
381
382         return dict(defs)
383
384     def __pif_on_host(self,pif):
385         return self.__pifs.has_key(pif)
386
387     def __get_pif_records_from_xapi(self, session, host):
388         self.__pifs = {}
389         for (p,rec) in session.xenapi.PIF.get_all_records().items():
390             if rec['host'] != host:
391                 continue
392             self.__pifs[p] = {}
393             for f in _PIF_ATTRS:
394                 self.__pifs[p][f] = rec[f]
395             self.__pifs[p]['other_config'] = {}
396             for f in _PIF_OTHERCONFIG_ATTRS:
397                 if not rec['other_config'].has_key(f): continue
398                 self.__pifs[p]['other_config'][f] = rec['other_config'][f]
399
400     def __get_vlan_records_from_xapi(self, session):
401         self.__vlans = {}
402         for (v,rec) in session.xenapi.VLAN.get_all_records().items():
403             if not self.__pif_on_host(rec['untagged_PIF']):
404                 continue
405             self.__vlans[v] = {}
406             for f in _VLAN_ATTRS:
407                 self.__vlans[v][f] = rec[f]
408
409     def __get_tunnel_records_from_xapi(self, session):
410         self.__tunnels = {}
411         for t in session.xenapi.tunnel.get_all():
412             rec = session.xenapi.tunnel.get_record(t)
413             if not self.__pif_on_host(rec['transport_PIF']):
414                 continue
415             self.__tunnels[t] = {}
416             for f in _TUNNEL_ATTRS:
417                 self.__tunnels[t][f] = rec[f]
418
419     def __get_bond_records_from_xapi(self, session):
420         self.__bonds = {}
421         for (b,rec) in session.xenapi.Bond.get_all_records().items():
422             if not self.__pif_on_host(rec['master']):
423                 continue
424             self.__bonds[b] = {}
425             for f in _BOND_ATTRS:
426                 self.__bonds[b][f] = rec[f]
427
428     def __get_network_records_from_xapi(self, session):
429         self.__networks = {}
430         for (n,rec) in session.xenapi.network.get_all_records().items():
431             self.__networks[n] = {}
432             for f in _NETWORK_ATTRS:
433                 if f == "PIFs":
434                     # drop PIFs on other hosts
435                     self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)]
436                 elif f == "MTU" and f not in rec:
437                     # XenServer 5.5 network records did not have an
438                     # MTU field, so allow this to be missing.
439                     pass
440                 else:
441                     self.__networks[n][f] = rec[f]
442             self.__networks[n]['other_config'] = {}
443             for f in _NETWORK_OTHERCONFIG_ATTRS:
444                 if not rec['other_config'].has_key(f): continue
445                 self.__networks[n]['other_config'][f] = rec['other_config'][f]
446
447     def __get_pool_records_from_xapi(self, session):
448         self.__pools = {}
449         for p in session.xenapi.pool.get_all():
450             rec = session.xenapi.pool.get_record(p)
451
452             self.__pools[p] = {}
453
454             for f in _POOL_ATTRS:
455                 self.__pools[p][f] = rec[f]
456
457             for f in _POOL_OTHERCONFIG_ATTRS:
458                 if rec['other_config'].has_key(f):
459                     self.__pools[p]['other_config'][f] = rec['other_config'][f]
460
461     def __to_xml(self, xml, parent, key, ref, rec, attrs):
462         """Encode a database object as XML"""
463         e = xml.createElement(key)
464         parent.appendChild(e)
465         if ref:
466             e.setAttribute('ref', ref)
467
468         for n,v in rec.items():
469             if attrs.has_key(n):
470                 h,_ = attrs[n]
471                 h(xml, e, n, v)
472             else:
473                 raise Error("Unknown attribute %s" % n)
474     def __from_xml(self, e, attrs):
475         """Decode a database object from XML"""
476         ref = e.attributes['ref'].value
477         rec = {}
478         for n in e.childNodes:
479             if n.nodeName in attrs:
480                 _,h = attrs[n.nodeName]
481                 rec[n.nodeName] = h(n)
482         return (ref,rec)
483
484     def __init__(self, session_ref=None, cache_file=None):
485         if session_ref and cache_file:
486             raise Error("can't specify session reference and cache file")
487         if cache_file == None:
488             import XenAPI
489             session = XenAPI.xapi_local()
490
491             if not session_ref:
492                 log("No session ref given on command line, logging in.")
493                 session.xenapi.login_with_password("root", "")
494             else:
495                 session._session = session_ref
496
497             try:
498
499                 inventory = self.__read_xensource_inventory()
500                 assert(inventory.has_key('INSTALLATION_UUID'))
501                 log("host uuid is %s" % inventory['INSTALLATION_UUID'])
502
503                 host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID'])
504
505                 self.__get_pif_records_from_xapi(session, host)
506
507                 self.__get_tunnel_records_from_xapi(session)
508                 self.__get_pool_records_from_xapi(session)
509                 self.__get_vlan_records_from_xapi(session)
510                 self.__get_bond_records_from_xapi(session)
511                 self.__get_network_records_from_xapi(session)
512             finally:
513                 if not session_ref:
514                     session.xenapi.session.logout()
515         else:
516             log("Loading xapi database cache from %s" % cache_file)
517
518             xml = parseXML(root_prefix() + cache_file)
519
520             self.__pifs = {}
521             self.__bonds = {}
522             self.__vlans = {}
523             self.__pools = {}
524             self.__tunnels = {}
525             self.__networks = {}
526
527             assert(len(xml.childNodes) == 1)
528             toplevel = xml.childNodes[0]
529
530             assert(toplevel.nodeName == "xenserver-network-configuration")
531
532             for n in toplevel.childNodes:
533                 if n.nodeName == "#text":
534                     pass
535                 elif n.nodeName == _PIF_XML_TAG:
536                     (ref,rec) = self.__from_xml(n, _PIF_ATTRS)
537                     self.__pifs[ref] = rec
538                 elif n.nodeName == _BOND_XML_TAG:
539                     (ref,rec) = self.__from_xml(n, _BOND_ATTRS)
540                     self.__bonds[ref] = rec
541                 elif n.nodeName == _VLAN_XML_TAG:
542                     (ref,rec) = self.__from_xml(n, _VLAN_ATTRS)
543                     self.__vlans[ref] = rec
544                 elif n.nodeName == _TUNNEL_XML_TAG:
545                     (ref,rec) = self.__from_xml(n, _TUNNEL_ATTRS)
546                     self.__vlans[ref] = rec
547                 elif n.nodeName == _NETWORK_XML_TAG:
548                     (ref,rec) = self.__from_xml(n, _NETWORK_ATTRS)
549                     self.__networks[ref] = rec
550                 elif n.nodeName == _POOL_XML_TAG:
551                     (ref,rec) = self.__from_xml(n, _POOL_ATTRS)
552                     self.__pools[ref] = rec
553                 else:
554                     raise Error("Unknown XML element %s" % n.nodeName)
555
556     def save(self, cache_file):
557
558         xml = getDOMImplementation().createDocument(
559             None, "xenserver-network-configuration", None)
560         for (ref,rec) in self.__pifs.items():
561             self.__to_xml(xml, xml.documentElement, _PIF_XML_TAG, ref, rec, _PIF_ATTRS)
562         for (ref,rec) in self.__bonds.items():
563             self.__to_xml(xml, xml.documentElement, _BOND_XML_TAG, ref, rec, _BOND_ATTRS)
564         for (ref,rec) in self.__vlans.items():
565             self.__to_xml(xml, xml.documentElement, _VLAN_XML_TAG, ref, rec, _VLAN_ATTRS)
566         for (ref,rec) in self.__tunnels.items():
567             self.__to_xml(xml, xml.documentElement, _TUNNEL_XML_TAG, ref, rec, _TUNNEL_ATTRS)
568         for (ref,rec) in self.__networks.items():
569             self.__to_xml(xml, xml.documentElement, _NETWORK_XML_TAG, ref, rec,
570                           _NETWORK_ATTRS)
571         for (ref,rec) in self.__pools.items():
572             self.__to_xml(xml, xml.documentElement, _POOL_XML_TAG, ref, rec, _POOL_ATTRS)
573
574         f = open(cache_file, 'w')
575         f.write(xml.toprettyxml())
576         f.close()
577
578     def get_pif_by_uuid(self, uuid):
579         pifs = map(lambda (ref,rec): ref,
580                   filter(lambda (ref,rec): uuid == rec['uuid'],
581                          self.__pifs.items()))
582         if len(pifs) == 0:
583             raise Error("Unknown PIF \"%s\"" % uuid)
584         elif len(pifs) > 1:
585             raise Error("Non-unique PIF \"%s\"" % uuid)
586
587         return pifs[0]
588
589     def get_pifs_by_device(self, device):
590         return map(lambda (ref,rec): ref,
591                    filter(lambda (ref,rec): rec['device'] == device,
592                           self.__pifs.items()))
593
594     def get_networks_with_bridge(self, bridge):
595         return map(lambda (ref,rec): ref,
596                   filter(lambda (ref,rec): rec['bridge'] == bridge,
597                          self.__networks.items()))
598
599     def get_network_by_bridge(self, bridge):
600         #Assumes one network has bridge.
601         try:
602             return self.get_networks_with_bridge(bridge)[0]
603         except KeyError:
604             return None
605
606     def get_pif_by_bridge(self, bridge):
607         networks = self.get_networks_with_bridge(bridge)
608
609         if len(networks) == 0:
610             raise Error("No matching network \"%s\"" % bridge)
611
612         answer = None
613         for network in networks:
614             nwrec = self.get_network_record(network)
615             for pif in nwrec['PIFs']:
616                 pifrec = self.get_pif_record(pif)
617                 if answer:
618                     raise Error("Multiple PIFs on host for network %s" % (bridge))
619                 answer = pif
620         if not answer:
621             raise Error("No PIF on host for network %s" % (bridge))
622         return answer
623
624     def get_pif_record(self, pif):
625         if self.__pifs.has_key(pif):
626             return self.__pifs[pif]
627         raise Error("Unknown PIF \"%s\"" % pif)
628     def get_all_pifs(self):
629         return self.__pifs
630     def pif_exists(self, pif):
631         return self.__pifs.has_key(pif)
632
633     def get_management_pif(self):
634         """ Returns the management pif on host
635         """
636         all = self.get_all_pifs()
637         for pif in all:
638             pifrec = self.get_pif_record(pif)
639             if pifrec['management']: return pif
640         return None
641
642     def get_network_record(self, network):
643         if self.__networks.has_key(network):
644             return self.__networks[network]
645         raise Error("Unknown network \"%s\"" % network)
646
647     def get_bond_record(self, bond):
648         if self.__bonds.has_key(bond):
649             return self.__bonds[bond]
650         else:
651             return None
652
653     def get_vlan_record(self, vlan):
654         if self.__vlans.has_key(vlan):
655             return self.__vlans[vlan]
656         else:
657             return None
658
659     def get_pool_record(self):
660         if len(self.__pools) > 0:
661             return self.__pools.values()[0]
662
663 #
664 #
665 #
666 PIF_OTHERCONFIG_DEFAULTS = {'gro': 'off', 'lro': 'off'}
667
668 def ethtool_settings(oc, defaults = {}):
669     settings = []
670     if oc.has_key('ethtool-speed'):
671         val = oc['ethtool-speed']
672         if val in ["10", "100", "1000"]:
673             settings += ['speed', val]
674         else:
675             log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
676     if oc.has_key('ethtool-duplex'):
677         val = oc['ethtool-duplex']
678         if val in ["half", "full"]:
679             settings += ['duplex', val]
680         else:
681             log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
682     if oc.has_key('ethtool-autoneg'):
683         val = oc['ethtool-autoneg']
684         if val in ["true", "on"]:
685             settings += ['autoneg', 'on']
686         elif val in ["false", "off"]:
687             settings += ['autoneg', 'off']
688         else:
689             log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
690     offload = []
691     for opt in ("rx", "tx", "sg", "tso", "ufo", "gso", "gro", "lro"):
692         if oc.has_key("ethtool-" + opt):
693             val = oc["ethtool-" + opt]
694             if val in ["true", "on"]:
695                 offload += [opt, 'on']
696             elif val in ["false", "off"]:
697                 offload += [opt, 'off']
698             else:
699                 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
700         elif opt in defaults:
701             offload += [opt, defaults[opt]]
702     return settings,offload
703
704 # By default the MTU is taken from the Network.MTU setting for VIF,
705 # PIF and Bridge. However it is possible to override this by using
706 # {VIF,PIF,Network}.other-config:mtu.
707 #
708 # type parameter is a string describing the object that the oc parameter
709 # is from. e.g. "PIF", "Network" 
710 def mtu_setting(nw, type, oc):
711     mtu = None
712
713     nwrec = db().get_network_record(nw)
714     if nwrec.has_key('MTU'):
715         mtu = nwrec['MTU']
716     else:
717         mtu = "1500"
718         
719     if oc.has_key('mtu'):
720         log("Override Network.MTU setting on bridge %s from %s.MTU is %s" % \
721             (nwrec['bridge'], type, mtu))
722         mtu = oc['mtu']
723
724     if mtu is not None:
725         try:
726             int(mtu)      # Check that the value is an integer
727             return mtu
728         except ValueError, x:
729             log("Invalid value for mtu = %s" % mtu)
730
731     return None
732
733 #
734 # IP Network Devices -- network devices with IP configuration
735 #
736 def pif_ipdev_name(pif):
737     """Return the ipdev name associated with pif"""
738     pifrec = db().get_pif_record(pif)
739     nwrec = db().get_network_record(pifrec['network'])
740
741     if nwrec['bridge']:
742         # TODO: sanity check that nwrec['bridgeless'] != 'true'
743         return nwrec['bridge']
744     else:
745         # TODO: sanity check that nwrec['bridgeless'] == 'true'
746         return pif_netdev_name(pif)
747
748 #
749 # Bare Network Devices -- network devices without IP configuration
750 #
751
752 def netdev_exists(netdev):
753     return os.path.exists(root_prefix() + "/sys/class/net/" + netdev)
754
755 def pif_netdev_name(pif):
756     """Get the netdev name for a PIF."""
757
758     pifrec = db().get_pif_record(pif)
759
760     if pif_is_vlan(pif):
761         return "%(device)s.%(VLAN)s" % pifrec
762     else:
763         return pifrec['device']
764
765 #
766 # Bridges
767 #
768
769 def pif_is_bridged(pif):
770     pifrec = db().get_pif_record(pif)
771     nwrec = db().get_network_record(pifrec['network'])
772
773     if nwrec['bridge']:
774         # TODO: sanity check that nwrec['bridgeless'] != 'true'
775         return True
776     else:
777         # TODO: sanity check that nwrec['bridgeless'] == 'true'
778         return False
779
780 def pif_bridge_name(pif):
781     """Return the bridge name of a pif.
782
783     PIF must be a bridged PIF."""
784     pifrec = db().get_pif_record(pif)
785
786     nwrec = db().get_network_record(pifrec['network'])
787
788     if nwrec['bridge']:
789         return nwrec['bridge']
790     else:
791         raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
792
793 #
794 # Bonded PIFs
795 #
796 def pif_is_bond(pif):
797     pifrec = db().get_pif_record(pif)
798
799     return len(pifrec['bond_master_of']) > 0
800
801 def pif_get_bond_masters(pif):
802     """Returns a list of PIFs which are bond masters of this PIF"""
803
804     pifrec = db().get_pif_record(pif)
805
806     bso = pifrec['bond_slave_of']
807
808     # bond-slave-of is currently a single reference but in principle a
809     # PIF could be a member of several bonds which are not
810     # concurrently attached. Be robust to this possibility.
811     if not bso or bso == "OpaqueRef:NULL":
812         bso = []
813     elif not type(bso) == list:
814         bso = [bso]
815
816     bondrecs = [db().get_bond_record(bond) for bond in bso]
817     bondrecs = [rec for rec in bondrecs if rec]
818
819     return [bond['master'] for bond in bondrecs]
820
821 def pif_get_bond_slaves(pif):
822     """Returns a list of PIFs which make up the given bonded pif."""
823
824     pifrec = db().get_pif_record(pif)
825
826     bmo = pifrec['bond_master_of']
827     if len(bmo) > 1:
828         raise Error("Bond-master-of contains too many elements")
829
830     if len(bmo) == 0:
831         return []
832
833     bondrec = db().get_bond_record(bmo[0])
834     if not bondrec:
835         raise Error("No bond record for bond master PIF")
836
837     return bondrec['slaves']
838
839 #
840 # VLAN PIFs
841 #
842
843 def pif_is_vlan(pif):
844     return db().get_pif_record(pif)['VLAN'] != '-1'
845
846 def pif_get_vlan_slave(pif):
847     """Find the PIF which is the VLAN slave of pif.
848
849 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
850
851     pifrec = db().get_pif_record(pif)
852
853     vlan = pifrec['VLAN_master_of']
854     if not vlan or vlan == "OpaqueRef:NULL":
855         raise Error("PIF is not a VLAN master")
856
857     vlanrec = db().get_vlan_record(vlan)
858     if not vlanrec:
859         raise Error("No VLAN record found for PIF")
860
861     return vlanrec['tagged_PIF']
862
863 def pif_get_vlan_masters(pif):
864     """Returns a list of PIFs which are VLANs on top of the given pif."""
865
866     pifrec = db().get_pif_record(pif)
867     vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
868     return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])]
869
870 #
871 # Tunnel PIFs
872 #
873 def pif_is_tunnel(pif):
874     return len(db().get_pif_record(pif)['tunnel_access_PIF_of']) > 0
875
876 #
877 # Datapath base class
878 #
879
880 class Datapath(object):
881     """Object encapsulating the actions necessary to (de)configure the
882        datapath for a given PIF. Does not include configuration of the
883        IP address on the ipdev.
884     """
885     
886     def __init__(self, pif):
887         self._pif = pif
888
889     @classmethod
890     def rewrite(cls):
891         """Class method called when write action is called. Can be used
892            to update any backend specific configuration."""
893         pass
894
895     def configure_ipdev(self, cfg):
896         """Write ifcfg TYPE field for an IPdev, plus any type specific
897            fields to cfg
898         """
899         raise NotImplementedError        
900
901     def preconfigure(self, parent):
902         """Prepare datapath configuration for PIF, but do not actually
903            apply any changes.
904
905            Any configuration files should be attached to parent.
906         """
907         raise NotImplementedError
908     
909     def bring_down_existing(self):
910         """Tear down any existing network device configuration which
911            needs to be undone in order to bring this PIF up.
912         """
913         raise NotImplementedError
914
915     def configure(self):
916         """Apply the configuration prepared in the preconfigure stage.
917
918            Should assume any configuration files changed attached in
919            the preconfigure stage are applied and bring up the
920            necesary devices to provide the datapath for the
921            PIF.
922
923            Should not bring up the IPdev.
924         """
925         raise NotImplementedError
926     
927     def post(self):
928         """Called after the IPdev has been brought up.
929
930            Should do any final setup, including reinstating any
931            devices which were taken down in the bring_down_existing
932            hook.
933         """
934         raise NotImplementedError
935
936     def bring_down(self):
937         """Tear down and deconfigure the datapath. Should assume the
938            IPdev has already been brought down.
939         """
940         raise NotImplementedError
941         
942 def DatapathFactory():
943     # XXX Need a datapath object for bridgeless PIFs
944
945     try:
946         network_conf = open(root_prefix() + "/etc/xensource/network.conf", 'r')
947         network_backend = network_conf.readline().strip()
948         network_conf.close()                
949     except Exception, e:
950         raise Error("failed to determine network backend:" + e)
951     
952     if network_backend == "bridge":
953         from InterfaceReconfigureBridge import DatapathBridge
954         return DatapathBridge
955     elif network_backend in ["openvswitch", "vswitch"]:
956         from InterfaceReconfigureVswitch import DatapathVswitch
957         return DatapathVswitch
958     else:
959         raise Error("unknown network backend %s" % network_backend)