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