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