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