]> git.proxmox.com Git - mirror_ovs.git/blob - xenserver/opt_xensource_libexec_InterfaceReconfigure.py
ovs-xapi-sync: Add unixctl support.
[mirror_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', 'gro', 'lro' ]
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 [ '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 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:
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,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 not rec['other_config'].has_key(f): 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 rec['other_config'].has_key(f):
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 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 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,
586 filter(lambda (ref,rec): uuid == rec['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 map(lambda (ref,rec): ref,
597 filter(lambda (ref,rec): rec['device'] == device,
598 self.__pifs.items()))
599
600 def get_networks_with_bridge(self, bridge):
601 return map(lambda (ref,rec): ref,
602 filter(lambda (ref,rec): rec['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 self.__pifs.has_key(pif):
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 self.__pifs.has_key(pif)
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 self.__networks.has_key(network):
650 return self.__networks[network]
651 raise Error("Unknown network \"%s\"" % network)
652
653 def get_bond_record(self, bond):
654 if self.__bonds.has_key(bond):
655 return self.__bonds[bond]
656 else:
657 return None
658
659 def get_vlan_record(self, vlan):
660 if self.__vlans.has_key(vlan):
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 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 oc.has_key('ethtool-speed'):
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 oc.has_key('ethtool-duplex'):
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 oc.has_key('ethtool-autoneg'):
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 oc.has_key("ethtool-" + opt):
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 nwrec.has_key('MTU'):
721 mtu = nwrec['MTU']
722 else:
723 mtu = "1500"
724
725 if oc.has_key('mtu'):
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, 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 def pif_netdev_name(pif):
762 """Get the netdev name for a PIF."""
763
764 pifrec = db().get_pif_record(pif)
765
766 if pif_is_vlan(pif):
767 return "%(device)s.%(VLAN)s" % pifrec
768 else:
769 return pifrec['device']
770
771 #
772 # Bridges
773 #
774
775 def pif_is_bridged(pif):
776 pifrec = db().get_pif_record(pif)
777 nwrec = db().get_network_record(pifrec['network'])
778
779 if nwrec['bridge']:
780 # TODO: sanity check that nwrec['bridgeless'] != 'true'
781 return True
782 else:
783 # TODO: sanity check that nwrec['bridgeless'] == 'true'
784 return False
785
786 def pif_bridge_name(pif):
787 """Return the bridge name of a pif.
788
789 PIF must be a bridged PIF."""
790 pifrec = db().get_pif_record(pif)
791
792 nwrec = db().get_network_record(pifrec['network'])
793
794 if nwrec['bridge']:
795 return nwrec['bridge']
796 else:
797 raise Error("PIF %(uuid)s does not have a bridge name" % pifrec)
798
799 #
800 # Bonded PIFs
801 #
802 def pif_is_bond(pif):
803 pifrec = db().get_pif_record(pif)
804
805 return len(pifrec['bond_master_of']) > 0
806
807 def pif_get_bond_masters(pif):
808 """Returns a list of PIFs which are bond masters of this PIF"""
809
810 pifrec = db().get_pif_record(pif)
811
812 bso = pifrec['bond_slave_of']
813
814 # bond-slave-of is currently a single reference but in principle a
815 # PIF could be a member of several bonds which are not
816 # concurrently attached. Be robust to this possibility.
817 if not bso or bso == "OpaqueRef:NULL":
818 bso = []
819 elif not type(bso) == list:
820 bso = [bso]
821
822 bondrecs = [db().get_bond_record(bond) for bond in bso]
823 bondrecs = [rec for rec in bondrecs if rec]
824
825 return [bond['master'] for bond in bondrecs]
826
827 def pif_get_bond_slaves(pif):
828 """Returns a list of PIFs which make up the given bonded pif."""
829
830 pifrec = db().get_pif_record(pif)
831
832 bmo = pifrec['bond_master_of']
833 if len(bmo) > 1:
834 raise Error("Bond-master-of contains too many elements")
835
836 if len(bmo) == 0:
837 return []
838
839 bondrec = db().get_bond_record(bmo[0])
840 if not bondrec:
841 raise Error("No bond record for bond master PIF")
842
843 return bondrec['slaves']
844
845 #
846 # VLAN PIFs
847 #
848
849 def pif_is_vlan(pif):
850 return db().get_pif_record(pif)['VLAN'] != '-1'
851
852 def pif_get_vlan_slave(pif):
853 """Find the PIF which is the VLAN slave of pif.
854
855 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
856
857 pifrec = db().get_pif_record(pif)
858
859 vlan = pifrec['VLAN_master_of']
860 if not vlan or vlan == "OpaqueRef:NULL":
861 raise Error("PIF is not a VLAN master")
862
863 vlanrec = db().get_vlan_record(vlan)
864 if not vlanrec:
865 raise Error("No VLAN record found for PIF")
866
867 return vlanrec['tagged_PIF']
868
869 def pif_get_vlan_masters(pif):
870 """Returns a list of PIFs which are VLANs on top of the given pif."""
871
872 pifrec = db().get_pif_record(pif)
873 vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']]
874 return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])]
875
876 #
877 # Tunnel PIFs
878 #
879 def pif_is_tunnel(pif):
880 return len(db().get_pif_record(pif)['tunnel_access_PIF_of']) > 0
881
882 #
883 # Datapath base class
884 #
885
886 class Datapath(object):
887 """Object encapsulating the actions necessary to (de)configure the
888 datapath for a given PIF. Does not include configuration of the
889 IP address on the ipdev.
890 """
891
892 def __init__(self, pif):
893 self._pif = pif
894
895 @classmethod
896 def rewrite(cls):
897 """Class method called when write action is called. Can be used
898 to update any backend specific configuration."""
899 pass
900
901 def configure_ipdev(self, cfg):
902 """Write ifcfg TYPE field for an IPdev, plus any type specific
903 fields to cfg
904 """
905 raise NotImplementedError
906
907 def preconfigure(self, parent):
908 """Prepare datapath configuration for PIF, but do not actually
909 apply any changes.
910
911 Any configuration files should be attached to parent.
912 """
913 raise NotImplementedError
914
915 def bring_down_existing(self):
916 """Tear down any existing network device configuration which
917 needs to be undone in order to bring this PIF up.
918 """
919 raise NotImplementedError
920
921 def configure(self):
922 """Apply the configuration prepared in the preconfigure stage.
923
924 Should assume any configuration files changed attached in
925 the preconfigure stage are applied and bring up the
926 necesary devices to provide the datapath for the
927 PIF.
928
929 Should not bring up the IPdev.
930 """
931 raise NotImplementedError
932
933 def post(self):
934 """Called after the IPdev has been brought up.
935
936 Should do any final setup, including reinstating any
937 devices which were taken down in the bring_down_existing
938 hook.
939 """
940 raise NotImplementedError
941
942 def bring_down(self):
943 """Tear down and deconfigure the datapath. Should assume the
944 IPdev has already been brought down.
945 """
946 raise NotImplementedError
947
948 def DatapathFactory():
949 # XXX Need a datapath object for bridgeless PIFs
950
951 try:
952 network_conf = open(root_prefix() + "/etc/xensource/network.conf", 'r')
953 network_backend = network_conf.readline().strip()
954 network_conf.close()
955 except Exception, e:
956 raise Error("failed to determine network backend:" + e)
957
958 if network_backend == "bridge":
959 from InterfaceReconfigureBridge import DatapathBridge
960 return DatapathBridge
961 elif network_backend in ["openvswitch", "vswitch"]:
962 from InterfaceReconfigureVswitch import DatapathVswitch
963 return DatapathVswitch
964 else:
965 raise Error("unknown network backend %s" % network_backend)