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