]>
Commit | Line | Data |
---|---|---|
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 | 13 | import sys |
b3080599 IC |
14 | import syslog |
15 | import os | |
16 | ||
17 | from xml.dom.minidom import getDOMImplementation | |
18 | from xml.dom.minidom import parse as parseXML | |
19 | ||
64ddb6fe BP |
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 | ||
b63fadcf BP |
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 | ||
b3080599 IC |
39 | # |
40 | # Logging. | |
41 | # | |
42 | ||
43 | def 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 | ||
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)) | |
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 | ||
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 | ||
92e906e4 IC |
244 | def _map_to_xml(xml, parent, tag, val, attrs): |
245 | e = xml.createElement(tag) | |
246 | parent.appendChild(e) | |
b3080599 | 247 | for n,v in val.items(): |
3dfa61ca EJ |
248 | if n in attrs: |
249 | _str_to_xml(xml, e, n, v) | |
250 | else: | |
251 | log("Unknown other-config attribute: %s" % n) | |
92e906e4 IC |
252 | |
253 | def _map_from_xml(n, attrs): | |
b3080599 IC |
254 | ret = {} |
255 | for n in n.childNodes: | |
256 | if n.nodeName in attrs: | |
257 | ret[n.nodeName] = _str_from_xml(n) | |
258 | return ret | |
259 | ||
92e906e4 IC |
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 | ||
b3080599 IC |
265 | # |
266 | # Definitions of the database objects (and their attributes) used by interface-reconfigure. | |
267 | # | |
268 | # Each object is defined by a dictionary mapping an attribute name in | |
269 | # the xapi database to a tuple containing two items: | |
270 | # - a function which takes this attribute and encodes it as XML. | |
271 | # - a function which takes XML and decocdes it into a value. | |
272 | # | |
273 | # other-config attributes are specified as a simple array of strings | |
274 | ||
275 | _PIF_XML_TAG = "pif" | |
276 | _VLAN_XML_TAG = "vlan" | |
92e906e4 | 277 | _TUNNEL_XML_TAG = "tunnel" |
b3080599 IC |
278 | _BOND_XML_TAG = "bond" |
279 | _NETWORK_XML_TAG = "network" | |
939e5a1b | 280 | _POOL_XML_TAG = "pool" |
b3080599 | 281 | |
404c1692 | 282 | _ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso', 'gro', 'lro' ] |
b3080599 IC |
283 | |
284 | _PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \ | |
2776e408 EJ |
285 | [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', |
286 | '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 | ||
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): | |
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 IC |
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: | |
404c1692 | 399 | self.__pifs[p][f] = rec[f] |
b3080599 IC |
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 = {} | |
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: | |
449 | if not rec['other_config'].has_key(f): continue | |
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: | |
463 | if rec['other_config'].has_key(f): | |
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(): | |
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 | ||
404c1692 | 512 | self.__get_tunnel_records_from_xapi(session) |
939e5a1b | 513 | self.__get_pool_records_from_xapi(session) |
b3080599 IC |
514 | self.__get_vlan_records_from_xapi(session) |
515 | self.__get_bond_records_from_xapi(session) | |
516 | self.__get_network_records_from_xapi(session) | |
517 | finally: | |
518 | if not session_ref: | |
519 | session.xenapi.session.logout() | |
520 | else: | |
521 | log("Loading xapi database cache from %s" % cache_file) | |
522 | ||
64ddb6fe | 523 | xml = parseXML(root_prefix() + cache_file) |
b3080599 IC |
524 | |
525 | self.__pifs = {} | |
526 | self.__bonds = {} | |
527 | self.__vlans = {} | |
939e5a1b | 528 | self.__pools = {} |
92e906e4 | 529 | self.__tunnels = {} |
b3080599 IC |
530 | self.__networks = {} |
531 | ||
532 | assert(len(xml.childNodes) == 1) | |
533 | toplevel = xml.childNodes[0] | |
534 | ||
535 | assert(toplevel.nodeName == "xenserver-network-configuration") | |
536 | ||
537 | for n in toplevel.childNodes: | |
538 | if n.nodeName == "#text": | |
539 | pass | |
540 | elif n.nodeName == _PIF_XML_TAG: | |
541 | (ref,rec) = self.__from_xml(n, _PIF_ATTRS) | |
542 | self.__pifs[ref] = rec | |
543 | elif n.nodeName == _BOND_XML_TAG: | |
544 | (ref,rec) = self.__from_xml(n, _BOND_ATTRS) | |
545 | self.__bonds[ref] = rec | |
546 | elif n.nodeName == _VLAN_XML_TAG: | |
547 | (ref,rec) = self.__from_xml(n, _VLAN_ATTRS) | |
548 | self.__vlans[ref] = rec | |
92e906e4 IC |
549 | elif n.nodeName == _TUNNEL_XML_TAG: |
550 | (ref,rec) = self.__from_xml(n, _TUNNEL_ATTRS) | |
551 | self.__vlans[ref] = rec | |
b3080599 IC |
552 | elif n.nodeName == _NETWORK_XML_TAG: |
553 | (ref,rec) = self.__from_xml(n, _NETWORK_ATTRS) | |
554 | self.__networks[ref] = rec | |
939e5a1b EJ |
555 | elif n.nodeName == _POOL_XML_TAG: |
556 | (ref,rec) = self.__from_xml(n, _POOL_ATTRS) | |
557 | self.__pools[ref] = rec | |
b3080599 IC |
558 | else: |
559 | raise Error("Unknown XML element %s" % n.nodeName) | |
560 | ||
561 | def save(self, cache_file): | |
562 | ||
563 | xml = getDOMImplementation().createDocument( | |
564 | None, "xenserver-network-configuration", None) | |
565 | for (ref,rec) in self.__pifs.items(): | |
566 | self.__to_xml(xml, xml.documentElement, _PIF_XML_TAG, ref, rec, _PIF_ATTRS) | |
567 | for (ref,rec) in self.__bonds.items(): | |
568 | self.__to_xml(xml, xml.documentElement, _BOND_XML_TAG, ref, rec, _BOND_ATTRS) | |
569 | for (ref,rec) in self.__vlans.items(): | |
570 | self.__to_xml(xml, xml.documentElement, _VLAN_XML_TAG, ref, rec, _VLAN_ATTRS) | |
92e906e4 IC |
571 | for (ref,rec) in self.__tunnels.items(): |
572 | self.__to_xml(xml, xml.documentElement, _TUNNEL_XML_TAG, ref, rec, _TUNNEL_ATTRS) | |
b3080599 IC |
573 | for (ref,rec) in self.__networks.items(): |
574 | self.__to_xml(xml, xml.documentElement, _NETWORK_XML_TAG, ref, rec, | |
575 | _NETWORK_ATTRS) | |
939e5a1b EJ |
576 | for (ref,rec) in self.__pools.items(): |
577 | self.__to_xml(xml, xml.documentElement, _POOL_XML_TAG, ref, rec, _POOL_ATTRS) | |
b3080599 IC |
578 | |
579 | f = open(cache_file, 'w') | |
580 | f.write(xml.toprettyxml()) | |
581 | f.close() | |
582 | ||
583 | def get_pif_by_uuid(self, uuid): | |
584 | pifs = map(lambda (ref,rec): ref, | |
585 | filter(lambda (ref,rec): uuid == rec['uuid'], | |
586 | self.__pifs.items())) | |
587 | if len(pifs) == 0: | |
588 | raise Error("Unknown PIF \"%s\"" % uuid) | |
589 | elif len(pifs) > 1: | |
590 | raise Error("Non-unique PIF \"%s\"" % uuid) | |
591 | ||
592 | return pifs[0] | |
593 | ||
594 | def get_pifs_by_device(self, device): | |
595 | return map(lambda (ref,rec): ref, | |
596 | filter(lambda (ref,rec): rec['device'] == device, | |
597 | self.__pifs.items())) | |
598 | ||
2dd26837 EJ |
599 | def get_networks_with_bridge(self, bridge): |
600 | return map(lambda (ref,rec): ref, | |
601 | filter(lambda (ref,rec): rec['bridge'] == bridge, | |
602 | self.__networks.items())) | |
603 | ||
604 | def get_network_by_bridge(self, bridge): | |
605 | #Assumes one network has bridge. | |
606 | try: | |
607 | return self.get_networks_with_bridge(bridge)[0] | |
608 | except KeyError: | |
609 | return None | |
610 | ||
b3080599 | 611 | def get_pif_by_bridge(self, bridge): |
2dd26837 EJ |
612 | networks = self.get_networks_with_bridge(bridge) |
613 | ||
b3080599 IC |
614 | if len(networks) == 0: |
615 | raise Error("No matching network \"%s\"" % bridge) | |
616 | ||
617 | answer = None | |
618 | for network in networks: | |
619 | nwrec = self.get_network_record(network) | |
620 | for pif in nwrec['PIFs']: | |
621 | pifrec = self.get_pif_record(pif) | |
622 | if answer: | |
623 | raise Error("Multiple PIFs on host for network %s" % (bridge)) | |
624 | answer = pif | |
625 | if not answer: | |
626 | raise Error("No PIF on host for network %s" % (bridge)) | |
627 | return answer | |
628 | ||
629 | def get_pif_record(self, pif): | |
630 | if self.__pifs.has_key(pif): | |
631 | return self.__pifs[pif] | |
632 | raise Error("Unknown PIF \"%s\"" % pif) | |
633 | def get_all_pifs(self): | |
634 | return self.__pifs | |
635 | def pif_exists(self, pif): | |
636 | return self.__pifs.has_key(pif) | |
637 | ||
638 | def get_management_pif(self): | |
639 | """ Returns the management pif on host | |
640 | """ | |
641 | all = self.get_all_pifs() | |
642 | for pif in all: | |
643 | pifrec = self.get_pif_record(pif) | |
644 | if pifrec['management']: return pif | |
645 | return None | |
646 | ||
647 | def get_network_record(self, network): | |
648 | if self.__networks.has_key(network): | |
649 | return self.__networks[network] | |
650 | raise Error("Unknown network \"%s\"" % network) | |
651 | ||
652 | def get_bond_record(self, bond): | |
653 | if self.__bonds.has_key(bond): | |
654 | return self.__bonds[bond] | |
655 | else: | |
656 | return None | |
657 | ||
658 | def get_vlan_record(self, vlan): | |
659 | if self.__vlans.has_key(vlan): | |
660 | return self.__vlans[vlan] | |
661 | else: | |
662 | return None | |
663 | ||
939e5a1b | 664 | def get_pool_record(self): |
0671665d EJ |
665 | if len(self.__pools) > 0: |
666 | return self.__pools.values()[0] | |
939e5a1b | 667 | |
b3080599 IC |
668 | # |
669 | # | |
670 | # | |
404c1692 | 671 | PIF_OTHERCONFIG_DEFAULTS = {'gro': 'off', 'lro': 'off'} |
b3080599 | 672 | |
404c1692 | 673 | def ethtool_settings(oc, defaults = {}): |
b3080599 IC |
674 | settings = [] |
675 | if oc.has_key('ethtool-speed'): | |
676 | val = oc['ethtool-speed'] | |
677 | if val in ["10", "100", "1000"]: | |
678 | settings += ['speed', val] | |
679 | else: | |
680 | log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val) | |
681 | if oc.has_key('ethtool-duplex'): | |
682 | val = oc['ethtool-duplex'] | |
404c1692 AE |
683 | if val in ["half", "full"]: |
684 | settings += ['duplex', val] | |
b3080599 IC |
685 | else: |
686 | log("Invalid value for ethtool-duplex = %s. Must be half|full." % val) | |
687 | if oc.has_key('ethtool-autoneg'): | |
688 | val = oc['ethtool-autoneg'] | |
689 | if val in ["true", "on"]: | |
690 | settings += ['autoneg', 'on'] | |
691 | elif val in ["false", "off"]: | |
692 | settings += ['autoneg', 'off'] | |
693 | else: | |
694 | log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val) | |
695 | offload = [] | |
404c1692 | 696 | for opt in ("rx", "tx", "sg", "tso", "ufo", "gso", "gro", "lro"): |
b3080599 IC |
697 | if oc.has_key("ethtool-" + opt): |
698 | val = oc["ethtool-" + opt] | |
699 | if val in ["true", "on"]: | |
700 | offload += [opt, 'on'] | |
701 | elif val in ["false", "off"]: | |
702 | offload += [opt, 'off'] | |
703 | else: | |
704 | log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val)) | |
404c1692 AE |
705 | elif opt in defaults: |
706 | offload += [opt, defaults[opt]] | |
b3080599 IC |
707 | return settings,offload |
708 | ||
9a2b1175 IC |
709 | # By default the MTU is taken from the Network.MTU setting for VIF, |
710 | # PIF and Bridge. However it is possible to override this by using | |
711 | # {VIF,PIF,Network}.other-config:mtu. | |
712 | # | |
713 | # type parameter is a string describing the object that the oc parameter | |
714 | # is from. e.g. "PIF", "Network" | |
715 | def mtu_setting(nw, type, oc): | |
716 | mtu = None | |
717 | ||
718 | nwrec = db().get_network_record(nw) | |
719 | if nwrec.has_key('MTU'): | |
720 | mtu = nwrec['MTU'] | |
721 | else: | |
722 | mtu = "1500" | |
723 | ||
b3080599 | 724 | if oc.has_key('mtu'): |
9a2b1175 IC |
725 | log("Override Network.MTU setting on bridge %s from %s.MTU is %s" % \ |
726 | (nwrec['bridge'], type, mtu)) | |
727 | mtu = oc['mtu'] | |
728 | ||
729 | if mtu is not None: | |
b3080599 | 730 | try: |
9a2b1175 IC |
731 | int(mtu) # Check that the value is an integer |
732 | return mtu | |
b3080599 | 733 | except ValueError, x: |
9a2b1175 IC |
734 | log("Invalid value for mtu = %s" % mtu) |
735 | ||
b3080599 IC |
736 | return None |
737 | ||
738 | # | |
739 | # IP Network Devices -- network devices with IP configuration | |
740 | # | |
741 | def pif_ipdev_name(pif): | |
742 | """Return the ipdev name associated with pif""" | |
743 | pifrec = db().get_pif_record(pif) | |
744 | nwrec = db().get_network_record(pifrec['network']) | |
745 | ||
746 | if nwrec['bridge']: | |
747 | # TODO: sanity check that nwrec['bridgeless'] != 'true' | |
748 | return nwrec['bridge'] | |
749 | else: | |
750 | # TODO: sanity check that nwrec['bridgeless'] == 'true' | |
751 | return pif_netdev_name(pif) | |
752 | ||
753 | # | |
754 | # Bare Network Devices -- network devices without IP configuration | |
755 | # | |
756 | ||
757 | def netdev_exists(netdev): | |
64ddb6fe | 758 | return os.path.exists(root_prefix() + "/sys/class/net/" + netdev) |
b3080599 IC |
759 | |
760 | def pif_netdev_name(pif): | |
761 | """Get the netdev name for a PIF.""" | |
762 | ||
763 | pifrec = db().get_pif_record(pif) | |
764 | ||
765 | if pif_is_vlan(pif): | |
766 | return "%(device)s.%(VLAN)s" % pifrec | |
767 | else: | |
768 | return pifrec['device'] | |
769 | ||
96c7918c BP |
770 | # |
771 | # Bridges | |
772 | # | |
773 | ||
774 | def pif_is_bridged(pif): | |
775 | pifrec = db().get_pif_record(pif) | |
776 | nwrec = db().get_network_record(pifrec['network']) | |
777 | ||
778 | if nwrec['bridge']: | |
779 | # TODO: sanity check that nwrec['bridgeless'] != 'true' | |
780 | return True | |
781 | else: | |
782 | # TODO: sanity check that nwrec['bridgeless'] == 'true' | |
783 | return False | |
784 | ||
785 | def pif_bridge_name(pif): | |
786 | """Return the bridge name of a pif. | |
787 | ||
788 | PIF must be a bridged PIF.""" | |
789 | pifrec = db().get_pif_record(pif) | |
790 | ||
791 | nwrec = db().get_network_record(pifrec['network']) | |
792 | ||
793 | if nwrec['bridge']: | |
794 | return nwrec['bridge'] | |
795 | else: | |
796 | raise Error("PIF %(uuid)s does not have a bridge name" % pifrec) | |
797 | ||
b3080599 IC |
798 | # |
799 | # Bonded PIFs | |
800 | # | |
801 | def pif_is_bond(pif): | |
802 | pifrec = db().get_pif_record(pif) | |
803 | ||
804 | return len(pifrec['bond_master_of']) > 0 | |
805 | ||
806 | def pif_get_bond_masters(pif): | |
807 | """Returns a list of PIFs which are bond masters of this PIF""" | |
808 | ||
809 | pifrec = db().get_pif_record(pif) | |
810 | ||
811 | bso = pifrec['bond_slave_of'] | |
812 | ||
813 | # bond-slave-of is currently a single reference but in principle a | |
814 | # PIF could be a member of several bonds which are not | |
815 | # concurrently attached. Be robust to this possibility. | |
816 | if not bso or bso == "OpaqueRef:NULL": | |
817 | bso = [] | |
818 | elif not type(bso) == list: | |
819 | bso = [bso] | |
820 | ||
821 | bondrecs = [db().get_bond_record(bond) for bond in bso] | |
822 | bondrecs = [rec for rec in bondrecs if rec] | |
823 | ||
824 | return [bond['master'] for bond in bondrecs] | |
825 | ||
826 | def pif_get_bond_slaves(pif): | |
827 | """Returns a list of PIFs which make up the given bonded pif.""" | |
828 | ||
829 | pifrec = db().get_pif_record(pif) | |
830 | ||
831 | bmo = pifrec['bond_master_of'] | |
832 | if len(bmo) > 1: | |
833 | raise Error("Bond-master-of contains too many elements") | |
834 | ||
835 | if len(bmo) == 0: | |
836 | return [] | |
837 | ||
838 | bondrec = db().get_bond_record(bmo[0]) | |
839 | if not bondrec: | |
840 | raise Error("No bond record for bond master PIF") | |
841 | ||
842 | return bondrec['slaves'] | |
843 | ||
844 | # | |
845 | # VLAN PIFs | |
846 | # | |
847 | ||
848 | def pif_is_vlan(pif): | |
849 | return db().get_pif_record(pif)['VLAN'] != '-1' | |
850 | ||
851 | def pif_get_vlan_slave(pif): | |
852 | """Find the PIF which is the VLAN slave of pif. | |
853 | ||
854 | Returns the 'physical' PIF underneath the a VLAN PIF @pif.""" | |
855 | ||
856 | pifrec = db().get_pif_record(pif) | |
857 | ||
858 | vlan = pifrec['VLAN_master_of'] | |
859 | if not vlan or vlan == "OpaqueRef:NULL": | |
860 | raise Error("PIF is not a VLAN master") | |
861 | ||
862 | vlanrec = db().get_vlan_record(vlan) | |
863 | if not vlanrec: | |
864 | raise Error("No VLAN record found for PIF") | |
865 | ||
866 | return vlanrec['tagged_PIF'] | |
867 | ||
868 | def pif_get_vlan_masters(pif): | |
869 | """Returns a list of PIFs which are VLANs on top of the given pif.""" | |
870 | ||
871 | pifrec = db().get_pif_record(pif) | |
872 | vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']] | |
873 | return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])] | |
874 | ||
92e906e4 IC |
875 | # |
876 | # Tunnel PIFs | |
877 | # | |
878 | def pif_is_tunnel(pif): | |
404c1692 | 879 | return len(db().get_pif_record(pif)['tunnel_access_PIF_of']) > 0 |
92e906e4 | 880 | |
b3080599 IC |
881 | # |
882 | # Datapath base class | |
883 | # | |
884 | ||
885 | class Datapath(object): | |
886 | """Object encapsulating the actions necessary to (de)configure the | |
887 | datapath for a given PIF. Does not include configuration of the | |
888 | IP address on the ipdev. | |
889 | """ | |
890 | ||
891 | def __init__(self, pif): | |
892 | self._pif = pif | |
893 | ||
823c5699 IC |
894 | @classmethod |
895 | def rewrite(cls): | |
896 | """Class method called when write action is called. Can be used | |
897 | to update any backend specific configuration.""" | |
898 | pass | |
899 | ||
b3080599 IC |
900 | def configure_ipdev(self, cfg): |
901 | """Write ifcfg TYPE field for an IPdev, plus any type specific | |
902 | fields to cfg | |
903 | """ | |
904 | raise NotImplementedError | |
905 | ||
906 | def preconfigure(self, parent): | |
907 | """Prepare datapath configuration for PIF, but do not actually | |
908 | apply any changes. | |
909 | ||
910 | Any configuration files should be attached to parent. | |
911 | """ | |
912 | raise NotImplementedError | |
913 | ||
914 | def bring_down_existing(self): | |
915 | """Tear down any existing network device configuration which | |
916 | needs to be undone in order to bring this PIF up. | |
917 | """ | |
918 | raise NotImplementedError | |
919 | ||
920 | def configure(self): | |
921 | """Apply the configuration prepared in the preconfigure stage. | |
922 | ||
923 | Should assume any configuration files changed attached in | |
924 | the preconfigure stage are applied and bring up the | |
925 | necesary devices to provide the datapath for the | |
926 | PIF. | |
927 | ||
928 | Should not bring up the IPdev. | |
929 | """ | |
930 | raise NotImplementedError | |
931 | ||
932 | def post(self): | |
933 | """Called after the IPdev has been brought up. | |
934 | ||
935 | Should do any final setup, including reinstating any | |
936 | devices which were taken down in the bring_down_existing | |
937 | hook. | |
938 | """ | |
939 | raise NotImplementedError | |
940 | ||
941 | def bring_down(self): | |
942 | """Tear down and deconfigure the datapath. Should assume the | |
943 | IPdev has already been brought down. | |
944 | """ | |
945 | raise NotImplementedError | |
946 | ||
823c5699 | 947 | def DatapathFactory(): |
b3080599 IC |
948 | # XXX Need a datapath object for bridgeless PIFs |
949 | ||
950 | try: | |
64ddb6fe | 951 | network_conf = open(root_prefix() + "/etc/xensource/network.conf", 'r') |
b3080599 IC |
952 | network_backend = network_conf.readline().strip() |
953 | network_conf.close() | |
954 | except Exception, e: | |
955 | raise Error("failed to determine network backend:" + e) | |
956 | ||
957 | if network_backend == "bridge": | |
958 | from InterfaceReconfigureBridge import DatapathBridge | |
823c5699 | 959 | return DatapathBridge |
aeb2b7a1 | 960 | elif network_backend in ["openvswitch", "vswitch"]: |
b3080599 | 961 | from InterfaceReconfigureVswitch import DatapathVswitch |
823c5699 | 962 | return DatapathVswitch |
b3080599 IC |
963 | else: |
964 | raise Error("unknown network backend %s" % network_backend) |