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