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