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