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