]> git.proxmox.com Git - mirror_ovs.git/blame - xenserver/opt_xensource_libexec_InterfaceReconfigure.py
ovs-vsctl: Log better warnings for duplicate ports and interfaces.
[mirror_ovs.git] / xenserver / opt_xensource_libexec_InterfaceReconfigure.py
CommitLineData
b3080599
IC
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#
b63fadcf 13import sys
b3080599
IC
14import syslog
15import os
16
17from xml.dom.minidom import getDOMImplementation
18from xml.dom.minidom import parse as parseXML
19
64ddb6fe
BP
20the_root_prefix = ""
21def root_prefix():
22 """Returns a string to prefix to all file name references, which
23 is useful for testing."""
24 return the_root_prefix
25def set_root_prefix(prefix):
26 global the_root_prefix
27 the_root_prefix = prefix
28
b63fadcf
BP
29log_destination = "syslog"
30def 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
35def set_log_destination(dest):
36 global log_destination
37 log_destination = dest
38
b3080599
IC
39#
40# Logging.
41#
42
43def log(s):
b63fadcf
BP
44 if get_log_destination() == 'syslog':
45 syslog.syslog(s)
46 else:
47 print >>sys.stderr, s
b3080599
IC
48
49#
50# Exceptions.
51#
52
53class Error(Exception):
54 def __init__(self, msg):
55 Exception.__init__(self)
56 self.msg = msg
57
58#
59# Run external utilities
60#
61
62def run_command(command):
63 log("Running command: " + ' '.join(command))
64ddb6fe 64 rc = os.spawnl(os.P_WAIT, root_prefix() + command[0], *command)
b3080599
IC
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
74class 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
201def _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)
206def _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
215def _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")
220def _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
229def _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)
237def _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
92e906e4
IC
244def _map_to_xml(xml, parent, tag, val, attrs):
245 e = xml.createElement(tag)
246 parent.appendChild(e)
b3080599 247 for n,v in val.items():
3dfa61ca
EJ
248 if n in attrs:
249 _str_to_xml(xml, e, n, v)
250 else:
251 log("Unknown other-config attribute: %s" % n)
92e906e4
IC
252
253def _map_from_xml(n, attrs):
b3080599
IC
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
92e906e4
IC
260def _otherconfig_to_xml(xml, parent, val, attrs):
261 return _map_to_xml(xml, parent, "other_config", val, attrs)
262def _otherconfig_from_xml(n, attrs):
263 return _map_from_xml(n, attrs)
264
b3080599
IC
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"
92e906e4 277_TUNNEL_XML_TAG = "tunnel"
b3080599
IC
278_BOND_XML_TAG = "bond"
279_NETWORK_XML_TAG = "network"
939e5a1b 280_POOL_XML_TAG = "pool"
b3080599 281
404c1692 282_ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso', 'gro', 'lro' ]
b3080599
IC
283
284_PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
2776e408
EJ
285 [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay',
286 'updelay', 'use_carrier', 'hashing-algorithm' ] + \
431488e6 287 [ 'vlan-bug-workaround' ] + \
b3080599
IC
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')),
92e906e4
IC
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')),
b3080599
IC
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
92e906e4
IC
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 }
b3080599
IC
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
da54975c
AE
336_NETWORK_OTHERCONFIG_ATTRS = [ 'mtu',
337 'static-routes',
338 'vswitch-controller-fail-mode',
339 'vswitch-disable-in-band' ] \
340 + _ETHTOOL_OTHERCONFIG_ATTRS
b3080599
IC
341
342_NETWORK_ATTRS = { 'uuid': (_str_to_xml,_str_from_xml),
343 'bridge': (_str_to_xml,_str_from_xml),
9a2b1175 344 'MTU': (_str_to_xml,_str_from_xml),
b3080599
IC
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
939e5a1b
EJ
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
b3080599
IC
357#
358# Database Cache object
359#
360
361_db = None
362
363def db():
364 assert(_db is not None)
365 return _db
366
367def db_init_from_cache(cache):
368 global _db
369 assert(_db is None)
370 _db = DatabaseCache(cache_file=cache)
371
372def db_init_from_xenapi(session):
373 global _db
374 assert(_db is None)
375 _db = DatabaseCache(session_ref=session)
376
377class DatabaseCache(object):
378 def __read_xensource_inventory(self):
64ddb6fe 379 filename = root_prefix() + "/etc/xensource-inventory"
b3080599
IC
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)
13ffee26 388
b3080599
IC
389 def __pif_on_host(self,pif):
390 return self.__pifs.has_key(pif)
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:
404c1692 399 self.__pifs[p][f] = rec[f]
b3080599
IC
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 = {}
404c1692 407 for (v,rec) in session.xenapi.VLAN.get_all_records().items():
b3080599
IC
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
92e906e4
IC
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
b3080599
IC
424 def __get_bond_records_from_xapi(self, session):
425 self.__bonds = {}
404c1692 426 for (b,rec) in session.xenapi.Bond.get_all_records().items():
b3080599
IC
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 = {}
404c1692 435 for (n,rec) in session.xenapi.network.get_all_records().items():
b3080599
IC
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)]
3a32d4ca
BP
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
b3080599
IC
445 else:
446 self.__networks[n][f] = rec[f]
447 self.__networks[n]['other_config'] = {}
448 for f in _NETWORK_OTHERCONFIG_ATTRS:
449 if not rec['other_config'].has_key(f): continue
450 self.__networks[n]['other_config'][f] = rec['other_config'][f]
451
939e5a1b
EJ
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 rec['other_config'].has_key(f):
464 self.__pools[p]['other_config'][f] = rec['other_config'][f]
465
b3080599
IC
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 attrs.has_key(n):
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(inventory.has_key('INSTALLATION_UUID'))
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
404c1692 512 self.__get_tunnel_records_from_xapi(session)
939e5a1b 513 self.__get_pool_records_from_xapi(session)
b3080599
IC
514 self.__get_vlan_records_from_xapi(session)
515 self.__get_bond_records_from_xapi(session)
516 self.__get_network_records_from_xapi(session)
517 finally:
518 if not session_ref:
519 session.xenapi.session.logout()
520 else:
521 log("Loading xapi database cache from %s" % cache_file)
522
64ddb6fe 523 xml = parseXML(root_prefix() + cache_file)
b3080599
IC
524
525 self.__pifs = {}
526 self.__bonds = {}
527 self.__vlans = {}
939e5a1b 528 self.__pools = {}
92e906e4 529 self.__tunnels = {}
b3080599
IC
530 self.__networks = {}
531
532 assert(len(xml.childNodes) == 1)
533 toplevel = xml.childNodes[0]
534
535 assert(toplevel.nodeName == "xenserver-network-configuration")
536
537 for n in toplevel.childNodes:
538 if n.nodeName == "#text":
539 pass
540 elif n.nodeName == _PIF_XML_TAG:
541 (ref,rec) = self.__from_xml(n, _PIF_ATTRS)
542 self.__pifs[ref] = rec
543 elif n.nodeName == _BOND_XML_TAG:
544 (ref,rec) = self.__from_xml(n, _BOND_ATTRS)
545 self.__bonds[ref] = rec
546 elif n.nodeName == _VLAN_XML_TAG:
547 (ref,rec) = self.__from_xml(n, _VLAN_ATTRS)
548 self.__vlans[ref] = rec
92e906e4
IC
549 elif n.nodeName == _TUNNEL_XML_TAG:
550 (ref,rec) = self.__from_xml(n, _TUNNEL_ATTRS)
551 self.__vlans[ref] = rec
b3080599
IC
552 elif n.nodeName == _NETWORK_XML_TAG:
553 (ref,rec) = self.__from_xml(n, _NETWORK_ATTRS)
554 self.__networks[ref] = rec
939e5a1b
EJ
555 elif n.nodeName == _POOL_XML_TAG:
556 (ref,rec) = self.__from_xml(n, _POOL_ATTRS)
557 self.__pools[ref] = rec
b3080599
IC
558 else:
559 raise Error("Unknown XML element %s" % n.nodeName)
560
561 def save(self, cache_file):
562
563 xml = getDOMImplementation().createDocument(
564 None, "xenserver-network-configuration", None)
565 for (ref,rec) in self.__pifs.items():
566 self.__to_xml(xml, xml.documentElement, _PIF_XML_TAG, ref, rec, _PIF_ATTRS)
567 for (ref,rec) in self.__bonds.items():
568 self.__to_xml(xml, xml.documentElement, _BOND_XML_TAG, ref, rec, _BOND_ATTRS)
569 for (ref,rec) in self.__vlans.items():
570 self.__to_xml(xml, xml.documentElement, _VLAN_XML_TAG, ref, rec, _VLAN_ATTRS)
92e906e4
IC
571 for (ref,rec) in self.__tunnels.items():
572 self.__to_xml(xml, xml.documentElement, _TUNNEL_XML_TAG, ref, rec, _TUNNEL_ATTRS)
b3080599
IC
573 for (ref,rec) in self.__networks.items():
574 self.__to_xml(xml, xml.documentElement, _NETWORK_XML_TAG, ref, rec,
575 _NETWORK_ATTRS)
939e5a1b
EJ
576 for (ref,rec) in self.__pools.items():
577 self.__to_xml(xml, xml.documentElement, _POOL_XML_TAG, ref, rec, _POOL_ATTRS)
b3080599
IC
578
579 f = open(cache_file, 'w')
580 f.write(xml.toprettyxml())
581 f.close()
582
583 def get_pif_by_uuid(self, uuid):
584 pifs = map(lambda (ref,rec): ref,
585 filter(lambda (ref,rec): uuid == rec['uuid'],
586 self.__pifs.items()))
587 if len(pifs) == 0:
588 raise Error("Unknown PIF \"%s\"" % uuid)
589 elif len(pifs) > 1:
590 raise Error("Non-unique PIF \"%s\"" % uuid)
591
592 return pifs[0]
593
594 def get_pifs_by_device(self, device):
595 return map(lambda (ref,rec): ref,
596 filter(lambda (ref,rec): rec['device'] == device,
597 self.__pifs.items()))
598
2dd26837
EJ
599 def get_networks_with_bridge(self, bridge):
600 return map(lambda (ref,rec): ref,
601 filter(lambda (ref,rec): rec['bridge'] == bridge,
602 self.__networks.items()))
603
604 def get_network_by_bridge(self, bridge):
605 #Assumes one network has bridge.
606 try:
607 return self.get_networks_with_bridge(bridge)[0]
608 except KeyError:
609 return None
610
b3080599 611 def get_pif_by_bridge(self, bridge):
2dd26837
EJ
612 networks = self.get_networks_with_bridge(bridge)
613
b3080599
IC
614 if len(networks) == 0:
615 raise Error("No matching network \"%s\"" % bridge)
616
617 answer = None
618 for network in networks:
619 nwrec = self.get_network_record(network)
620 for pif in nwrec['PIFs']:
621 pifrec = self.get_pif_record(pif)
622 if answer:
623 raise Error("Multiple PIFs on host for network %s" % (bridge))
624 answer = pif
625 if not answer:
626 raise Error("No PIF on host for network %s" % (bridge))
627 return answer
628
629 def get_pif_record(self, pif):
630 if self.__pifs.has_key(pif):
631 return self.__pifs[pif]
632 raise Error("Unknown PIF \"%s\"" % pif)
633 def get_all_pifs(self):
634 return self.__pifs
635 def pif_exists(self, pif):
636 return self.__pifs.has_key(pif)
637
638 def get_management_pif(self):
639 """ Returns the management pif on host
640 """
641 all = self.get_all_pifs()
642 for pif in all:
643 pifrec = self.get_pif_record(pif)
644 if pifrec['management']: return pif
645 return None
646
647 def get_network_record(self, network):
648 if self.__networks.has_key(network):
649 return self.__networks[network]
650 raise Error("Unknown network \"%s\"" % network)
651
652 def get_bond_record(self, bond):
653 if self.__bonds.has_key(bond):
654 return self.__bonds[bond]
655 else:
656 return None
657
658 def get_vlan_record(self, vlan):
659 if self.__vlans.has_key(vlan):
660 return self.__vlans[vlan]
661 else:
662 return None
663
939e5a1b 664 def get_pool_record(self):
0671665d
EJ
665 if len(self.__pools) > 0:
666 return self.__pools.values()[0]
939e5a1b 667
b3080599
IC
668#
669#
670#
404c1692 671PIF_OTHERCONFIG_DEFAULTS = {'gro': 'off', 'lro': 'off'}
b3080599 672
404c1692 673def ethtool_settings(oc, defaults = {}):
b3080599
IC
674 settings = []
675 if oc.has_key('ethtool-speed'):
676 val = oc['ethtool-speed']
677 if val in ["10", "100", "1000"]:
678 settings += ['speed', val]
679 else:
680 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val)
681 if oc.has_key('ethtool-duplex'):
682 val = oc['ethtool-duplex']
404c1692
AE
683 if val in ["half", "full"]:
684 settings += ['duplex', val]
b3080599
IC
685 else:
686 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val)
687 if oc.has_key('ethtool-autoneg'):
688 val = oc['ethtool-autoneg']
689 if val in ["true", "on"]:
690 settings += ['autoneg', 'on']
691 elif val in ["false", "off"]:
692 settings += ['autoneg', 'off']
693 else:
694 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val)
695 offload = []
404c1692 696 for opt in ("rx", "tx", "sg", "tso", "ufo", "gso", "gro", "lro"):
b3080599
IC
697 if oc.has_key("ethtool-" + opt):
698 val = oc["ethtool-" + opt]
699 if val in ["true", "on"]:
700 offload += [opt, 'on']
701 elif val in ["false", "off"]:
702 offload += [opt, 'off']
703 else:
704 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val))
404c1692
AE
705 elif opt in defaults:
706 offload += [opt, defaults[opt]]
b3080599
IC
707 return settings,offload
708
9a2b1175
IC
709# By default the MTU is taken from the Network.MTU setting for VIF,
710# PIF and Bridge. However it is possible to override this by using
711# {VIF,PIF,Network}.other-config:mtu.
712#
713# type parameter is a string describing the object that the oc parameter
714# is from. e.g. "PIF", "Network"
715def mtu_setting(nw, type, oc):
716 mtu = None
717
718 nwrec = db().get_network_record(nw)
719 if nwrec.has_key('MTU'):
720 mtu = nwrec['MTU']
721 else:
722 mtu = "1500"
723
b3080599 724 if oc.has_key('mtu'):
9a2b1175
IC
725 log("Override Network.MTU setting on bridge %s from %s.MTU is %s" % \
726 (nwrec['bridge'], type, mtu))
727 mtu = oc['mtu']
728
729 if mtu is not None:
b3080599 730 try:
9a2b1175
IC
731 int(mtu) # Check that the value is an integer
732 return mtu
b3080599 733 except ValueError, x:
9a2b1175
IC
734 log("Invalid value for mtu = %s" % mtu)
735
b3080599
IC
736 return None
737
738#
739# IP Network Devices -- network devices with IP configuration
740#
741def pif_ipdev_name(pif):
742 """Return the ipdev name associated with pif"""
743 pifrec = db().get_pif_record(pif)
744 nwrec = db().get_network_record(pifrec['network'])
745
746 if nwrec['bridge']:
747 # TODO: sanity check that nwrec['bridgeless'] != 'true'
748 return nwrec['bridge']
749 else:
750 # TODO: sanity check that nwrec['bridgeless'] == 'true'
751 return pif_netdev_name(pif)
752
753#
754# Bare Network Devices -- network devices without IP configuration
755#
756
757def netdev_exists(netdev):
64ddb6fe 758 return os.path.exists(root_prefix() + "/sys/class/net/" + netdev)
b3080599
IC
759
760def pif_netdev_name(pif):
761 """Get the netdev name for a PIF."""
762
763 pifrec = db().get_pif_record(pif)
764
765 if pif_is_vlan(pif):
766 return "%(device)s.%(VLAN)s" % pifrec
767 else:
768 return pifrec['device']
769
96c7918c
BP
770#
771# Bridges
772#
773
774def pif_is_bridged(pif):
775 pifrec = db().get_pif_record(pif)
776 nwrec = db().get_network_record(pifrec['network'])
777
778 if nwrec['bridge']:
779 # TODO: sanity check that nwrec['bridgeless'] != 'true'
780 return True
781 else:
782 # TODO: sanity check that nwrec['bridgeless'] == 'true'
783 return False
784
785def pif_bridge_name(pif):
786 """Return the bridge name of a pif.
787
788 PIF must be a bridged PIF."""
789 pifrec = db().get_pif_record(pif)
790
791 nwrec = db().get_network_record(pifrec['network'])
792
793 if nwrec['bridge']:
794 return nwrec['bridge']
795 else:
796 raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
797
b3080599
IC
798#
799# Bonded PIFs
800#
801def pif_is_bond(pif):
802 pifrec = db().get_pif_record(pif)
803
804 return len(pifrec['bond_master_of']) > 0
805
806def pif_get_bond_masters(pif):
807 """Returns a list of PIFs which are bond masters of this PIF"""
808
809 pifrec = db().get_pif_record(pif)
810
811 bso = pifrec['bond_slave_of']
812
813 # bond-slave-of is currently a single reference but in principle a
814 # PIF could be a member of several bonds which are not
815 # concurrently attached. Be robust to this possibility.
816 if not bso or bso == "OpaqueRef:NULL":
817 bso = []
818 elif not type(bso) == list:
819 bso = [bso]
820
821 bondrecs = [db().get_bond_record(bond) for bond in bso]
822 bondrecs = [rec for rec in bondrecs if rec]
823
824 return [bond['master'] for bond in bondrecs]
825
826def pif_get_bond_slaves(pif):
827 """Returns a list of PIFs which make up the given bonded pif."""
828
829 pifrec = db().get_pif_record(pif)
830
831 bmo = pifrec['bond_master_of']
832 if len(bmo) > 1:
833 raise Error("Bond-master-of contains too many elements")
834
835 if len(bmo) == 0:
836 return []
837
838 bondrec = db().get_bond_record(bmo[0])
839 if not bondrec:
840 raise Error("No bond record for bond master PIF")
841
842 return bondrec['slaves']
843
844#
845# VLAN PIFs
846#
847
848def pif_is_vlan(pif):
849 return db().get_pif_record(pif)['VLAN'] != '-1'
850
851def pif_get_vlan_slave(pif):
852 """Find the PIF which is the VLAN slave of pif.
853
854Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
855
856 pifrec = db().get_pif_record(pif)
857
858 vlan = pifrec['VLAN_master_of']
859 if not vlan or vlan == "OpaqueRef:NULL":
860 raise Error("PIF is not a VLAN master")
861
862 vlanrec = db().get_vlan_record(vlan)
863 if not vlanrec:
864 raise Error("No VLAN record found for PIF")
865
866 return vlanrec['tagged_PIF']
867
868def pif_get_vlan_masters(pif):
869 """Returns a list of PIFs which are VLANs on top of the given pif."""
870
871 pifrec = db().get_pif_record(pif)
872 vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
873 return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])]
874
92e906e4
IC
875#
876# Tunnel PIFs
877#
878def pif_is_tunnel(pif):
404c1692 879 return len(db().get_pif_record(pif)['tunnel_access_PIF_of']) > 0
92e906e4 880
b3080599
IC
881#
882# Datapath base class
883#
884
885class Datapath(object):
886 """Object encapsulating the actions necessary to (de)configure the
887 datapath for a given PIF. Does not include configuration of the
888 IP address on the ipdev.
889 """
890
891 def __init__(self, pif):
892 self._pif = pif
893
823c5699
IC
894 @classmethod
895 def rewrite(cls):
896 """Class method called when write action is called. Can be used
897 to update any backend specific configuration."""
898 pass
899
b3080599
IC
900 def configure_ipdev(self, cfg):
901 """Write ifcfg TYPE field for an IPdev, plus any type specific
902 fields to cfg
903 """
904 raise NotImplementedError
905
906 def preconfigure(self, parent):
907 """Prepare datapath configuration for PIF, but do not actually
908 apply any changes.
909
910 Any configuration files should be attached to parent.
911 """
912 raise NotImplementedError
913
914 def bring_down_existing(self):
915 """Tear down any existing network device configuration which
916 needs to be undone in order to bring this PIF up.
917 """
918 raise NotImplementedError
919
920 def configure(self):
921 """Apply the configuration prepared in the preconfigure stage.
922
923 Should assume any configuration files changed attached in
924 the preconfigure stage are applied and bring up the
925 necesary devices to provide the datapath for the
926 PIF.
927
928 Should not bring up the IPdev.
929 """
930 raise NotImplementedError
931
932 def post(self):
933 """Called after the IPdev has been brought up.
934
935 Should do any final setup, including reinstating any
936 devices which were taken down in the bring_down_existing
937 hook.
938 """
939 raise NotImplementedError
940
941 def bring_down(self):
942 """Tear down and deconfigure the datapath. Should assume the
943 IPdev has already been brought down.
944 """
945 raise NotImplementedError
946
823c5699 947def DatapathFactory():
b3080599
IC
948 # XXX Need a datapath object for bridgeless PIFs
949
950 try:
64ddb6fe 951 network_conf = open(root_prefix() + "/etc/xensource/network.conf", 'r')
b3080599
IC
952 network_backend = network_conf.readline().strip()
953 network_conf.close()
954 except Exception, e:
955 raise Error("failed to determine network backend:" + e)
956
957 if network_backend == "bridge":
958 from InterfaceReconfigureBridge import DatapathBridge
823c5699 959 return DatapathBridge
aeb2b7a1 960 elif network_backend in ["openvswitch", "vswitch"]:
b3080599 961 from InterfaceReconfigureVswitch import DatapathVswitch
823c5699 962 return DatapathVswitch
b3080599
IC
963 else:
964 raise Error("unknown network backend %s" % network_backend)