]>
Commit | Line | Data |
---|---|---|
064af421 BP |
1 | #!/usr/bin/python |
2 | # | |
42ab0203 | 3 | # Copyright (c) 2008,2009 Citrix Systems, Inc. All rights reserved. |
064af421 BP |
4 | # Copyright (c) 2009 Nicira Networks. |
5 | # | |
6 | """Usage: | |
7 | ||
057fed2b BP |
8 | %(command-name)s <PIF> up |
9 | %(command-name)s <PIF> down | |
10 | %(command-name)s [<PIF>] rewrite | |
11 | %(command-name)s --force <BRIDGE> up | |
12 | %(command-name)s --force <BRIDGE> down | |
13 | %(command-name)s --force <BRIDGE> rewrite --device=<INTERFACE> <CONFIG> | |
064af421 BP |
14 | %(command-name)s --force all down |
15 | ||
057fed2b BP |
16 | where <PIF> is one of: |
17 | --session <SESSION-REF> --pif <PIF-REF> | |
18 | --pif-uuid <PIF-UUID> | |
19 | and <CONFIG> is one of: | |
20 | --mode=dhcp | |
21 | --mode=static --ip=<IPADDR> --netmask=<NM> [--gateway=<GW>] | |
064af421 BP |
22 | |
23 | Options: | |
24 | --session A session reference to use to access the xapi DB | |
057fed2b BP |
25 | --pif A PIF reference within the session. |
26 | --pif-uuid The UUID of a PIF. | |
27 | --force An interface name. | |
064af421 BP |
28 | """ |
29 | ||
30 | # | |
31 | # Undocumented parameters for test & dev: | |
32 | # | |
33 | # --output-directory=<DIR> Write configuration to <DIR>. Also disables actually | |
34 | # raising/lowering the interfaces | |
064af421 BP |
35 | # |
36 | # | |
37 | # | |
38 | # Notes: | |
39 | # 1. Every pif belongs to exactly one network | |
40 | # 2. Every network has zero or one pifs | |
41 | # 3. A network may have an associated bridge, allowing vifs to be attached | |
42 | # 4. A network may be bridgeless (there's no point having a bridge over a storage pif) | |
43 | ||
064af421 BP |
44 | import XenAPI |
45 | import os, sys, getopt, time, signal | |
46 | import syslog | |
47 | import traceback | |
48 | import time | |
49 | import re | |
2bb451b6 | 50 | import random |
6dd3fad4 IC |
51 | from xml.dom.minidom import getDOMImplementation |
52 | from xml.dom.minidom import parse as parseXML | |
064af421 BP |
53 | |
54 | output_directory = None | |
55 | ||
56 | db = None | |
57 | management_pif = None | |
58 | ||
e44d1844 IC |
59 | vswitch_state_dir = "/var/lib/openvswitch/" |
60 | dbcache_file = vswitch_state_dir + "dbcache" | |
064af421 BP |
61 | |
62 | class Usage(Exception): | |
63 | def __init__(self, msg): | |
64 | Exception.__init__(self) | |
65 | self.msg = msg | |
66 | ||
67 | class Error(Exception): | |
68 | def __init__(self, msg): | |
69 | Exception.__init__(self) | |
70 | self.msg = msg | |
71 | ||
72 | class ConfigurationFile(object): | |
73 | """Write a file, tracking old and new versions. | |
74 | ||
75 | Supports writing a new version of a file and applying and | |
76 | reverting those changes. | |
77 | """ | |
78 | ||
79 | __STATE = {"OPEN":"OPEN", | |
80 | "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED", | |
81 | "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"} | |
82 | ||
83 | def __init__(self, fname, path="/etc/sysconfig/network-scripts"): | |
84 | ||
85 | self.__state = self.__STATE['OPEN'] | |
86 | self.__fname = fname | |
87 | self.__children = [] | |
88 | ||
89 | if debug_mode(): | |
90 | dirname = output_directory | |
91 | else: | |
92 | dirname = path | |
93 | ||
94 | self.__path = os.path.join(dirname, fname) | |
95 | self.__oldpath = os.path.join(dirname, "." + fname + ".xapi-old") | |
96 | self.__newpath = os.path.join(dirname, "." + fname + ".xapi-new") | |
97 | self.__unlink = False | |
98 | ||
99 | self.__f = open(self.__newpath, "w") | |
100 | ||
101 | def attach_child(self, child): | |
102 | self.__children.append(child) | |
103 | ||
104 | def path(self): | |
105 | return self.__path | |
106 | ||
107 | def readlines(self): | |
108 | try: | |
109 | return open(self.path()).readlines() | |
110 | except: | |
111 | return "" | |
112 | ||
113 | def write(self, args): | |
114 | if self.__state != self.__STATE['OPEN']: | |
115 | raise Error("Attempt to write to file in state %s" % self.__state) | |
116 | self.__f.write(args) | |
117 | ||
118 | def unlink(self): | |
119 | if self.__state != self.__STATE['OPEN']: | |
120 | raise Error("Attempt to unlink file in state %s" % self.__state) | |
121 | self.__unlink = True | |
122 | self.__f.close() | |
123 | self.__state = self.__STATE['NOT-APPLIED'] | |
124 | ||
125 | def close(self): | |
126 | if self.__state != self.__STATE['OPEN']: | |
127 | raise Error("Attempt to close file in state %s" % self.__state) | |
128 | ||
129 | self.__f.close() | |
130 | self.__state = self.__STATE['NOT-APPLIED'] | |
131 | ||
132 | def changed(self): | |
133 | if self.__state != self.__STATE['NOT-APPLIED']: | |
134 | raise Error("Attempt to compare file in state %s" % self.__state) | |
135 | ||
136 | return True | |
137 | ||
138 | def apply(self): | |
139 | if self.__state != self.__STATE['NOT-APPLIED']: | |
140 | raise Error("Attempt to apply configuration from state %s" % self.__state) | |
141 | ||
142 | for child in self.__children: | |
143 | child.apply() | |
144 | ||
145 | log("Applying changes to %s configuration" % self.__fname) | |
146 | ||
147 | # Remove previous backup. | |
148 | if os.access(self.__oldpath, os.F_OK): | |
149 | os.unlink(self.__oldpath) | |
150 | ||
151 | # Save current configuration. | |
152 | if os.access(self.__path, os.F_OK): | |
153 | os.link(self.__path, self.__oldpath) | |
154 | os.unlink(self.__path) | |
155 | ||
156 | # Apply new configuration. | |
157 | assert(os.path.exists(self.__newpath)) | |
158 | if not self.__unlink: | |
159 | os.link(self.__newpath, self.__path) | |
160 | else: | |
161 | pass # implicit unlink of original file | |
162 | ||
163 | # Remove temporary file. | |
164 | os.unlink(self.__newpath) | |
165 | ||
166 | self.__state = self.__STATE['APPLIED'] | |
167 | ||
168 | def revert(self): | |
169 | if self.__state != self.__STATE['APPLIED']: | |
170 | raise Error("Attempt to revert configuration from state %s" % self.__state) | |
171 | ||
172 | for child in self.__children: | |
173 | child.revert() | |
174 | ||
175 | log("Reverting changes to %s configuration" % self.__fname) | |
176 | ||
177 | # Remove existing new configuration | |
178 | if os.access(self.__newpath, os.F_OK): | |
179 | os.unlink(self.__newpath) | |
180 | ||
181 | # Revert new configuration. | |
182 | if os.access(self.__path, os.F_OK): | |
183 | os.link(self.__path, self.__newpath) | |
184 | os.unlink(self.__path) | |
185 | ||
186 | # Revert to old configuration. | |
187 | if os.access(self.__oldpath, os.F_OK): | |
188 | os.link(self.__oldpath, self.__path) | |
189 | os.unlink(self.__oldpath) | |
190 | ||
191 | # Leave .*.xapi-new as an aid to debugging. | |
192 | ||
193 | self.__state = self.__STATE['REVERTED'] | |
194 | ||
195 | def commit(self): | |
196 | if self.__state != self.__STATE['APPLIED']: | |
197 | raise Error("Attempt to commit configuration from state %s" % self.__state) | |
198 | ||
199 | for child in self.__children: | |
200 | child.commit() | |
201 | ||
202 | log("Committing changes to %s configuration" % self.__fname) | |
203 | ||
204 | if os.access(self.__oldpath, os.F_OK): | |
205 | os.unlink(self.__oldpath) | |
206 | if os.access(self.__newpath, os.F_OK): | |
207 | os.unlink(self.__newpath) | |
208 | ||
209 | self.__state = self.__STATE['COMMITTED'] | |
210 | ||
211 | def debug_mode(): | |
212 | return output_directory is not None | |
213 | ||
214 | def log(s): | |
215 | if debug_mode(): | |
216 | print >>sys.stderr, s | |
217 | else: | |
218 | syslog.syslog(s) | |
219 | ||
220 | def check_allowed(pif): | |
221 | pifrec = db.get_pif_record(pif) | |
222 | try: | |
223 | f = open("/proc/ardence") | |
224 | macline = filter(lambda x: x.startswith("HWaddr:"), f.readlines()) | |
225 | f.close() | |
226 | if len(macline) == 1: | |
227 | p = re.compile(".*\s%(MAC)s\s.*" % pifrec, re.IGNORECASE) | |
228 | if p.match(macline[0]): | |
229 | log("Skipping PVS device %(device)s (%(MAC)s)" % pifrec) | |
230 | return False | |
231 | except IOError: | |
232 | pass | |
233 | return True | |
234 | ||
235 | def interface_exists(i): | |
236 | return os.path.exists("/sys/class/net/" + i) | |
237 | ||
2bb451b6 BP |
238 | def get_netdev_mac(device): |
239 | try: | |
240 | return read_first_line_of_file("/sys/class/net/%s/address" % device) | |
241 | except: | |
242 | # Probably no such device. | |
243 | return None | |
244 | ||
245 | def get_netdev_tx_queue_len(device): | |
246 | try: | |
247 | return int(read_first_line_of_file("/sys/class/net/%s/tx_queue_len" | |
248 | % device)) | |
249 | except: | |
250 | # Probably no such device. | |
251 | return None | |
252 | ||
253 | def get_netdev_by_mac(mac): | |
2bb451b6 | 254 | for device in os.listdir("/sys/class/net"): |
0ceda58c | 255 | dev_mac = get_netdev_mac(device) |
9d04e270 BP |
256 | if (dev_mac and mac.lower() == dev_mac.lower() and |
257 | get_netdev_tx_queue_len(device)): | |
2bb451b6 | 258 | return device |
9d04e270 | 259 | return None |
2bb451b6 | 260 | |
6dd3fad4 IC |
261 | # |
262 | # Helper functions for encoding/decoding database attributes to/from XML. | |
263 | # | |
264 | def str_to_xml(xml, parent, tag, val): | |
265 | e = xml.createElement(tag) | |
266 | parent.appendChild(e) | |
267 | v = xml.createTextNode(val) | |
268 | e.appendChild(v) | |
269 | def str_from_xml(n): | |
270 | def getText(nodelist): | |
271 | rc = "" | |
272 | for node in nodelist: | |
273 | if node.nodeType == node.TEXT_NODE: | |
274 | rc = rc + node.data | |
275 | return rc | |
276 | return getText(n.childNodes).strip() | |
277 | ||
278 | ||
279 | def bool_to_xml(xml, parent, tag, val): | |
280 | if val: | |
281 | str_to_xml(xml, parent, tag, "True") | |
282 | else: | |
283 | str_to_xml(xml, parent, tag, "False") | |
284 | def bool_from_xml(n): | |
285 | s = str_from_xml(n) | |
286 | if s == "True": | |
287 | return True | |
288 | elif s == "False": | |
289 | return False | |
290 | else: | |
291 | raise Error("Unknown boolean value %s" % s); | |
292 | ||
293 | def strlist_to_xml(xml, parent, ltag, itag, val): | |
294 | e = xml.createElement(ltag) | |
295 | parent.appendChild(e) | |
296 | for v in val: | |
297 | c = xml.createElement(itag) | |
298 | e.appendChild(c) | |
299 | cv = xml.createTextNode(v) | |
300 | c.appendChild(cv) | |
301 | def strlist_from_xml(n, ltag, itag): | |
302 | ret = [] | |
303 | for n in n.childNodes: | |
304 | if n.nodeName == itag: | |
305 | ret.append(str_from_xml(n)) | |
306 | return ret | |
307 | ||
308 | def otherconfig_to_xml(xml, parent, val, attrs): | |
309 | otherconfig = xml.createElement("other_config") | |
310 | parent.appendChild(otherconfig) | |
311 | for n,v in val.items(): | |
312 | if not n in attrs: | |
313 | raise Error("Unknown other-config attribute: %s" % n) | |
314 | str_to_xml(xml, otherconfig, n, v) | |
315 | def otherconfig_from_xml(n, attrs): | |
316 | ret = {} | |
317 | for n in n.childNodes: | |
318 | if n.nodeName in attrs: | |
319 | ret[n.nodeName] = str_from_xml(n) | |
320 | return ret | |
321 | ||
322 | # | |
323 | # Definitions of the database objects (and their attributes) used by interface-reconfigure. | |
324 | # | |
325 | # Each object is defined by a dictionary mapping an attribute name in | |
326 | # the xapi database to a tuple containing two items: | |
327 | # - a function which takes this attribute and encodes it as XML. | |
328 | # - a function which takes XML and decocdes it into a value. | |
329 | # | |
330 | # other-config attributes are specified as a simple array of strings | |
331 | ||
332 | PIF_XML_TAG = "pif" | |
333 | VLAN_XML_TAG = "vlan" | |
334 | BOND_XML_TAG = "bond" | |
335 | NETWORK_XML_TAG = "network" | |
336 | ||
171ed168 IC |
337 | ETHTOOL_OTHERCONFIG_ATTRS = ['ethtool-%s' % x for x in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ] |
338 | ||
6dd3fad4 IC |
339 | PIF_ATTRS = { 'uuid': (str_to_xml,str_from_xml), |
340 | 'management': (bool_to_xml,bool_from_xml), | |
341 | 'network': (str_to_xml,str_from_xml), | |
342 | 'device': (str_to_xml,str_from_xml), | |
343 | 'bond_master_of': (lambda x, p, t, v: strlist_to_xml(x, p, 'bond_master_of', 'slave', v), | |
344 | lambda n: strlist_from_xml(n, 'bond_master_of', 'slave')), | |
345 | 'bond_slave_of': (str_to_xml,str_from_xml), | |
346 | 'VLAN': (str_to_xml,str_from_xml), | |
347 | 'VLAN_master_of': (str_to_xml,str_from_xml), | |
348 | 'VLAN_slave_of': (lambda x, p, t, v: strlist_to_xml(x, p, 'VLAN_slave_of', 'master', v), | |
349 | lambda n: strlist_from_xml(n, 'VLAN_slave_Of', 'master')), | |
350 | 'ip_configuration_mode': (str_to_xml,str_from_xml), | |
351 | 'IP': (str_to_xml,str_from_xml), | |
352 | 'netmask': (str_to_xml,str_from_xml), | |
353 | 'gateway': (str_to_xml,str_from_xml), | |
354 | 'DNS': (str_to_xml,str_from_xml), | |
355 | 'MAC': (str_to_xml,str_from_xml), | |
356 | 'other_config': (lambda x, p, t, v: otherconfig_to_xml(x, p, v, PIF_OTHERCONFIG_ATTRS), | |
357 | lambda n: otherconfig_from_xml(n, PIF_OTHERCONFIG_ATTRS)), | |
b88907e7 IC |
358 | |
359 | # Special case: We write the current value | |
360 | # PIF.currently-attached to the cache but since it will | |
361 | # not be valid when we come to use the cache later | |
362 | # (i.e. after a reboot) we always read it as False. | |
363 | 'currently_attached': (bool_to_xml, lambda n: False), | |
6dd3fad4 | 364 | } |
171ed168 IC |
365 | |
366 | PIF_OTHERCONFIG_ATTRS = [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \ | |
367 | [ 'bond-%s' % x for x in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \ | |
368 | ETHTOOL_OTHERCONFIG_ATTRS | |
369 | ||
6dd3fad4 IC |
370 | VLAN_ATTRS = { 'uuid': (str_to_xml,str_from_xml), |
371 | 'tagged_PIF': (str_to_xml,str_from_xml), | |
372 | 'untagged_PIF': (str_to_xml,str_from_xml), | |
373 | } | |
171ed168 | 374 | |
6dd3fad4 IC |
375 | BOND_ATTRS = { 'uuid': (str_to_xml,str_from_xml), |
376 | 'master': (str_to_xml,str_from_xml), | |
377 | 'slaves': (lambda x, p, t, v: strlist_to_xml(x, p, 'slaves', 'slave', v), | |
378 | lambda n: strlist_from_xml(n, 'slaves', 'slave')), | |
379 | } | |
380 | ||
381 | NETWORK_ATTRS = { 'uuid': (str_to_xml,str_from_xml), | |
382 | 'bridge': (str_to_xml,str_from_xml), | |
383 | 'PIFs': (lambda x, p, t, v: strlist_to_xml(x, p, 'PIFs', 'PIF', v), | |
384 | lambda n: strlist_from_xml(n, 'PIFs', 'PIF')), | |
385 | 'other_config': (lambda x, p, t, v: otherconfig_to_xml(x, p, v, NETWORK_OTHERCONFIG_ATTRS), | |
386 | lambda n: otherconfig_from_xml(n, NETWORK_OTHERCONFIG_ATTRS)), | |
387 | } | |
171ed168 IC |
388 | |
389 | NETWORK_OTHERCONFIG_ATTRS = [ 'mtu', 'static-routes' ] + ETHTOOL_OTHERCONFIG_ATTRS | |
390 | ||
064af421 | 391 | class DatabaseCache(object): |
c798b21c IC |
392 | def __read_xensource_inventory(self): |
393 | filename = "/etc/xensource-inventory" | |
394 | f = open(filename, "r") | |
395 | lines = [x.strip("\n") for x in f.readlines()] | |
396 | f.close() | |
397 | ||
398 | defs = [ (l[:l.find("=")], l[(l.find("=") + 1):]) for l in lines ] | |
399 | defs = [ (a, b.strip("'")) for (a,b) in defs ] | |
400 | ||
401 | return dict(defs) | |
c798b21c IC |
402 | def __pif_on_host(self,pif): |
403 | return self.__pifs.has_key(pif) | |
171ed168 | 404 | |
c798b21c | 405 | def __get_pif_records_from_xapi(self, session, host): |
171ed168 IC |
406 | self.__pifs = {} |
407 | for (p,rec) in session.xenapi.PIF.get_all_records().items(): | |
408 | if rec['host'] != host: | |
409 | continue | |
410 | self.__pifs[p] = {} | |
411 | for f in PIF_ATTRS: | |
412 | self.__pifs[p][f] = rec[f] | |
413 | self.__pifs[p]['other_config'] = {} | |
414 | for f in PIF_OTHERCONFIG_ATTRS: | |
415 | if not rec['other_config'].has_key(f): continue | |
416 | self.__pifs[p]['other_config'][f] = rec['other_config'][f] | |
c798b21c | 417 | |
d8ba4acf | 418 | def __get_vlan_records_from_xapi(self, session): |
171ed168 IC |
419 | self.__vlans = {} |
420 | for v in session.xenapi.VLAN.get_all(): | |
421 | rec = session.xenapi.VLAN.get_record(v) | |
422 | if not self.__pif_on_host(rec['untagged_PIF']): | |
423 | continue | |
424 | self.__vlans[v] = {} | |
425 | for f in VLAN_ATTRS: | |
426 | self.__vlans[v][f] = rec[f] | |
427 | ||
d8ba4acf | 428 | def __get_bond_records_from_xapi(self, session): |
171ed168 IC |
429 | self.__bonds = {} |
430 | for b in session.xenapi.Bond.get_all(): | |
431 | rec = session.xenapi.Bond.get_record(b) | |
432 | if not self.__pif_on_host(rec['master']): | |
433 | continue | |
434 | self.__bonds[b] = {} | |
435 | for f in BOND_ATTRS: | |
436 | self.__bonds[b][f] = rec[f] | |
c798b21c | 437 | |
d8ba4acf | 438 | def __get_network_records_from_xapi(self, session): |
171ed168 IC |
439 | self.__networks = {} |
440 | for n in session.xenapi.network.get_all(): | |
441 | rec = session.xenapi.network.get_record(n) | |
442 | self.__networks[n] = {} | |
443 | for f in NETWORK_ATTRS: | |
5de7052b IC |
444 | if f == "PIFs": |
445 | # drop PIFs on other hosts | |
446 | self.__networks[n][f] = [p for p in rec[f] if self.__pif_on_host(p)] | |
447 | else: | |
448 | self.__networks[n][f] = rec[f] | |
171ed168 IC |
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] | |
6dd3fad4 IC |
453 | |
454 | def __to_xml(self, xml, parent, key, ref, rec, attrs): | |
455 | """Encode a database object as XML""" | |
456 | e = xml.createElement(key) | |
457 | parent.appendChild(e) | |
458 | if ref: | |
459 | e.setAttribute('ref', ref) | |
460 | ||
461 | for n,v in rec.items(): | |
462 | if attrs.has_key(n): | |
463 | h,_ = attrs[n] | |
464 | h(xml, e, n, v) | |
465 | else: | |
466 | raise Error("Unknown attribute %s" % n) | |
467 | def __from_xml(self, e, attrs): | |
468 | """Decode a database object from XML""" | |
469 | ref = e.attributes['ref'].value | |
470 | rec = {} | |
471 | for n in e.childNodes: | |
472 | if n.nodeName in attrs: | |
473 | _,h = attrs[n.nodeName] | |
474 | rec[n.nodeName] = h(n) | |
475 | return (ref,rec) | |
476 | ||
064af421 BP |
477 | def __init__(self, session_ref=None, cache_file=None): |
478 | if session_ref and cache_file: | |
479 | raise Error("can't specify session reference and cache file") | |
064af421 BP |
480 | if cache_file == None: |
481 | session = XenAPI.xapi_local() | |
482 | ||
483 | if not session_ref: | |
484 | log("No session ref given on command line, logging in.") | |
485 | session.xenapi.login_with_password("root", "") | |
486 | else: | |
487 | session._session = session_ref | |
488 | ||
489 | try: | |
c798b21c IC |
490 | |
491 | inventory = self.__read_xensource_inventory() | |
492 | assert(inventory.has_key('INSTALLATION_UUID')) | |
493 | log("host uuid is %s" % inventory['INSTALLATION_UUID']) | |
494 | ||
495 | host = session.xenapi.host.get_by_uuid(inventory['INSTALLATION_UUID']) | |
496 | ||
497 | self.__get_pif_records_from_xapi(session, host) | |
498 | ||
d8ba4acf IC |
499 | self.__get_vlan_records_from_xapi(session) |
500 | self.__get_bond_records_from_xapi(session) | |
501 | self.__get_network_records_from_xapi(session) | |
064af421 BP |
502 | finally: |
503 | if not session_ref: | |
504 | session.xenapi.session.logout() | |
505 | else: | |
506 | log("Loading xapi database cache from %s" % cache_file) | |
064af421 | 507 | |
6dd3fad4 IC |
508 | xml = parseXML(cache_file) |
509 | ||
510 | self.__pifs = {} | |
511 | self.__bonds = {} | |
512 | self.__vlans = {} | |
513 | self.__networks = {} | |
064af421 | 514 | |
6dd3fad4 IC |
515 | assert(len(xml.childNodes) == 1) |
516 | toplevel = xml.childNodes[0] | |
517 | ||
518 | assert(toplevel.nodeName == "xenserver-network-configuration") | |
519 | ||
520 | for n in toplevel.childNodes: | |
521 | if n.nodeName == "#text": | |
522 | pass | |
523 | elif n.nodeName == PIF_XML_TAG: | |
524 | (ref,rec) = self.__from_xml(n, PIF_ATTRS) | |
525 | self.__pifs[ref] = rec | |
526 | elif n.nodeName == BOND_XML_TAG: | |
527 | (ref,rec) = self.__from_xml(n, BOND_ATTRS) | |
528 | self.__bonds[ref] = rec | |
529 | elif n.nodeName == VLAN_XML_TAG: | |
530 | (ref,rec) = self.__from_xml(n, VLAN_ATTRS) | |
531 | self.__vlans[ref] = rec | |
532 | elif n.nodeName == NETWORK_XML_TAG: | |
533 | (ref,rec) = self.__from_xml(n, NETWORK_ATTRS) | |
534 | self.__networks[ref] = rec | |
535 | else: | |
536 | raise Error("Unknown XML element %s" % n.nodeName) | |
064af421 | 537 | |
c798b21c | 538 | def save(self, cache_file): |
6dd3fad4 IC |
539 | |
540 | xml = getDOMImplementation().createDocument( | |
541 | None, "xenserver-network-configuration", None) | |
542 | for (ref,rec) in self.__pifs.items(): | |
543 | self.__to_xml(xml, xml.documentElement, PIF_XML_TAG, ref, rec, PIF_ATTRS) | |
544 | for (ref,rec) in self.__bonds.items(): | |
545 | self.__to_xml(xml, xml.documentElement, BOND_XML_TAG, ref, rec, BOND_ATTRS) | |
546 | for (ref,rec) in self.__vlans.items(): | |
547 | self.__to_xml(xml, xml.documentElement, VLAN_XML_TAG, ref, rec, VLAN_ATTRS) | |
548 | for (ref,rec) in self.__networks.items(): | |
549 | self.__to_xml(xml, xml.documentElement, NETWORK_XML_TAG, ref, rec, | |
550 | NETWORK_ATTRS) | |
551 | ||
064af421 | 552 | f = open(cache_file, 'w') |
6dd3fad4 | 553 | f.write(xml.toprettyxml()) |
064af421 BP |
554 | f.close() |
555 | ||
556 | def get_pif_by_uuid(self, uuid): | |
557 | pifs = map(lambda (ref,rec): ref, | |
558 | filter(lambda (ref,rec): uuid == rec['uuid'], | |
559 | self.__pifs.items())) | |
560 | if len(pifs) == 0: | |
561 | raise Error("Unknown PIF \"%s\"" % uuid) | |
562 | elif len(pifs) > 1: | |
563 | raise Error("Non-unique PIF \"%s\"" % uuid) | |
564 | ||
565 | return pifs[0] | |
566 | ||
c798b21c | 567 | def get_pifs_by_device(self, device): |
064af421 | 568 | return map(lambda (ref,rec): ref, |
c798b21c | 569 | filter(lambda (ref,rec): rec['device'] == device, |
064af421 BP |
570 | self.__pifs.items())) |
571 | ||
c798b21c | 572 | def get_pif_by_bridge(self, bridge): |
064af421 BP |
573 | networks = map(lambda (ref,rec): ref, |
574 | filter(lambda (ref,rec): rec['bridge'] == bridge, | |
575 | self.__networks.items())) | |
576 | if len(networks) == 0: | |
577 | raise Error("No matching network \"%s\"") | |
578 | ||
579 | answer = None | |
580 | for network in networks: | |
581 | nwrec = self.get_network_record(network) | |
582 | for pif in nwrec['PIFs']: | |
583 | pifrec = self.get_pif_record(pif) | |
064af421 | 584 | if answer: |
c798b21c | 585 | raise Error("Multiple PIFs on host for network %s" % (bridge)) |
064af421 BP |
586 | answer = pif |
587 | if not answer: | |
c798b21c | 588 | raise Error("No PIF on host for network %s" % (bridge)) |
064af421 BP |
589 | return answer |
590 | ||
591 | def get_pif_record(self, pif): | |
592 | if self.__pifs.has_key(pif): | |
593 | return self.__pifs[pif] | |
6dd3fad4 | 594 | raise Error("Unknown PIF \"%s\" (get_pif_record)" % pif) |
064af421 BP |
595 | def get_all_pifs(self): |
596 | return self.__pifs | |
597 | def pif_exists(self, pif): | |
598 | return self.__pifs.has_key(pif) | |
599 | ||
c798b21c | 600 | def get_management_pif(self): |
064af421 BP |
601 | """ Returns the management pif on host |
602 | """ | |
603 | all = self.get_all_pifs() | |
604 | for pif in all: | |
605 | pifrec = self.get_pif_record(pif) | |
c798b21c | 606 | if pifrec['management']: return pif |
064af421 BP |
607 | return None |
608 | ||
609 | def get_network_record(self, network): | |
610 | if self.__networks.has_key(network): | |
611 | return self.__networks[network] | |
612 | raise Error("Unknown network \"%s\"" % network) | |
613 | def get_all_networks(self): | |
614 | return self.__networks | |
615 | ||
616 | def get_bond_record(self, bond): | |
617 | if self.__bonds.has_key(bond): | |
618 | return self.__bonds[bond] | |
619 | else: | |
620 | return None | |
621 | ||
622 | def get_vlan_record(self, vlan): | |
623 | if self.__vlans.has_key(vlan): | |
624 | return self.__vlans[vlan] | |
625 | else: | |
626 | return None | |
627 | ||
628 | def bridge_name(pif): | |
629 | """Return the bridge name associated with pif, or None if network is bridgeless""" | |
630 | pifrec = db.get_pif_record(pif) | |
631 | nwrec = db.get_network_record(pifrec['network']) | |
632 | ||
633 | if nwrec['bridge']: | |
634 | # TODO: sanity check that nwrec['bridgeless'] != 'true' | |
635 | return nwrec['bridge'] | |
636 | else: | |
637 | # TODO: sanity check that nwrec['bridgeless'] == 'true' | |
638 | return None | |
639 | ||
640 | def interface_name(pif): | |
641 | """Construct an interface name from the given PIF record.""" | |
642 | ||
643 | pifrec = db.get_pif_record(pif) | |
644 | ||
645 | if pifrec['VLAN'] == '-1': | |
646 | return pifrec['device'] | |
647 | else: | |
648 | return "%(device)s.%(VLAN)s" % pifrec | |
649 | ||
650 | def datapath_name(pif): | |
651 | """Return the OpenFlow datapath name associated with pif. | |
652 | For a non-VLAN PIF, the datapath name is the bridge name. | |
653 | For a VLAN PIF, the datapath name is the bridge name for the PIF's VLAN slave. | |
654 | (xapi will create a datapath named with the bridge name even though we won't | |
655 | use it.) | |
656 | """ | |
657 | ||
6dd3fad4 | 658 | |
064af421 BP |
659 | pifrec = db.get_pif_record(pif) |
660 | ||
661 | if pifrec['VLAN'] == '-1': | |
662 | return bridge_name(pif) | |
663 | else: | |
664 | return bridge_name(get_vlan_slave_of_pif(pif)) | |
665 | ||
666 | def ipdev_name(pif): | |
667 | """Return the the name of the network device that carries the | |
668 | IP configuration (if any) associated with pif. | |
669 | The ipdev name is the same as the bridge name. | |
670 | """ | |
671 | ||
672 | pifrec = db.get_pif_record(pif) | |
673 | return bridge_name(pif) | |
674 | ||
bc952bb3 | 675 | def get_physdev_pifs(pif): |
2bb451b6 BP |
676 | """Return the PIFs for the physical network device(s) associated with pif. |
677 | For a VLAN PIF, this is the VLAN slave's physical device PIF. | |
678 | For a bond master PIF, these are the bond slave PIFs. | |
679 | For a non-VLAN, non-bond master PIF, the PIF is its own physical device PIF. | |
064af421 | 680 | """ |
064af421 BP |
681 | pifrec = db.get_pif_record(pif) |
682 | ||
683 | if pifrec['VLAN'] != '-1': | |
eeb569bf | 684 | return get_physdev_pifs(get_vlan_slave_of_pif(pif)) |
064af421 | 685 | elif len(pifrec['bond_master_of']) != 0: |
2bb451b6 | 686 | return get_bond_slaves_of_pif(pif) |
064af421 | 687 | else: |
2bb451b6 BP |
688 | return [pif] |
689 | ||
bc952bb3 | 690 | def get_physdev_names(pif): |
2bb451b6 BP |
691 | """Return the name(s) of the physical network device(s) associated with pif. |
692 | For a VLAN PIF, the physical devices are the VLAN slave's physical devices. | |
693 | For a bond master PIF, the physical devices are the bond slaves. | |
694 | For a non-VLAN, non-bond master PIF, the physical device is the PIF itself. | |
695 | """ | |
696 | ||
bc952bb3 | 697 | return [db.get_pif_record(phys)['device'] for phys in get_physdev_pifs(pif)] |
064af421 BP |
698 | |
699 | def log_pif_action(action, pif): | |
700 | pifrec = db.get_pif_record(pif) | |
6dd3fad4 IC |
701 | rec = {} |
702 | rec['uuid'] = pifrec['uuid'] | |
703 | rec['ip_configuration_mode'] = pifrec['ip_configuration_mode'] | |
704 | rec['action'] = action | |
705 | rec['interface-name'] = interface_name(pif) | |
057fed2b | 706 | rec['message'] = "Bring %(action)s PIF %(uuid)s" % rec |
6dd3fad4 | 707 | log("%(message)s: %(interface-name)s configured as %(ip_configuration_mode)s" % rec) |
064af421 BP |
708 | |
709 | def get_bond_masters_of_pif(pif): | |
710 | """Returns a list of PIFs which are bond masters of this PIF""" | |
711 | ||
712 | pifrec = db.get_pif_record(pif) | |
713 | ||
714 | bso = pifrec['bond_slave_of'] | |
715 | ||
716 | # bond-slave-of is currently a single reference but in principle a | |
717 | # PIF could be a member of several bonds which are not | |
718 | # concurrently attached. Be robust to this possibility. | |
719 | if not bso or bso == "OpaqueRef:NULL": | |
720 | bso = [] | |
721 | elif not type(bso) == list: | |
722 | bso = [bso] | |
723 | ||
724 | bondrecs = [db.get_bond_record(bond) for bond in bso] | |
725 | bondrecs = [rec for rec in bondrecs if rec] | |
726 | ||
727 | return [bond['master'] for bond in bondrecs] | |
728 | ||
729 | def get_bond_slaves_of_pif(pif): | |
730 | """Returns a list of PIFs which make up the given bonded pif.""" | |
731 | ||
732 | pifrec = db.get_pif_record(pif) | |
064af421 BP |
733 | |
734 | bmo = pifrec['bond_master_of'] | |
735 | if len(bmo) > 1: | |
736 | raise Error("Bond-master-of contains too many elements") | |
737 | ||
738 | if len(bmo) == 0: | |
739 | return [] | |
740 | ||
741 | bondrec = db.get_bond_record(bmo[0]) | |
742 | if not bondrec: | |
743 | raise Error("No bond record for bond master PIF") | |
744 | ||
745 | return bondrec['slaves'] | |
746 | ||
747 | def get_vlan_slave_of_pif(pif): | |
748 | """Find the PIF which is the VLAN slave of pif. | |
749 | ||
750 | Returns the 'physical' PIF underneath the a VLAN PIF @pif.""" | |
751 | ||
752 | pifrec = db.get_pif_record(pif) | |
753 | ||
754 | vlan = pifrec['VLAN_master_of'] | |
755 | if not vlan or vlan == "OpaqueRef:NULL": | |
756 | raise Error("PIF is not a VLAN master") | |
757 | ||
758 | vlanrec = db.get_vlan_record(vlan) | |
759 | if not vlanrec: | |
760 | raise Error("No VLAN record found for PIF") | |
761 | ||
762 | return vlanrec['tagged_PIF'] | |
763 | ||
764 | def get_vlan_masters_of_pif(pif): | |
765 | """Returns a list of PIFs which are VLANs on top of the given pif.""" | |
766 | ||
767 | pifrec = db.get_pif_record(pif) | |
768 | vlans = [db.get_vlan_record(v) for v in pifrec['VLAN_slave_of']] | |
769 | return [v['untagged_PIF'] for v in vlans if v and db.pif_exists(v['untagged_PIF'])] | |
770 | ||
771 | def interface_deconfigure_commands(interface): | |
772 | # The use of [!0-9] keeps an interface of 'eth0' from matching | |
773 | # VLANs attached to eth0 (such as 'eth0.123'), which are distinct | |
774 | # interfaces. | |
775 | return ['--del-match=bridge.*.port=%s' % interface, | |
776 | '--del-match=bonding.%s.[!0-9]*' % interface, | |
777 | '--del-match=bonding.*.slave=%s' % interface, | |
778 | '--del-match=vlan.%s.[!0-9]*' % interface, | |
779 | '--del-match=port.%s.[!0-9]*' % interface, | |
780 | '--del-match=iface.%s.[!0-9]*' % interface] | |
781 | ||
782 | def run_command(command): | |
783 | log("Running command: " + ' '.join(command)) | |
784 | if os.spawnl(os.P_WAIT, command[0], *command) != 0: | |
785 | log("Command failed: " + ' '.join(command)) | |
786 | return False | |
787 | return True | |
788 | ||
2bb451b6 BP |
789 | def rename_netdev(old_name, new_name): |
790 | log("Changing the name of %s to %s" % (old_name, new_name)) | |
791 | run_command(['/sbin/ifconfig', old_name, 'down']) | |
792 | if not run_command(['/sbin/ip', 'link', 'set', old_name, | |
793 | 'name', new_name]): | |
794 | raise Error("Could not rename %s to %s" % (old_name, new_name)) | |
795 | ||
796 | # Check whether 'pif' exists and has the correct MAC. | |
797 | # If not, try to find a device with the correct MAC and rename it. | |
798 | # 'already_renamed' is used to avoid infinite recursion. | |
799 | def remap_pif(pif, already_renamed=[]): | |
800 | pifrec = db.get_pif_record(pif) | |
801 | device = pifrec['device'] | |
802 | mac = pifrec['MAC'] | |
803 | ||
804 | # Is there a network device named 'device' at all? | |
805 | device_exists = interface_exists(device) | |
806 | if device_exists: | |
807 | # Yes. Does it have MAC 'mac'? | |
808 | found_mac = get_netdev_mac(device) | |
809 | if found_mac and mac.lower() == found_mac.lower(): | |
810 | # Yes, everything checks out the way we want. Nothing to do. | |
811 | return | |
812 | else: | |
813 | log("No network device %s" % device) | |
814 | ||
815 | # What device has MAC 'mac'? | |
816 | cur_device = get_netdev_by_mac(mac) | |
817 | if not cur_device: | |
818 | log("No network device has MAC %s" % mac) | |
819 | return | |
820 | ||
821 | # First rename 'device', if it exists, to get it out of the way | |
822 | # for 'cur_device' to replace it. | |
823 | if device_exists: | |
824 | rename_netdev(device, "dev%d" % random.getrandbits(24)) | |
825 | ||
826 | # Rename 'cur_device' to 'device'. | |
827 | rename_netdev(cur_device, device) | |
828 | ||
449776d8 BP |
829 | def read_first_line_of_file(name): |
830 | file = None | |
831 | try: | |
832 | file = open(name, 'r') | |
833 | return file.readline().rstrip('\n') | |
834 | finally: | |
835 | if file != None: | |
836 | file.close() | |
837 | ||
064af421 BP |
838 | def down_netdev(interface, deconfigure=True): |
839 | if not interface_exists(interface): | |
840 | log("down_netdev: interface %s does not exist, ignoring" % interface) | |
841 | return | |
064af421 | 842 | if deconfigure: |
064af421 BP |
843 | # Kill dhclient. |
844 | pidfile_name = '/var/run/dhclient-%s.pid' % interface | |
064af421 | 845 | try: |
449776d8 | 846 | os.kill(int(read_first_line_of_file(pidfile_name)), signal.SIGTERM) |
064af421 BP |
847 | except: |
848 | pass | |
064af421 BP |
849 | |
850 | # Remove dhclient pidfile. | |
851 | try: | |
852 | os.remove(pidfile_name) | |
853 | except: | |
854 | pass | |
3cc42ebb BP |
855 | |
856 | run_command(["/sbin/ifconfig", interface, '0.0.0.0']) | |
857 | ||
858 | run_command(["/sbin/ifconfig", interface, 'down']) | |
064af421 BP |
859 | |
860 | def up_netdev(interface): | |
861 | run_command(["/sbin/ifconfig", interface, 'up']) | |
862 | ||
863 | def find_distinguished_pifs(pif): | |
864 | """Returns the PIFs on host that own DNS and the default route. | |
865 | The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set. | |
866 | The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set. | |
867 | ||
868 | Note: we prune out the bond master pif (if it exists). | |
869 | This is because when we are called to bring up an interface with a bond master, it is implicit that | |
870 | we should bring down that master.""" | |
871 | ||
872 | pifrec = db.get_pif_record(pif) | |
064af421 | 873 | |
c798b21c | 874 | pifs = [ __pif for __pif in db.get_all_pifs() if |
064af421 BP |
875 | (not __pif in get_bond_masters_of_pif(pif)) ] |
876 | ||
877 | peerdns_pif = None | |
878 | defaultroute_pif = None | |
879 | ||
880 | # loop through all the pifs on this host looking for one with | |
881 | # other-config:peerdns = true, and one with | |
882 | # other-config:default-route=true | |
c798b21c | 883 | for __pif in pifs: |
064af421 BP |
884 | __pifrec = db.get_pif_record(__pif) |
885 | __oc = __pifrec['other_config'] | |
886 | if __oc.has_key('peerdns') and __oc['peerdns'] == 'true': | |
887 | if peerdns_pif == None: | |
888 | peerdns_pif = __pif | |
889 | else: | |
890 | log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \ | |
891 | (db.get_pif_record(peerdns_pif)['device'], __pifrec['device'])) | |
892 | if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true': | |
893 | if defaultroute_pif == None: | |
894 | defaultroute_pif = __pif | |
895 | else: | |
896 | log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \ | |
897 | (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device'])) | |
898 | ||
899 | # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute | |
900 | if peerdns_pif == None: | |
901 | peerdns_pif = management_pif | |
902 | if defaultroute_pif == None: | |
903 | defaultroute_pif = management_pif | |
904 | ||
905 | return peerdns_pif, defaultroute_pif | |
906 | ||
05e2ad78 BP |
907 | def run_ethtool(device, oc): |
908 | # Run "ethtool -s" if there are any settings. | |
064af421 BP |
909 | settings = [] |
910 | if oc.has_key('ethtool-speed'): | |
911 | val = oc['ethtool-speed'] | |
912 | if val in ["10", "100", "1000"]: | |
913 | settings += ['speed', val] | |
914 | else: | |
915 | log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val) | |
916 | if oc.has_key('ethtool-duplex'): | |
917 | val = oc['ethtool-duplex'] | |
918 | if val in ["10", "100", "1000"]: | |
919 | settings += ['duplex', 'val'] | |
920 | else: | |
921 | log("Invalid value for ethtool-duplex = %s. Must be half|full." % val) | |
922 | if oc.has_key('ethtool-autoneg'): | |
923 | val = oc['ethtool-autoneg'] | |
924 | if val in ["true", "on"]: | |
925 | settings += ['autoneg', 'on'] | |
926 | elif val in ["false", "off"]: | |
927 | settings += ['autoneg', 'off'] | |
928 | else: | |
929 | log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val) | |
05e2ad78 BP |
930 | if settings: |
931 | run_command(['/sbin/ethtool', '-s', device] + settings) | |
064af421 | 932 | |
05e2ad78 | 933 | # Run "ethtool -K" if there are any offload settings. |
064af421 BP |
934 | offload = [] |
935 | for opt in ("rx", "tx", "sg", "tso", "ufo", "gso"): | |
936 | if oc.has_key("ethtool-" + opt): | |
937 | val = oc["ethtool-" + opt] | |
938 | if val in ["true", "on"]: | |
939 | offload += [opt, 'on'] | |
940 | elif val in ["false", "off"]: | |
941 | offload += [opt, 'off'] | |
942 | else: | |
943 | log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val)) | |
05e2ad78 BP |
944 | if offload: |
945 | run_command(['/sbin/ethtool', '-K', device] + offload) | |
064af421 | 946 | |
05e2ad78 BP |
947 | def mtu_setting(oc): |
948 | if oc.has_key('mtu'): | |
949 | try: | |
950 | int(oc['mtu']) # Check that the value is an integer | |
951 | return ['mtu', oc['mtu']] | |
952 | except ValueError, x: | |
953 | log("Invalid value for mtu = %s" % mtu) | |
954 | return [] | |
064af421 | 955 | |
88acec3b | 956 | def configure_local_port(pif): |
064af421 BP |
957 | pifrec = db.get_pif_record(pif) |
958 | datapath = datapath_name(pif) | |
959 | ipdev = ipdev_name(pif) | |
960 | ||
064af421 BP |
961 | nw = pifrec['network'] |
962 | nwrec = db.get_network_record(nw) | |
963 | ||
c87d1024 BP |
964 | pif_oc = pifrec['other_config'] |
965 | nw_oc = nwrec['other_config'] | |
966 | ||
967 | # IP (except DHCP) and MTU. | |
064af421 BP |
968 | ifconfig_argv = ['/sbin/ifconfig', ipdev, 'up'] |
969 | gateway = '' | |
970 | if pifrec['ip_configuration_mode'] == "DHCP": | |
971 | pass | |
972 | elif pifrec['ip_configuration_mode'] == "Static": | |
973 | ifconfig_argv += [pifrec['IP']] | |
974 | ifconfig_argv += ['netmask', pifrec['netmask']] | |
975 | gateway = pifrec['gateway'] | |
976 | elif pifrec['ip_configuration_mode'] == "None": | |
977 | # Nothing to do. | |
978 | pass | |
979 | else: | |
980 | raise Error("Unknown IP-configuration-mode %s" % pifrec['ip_configuration_mode']) | |
c87d1024 | 981 | ifconfig_argv += mtu_setting(nw_oc) |
064af421 BP |
982 | run_command(ifconfig_argv) |
983 | ||
984 | (peerdns_pif, defaultroute_pif) = find_distinguished_pifs(pif) | |
985 | ||
c87d1024 | 986 | # /etc/resolv.conf |
064af421 BP |
987 | if peerdns_pif == pif: |
988 | f = ConfigurationFile('resolv.conf', "/etc") | |
c87d1024 BP |
989 | if pif_oc.has_key('domain'): |
990 | f.write("search %s\n" % pif_oc['domain']) | |
064af421 BP |
991 | for dns in pifrec['DNS'].split(","): |
992 | f.write("nameserver %s\n" % dns) | |
993 | f.close() | |
994 | f.apply() | |
995 | f.commit() | |
996 | ||
c87d1024 | 997 | # Routing. |
064af421 BP |
998 | if defaultroute_pif == pif and gateway != '': |
999 | run_command(['/sbin/ip', 'route', 'replace', 'default', | |
1000 | 'via', gateway, 'dev', ipdev]) | |
c87d1024 BP |
1001 | if nw_oc.has_key('static-routes'): |
1002 | for line in nw_oc['static-routes'].split(','): | |
064af421 BP |
1003 | network, masklen, gateway = line.split('/') |
1004 | run_command(['/sbin/ip', 'route', 'add', | |
8f749dac | 1005 | '%s/%s' % (network, masklen), 'via', gateway, |
064af421 BP |
1006 | 'dev', ipdev]) |
1007 | ||
05e2ad78 | 1008 | # Ethtool. |
c87d1024 | 1009 | run_ethtool(ipdev, nw_oc) |
064af421 | 1010 | |
c87d1024 | 1011 | # DHCP. |
064af421 BP |
1012 | if pifrec['ip_configuration_mode'] == "DHCP": |
1013 | ||
1014 | print "Determining IP information for %s..." % ipdev, | |
1015 | argv = ['/sbin/dhclient', '-q', | |
1016 | '-lf', '/var/lib/dhclient/dhclient-%s.leases' % ipdev, | |
1017 | '-pf', '/var/run/dhclient-%s.pid' % ipdev, | |
1018 | ipdev] | |
1019 | if run_command(argv): | |
1020 | print 'done.' | |
1021 | else: | |
1022 | print 'failed.' | |
1023 | ||
88acec3b BP |
1024 | def configure_physdev(pif): |
1025 | pifrec = db.get_pif_record(pif) | |
1026 | device = pifrec['device'] | |
1027 | oc = pifrec['other_config'] | |
1028 | ||
1029 | run_command(['/sbin/ifconfig', device, 'up'] + mtu_setting(oc)) | |
1030 | run_ethtool(device, oc) | |
1031 | ||
064af421 | 1032 | def modify_config(commands): |
eaa3c7e8 | 1033 | run_command(['/usr/bin/ovs-cfg-mod', '-vANY:console:emer', |
064af421 BP |
1034 | '-F', '/etc/ovs-vswitchd.conf'] |
1035 | + commands + ['-c']) | |
1036 | run_command(['/sbin/service', 'vswitch', 'reload']) | |
1037 | ||
1038 | def is_bond_pif(pif): | |
1039 | pifrec = db.get_pif_record(pif) | |
1040 | return len(pifrec['bond_master_of']) != 0 | |
1041 | ||
1042 | def configure_bond(pif): | |
1043 | pifrec = db.get_pif_record(pif) | |
1044 | interface = interface_name(pif) | |
1045 | ipdev = ipdev_name(pif) | |
1046 | datapath = datapath_name(pif) | |
bc952bb3 | 1047 | physdev_names = get_physdev_names(pif) |
064af421 BP |
1048 | |
1049 | argv = ['--del-match=bonding.%s.[!0-9]*' % interface] | |
1050 | argv += ["--add=bonding.%s.slave=%s" % (interface, slave) | |
bc952bb3 | 1051 | for slave in physdev_names] |
d2cd45db | 1052 | argv += ['--add=bonding.%s.fake-iface=true' % interface] |
064af421 | 1053 | |
58b7527e BP |
1054 | if pifrec['MAC'] != "": |
1055 | argv += ['--add=port.%s.mac=%s' % (interface, pifrec['MAC'])] | |
1056 | ||
064af421 BP |
1057 | # Bonding options. |
1058 | bond_options = { | |
1059 | "mode": "balance-slb", | |
1060 | "miimon": "100", | |
1061 | "downdelay": "200", | |
1062 | "updelay": "31000", | |
1063 | "use_carrier": "1", | |
1064 | } | |
1065 | # override defaults with values from other-config whose keys | |
1066 | # being with "bond-" | |
1067 | oc = pifrec['other_config'] | |
1068 | overrides = filter(lambda (key,val): | |
1069 | key.startswith("bond-"), oc.items()) | |
1070 | overrides = map(lambda (key,val): (key[5:], val), overrides) | |
1071 | bond_options.update(overrides) | |
1072 | for (name,val) in bond_options.items(): | |
1073 | argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)] | |
1074 | return argv | |
1075 | ||
1076 | def action_up(pif): | |
1077 | pifrec = db.get_pif_record(pif) | |
1078 | ||
1079 | bridge = bridge_name(pif) | |
1080 | interface = interface_name(pif) | |
1081 | ipdev = ipdev_name(pif) | |
1082 | datapath = datapath_name(pif) | |
bc952bb3 BP |
1083 | physdev_pifs = get_physdev_pifs(pif) |
1084 | physdev_names = get_physdev_names(pif) | |
064af421 BP |
1085 | vlan_slave = None |
1086 | if pifrec['VLAN'] != '-1': | |
1087 | vlan_slave = get_vlan_slave_of_pif(pif) | |
1088 | if vlan_slave and is_bond_pif(vlan_slave): | |
1089 | bond_master = vlan_slave | |
1090 | elif is_bond_pif(pif): | |
1091 | bond_master = pif | |
1092 | else: | |
1093 | bond_master = None | |
8826590a BP |
1094 | if bond_master: |
1095 | bond_slaves = get_bond_slaves_of_pif(bond_master) | |
1096 | else: | |
1097 | bond_slaves = [] | |
064af421 BP |
1098 | bond_masters = get_bond_masters_of_pif(pif) |
1099 | ||
1100 | # Support "rpm -e vswitch" gracefully by keeping Centos configuration | |
1101 | # files up-to-date, even though we don't use them or need them. | |
1102 | f = configure_pif(pif) | |
1103 | mode = pifrec['ip_configuration_mode'] | |
1104 | if bridge: | |
1105 | log("Configuring %s using %s configuration" % (bridge, mode)) | |
1106 | br = open_network_ifcfg(pif) | |
1107 | configure_network(pif, br) | |
1108 | br.close() | |
1109 | f.attach_child(br) | |
1110 | else: | |
1111 | log("Configuring %s using %s configuration" % (interface, mode)) | |
1112 | configure_network(pif, f) | |
1113 | f.close() | |
1114 | for master in bond_masters: | |
1115 | master_bridge = bridge_name(master) | |
1116 | removed = unconfigure_pif(master) | |
1117 | f.attach_child(removed) | |
1118 | if master_bridge: | |
1119 | removed = open_network_ifcfg(master) | |
1120 | log("Unlinking stale file %s" % removed.path()) | |
1121 | removed.unlink() | |
1122 | f.attach_child(removed) | |
1123 | ||
1124 | # /etc/xensource/scripts/vif needs to know where to add VIFs. | |
1125 | if vlan_slave: | |
e44d1844 IC |
1126 | if not os.path.exists(vswitch_state_dir): |
1127 | os.mkdir(vswitch_state_dir) | |
1128 | br = ConfigurationFile("br-%s" % bridge, vswitch_state_dir) | |
064af421 BP |
1129 | br.write("VLAN_SLAVE=%s\n" % datapath) |
1130 | br.write("VLAN_VID=%s\n" % pifrec['VLAN']) | |
1131 | br.close() | |
1132 | f.attach_child(br) | |
1133 | ||
1134 | # Update all configuration files (both ours and Centos's). | |
1135 | f.apply() | |
1136 | f.commit() | |
1137 | ||
2bb451b6 BP |
1138 | # Check the MAC address of each network device and remap if |
1139 | # necessary to make names match our expectations. | |
bc952bb3 | 1140 | for physdev_pif in physdev_pifs: |
2bb451b6 BP |
1141 | remap_pif(physdev_pif) |
1142 | ||
064af421 BP |
1143 | # "ifconfig down" the network device and delete its IP address, etc. |
1144 | down_netdev(ipdev) | |
bc952bb3 BP |
1145 | for physdev_name in physdev_names: |
1146 | down_netdev(physdev_name) | |
064af421 | 1147 | |
7a3696ad BP |
1148 | # If we are bringing up a bond, remove IP addresses from the |
1149 | # slaves (because we are implicitly being asked to take them down). | |
1150 | # | |
1151 | # Conversely, if we are bringing up an interface that has bond | |
1152 | # masters, remove IP addresses from the bond master (because we | |
1153 | # are implicitly being asked to take it down). | |
1154 | for bond_pif in bond_slaves + bond_masters: | |
1155 | run_command(["/sbin/ifconfig", ipdev_name(bond_pif), '0.0.0.0']) | |
1156 | ||
064af421 | 1157 | # Remove all keys related to pif and any bond masters linked to PIF. |
bc952bb3 | 1158 | del_ports = [ipdev] + physdev_names + bond_masters |
064af421 BP |
1159 | if vlan_slave and bond_master: |
1160 | del_ports += [interface_name(bond_master)] | |
1161 | ||
1162 | # What ports do we need to add to the datapath? | |
1163 | # | |
1164 | # We definitely need the ipdev, and ordinarily we want the | |
1165 | # physical devices too, but for bonds we need the bond as bridge | |
1166 | # port. | |
1167 | add_ports = [ipdev, datapath] | |
1168 | if not bond_master: | |
bc952bb3 | 1169 | add_ports += physdev_names |
064af421 BP |
1170 | else: |
1171 | add_ports += [interface_name(bond_master)] | |
1172 | ||
1173 | # What ports do we need to delete? | |
1174 | # | |
1175 | # - All the ports that we add, to avoid duplication and to drop | |
1176 | # them from another datapath in case they're misassigned. | |
1177 | # | |
1178 | # - The physical devices, since they will either be in add_ports | |
1179 | # or added to the bonding device (see below). | |
1180 | # | |
1181 | # - The bond masters for pif. (Ordinarily pif shouldn't have any | |
1182 | # bond masters. If it does then interface-reconfigure is | |
1183 | # implicitly being asked to take them down.) | |
bc952bb3 | 1184 | del_ports = add_ports + physdev_names + bond_masters |
064af421 BP |
1185 | |
1186 | # What networks does this datapath carry? | |
1187 | # | |
1188 | # - The network corresponding to the datapath's PIF. | |
1189 | # | |
1190 | # - The networks corresponding to any VLANs attached to the | |
1191 | # datapath's PIF. | |
1192 | network_uuids = [] | |
f134604b | 1193 | for nwpif in db.get_pifs_by_device(pifrec['device']): |
064af421 BP |
1194 | net = db.get_pif_record(nwpif)['network'] |
1195 | network_uuids += [db.get_network_record(net)['uuid']] | |
1196 | ||
8826590a BP |
1197 | # Bring up bond slaves early, because ovs-vswitchd initially |
1198 | # enables or disables bond slaves based on whether carrier is | |
1199 | # detected when they are added, and a network device that is down | |
1200 | # always reports "no carrier". | |
88acec3b | 1201 | bond_slave_physdev_pifs = [] |
8826590a | 1202 | for slave in bond_slaves: |
88acec3b BP |
1203 | bond_slave_physdev_pifs += get_physdev_pifs(slave) |
1204 | for slave_physdev_pif in set(bond_slave_physdev_pifs): | |
1205 | configure_physdev(slave_physdev_pif) | |
8826590a | 1206 | |
064af421 BP |
1207 | # Now modify the ovs-vswitchd config file. |
1208 | argv = [] | |
1209 | for port in set(del_ports): | |
1210 | argv += interface_deconfigure_commands(port) | |
1211 | for port in set(add_ports): | |
1212 | argv += ['--add=bridge.%s.port=%s' % (datapath, port)] | |
1213 | if vlan_slave: | |
1214 | argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])] | |
1215 | argv += ['--add=iface.%s.internal=true' % (ipdev)] | |
1216 | ||
1217 | # xapi creates a bridge by the name of the ipdev and requires | |
1218 | # that the IP address will be on it. We need to delete this | |
1219 | # bridge because we need that device to be a member of our | |
1220 | # datapath. | |
1221 | argv += ['--del-match=bridge.%s.[!0-9]*' % ipdev] | |
1222 | ||
1223 | # xapi insists that its attempts to create the bridge succeed, | |
1224 | # so force that to happen. | |
1225 | argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)] | |
1226 | else: | |
1227 | try: | |
e44d1844 | 1228 | os.unlink("%s/br-%s" % (vswitch_state_dir, bridge)) |
064af421 BP |
1229 | except OSError: |
1230 | pass | |
1231 | argv += ['--del-match=bridge.%s.xs-network-uuids=*' % datapath] | |
1232 | argv += ['--add=bridge.%s.xs-network-uuids=%s' % (datapath, uuid) | |
1233 | for uuid in set(network_uuids)] | |
1234 | if bond_master: | |
1235 | argv += configure_bond(bond_master) | |
1236 | modify_config(argv) | |
1237 | ||
8826590a BP |
1238 | # Bring up VLAN slave, plus physical devices other than bond |
1239 | # slaves (which we brought up earlier). | |
fa2bec94 IC |
1240 | # XXX need to bring up bond itself. |
1241 | # XXX need to set MAC address on fake bridge | |
064af421 BP |
1242 | if vlan_slave: |
1243 | up_netdev(ipdev_name(vlan_slave)) | |
88acec3b BP |
1244 | for physdev_pif in set(physdev_pifs) - set(bond_slave_physdev_pifs): |
1245 | configure_physdev(physdev_pif) | |
064af421 | 1246 | |
88acec3b BP |
1247 | # Configure network device for local port. |
1248 | configure_local_port(pif) | |
0d7e8aac | 1249 | |
064af421 BP |
1250 | # Update /etc/issue (which contains the IP address of the management interface) |
1251 | os.system("/sbin/update-issue") | |
7a3696ad BP |
1252 | |
1253 | if bond_slaves: | |
1254 | # There seems to be a race somewhere: without this sleep, using | |
1255 | # XenCenter to create a bond that becomes the management interface | |
1256 | # fails with "The underlying connection was closed: A connection that | |
1257 | # was expected to be kept alive was closed by the server." on every | |
1258 | # second or third try, even though /var/log/messages doesn't show | |
1259 | # anything unusual. | |
1260 | # | |
1261 | # The race is probably present even without vswitch, but bringing up a | |
1262 | # bond without vswitch involves a built-in pause of 10 seconds or more | |
1263 | # to wait for the bond to transition from learning to forwarding state. | |
1264 | time.sleep(5) | |
064af421 BP |
1265 | |
1266 | def action_down(pif): | |
1267 | rec = db.get_pif_record(pif) | |
1268 | interface = interface_name(pif) | |
1269 | bridge = bridge_name(pif) | |
1270 | ipdev = ipdev_name(pif) | |
1271 | ||
1272 | # Support "rpm -e vswitch" gracefully by keeping Centos configuration | |
1273 | # files up-to-date, even though we don't use them or need them. | |
1274 | f = unconfigure_pif(pif) | |
1275 | if bridge: | |
1276 | br = open_network_ifcfg(pif) | |
1277 | log("Unlinking stale file %s" % br.path()) | |
1278 | br.unlink() | |
1279 | f.attach_child(br) | |
1280 | try: | |
1281 | f.apply() | |
1282 | f.commit() | |
1283 | except Error, e: | |
1284 | log("action_down failed to apply changes: %s" % e.msg) | |
1285 | f.revert() | |
1286 | raise | |
1287 | ||
1288 | argv = [] | |
1289 | if rec['VLAN'] != '-1': | |
1290 | # Get rid of the VLAN device itself. | |
1291 | down_netdev(ipdev) | |
1292 | argv += interface_deconfigure_commands(ipdev) | |
1293 | ||
1294 | # If the VLAN's slave is attached, stop here. | |
1295 | slave = get_vlan_slave_of_pif(pif) | |
1296 | if db.get_pif_record(slave)['currently_attached']: | |
1297 | log("VLAN slave is currently attached") | |
1298 | modify_config(argv) | |
1299 | return | |
1300 | ||
1301 | # If the VLAN's slave has other VLANs that are attached, stop here. | |
1302 | masters = get_vlan_masters_of_pif(slave) | |
1303 | for m in masters: | |
1304 | if m != pif and db.get_pif_record(m)['currently_attached']: | |
1305 | log("VLAN slave has other master %s" % interface_naem(m)) | |
1306 | modify_config(argv) | |
1307 | return | |
1308 | ||
1309 | # Otherwise, take down the VLAN's slave too. | |
1310 | log("No more masters, bring down vlan slave %s" % interface_name(slave)) | |
1311 | pif = slave | |
1312 | else: | |
1313 | # Stop here if this PIF has attached VLAN masters. | |
1314 | vlan_masters = get_vlan_masters_of_pif(pif) | |
1315 | log("VLAN masters of %s - %s" % (rec['device'], [interface_name(m) for m in vlan_masters])) | |
1316 | for m in vlan_masters: | |
1317 | if db.get_pif_record(m)['currently_attached']: | |
fa2bec94 | 1318 | # XXX remove IP address |
064af421 BP |
1319 | log("Leaving %s up due to currently attached VLAN master %s" % (interface, interface_name(m))) |
1320 | return | |
1321 | ||
1322 | # pif is now either a bond or a physical device which needs to be | |
1323 | # brought down. pif might have changed so re-check all its attributes. | |
1324 | rec = db.get_pif_record(pif) | |
1325 | interface = interface_name(pif) | |
1326 | bridge = bridge_name(pif) | |
1327 | ipdev = ipdev_name(pif) | |
1328 | ||
1329 | ||
1330 | bond_slaves = get_bond_slaves_of_pif(pif) | |
1331 | log("bond slaves of %s - %s" % (rec['device'], [interface_name(s) for s in bond_slaves])) | |
1332 | for slave in bond_slaves: | |
1333 | slave_interface = interface_name(slave) | |
1334 | log("bring down bond slave %s" % slave_interface) | |
1335 | argv += interface_deconfigure_commands(slave_interface) | |
1336 | down_netdev(slave_interface) | |
1337 | ||
1338 | argv += interface_deconfigure_commands(ipdev) | |
1339 | down_netdev(ipdev) | |
1340 | ||
1341 | argv += ['--del-match', 'bridge.%s.*' % datapath_name(pif)] | |
1342 | argv += ['--del-match', 'bonding.%s.[!0-9]*' % interface] | |
1343 | modify_config(argv) | |
1344 | ||
1345 | def action_rewrite(pif): | |
1346 | # Support "rpm -e vswitch" gracefully by keeping Centos configuration | |
1347 | # files up-to-date, even though we don't use them or need them. | |
1348 | pifrec = db.get_pif_record(pif) | |
1349 | f = configure_pif(pif) | |
1350 | interface = interface_name(pif) | |
1351 | bridge = bridge_name(pif) | |
1352 | mode = pifrec['ip_configuration_mode'] | |
1353 | if bridge: | |
1354 | log("Configuring %s using %s configuration" % (bridge, mode)) | |
1355 | br = open_network_ifcfg(pif) | |
1356 | configure_network(pif, br) | |
1357 | br.close() | |
1358 | f.attach_child(br) | |
1359 | else: | |
1360 | log("Configuring %s using %s configuration" % (interface, mode)) | |
1361 | configure_network(pif, f) | |
1362 | f.close() | |
1363 | try: | |
1364 | f.apply() | |
1365 | f.commit() | |
1366 | except Error, e: | |
1367 | log("failed to apply changes: %s" % e.msg) | |
1368 | f.revert() | |
1369 | raise | |
1370 | ||
1371 | # We have no code of our own to run here. | |
1372 | pass | |
1373 | ||
fa2bec94 IC |
1374 | def action_force_rewrite(interface, config): |
1375 | raise Error("Force rewrite is not implemented yet.") | |
1376 | ||
064af421 BP |
1377 | def main(argv=None): |
1378 | global output_directory, management_pif | |
1379 | ||
1380 | session = None | |
1381 | pif_uuid = None | |
1382 | pif = None | |
1383 | ||
1384 | force_interface = None | |
1385 | force_management = False | |
1386 | ||
1387 | if argv is None: | |
1388 | argv = sys.argv | |
1389 | ||
1390 | try: | |
1391 | try: | |
1392 | shortops = "h" | |
1393 | longops = [ "output-directory=", | |
1394 | "pif=", "pif-uuid=", | |
1395 | "session=", | |
1396 | "force=", | |
1397 | "force-interface=", | |
1398 | "management", | |
064af421 BP |
1399 | "device=", "mode=", "ip=", "netmask=", "gateway=", |
1400 | "help" ] | |
1401 | arglist, args = getopt.gnu_getopt(argv[1:], shortops, longops) | |
1402 | except getopt.GetoptError, msg: | |
1403 | raise Usage(msg) | |
1404 | ||
1405 | force_rewrite_config = {} | |
1406 | ||
1407 | for o,a in arglist: | |
1408 | if o == "--output-directory": | |
1409 | output_directory = a | |
1410 | elif o == "--pif": | |
1411 | pif = a | |
1412 | elif o == "--pif-uuid": | |
1413 | pif_uuid = a | |
1414 | elif o == "--session": | |
1415 | session = a | |
1416 | elif o == "--force-interface" or o == "--force": | |
1417 | force_interface = a | |
1418 | elif o == "--management": | |
1419 | force_management = True | |
1420 | elif o in ["--device", "--mode", "--ip", "--netmask", "--gateway"]: | |
1421 | force_rewrite_config[o[2:]] = a | |
1422 | elif o == "-h" or o == "--help": | |
1423 | print __doc__ % {'command-name': os.path.basename(argv[0])} | |
1424 | return 0 | |
1425 | ||
1426 | if not debug_mode(): | |
1427 | syslog.openlog(os.path.basename(argv[0])) | |
1428 | log("Called as " + str.join(" ", argv)) | |
1429 | if len(args) < 1: | |
1430 | raise Usage("Required option <action> not present") | |
1431 | if len(args) > 1: | |
1432 | raise Usage("Too many arguments") | |
1433 | ||
1434 | action = args[0] | |
fa2bec94 IC |
1435 | |
1436 | if not action in ["up", "down", "rewrite", "rewrite-configuration"]: | |
1437 | raise Usage("Unknown action \"%s\"" % action) | |
1438 | ||
064af421 BP |
1439 | # backwards compatibility |
1440 | if action == "rewrite-configuration": action = "rewrite" | |
1441 | ||
1442 | if output_directory and ( session or pif ): | |
1443 | raise Usage("--session/--pif cannot be used with --output-directory") | |
1444 | if ( session or pif ) and pif_uuid: | |
1445 | raise Usage("--session/--pif and --pif-uuid are mutually exclusive.") | |
1446 | if ( session and not pif ) or ( not session and pif ): | |
1447 | raise Usage("--session and --pif must be used together.") | |
1448 | if force_interface and ( session or pif or pif_uuid ): | |
1449 | raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid") | |
fa2bec94 IC |
1450 | if force_interface == "all" and action != "down": |
1451 | raise Usage("\"--force all\" only valid for down action") | |
064af421 BP |
1452 | if len(force_rewrite_config) and not (force_interface and action == "rewrite"): |
1453 | raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway") | |
1454 | ||
1455 | global db | |
1456 | if force_interface: | |
1457 | log("Force interface %s %s" % (force_interface, action)) | |
1458 | ||
1459 | if action == "rewrite": | |
1460 | action_force_rewrite(force_interface, force_rewrite_config) | |
fa2bec94 IC |
1461 | elif action in ["up", "down"]: |
1462 | if action == "down" and force_interface == "all": | |
1463 | raise Error("Force all interfaces down not implemented yet") | |
1464 | ||
064af421 | 1465 | db = DatabaseCache(cache_file=dbcache_file) |
c798b21c IC |
1466 | pif = db.get_pif_by_bridge(force_interface) |
1467 | management_pif = db.get_management_pif() | |
064af421 BP |
1468 | |
1469 | if action == "up": | |
1470 | action_up(pif) | |
1471 | elif action == "down": | |
1472 | action_down(pif) | |
fa2bec94 IC |
1473 | else: |
1474 | raise Error("Unknown action %s" % action) | |
064af421 BP |
1475 | else: |
1476 | db = DatabaseCache(session_ref=session) | |
1477 | ||
1478 | if pif_uuid: | |
1479 | pif = db.get_pif_by_uuid(pif_uuid) | |
064af421 | 1480 | |
057fed2b BP |
1481 | if action == "rewrite" and not pif: |
1482 | pass | |
064af421 | 1483 | else: |
057fed2b BP |
1484 | if not pif: |
1485 | raise Usage("No PIF given") | |
064af421 | 1486 | |
057fed2b BP |
1487 | if force_management: |
1488 | # pif is going to be the management pif | |
1489 | management_pif = pif | |
1490 | else: | |
1491 | # pif is not going to be the management pif. | |
1492 | # Search DB cache for pif on same host with management=true | |
1493 | pifrec = db.get_pif_record(pif) | |
1494 | management_pif = db.get_management_pif() | |
064af421 | 1495 | |
057fed2b | 1496 | log_pif_action(action, pif) |
064af421 | 1497 | |
057fed2b BP |
1498 | if not check_allowed(pif): |
1499 | return 0 | |
1500 | ||
1501 | if action == "up": | |
1502 | action_up(pif) | |
1503 | elif action == "down": | |
1504 | action_down(pif) | |
1505 | elif action == "rewrite": | |
1506 | action_rewrite(pif) | |
1507 | else: | |
fa2bec94 | 1508 | raise Error("Unknown action %s" % action) |
064af421 BP |
1509 | |
1510 | # Save cache. | |
c798b21c | 1511 | db.save(dbcache_file) |
064af421 BP |
1512 | |
1513 | except Usage, err: | |
1514 | print >>sys.stderr, err.msg | |
1515 | print >>sys.stderr, "For help use --help." | |
1516 | return 2 | |
1517 | except Error, err: | |
1518 | log(err.msg) | |
1519 | return 1 | |
1520 | ||
1521 | return 0 | |
1522 | \f | |
1523 | # The following code allows interface-reconfigure to keep Centos | |
1524 | # network configuration files up-to-date, even though the vswitch | |
1525 | # never uses them. In turn, that means that "rpm -e vswitch" does not | |
1526 | # have to update any configuration files. | |
1527 | ||
1528 | def configure_ethtool(oc, f): | |
1529 | # Options for "ethtool -s" | |
1530 | settings = None | |
1531 | setting_opts = ["autoneg", "speed", "duplex"] | |
1532 | # Options for "ethtool -K" | |
1533 | offload = None | |
1534 | offload_opts = ["rx", "tx", "sg", "tso", "ufo", "gso"] | |
1535 | ||
1536 | for opt in [opt for opt in setting_opts + offload_opts if oc.has_key("ethtool-" + opt)]: | |
1537 | val = oc["ethtool-" + opt] | |
1538 | ||
1539 | if opt in ["speed"]: | |
1540 | if val in ["10", "100", "1000"]: | |
1541 | val = "speed " + val | |
1542 | else: | |
1543 | log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val) | |
1544 | val = None | |
1545 | elif opt in ["duplex"]: | |
1546 | if val in ["half", "full"]: | |
1547 | val = "duplex " + val | |
1548 | else: | |
1549 | log("Invalid value for ethtool-duplex = %s. Must be half|full." % val) | |
1550 | val = None | |
1551 | elif opt in ["autoneg"] + offload_opts: | |
1552 | if val in ["true", "on"]: | |
1553 | val = opt + " on" | |
1554 | elif val in ["false", "off"]: | |
1555 | val = opt + " off" | |
1556 | else: | |
1557 | log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt, val)) | |
1558 | val = None | |
1559 | ||
1560 | if opt in setting_opts: | |
1561 | if val and settings: | |
1562 | settings = settings + " " + val | |
1563 | else: | |
1564 | settings = val | |
1565 | elif opt in offload_opts: | |
1566 | if val and offload: | |
1567 | offload = offload + " " + val | |
1568 | else: | |
1569 | offload = val | |
1570 | ||
1571 | if settings: | |
1572 | f.write("ETHTOOL_OPTS=\"%s\"\n" % settings) | |
1573 | if offload: | |
1574 | f.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % offload) | |
1575 | ||
1576 | def configure_mtu(oc, f): | |
1577 | if not oc.has_key('mtu'): | |
1578 | return | |
1579 | ||
1580 | try: | |
1581 | mtu = int(oc['mtu']) | |
1582 | f.write("MTU=%d\n" % mtu) | |
1583 | except ValueError, x: | |
1584 | log("Invalid value for mtu = %s" % mtu) | |
1585 | ||
1586 | def configure_static_routes(interface, oc, f): | |
1587 | """Open a route-<interface> file for static routes. | |
1588 | ||
1589 | Opens the static routes configuration file for interface and writes one | |
1590 | line for each route specified in the network's other config "static-routes" value. | |
1591 | E.g. if | |
1592 | interface ( RO): xenbr1 | |
1593 | other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;... | |
1594 | ||
1595 | Then route-xenbr1 should be | |
1596 | 172.16.0.0/15 via 192.168.0.3 dev xenbr1 | |
1597 | 172.18.0.0/16 via 192.168.0.4 dev xenbr1 | |
1598 | """ | |
1599 | fname = "route-%s" % interface | |
1600 | if oc.has_key('static-routes'): | |
1601 | # The key is present - extract comma seperates entries | |
1602 | lines = oc['static-routes'].split(',') | |
1603 | else: | |
1604 | # The key is not present, i.e. there are no static routes | |
1605 | lines = [] | |
1606 | ||
1607 | child = ConfigurationFile(fname) | |
1608 | child.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \ | |
1609 | (os.path.basename(child.path()), os.path.basename(sys.argv[0]))) | |
1610 | ||
1611 | try: | |
1612 | for l in lines: | |
1613 | network, masklen, gateway = l.split('/') | |
1614 | child.write("%s/%s via %s dev %s\n" % (network, masklen, gateway, interface)) | |
1615 | ||
1616 | f.attach_child(child) | |
1617 | child.close() | |
1618 | ||
1619 | except ValueError, e: | |
1620 | log("Error in other-config['static-routes'] format for network %s: %s" % (interface, e)) | |
1621 | ||
1622 | def __open_ifcfg(interface): | |
1623 | """Open a network interface configuration file. | |
1624 | ||
1625 | Opens the configuration file for interface, writes a header and | |
1626 | common options and returns the file object. | |
1627 | """ | |
1628 | fname = "ifcfg-%s" % interface | |
1629 | f = ConfigurationFile(fname) | |
1630 | ||
1631 | f.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \ | |
1632 | (os.path.basename(f.path()), os.path.basename(sys.argv[0]))) | |
1633 | f.write("XEMANAGED=yes\n") | |
1634 | f.write("DEVICE=%s\n" % interface) | |
1635 | f.write("ONBOOT=no\n") | |
1636 | ||
1637 | return f | |
1638 | ||
1639 | def open_network_ifcfg(pif): | |
1640 | bridge = bridge_name(pif) | |
1641 | interface = interface_name(pif) | |
1642 | if bridge: | |
1643 | return __open_ifcfg(bridge) | |
1644 | else: | |
1645 | return __open_ifcfg(interface) | |
1646 | ||
1647 | ||
1648 | def open_pif_ifcfg(pif): | |
1649 | pifrec = db.get_pif_record(pif) | |
1650 | ||
1651 | log("Configuring %s (%s)" % (interface_name(pif), pifrec['MAC'])) | |
1652 | ||
1653 | f = __open_ifcfg(interface_name(pif)) | |
1654 | ||
1655 | if pifrec.has_key('other_config'): | |
1656 | configure_ethtool(pifrec['other_config'], f) | |
1657 | configure_mtu(pifrec['other_config'], f) | |
1658 | ||
1659 | return f | |
1660 | ||
1661 | def configure_network(pif, f): | |
1662 | """Write the configuration file for a network. | |
1663 | ||
1664 | Writes configuration derived from the network object into the relevant | |
1665 | ifcfg file. The configuration file is passed in, but if the network is | |
1666 | bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>. | |
1667 | ||
1668 | This routine may also write ifcfg files of the networks corresponding to other PIFs | |
1669 | in order to maintain consistency. | |
1670 | ||
1671 | params: | |
1672 | pif: Opaque_ref of pif | |
1673 | f : ConfigurationFile(/path/to/ifcfg) to which we append network configuration | |
1674 | """ | |
1675 | ||
1676 | pifrec = db.get_pif_record(pif) | |
064af421 BP |
1677 | nw = pifrec['network'] |
1678 | nwrec = db.get_network_record(nw) | |
1679 | oc = None | |
1680 | bridge = bridge_name(pif) | |
1681 | interface = interface_name(pif) | |
1682 | if bridge: | |
1683 | device = bridge | |
1684 | else: | |
1685 | device = interface | |
1686 | ||
1687 | if nwrec.has_key('other_config'): | |
1688 | configure_ethtool(nwrec['other_config'], f) | |
1689 | configure_mtu(nwrec['other_config'], f) | |
1690 | configure_static_routes(device, nwrec['other_config'], f) | |
1691 | ||
1692 | ||
1693 | if pifrec.has_key('other_config'): | |
1694 | oc = pifrec['other_config'] | |
1695 | ||
1696 | if device == bridge: | |
1697 | f.write("TYPE=Bridge\n") | |
1698 | f.write("DELAY=0\n") | |
1699 | f.write("STP=off\n") | |
1700 | f.write("PIFDEV=%s\n" % interface_name(pif)) | |
1701 | ||
1702 | if pifrec['ip_configuration_mode'] == "DHCP": | |
1703 | f.write("BOOTPROTO=dhcp\n") | |
1704 | f.write("PERSISTENT_DHCLIENT=yes\n") | |
1705 | elif pifrec['ip_configuration_mode'] == "Static": | |
1706 | f.write("BOOTPROTO=none\n") | |
1707 | f.write("NETMASK=%(netmask)s\n" % pifrec) | |
1708 | f.write("IPADDR=%(IP)s\n" % pifrec) | |
1709 | f.write("GATEWAY=%(gateway)s\n" % pifrec) | |
1710 | elif pifrec['ip_configuration_mode'] == "None": | |
1711 | f.write("BOOTPROTO=none\n") | |
1712 | else: | |
1713 | raise Error("Unknown ip-configuration-mode %s" % pifrec['ip_configuration_mode']) | |
1714 | ||
1715 | if pifrec.has_key('DNS') and pifrec['DNS'] != "": | |
1716 | ServerList = pifrec['DNS'].split(",") | |
1717 | for i in range(len(ServerList)): f.write("DNS%d=%s\n" % (i+1, ServerList[i])) | |
1718 | if oc and oc.has_key('domain'): | |
1719 | f.write("DOMAIN='%s'\n" % oc['domain'].replace(',', ' ')) | |
1720 | ||
1721 | # We only allow one ifcfg-xenbr* to have PEERDNS=yes and there can be only one GATEWAYDEV in /etc/sysconfig/network. | |
1722 | # The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set. | |
1723 | # The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set. | |
1724 | ||
1725 | # Work out which pif on this host should be the one with PEERDNS=yes and which should be the GATEWAYDEV | |
1726 | # | |
1727 | # Note: we prune out the bond master pif (if it exists). | |
1728 | # This is because when we are called to bring up an interface with a bond master, it is implicit that | |
1729 | # we should bring down that master. | |
1730 | pifs_on_host = [ __pif for __pif in db.get_all_pifs() if | |
c798b21c | 1731 | not __pif in get_bond_masters_of_pif(pif) ] |
064af421 BP |
1732 | other_pifs_on_host = [ __pif for __pif in pifs_on_host if __pif != pif ] |
1733 | ||
1734 | peerdns_pif = None | |
1735 | defaultroute_pif = None | |
1736 | ||
1737 | # loop through all the pifs on this host looking for one with | |
1738 | # other-config:peerdns = true, and one with | |
1739 | # other-config:default-route=true | |
1740 | for __pif in pifs_on_host: | |
1741 | __pifrec = db.get_pif_record(__pif) | |
1742 | __oc = __pifrec['other_config'] | |
1743 | if __oc.has_key('peerdns') and __oc['peerdns'] == 'true': | |
1744 | if peerdns_pif == None: | |
1745 | peerdns_pif = __pif | |
1746 | else: | |
1747 | log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \ | |
1748 | (db.get_pif_record(peerdns_pif)['device'], __pifrec['device'])) | |
1749 | if __oc.has_key('defaultroute') and __oc['defaultroute'] == 'true': | |
1750 | if defaultroute_pif == None: | |
1751 | defaultroute_pif = __pif | |
1752 | else: | |
1753 | log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \ | |
1754 | (db.get_pif_record(defaultroute_pif)['device'], __pifrec['device'])) | |
1755 | ||
1756 | # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute | |
1757 | if peerdns_pif == None: | |
1758 | peerdns_pif = management_pif | |
1759 | if defaultroute_pif == None: | |
1760 | defaultroute_pif = management_pif | |
1761 | ||
1762 | # Update all the other network's ifcfg files and ensure consistency | |
1763 | for __pif in other_pifs_on_host: | |
1764 | __f = open_network_ifcfg(__pif) | |
1765 | peerdns_line_wanted = 'PEERDNS=%s\n' % ((__pif == peerdns_pif) and 'yes' or 'no') | |
1766 | lines = __f.readlines() | |
1767 | ||
1768 | if not peerdns_line_wanted in lines: | |
1769 | # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting | |
1770 | for line in lines: | |
1771 | if not line.lstrip().startswith('PEERDNS'): | |
1772 | __f.write(line) | |
1773 | log("Setting %s in %s" % (peerdns_line_wanted.strip(), __f.path())) | |
1774 | __f.write(peerdns_line_wanted) | |
1775 | __f.close() | |
1776 | f.attach_child(__f) | |
1777 | ||
1778 | else: | |
1779 | # There is no need to change this ifcfg file. So don't attach_child. | |
1780 | pass | |
1781 | ||
1782 | # ... and for this pif too | |
1783 | f.write('PEERDNS=%s\n' % ((pif == peerdns_pif) and 'yes' or 'no')) | |
1784 | ||
1785 | # Update gatewaydev | |
1786 | fnetwork = ConfigurationFile("network", "/etc/sysconfig") | |
1787 | for line in fnetwork.readlines(): | |
1788 | if line.lstrip().startswith('GATEWAY') : | |
1789 | continue | |
1790 | fnetwork.write(line) | |
1791 | if defaultroute_pif: | |
1792 | gatewaydev = bridge_name(defaultroute_pif) | |
1793 | if not gatewaydev: | |
1794 | gatewaydev = interface_name(defaultroute_pif) | |
1795 | fnetwork.write('GATEWAYDEV=%s\n' % gatewaydev) | |
1796 | fnetwork.close() | |
1797 | f.attach_child(fnetwork) | |
1798 | ||
1799 | return | |
1800 | ||
1801 | ||
1802 | def configure_physical_interface(pif): | |
1803 | """Write the configuration for a physical interface. | |
1804 | ||
1805 | Writes the configuration file for the physical interface described by | |
1806 | the pif object. | |
1807 | ||
1808 | Returns the open file handle for the interface configuration file. | |
1809 | """ | |
1810 | ||
1811 | pifrec = db.get_pif_record(pif) | |
1812 | ||
1813 | f = open_pif_ifcfg(pif) | |
1814 | ||
1815 | f.write("TYPE=Ethernet\n") | |
1816 | f.write("HWADDR=%(MAC)s\n" % pifrec) | |
1817 | ||
1818 | return f | |
1819 | ||
1820 | def configure_bond_interface(pif): | |
1821 | """Write the configuration for a bond interface. | |
1822 | ||
1823 | Writes the configuration file for the bond interface described by | |
1824 | the pif object. Handles writing the configuration for the slave | |
1825 | interfaces. | |
1826 | ||
1827 | Returns the open file handle for the bond interface configuration | |
1828 | file. | |
1829 | """ | |
1830 | ||
1831 | pifrec = db.get_pif_record(pif) | |
1832 | oc = pifrec['other_config'] | |
1833 | f = open_pif_ifcfg(pif) | |
1834 | ||
1835 | if pifrec['MAC'] != "": | |
1836 | f.write("MACADDR=%s\n" % pifrec['MAC']) | |
1837 | ||
1838 | for slave in get_bond_slaves_of_pif(pif): | |
1839 | s = configure_physical_interface(slave) | |
1840 | s.write("MASTER=%(device)s\n" % pifrec) | |
1841 | s.write("SLAVE=yes\n") | |
1842 | s.close() | |
1843 | f.attach_child(s) | |
1844 | ||
1845 | # The bond option defaults | |
1846 | bond_options = { | |
1847 | "mode": "balance-slb", | |
1848 | "miimon": "100", | |
1849 | "downdelay": "200", | |
1850 | "updelay": "31000", | |
1851 | "use_carrier": "1", | |
1852 | } | |
1853 | ||
1854 | # override defaults with values from other-config whose keys being with "bond-" | |
1855 | overrides = filter(lambda (key,val): key.startswith("bond-"), oc.items()) | |
1856 | overrides = map(lambda (key,val): (key[5:], val), overrides) | |
1857 | bond_options.update(overrides) | |
1858 | ||
1859 | # write the bond options to ifcfg-bondX | |
1860 | f.write('BONDING_OPTS="') | |
1861 | for (name,val) in bond_options.items(): | |
1862 | f.write("%s=%s " % (name,val)) | |
1863 | f.write('"\n') | |
1864 | return f | |
1865 | ||
1866 | def configure_vlan_interface(pif): | |
1867 | """Write the configuration for a VLAN interface. | |
1868 | ||
1869 | Writes the configuration file for the VLAN interface described by | |
1870 | the pif object. Handles writing the configuration for the master | |
1871 | interface if necessary. | |
1872 | ||
1873 | Returns the open file handle for the VLAN interface configuration | |
1874 | file. | |
1875 | """ | |
1876 | ||
1877 | slave = configure_pif(get_vlan_slave_of_pif(pif)) | |
1878 | slave.close() | |
1879 | ||
1880 | f = open_pif_ifcfg(pif) | |
1881 | f.write("VLAN=yes\n") | |
1882 | f.attach_child(slave) | |
1883 | ||
1884 | return f | |
1885 | ||
1886 | def configure_pif(pif): | |
1887 | """Write the configuration for a PIF object. | |
1888 | ||
1889 | Writes the configuration file the PIF and all dependent | |
1890 | interfaces (bond slaves and VLAN masters etc). | |
1891 | ||
1892 | Returns the open file handle for the interface configuration file. | |
1893 | """ | |
1894 | ||
1895 | pifrec = db.get_pif_record(pif) | |
1896 | ||
1897 | if pifrec['VLAN'] != '-1': | |
1898 | f = configure_vlan_interface(pif) | |
1899 | elif len(pifrec['bond_master_of']) != 0: | |
1900 | f = configure_bond_interface(pif) | |
1901 | else: | |
1902 | f = configure_physical_interface(pif) | |
1903 | ||
1904 | bridge = bridge_name(pif) | |
1905 | if bridge: | |
1906 | f.write("BRIDGE=%s\n" % bridge) | |
1907 | ||
1908 | return f | |
1909 | ||
1910 | def unconfigure_pif(pif): | |
1911 | """Clear up the files created by configure_pif""" | |
1912 | f = open_pif_ifcfg(pif) | |
1913 | log("Unlinking stale file %s" % f.path()) | |
1914 | f.unlink() | |
1915 | return f | |
1916 | \f | |
1917 | if __name__ == "__main__": | |
1918 | rc = 1 | |
1919 | try: | |
1920 | rc = main() | |
1921 | except: | |
1922 | ex = sys.exc_info() | |
1923 | err = traceback.format_exception(*ex) | |
1924 | for exline in err: | |
1925 | log(exline) | |
1926 | ||
1927 | if not debug_mode(): | |
1928 | syslog.closelog() | |
1929 | ||
1930 | sys.exit(rc) |