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