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