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