]>
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) | |
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): | |
585 | pifs = map(lambda (ref,rec): ref, | |
586 | filter(lambda (ref,rec): uuid == rec['uuid'], | |
587 | self.__pifs.items())) | |
588 | if len(pifs) == 0: | |
589 | raise Error("Unknown PIF \"%s\"" % uuid) | |
590 | elif len(pifs) > 1: | |
591 | raise Error("Non-unique PIF \"%s\"" % uuid) | |
592 | ||
593 | return pifs[0] | |
594 | ||
595 | def get_pifs_by_device(self, device): | |
596 | return map(lambda (ref,rec): ref, | |
597 | filter(lambda (ref,rec): rec['device'] == device, | |
598 | self.__pifs.items())) | |
599 | ||
2dd26837 EJ |
600 | def get_networks_with_bridge(self, bridge): |
601 | return map(lambda (ref,rec): ref, | |
602 | filter(lambda (ref,rec): rec['bridge'] == bridge, | |
603 | self.__networks.items())) | |
604 | ||
605 | def get_network_by_bridge(self, bridge): | |
606 | #Assumes one network has bridge. | |
607 | try: | |
608 | return self.get_networks_with_bridge(bridge)[0] | |
609 | except KeyError: | |
610 | return None | |
611 | ||
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): | |
631 | if self.__pifs.has_key(pif): | |
632 | return self.__pifs[pif] | |
633 | raise Error("Unknown PIF \"%s\"" % pif) | |
634 | def get_all_pifs(self): | |
635 | return self.__pifs | |
636 | def pif_exists(self, pif): | |
637 | return self.__pifs.has_key(pif) | |
638 | ||
639 | def get_management_pif(self): | |
640 | """ Returns the management pif on host | |
641 | """ | |
642 | all = self.get_all_pifs() | |
643 | for pif in all: | |
644 | pifrec = self.get_pif_record(pif) | |
645 | if pifrec['management']: return pif | |
646 | return None | |
647 | ||
648 | def get_network_record(self, network): | |
649 | if self.__networks.has_key(network): | |
650 | return self.__networks[network] | |
651 | raise Error("Unknown network \"%s\"" % network) | |
652 | ||
653 | def get_bond_record(self, bond): | |
654 | if self.__bonds.has_key(bond): | |
655 | return self.__bonds[bond] | |
656 | else: | |
657 | return None | |
658 | ||
659 | def get_vlan_record(self, vlan): | |
660 | if self.__vlans.has_key(vlan): | |
661 | return self.__vlans[vlan] | |
662 | else: | |
663 | return None | |
664 | ||
939e5a1b | 665 | def get_pool_record(self): |
0671665d EJ |
666 | if len(self.__pools) > 0: |
667 | return self.__pools.values()[0] | |
939e5a1b | 668 | |
b3080599 IC |
669 | # |
670 | # | |
671 | # | |
404c1692 | 672 | PIF_OTHERCONFIG_DEFAULTS = {'gro': 'off', 'lro': 'off'} |
b3080599 | 673 | |
404c1692 | 674 | def ethtool_settings(oc, defaults = {}): |
b3080599 IC |
675 | settings = [] |
676 | if oc.has_key('ethtool-speed'): | |
677 | val = oc['ethtool-speed'] | |
678 | if val in ["10", "100", "1000"]: | |
679 | settings += ['speed', val] | |
680 | else: | |
681 | log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val) | |
682 | if oc.has_key('ethtool-duplex'): | |
683 | val = oc['ethtool-duplex'] | |
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) | |
688 | if oc.has_key('ethtool-autoneg'): | |
689 | val = oc['ethtool-autoneg'] | |
690 | if val in ["true", "on"]: | |
691 | settings += ['autoneg', 'on'] | |
692 | elif val in ["false", "off"]: | |
693 | settings += ['autoneg', 'off'] | |
694 | else: | |
695 | log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val) | |
696 | offload = [] | |
404c1692 | 697 | for opt in ("rx", "tx", "sg", "tso", "ufo", "gso", "gro", "lro"): |
b3080599 IC |
698 | if oc.has_key("ethtool-" + opt): |
699 | val = oc["ethtool-" + opt] | |
700 | if val in ["true", "on"]: | |
701 | offload += [opt, 'on'] | |
702 | elif val in ["false", "off"]: | |
703 | offload += [opt, 'off'] | |
704 | else: | |
705 | log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val)) | |
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" | |
716 | def mtu_setting(nw, type, oc): | |
717 | mtu = None | |
718 | ||
719 | nwrec = db().get_network_record(nw) | |
720 | if nwrec.has_key('MTU'): | |
721 | mtu = nwrec['MTU'] | |
722 | else: | |
723 | mtu = "1500" | |
724 | ||
b3080599 | 725 | if oc.has_key('mtu'): |
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 | |
b3080599 | 734 | except ValueError, 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 | # | |
742 | def pif_ipdev_name(pif): | |
743 | """Return the ipdev name associated with pif""" | |
744 | pifrec = db().get_pif_record(pif) | |
745 | nwrec = db().get_network_record(pifrec['network']) | |
746 | ||
747 | if nwrec['bridge']: | |
748 | # TODO: sanity check that nwrec['bridgeless'] != 'true' | |
749 | return nwrec['bridge'] | |
750 | else: | |
751 | # TODO: sanity check that nwrec['bridgeless'] == 'true' | |
752 | return pif_netdev_name(pif) | |
753 | ||
754 | # | |
755 | # Bare Network Devices -- network devices without IP configuration | |
756 | # | |
757 | ||
758 | def netdev_exists(netdev): | |
64ddb6fe | 759 | return os.path.exists(root_prefix() + "/sys/class/net/" + netdev) |
b3080599 IC |
760 | |
761 | def pif_netdev_name(pif): | |
762 | """Get the netdev name for a PIF.""" | |
763 | ||
764 | pifrec = db().get_pif_record(pif) | |
765 | ||
766 | if pif_is_vlan(pif): | |
767 | return "%(device)s.%(VLAN)s" % pifrec | |
768 | else: | |
769 | return pifrec['device'] | |
770 | ||
96c7918c BP |
771 | # |
772 | # Bridges | |
773 | # | |
774 | ||
775 | def pif_is_bridged(pif): | |
776 | pifrec = db().get_pif_record(pif) | |
777 | nwrec = db().get_network_record(pifrec['network']) | |
778 | ||
779 | if nwrec['bridge']: | |
780 | # TODO: sanity check that nwrec['bridgeless'] != 'true' | |
781 | return True | |
782 | else: | |
783 | # TODO: sanity check that nwrec['bridgeless'] == 'true' | |
784 | return False | |
785 | ||
786 | def pif_bridge_name(pif): | |
787 | """Return the bridge name of a pif. | |
788 | ||
789 | PIF must be a bridged PIF.""" | |
790 | pifrec = db().get_pif_record(pif) | |
791 | ||
792 | nwrec = db().get_network_record(pifrec['network']) | |
793 | ||
794 | if nwrec['bridge']: | |
795 | return nwrec['bridge'] | |
796 | else: | |
797 | raise Error("PIF %(uuid)s does not have a bridge name" % pifrec) | |
798 | ||
b3080599 IC |
799 | # |
800 | # Bonded PIFs | |
801 | # | |
802 | def pif_is_bond(pif): | |
803 | pifrec = db().get_pif_record(pif) | |
804 | ||
805 | return len(pifrec['bond_master_of']) > 0 | |
806 | ||
807 | def pif_get_bond_masters(pif): | |
808 | """Returns a list of PIFs which are bond masters of this PIF""" | |
809 | ||
810 | pifrec = db().get_pif_record(pif) | |
811 | ||
812 | bso = pifrec['bond_slave_of'] | |
813 | ||
814 | # bond-slave-of is currently a single reference but in principle a | |
815 | # PIF could be a member of several bonds which are not | |
816 | # concurrently attached. Be robust to this possibility. | |
817 | if not bso or bso == "OpaqueRef:NULL": | |
818 | bso = [] | |
819 | elif not type(bso) == list: | |
820 | bso = [bso] | |
821 | ||
822 | bondrecs = [db().get_bond_record(bond) for bond in bso] | |
823 | bondrecs = [rec for rec in bondrecs if rec] | |
824 | ||
825 | return [bond['master'] for bond in bondrecs] | |
826 | ||
827 | def pif_get_bond_slaves(pif): | |
828 | """Returns a list of PIFs which make up the given bonded pif.""" | |
829 | ||
830 | pifrec = db().get_pif_record(pif) | |
831 | ||
832 | bmo = pifrec['bond_master_of'] | |
833 | if len(bmo) > 1: | |
834 | raise Error("Bond-master-of contains too many elements") | |
835 | ||
836 | if len(bmo) == 0: | |
837 | return [] | |
838 | ||
839 | bondrec = db().get_bond_record(bmo[0]) | |
840 | if not bondrec: | |
841 | raise Error("No bond record for bond master PIF") | |
842 | ||
843 | return bondrec['slaves'] | |
844 | ||
845 | # | |
846 | # VLAN PIFs | |
847 | # | |
848 | ||
849 | def pif_is_vlan(pif): | |
850 | return db().get_pif_record(pif)['VLAN'] != '-1' | |
851 | ||
852 | def pif_get_vlan_slave(pif): | |
853 | """Find the PIF which is the VLAN slave of pif. | |
854 | ||
855 | Returns the 'physical' PIF underneath the a VLAN PIF @pif.""" | |
856 | ||
857 | pifrec = db().get_pif_record(pif) | |
858 | ||
859 | vlan = pifrec['VLAN_master_of'] | |
860 | if not vlan or vlan == "OpaqueRef:NULL": | |
861 | raise Error("PIF is not a VLAN master") | |
862 | ||
863 | vlanrec = db().get_vlan_record(vlan) | |
864 | if not vlanrec: | |
865 | raise Error("No VLAN record found for PIF") | |
866 | ||
867 | return vlanrec['tagged_PIF'] | |
868 | ||
869 | def pif_get_vlan_masters(pif): | |
870 | """Returns a list of PIFs which are VLANs on top of the given pif.""" | |
871 | ||
872 | pifrec = db().get_pif_record(pif) | |
873 | vlans = [db().get_vlan_record(v) for v in pifrec['VLAN_slave_of']] | |
874 | return [v['untagged_PIF'] for v in vlans if v and db().pif_exists(v['untagged_PIF'])] | |
875 | ||
92e906e4 IC |
876 | # |
877 | # Tunnel PIFs | |
878 | # | |
879 | def pif_is_tunnel(pif): | |
404c1692 | 880 | return len(db().get_pif_record(pif)['tunnel_access_PIF_of']) > 0 |
92e906e4 | 881 | |
b3080599 IC |
882 | # |
883 | # Datapath base class | |
884 | # | |
885 | ||
886 | class Datapath(object): | |
887 | """Object encapsulating the actions necessary to (de)configure the | |
888 | datapath for a given PIF. Does not include configuration of the | |
889 | IP address on the ipdev. | |
890 | """ | |
891 | ||
892 | def __init__(self, pif): | |
893 | self._pif = pif | |
894 | ||
823c5699 IC |
895 | @classmethod |
896 | def rewrite(cls): | |
897 | """Class method called when write action is called. Can be used | |
898 | to update any backend specific configuration.""" | |
899 | pass | |
900 | ||
b3080599 IC |
901 | def configure_ipdev(self, cfg): |
902 | """Write ifcfg TYPE field for an IPdev, plus any type specific | |
903 | fields to cfg | |
904 | """ | |
905 | raise NotImplementedError | |
906 | ||
907 | def preconfigure(self, parent): | |
908 | """Prepare datapath configuration for PIF, but do not actually | |
909 | apply any changes. | |
910 | ||
911 | Any configuration files should be attached to parent. | |
912 | """ | |
913 | raise NotImplementedError | |
914 | ||
915 | def bring_down_existing(self): | |
916 | """Tear down any existing network device configuration which | |
917 | needs to be undone in order to bring this PIF up. | |
918 | """ | |
919 | raise NotImplementedError | |
920 | ||
921 | def configure(self): | |
922 | """Apply the configuration prepared in the preconfigure stage. | |
923 | ||
924 | Should assume any configuration files changed attached in | |
925 | the preconfigure stage are applied and bring up the | |
ec9f40dc | 926 | necessary devices to provide the datapath for the |
b3080599 IC |
927 | PIF. |
928 | ||
929 | Should not bring up the IPdev. | |
930 | """ | |
931 | raise NotImplementedError | |
932 | ||
933 | def post(self): | |
934 | """Called after the IPdev has been brought up. | |
935 | ||
936 | Should do any final setup, including reinstating any | |
937 | devices which were taken down in the bring_down_existing | |
938 | hook. | |
939 | """ | |
940 | raise NotImplementedError | |
941 | ||
942 | def bring_down(self): | |
943 | """Tear down and deconfigure the datapath. Should assume the | |
944 | IPdev has already been brought down. | |
945 | """ | |
946 | raise NotImplementedError | |
947 | ||
823c5699 | 948 | def DatapathFactory(): |
b3080599 IC |
949 | # XXX Need a datapath object for bridgeless PIFs |
950 | ||
951 | try: | |
64ddb6fe | 952 | network_conf = open(root_prefix() + "/etc/xensource/network.conf", 'r') |
b3080599 IC |
953 | network_backend = network_conf.readline().strip() |
954 | network_conf.close() | |
955 | except Exception, e: | |
956 | raise Error("failed to determine network backend:" + e) | |
957 | ||
958 | if network_backend == "bridge": | |
959 | from InterfaceReconfigureBridge import DatapathBridge | |
823c5699 | 960 | return DatapathBridge |
aeb2b7a1 | 961 | elif network_backend in ["openvswitch", "vswitch"]: |
b3080599 | 962 | from InterfaceReconfigureVswitch import DatapathVswitch |
823c5699 | 963 | return DatapathVswitch |
b3080599 IC |
964 | else: |
965 | raise Error("unknown network backend %s" % network_backend) |