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