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