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