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