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