]>
Commit | Line | Data |
---|---|---|
d486dd0d JF |
1 | #!/usr/bin/python |
2 | # | |
3 | # Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved. | |
4 | # Authors: | |
5 | # Roopa Prabhu, roopa@cumulusnetworks.com | |
6 | # Julien Fortin, julien@cumulusnetworks.com | |
7 | # | |
8 | ||
9 | import os | |
10 | ||
11 | from sets import Set | |
12 | ||
13 | try: | |
14 | from ifupdown2.nlmanager.nlmanager import Link | |
15 | ||
16 | from ifupdown2.ifupdown.iface import * | |
17 | from ifupdown2.ifupdown.utils import utils | |
18 | from ifupdown2.ifupdown.netlink import netlink | |
19 | from ifupdown2.ifupdown.statemanager import statemanager_api as statemanager | |
20 | ||
21 | import ifupdown2.ifupdown.policymanager as policymanager | |
22 | import ifupdown2.ifupdown.ifupdownflags as ifupdownflags | |
23 | ||
24 | from ifupdown2.ifupdownaddons.LinkUtils import LinkUtils | |
25 | from ifupdown2.ifupdownaddons.modulebase import moduleBase | |
26 | except ImportError: | |
27 | from nlmanager.nlmanager import Link | |
28 | ||
29 | from ifupdown.iface import * | |
30 | from ifupdown.utils import utils | |
31 | from ifupdown.netlink import netlink | |
32 | from ifupdown.statemanager import statemanager_api as statemanager | |
33 | ||
34 | from ifupdownaddons.LinkUtils import LinkUtils | |
35 | from ifupdownaddons.modulebase import moduleBase | |
36 | ||
37 | import ifupdown.policymanager as policymanager | |
38 | import ifupdown.ifupdownflags as ifupdownflags | |
39 | ||
40 | class bond(moduleBase): | |
41 | """ ifupdown2 addon module to configure bond interfaces """ | |
42 | ||
43 | overrides_ifupdown_scripts = ['ifenslave', ] | |
44 | ||
45 | _modinfo = { 'mhelp' : 'bond configuration module', | |
46 | 'attrs' : { | |
47 | 'bond-use-carrier': | |
48 | {'help' : 'bond use carrier', | |
49 | 'validvals' : ['yes', 'no', '0', '1'], | |
50 | 'default' : 'yes', | |
51 | 'example': ['bond-use-carrier yes']}, | |
52 | 'bond-num-grat-arp': | |
53 | {'help' : 'bond use carrier', | |
54 | 'validrange' : ['0', '255'], | |
55 | 'default' : '1', | |
56 | 'example' : ['bond-num-grat-arp 1']}, | |
57 | 'bond-num-unsol-na' : | |
58 | {'help' : 'bond slave devices', | |
59 | 'validrange' : ['0', '255'], | |
60 | 'default' : '1', | |
61 | 'example' : ['bond-num-unsol-na 1']}, | |
62 | 'bond-xmit-hash-policy' : | |
63 | {'help' : 'bond slave devices', | |
64 | 'validvals' : ['0', 'layer2', | |
65 | '1', 'layer3+4', | |
66 | '2', 'layer2+3', | |
67 | '3', 'encap2+3', | |
68 | '4', 'encap3+4'], | |
69 | 'default' : 'layer2', | |
70 | 'example' : ['bond-xmit-hash-policy layer2']}, | |
71 | 'bond-miimon' : | |
72 | {'help' : 'bond miimon', | |
73 | 'validrange' : ['0', '255'], | |
74 | 'default' : '0', | |
75 | 'example' : ['bond-miimon 0']}, | |
76 | 'bond-mode' : | |
77 | {'help': 'bond mode', | |
78 | 'validvals': ['0', 'balance-rr', | |
79 | '1', 'active-backup', | |
80 | '2', 'balance-xor', | |
81 | '3', 'broadcast', | |
82 | '4', '802.3ad', | |
83 | '5', 'balance-tlb', | |
84 | '6', 'balance-alb'], | |
85 | 'default': 'balance-rr', | |
86 | 'example': ['bond-mode 802.3ad']}, | |
87 | 'bond-lacp-rate': | |
88 | {'help' : 'bond lacp rate', | |
89 | 'validvals' : ['0', 'slow', '1', 'fast'], | |
90 | 'default' : '0', | |
91 | 'example' : ['bond-lacp-rate 0']}, | |
92 | 'bond-min-links': | |
93 | {'help' : 'bond min links', | |
94 | 'default' : '0', | |
95 | 'validrange' : ['0', '255'], | |
96 | 'example' : ['bond-min-links 0']}, | |
97 | 'bond-ad-sys-priority': | |
98 | {'help' : '802.3ad system priority', | |
99 | 'default' : '65535', | |
100 | 'validrange' : ['0', '65535'], | |
101 | 'example' : ['bond-ad-sys-priority 65535'], | |
102 | 'deprecated' : True, | |
103 | 'new-attribute' : 'bond-ad-actor-sys-prio'}, | |
104 | 'bond-ad-actor-sys-prio': | |
105 | {'help' : '802.3ad system priority', | |
106 | 'default' : '65535', | |
107 | 'validrange' : ['0', '65535'], | |
108 | 'example' : ['bond-ad-actor-sys-prio 65535']}, | |
109 | 'bond-ad-sys-mac-addr': | |
110 | {'help' : '802.3ad system mac address', | |
111 | 'validvals': ['<mac>', ], | |
112 | 'example' : ['bond-ad-sys-mac-addr 00:00:00:00:00:00'], | |
113 | 'deprecated' : True, | |
114 | 'new-attribute' : 'bond-ad-actor-system'}, | |
115 | 'bond-ad-actor-system': | |
116 | {'help' : '802.3ad system mac address', | |
117 | 'validvals': ['<mac>', ], | |
118 | 'example' : ['bond-ad-actor-system 00:00:00:00:00:00'],}, | |
119 | 'bond-lacp-bypass-allow': | |
120 | {'help' : 'allow lacp bypass', | |
121 | 'validvals' : ['yes', 'no', '0', '1'], | |
122 | 'default' : 'no', | |
123 | 'example' : ['bond-lacp-bypass-allow no']}, | |
124 | 'bond-slaves' : | |
125 | {'help' : 'bond slaves', | |
126 | 'required' : True, | |
127 | 'multivalue' : True, | |
128 | 'validvals': ['<interface-list>'], | |
129 | 'example' : ['bond-slaves swp1 swp2', | |
130 | 'bond-slaves glob swp1-2', | |
131 | 'bond-slaves regex (swp[1|2)'], | |
132 | 'aliases': ['bond-ports']}, | |
133 | 'bond-updelay' : | |
134 | {'help' : 'bond updelay', | |
135 | 'default' : '0', | |
136 | 'validrange' : ['0', '65535'], | |
137 | 'example' : ['bond-updelay 100']}, | |
138 | 'bond-downdelay': | |
139 | {'help' : 'bond downdelay', | |
140 | 'default' : '0', | |
141 | 'validrange' : ['0', '65535'], | |
142 | 'example' : ['bond-downdelay 100']} | |
143 | }} | |
144 | ||
145 | _bond_attr_netlink_map = { | |
146 | 'bond-mode': Link.IFLA_BOND_MODE, | |
147 | 'bond-miimon': Link.IFLA_BOND_MIIMON, | |
148 | 'bond-use-carrier': Link.IFLA_BOND_USE_CARRIER, | |
149 | 'bond-lacp-rate': Link.IFLA_BOND_AD_LACP_RATE, | |
150 | 'bond-xmit-hash-policy': Link.IFLA_BOND_XMIT_HASH_POLICY, | |
151 | 'bond-min-links': Link.IFLA_BOND_MIN_LINKS, | |
152 | 'bond-num-grat-arp': Link.IFLA_BOND_NUM_PEER_NOTIF, | |
153 | 'bond-num-unsol-na': Link.IFLA_BOND_NUM_PEER_NOTIF, | |
154 | 'bond-ad-sys-mac-addr': Link.IFLA_BOND_AD_ACTOR_SYSTEM, | |
155 | 'bond-ad-actor-system': Link.IFLA_BOND_AD_ACTOR_SYSTEM, | |
156 | 'bond-ad-sys-priority': Link.IFLA_BOND_AD_ACTOR_SYS_PRIO, | |
157 | 'bond-ad-actor-sys-prio': Link.IFLA_BOND_AD_ACTOR_SYS_PRIO, | |
158 | 'bond-lacp-bypass-allow': Link.IFLA_BOND_AD_LACP_BYPASS, | |
159 | 'bond-updelay': Link.IFLA_BOND_UPDELAY, | |
160 | 'bond-downdelay': Link.IFLA_BOND_DOWNDELAY | |
161 | } | |
162 | ||
163 | # ifquery-check attr dictionary with callable object to translate user data to netlink format | |
164 | _bond_attr_ifquery_check_translate_func = { | |
165 | Link.IFLA_BOND_MODE: lambda x: Link.ifla_bond_mode_tbl[x], | |
166 | Link.IFLA_BOND_MIIMON: int, | |
167 | Link.IFLA_BOND_USE_CARRIER: utils.get_boolean_from_string, | |
168 | Link.IFLA_BOND_AD_LACP_RATE: lambda x: int(utils.get_boolean_from_string(x)), | |
169 | Link.IFLA_BOND_XMIT_HASH_POLICY: lambda x: Link.ifla_bond_xmit_hash_policy_tbl[x], | |
170 | Link.IFLA_BOND_MIN_LINKS: int, | |
171 | Link.IFLA_BOND_NUM_PEER_NOTIF: int, | |
172 | Link.IFLA_BOND_AD_ACTOR_SYSTEM: str, | |
173 | Link.IFLA_BOND_AD_ACTOR_SYS_PRIO: int, | |
174 | Link.IFLA_BOND_AD_LACP_BYPASS: lambda x: int(utils.get_boolean_from_string(x)), | |
175 | Link.IFLA_BOND_UPDELAY: int, | |
176 | Link.IFLA_BOND_DOWNDELAY: int | |
177 | } | |
178 | ||
179 | # ifup attr list with callable object to translate user data to netlink format | |
180 | # in the future this can be moved to a dictionary, whenever we detect that some | |
181 | # netlink capabilities are missing we can dynamically remove them from the dict. | |
182 | _bond_attr_set_list = ( | |
183 | ('bond-mode', Link.IFLA_BOND_MODE, lambda x: Link.ifla_bond_mode_tbl[x]), | |
184 | ('bond-xmit-hash-policy', Link.IFLA_BOND_XMIT_HASH_POLICY, lambda x: Link.ifla_bond_xmit_hash_policy_tbl[x]), | |
185 | ('bond-miimon', Link.IFLA_BOND_MIIMON, int), | |
186 | ('bond-min-links', Link.IFLA_BOND_MIN_LINKS, int), | |
187 | ('bond-num-grat-arp', Link.IFLA_BOND_NUM_PEER_NOTIF, int), | |
188 | ('bond-num-unsol-na', Link.IFLA_BOND_NUM_PEER_NOTIF, int), | |
189 | ('bond-ad-sys-priority', Link.IFLA_BOND_AD_ACTOR_SYS_PRIO, int), | |
190 | ('bond-ad-actor-sys-prio', Link.IFLA_BOND_AD_ACTOR_SYS_PRIO, int), | |
191 | ('bond-updelay', Link.IFLA_BOND_UPDELAY, int), | |
192 | ('bond-downdelay', Link.IFLA_BOND_DOWNDELAY, int), | |
193 | ('bond-use-carrier', Link.IFLA_BOND_USE_CARRIER, lambda x: int(utils.get_boolean_from_string(x))), | |
194 | ('bond-lacp-rate', Link.IFLA_BOND_AD_LACP_RATE, lambda x: int(utils.get_boolean_from_string(x))), | |
195 | ('bond-lacp-bypass-allow', Link.IFLA_BOND_AD_LACP_BYPASS, lambda x: int(utils.get_boolean_from_string(x))), | |
196 | ('bond-ad-sys-mac-addr', Link.IFLA_BOND_AD_ACTOR_SYSTEM, str), | |
197 | ('bond-ad-actor-system', Link.IFLA_BOND_AD_ACTOR_SYSTEM, str), | |
198 | ) | |
199 | ||
200 | def __init__(self, *args, **kargs): | |
201 | moduleBase.__init__(self, *args, **kargs) | |
202 | self.ipcmd = None | |
203 | self.bondcmd = None | |
204 | ||
205 | if not os.path.exists('/sys/class/net/bonding_masters'): | |
206 | utils.exec_command('modprobe -q bonding') | |
207 | ||
208 | @staticmethod | |
209 | def get_bond_slaves(ifaceobj): | |
210 | slaves = ifaceobj.get_attr_value_first('bond-slaves') | |
211 | if not slaves: | |
212 | slaves = ifaceobj.get_attr_value_first('bond-ports') | |
213 | return slaves | |
214 | ||
215 | def _is_bond(self, ifaceobj): | |
216 | # at first link_kind is not set but once ifupdownmain | |
217 | # calls get_dependent_ifacenames link_kind is set to BOND | |
218 | if ifaceobj.link_kind & ifaceLinkKind.BOND or self.get_bond_slaves(ifaceobj): | |
219 | return True | |
220 | return False | |
221 | ||
222 | def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None): | |
223 | """ Returns list of interfaces dependent on ifaceobj """ | |
224 | ||
225 | if not self._is_bond(ifaceobj): | |
226 | return None | |
227 | slave_list = self.parse_port_list(ifaceobj.name, | |
228 | self.get_bond_slaves(ifaceobj), | |
229 | ifacenames_all) | |
230 | ifaceobj.dependency_type = ifaceDependencyType.MASTER_SLAVE | |
231 | # Also save a copy for future use | |
232 | ifaceobj.priv_data = list(slave_list) | |
233 | if ifaceobj.link_type != ifaceLinkType.LINK_NA: | |
234 | ifaceobj.link_type = ifaceLinkType.LINK_MASTER | |
235 | ifaceobj.link_kind |= ifaceLinkKind.BOND | |
236 | ifaceobj.role |= ifaceRole.MASTER | |
237 | ||
238 | return slave_list | |
239 | ||
240 | def syntax_check(self, ifaceobj, ifaceobj_getfunc): | |
241 | return self.syntax_check_updown_delay(ifaceobj) | |
242 | ||
243 | def get_dependent_ifacenames_running(self, ifaceobj): | |
244 | self._init_command_handlers() | |
245 | return self.bondcmd.bond_get_slaves(ifaceobj.name) | |
246 | ||
247 | def _get_slave_list(self, ifaceobj): | |
248 | """ Returns slave list present in ifaceobj config """ | |
249 | ||
250 | # If priv data already has slave list use that first. | |
251 | if ifaceobj.priv_data: | |
252 | return ifaceobj.priv_data | |
253 | slaves = self.get_bond_slaves(ifaceobj) | |
254 | if slaves: | |
255 | return self.parse_port_list(ifaceobj.name, slaves) | |
256 | else: | |
257 | return None | |
258 | ||
259 | def _is_clag_bond(self, ifaceobj): | |
260 | if self.get_bond_slaves(ifaceobj): | |
261 | attrval = ifaceobj.get_attr_value_first('clag-id') | |
262 | if attrval and attrval != '0': | |
263 | return True | |
264 | return False | |
265 | ||
266 | def _add_slaves(self, ifaceobj, ifaceobj_getfunc=None): | |
267 | runningslaves = [] | |
268 | ||
269 | slaves = self._get_slave_list(ifaceobj) | |
270 | if not slaves: | |
271 | self.logger.debug('%s: no slaves found' %ifaceobj.name) | |
272 | return | |
273 | ||
274 | if not ifupdownflags.flags.PERFMODE: | |
275 | runningslaves = self.bondcmd.bond_get_slaves(ifaceobj.name) | |
276 | ||
277 | clag_bond = self._is_clag_bond(ifaceobj) | |
278 | ||
279 | for slave in Set(slaves).difference(Set(runningslaves)): | |
280 | if (not ifupdownflags.flags.PERFMODE and | |
281 | not self.ipcmd.link_exists(slave)): | |
282 | self.log_error('%s: skipping slave %s, does not exist' | |
283 | %(ifaceobj.name, slave), ifaceobj, | |
284 | raise_error=False) | |
285 | continue | |
286 | link_up = False | |
287 | if self.ipcmd.is_link_up(slave): | |
288 | netlink.link_set_updown(slave, "down") | |
289 | link_up = True | |
290 | # If clag bond place the slave in a protodown state; clagd | |
291 | # will protoup it when it is ready | |
292 | if clag_bond: | |
293 | try: | |
294 | netlink.link_set_protodown(slave, "on") | |
295 | except Exception, e: | |
296 | self.logger.error('%s: %s' % (ifaceobj.name, str(e))) | |
297 | netlink.link_set_master(slave, ifaceobj.name) | |
298 | if link_up or ifaceobj.link_type != ifaceLinkType.LINK_NA: | |
299 | try: | |
300 | if (ifaceobj_getfunc(slave)[0].link_privflags & | |
301 | ifaceLinkPrivFlags.KEEP_LINK_DOWN): | |
302 | netlink.link_set_updown(slave, "down") | |
303 | else: | |
304 | netlink.link_set_updown(slave, "up") | |
305 | except Exception, e: | |
306 | self.logger.debug('%s: %s' % (ifaceobj.name, str(e))) | |
307 | pass | |
308 | ||
309 | if runningslaves: | |
310 | for s in runningslaves: | |
311 | if s not in slaves: | |
312 | self.bondcmd.bond_remove_slave(ifaceobj.name, s) | |
313 | if clag_bond: | |
314 | try: | |
315 | netlink.link_set_protodown(s, "off") | |
316 | except Exception, e: | |
317 | self.logger.error('%s: %s' % (ifaceobj.name, str(e))) | |
318 | else: | |
319 | # apply link-down config changes on running slaves | |
320 | try: | |
321 | link_up = self.ipcmd.is_link_up(s) | |
322 | config_link_down = (ifaceobj_getfunc(s)[0].link_privflags & | |
323 | ifaceLinkPrivFlags.KEEP_LINK_DOWN) | |
324 | if (config_link_down and link_up): | |
325 | netlink.link_set_updown(s, "down") | |
326 | elif (not config_link_down and not link_up): | |
327 | netlink.link_set_updown(s, "up") | |
328 | except Exception, e: | |
329 | self.logger.warn('%s: %s' % (ifaceobj.name, str(e))) | |
330 | ||
331 | def _check_updown_delay_log(self, ifaceobj, attr_name, value): | |
332 | ifaceobj.status = ifaceStatus.ERROR | |
333 | self.logger.error('%s: unable to set %s %s as MII link monitoring is ' | |
334 | 'disabled' % (ifaceobj.name, attr_name, value)) | |
335 | # return False to notify syntax_check that an error has been logged | |
336 | return False | |
337 | ||
338 | def syntax_check_updown_delay(self, ifaceobj): | |
339 | result = True | |
340 | updelay = ifaceobj.get_attr_value_first('bond-updelay') | |
341 | downdelay = ifaceobj.get_attr_value_first('bond-downdelay') | |
342 | ||
343 | if not updelay and not downdelay: | |
344 | return True | |
345 | ||
346 | try: | |
347 | miimon = int(ifaceobj.get_attr_value_first('bond-miimon')) | |
348 | except: | |
349 | try: | |
350 | miimon = int(policymanager.policymanager_api.get_iface_default( | |
351 | module_name=self.__class__.__name__, | |
352 | ifname=ifaceobj.name, | |
353 | attr='bond-miimon')) | |
354 | except: | |
355 | miimon = 0 | |
356 | ||
357 | if not miimon: | |
358 | # self._check_updown_delay_log returns False no matter what | |
359 | if updelay and int(updelay): | |
360 | result = self._check_updown_delay_log(ifaceobj, 'bond-updelay', updelay) | |
361 | if downdelay and int(downdelay): | |
362 | result = self._check_updown_delay_log(ifaceobj, 'bond-downdelay', downdelay) | |
363 | ||
364 | return result | |
365 | ||
366 | _bond_updown_delay_nl_list = ( | |
367 | (Link.IFLA_BOND_UPDELAY, 'bond-updelay'), | |
368 | (Link.IFLA_BOND_DOWNDELAY, 'bond-downdelay') | |
369 | ) | |
370 | ||
371 | def check_updown_delay_nl(self, link_exists, ifaceobj, ifla_info_data): | |
372 | """ | |
373 | IFLA_BOND_MIIMON | |
374 | Specifies the time, in milliseconds, to wait before enabling a slave | |
375 | after a link recovery has been detected. This option is only valid | |
376 | for the miimon link monitor. The updelay value should be a multiple | |
377 | of the miimon value; if not, it will be rounded down to the nearest | |
378 | multiple. The default value is 0. | |
379 | ||
380 | This ifla_bond_miimon code should be move to get_ifla_bond_attr_from_user_config | |
381 | but we need to know if the operation was successful to update the cache accordingly | |
382 | """ | |
383 | ifla_bond_miimon = ifla_info_data.get(Link.IFLA_BOND_MIIMON) | |
384 | if link_exists and ifla_bond_miimon is None: | |
385 | ifla_bond_miimon = self.bondcmd.link_cache_get([ifaceobj.name, 'linkinfo', Link.IFLA_BOND_MIIMON]) | |
386 | ||
387 | if ifla_bond_miimon == 0: | |
388 | for nl_attr, attr_name in self._bond_updown_delay_nl_list: | |
389 | delay = ifla_info_data.get(nl_attr) | |
390 | # if up-down-delay exists we need to remove it, if non zero log error | |
391 | if delay is not None: | |
392 | if delay > 0: | |
393 | self._check_updown_delay_log(ifaceobj, attr_name, delay) | |
394 | del ifla_info_data[nl_attr] | |
395 | return True | |
396 | return False | |
397 | ||
398 | _bond_lacp_attrs = ( | |
399 | (Link.IFLA_BOND_AD_LACP_RATE, 'bond-lacp-rate'), | |
400 | (Link.IFLA_BOND_AD_LACP_BYPASS, 'bond-lacp-bypass') | |
401 | ) | |
402 | ||
403 | def _check_bond_mode_user_config(self, ifname, link_exists, ifla_info_data): | |
404 | ifla_bond_mode = ifla_info_data.get(Link.IFLA_BOND_MODE) | |
405 | if ifla_bond_mode is None and link_exists: | |
406 | ifla_bond_mode = self.bondcmd.link_cache_get([ifname, 'linkinfo', Link.IFLA_BOND_MODE]) | |
407 | # in this case the link already exists (we have a cached value): | |
408 | # if IFLA_BOND_MODE is not present in ifla_info_data it means: | |
409 | # - that bond-mode was present in the user config and didn't change | |
410 | # - never was in the user config so bond mode should be the system default value | |
411 | # - was removed from the stanza so we might have to reset it to default value | |
412 | # nevertheless we need to add it back to the ifla_info_data dict to check | |
413 | # if we need to reset the mode to system default | |
414 | ifla_info_data[Link.IFLA_BOND_MODE] = ifla_bond_mode | |
415 | ||
416 | if ifla_bond_mode == 4: # 802.3ad | |
417 | min_links = ifla_info_data.get(Link.IFLA_BOND_MIN_LINKS) | |
418 | if min_links is None: | |
419 | min_links = self.bondcmd.link_cache_get([ifname, 'linkinfo', Link.IFLA_BOND_MIN_LINKS]) | |
420 | # get_min_links_nl may return None so we need to strictly check 0 | |
421 | if min_links == 0: | |
422 | self.logger.warn('%s: attribute bond-min-links is set to \'0\'' % ifname) | |
423 | else: | |
424 | # IFLA_BOND_AD_LACP_RATE and IFLA_BOND_AD_LACP_BYPASS only for 802.3ad mode (4) | |
425 | for nl_attr, attr_name in self._bond_lacp_attrs: | |
426 | if nl_attr in ifla_info_data: | |
427 | self.logger.info('%s: ignoring %s: only available for 802.3ad mode (4)' % (ifname, attr_name)) | |
428 | del ifla_info_data[nl_attr] | |
429 | ||
430 | @staticmethod | |
431 | def get_saved_ifaceobj(link_exists, ifname): | |
432 | if link_exists: | |
433 | old_config = statemanager.get_ifaceobjs(ifname) | |
434 | if old_config: | |
435 | return old_config[0] | |
436 | return None | |
437 | ||
438 | def get_ifla_bond_attr_from_user_config(self, ifaceobj, link_exists): | |
439 | """ | |
440 | Potential issue: if a user load the bond driver with custom | |
441 | default values (say bond-mode 3), ifupdown2 has no knowledge | |
442 | of these default values. | |
443 | At bond creation everything should work, bonds will be created | |
444 | with mode 3 (even if not specified under the stanza). | |
445 | But, for example: if the user specifies a value under bond-mode | |
446 | and later on the user removes the bond-mode line from the stanza | |
447 | we will detect it and reset to MODINFO: BOND-MODE: DEFAULT aka 0 | |
448 | which is not the real default value that the user may expect. | |
449 | """ | |
450 | ifname = ifaceobj.name | |
451 | ifla_info_data = OrderedDict() | |
452 | old_config = self.get_saved_ifaceobj(link_exists, ifname) | |
453 | ||
454 | # for each bond attribute we fetch the user configuration | |
455 | # if no configuration is provided we look for a config in policy files | |
456 | for attr_name, netlink_attr, func_ptr in self._bond_attr_set_list: | |
457 | cached_value = None | |
458 | user_config = ifaceobj.get_attr_value_first(attr_name) | |
459 | ||
460 | if not user_config: | |
461 | user_config = policymanager.policymanager_api.get_iface_default( | |
462 | module_name=self.__class__.__name__, | |
463 | ifname=ifname, | |
464 | attr=attr_name) | |
465 | if user_config: | |
466 | self.logger.debug('%s: %s %s: extracted from policy files' | |
467 | % (ifname, attr_name, user_config)) | |
468 | ||
469 | # no policy override, do we need to reset an attr to default value? | |
470 | if not user_config and old_config and old_config.get_attr_value_first(attr_name): | |
471 | # if the link already exists but the value is set | |
472 | # (potentially removed from the stanza, we need to reset it to default) | |
473 | # might not work for specific cases, see explanation at the top of this function :) | |
474 | user_config = self.get_attr_default_value(attr_name) | |
475 | if user_config: | |
476 | self.logger.debug('%s: %s: removed from stanza, resetting to default value: %s' | |
477 | % (ifname, attr_name, user_config)) | |
478 | ||
479 | if user_config: | |
480 | try: | |
481 | nl_value = func_ptr(user_config.lower()) | |
482 | ||
483 | if link_exists: | |
484 | cached_value = self.bondcmd.link_cache_get([ifname, 'linkinfo', netlink_attr]) | |
485 | ||
486 | if link_exists and cached_value is None: | |
487 | # the link already exists but we don't have any value | |
488 | # cached for this attr, it probably means that the | |
489 | # capability is not available on this system (i.e old kernel) | |
490 | self.logger.debug('%s: ignoring %s %s: capability ' | |
491 | 'probably not supported on this system' | |
492 | % (ifname, attr_name, user_config)) | |
493 | continue | |
494 | elif link_exists: | |
495 | # there should be a cached value if the link already exists | |
496 | if cached_value == nl_value: | |
497 | # if the user value is already cached: continue | |
498 | continue | |
499 | ||
500 | # else: the link doesn't exist so we create the bond with | |
501 | # all the user/policy defined values without extra checks | |
502 | ifla_info_data[netlink_attr] = nl_value | |
503 | ||
504 | if cached_value is not None: | |
505 | self.logger.info('%s: set %s %s (cache %s)' % (ifname, attr_name, user_config, cached_value)) | |
506 | else: | |
507 | self.logger.info('%s: set %s %s' % (ifname, attr_name, user_config)) | |
508 | ||
509 | except KeyError: | |
510 | self.logger.warning('%s: invalid %s value %s' % (ifname, attr_name, user_config)) | |
511 | ||
512 | self._check_bond_mode_user_config(ifname, link_exists, ifla_info_data) | |
513 | return ifla_info_data | |
514 | ||
515 | _bond_down_nl_attributes_list = ( | |
516 | Link.IFLA_BOND_MODE, | |
517 | Link.IFLA_BOND_XMIT_HASH_POLICY, | |
518 | Link.IFLA_BOND_AD_LACP_RATE, | |
519 | Link.IFLA_BOND_MIN_LINKS | |
520 | ) | |
521 | ||
522 | def _should_down_bond(self, ifla_info_data): | |
523 | for nl_attr in self._bond_down_nl_attributes_list: | |
524 | if nl_attr in ifla_info_data: | |
525 | return True | |
526 | return False | |
527 | ||
528 | def should_update_bond_mode(self, ifaceobj, ifname, is_link_up, ifla_info_data): | |
529 | # if bond-mode was changed the bond needs to be brought | |
530 | # down and slaves un-slaved before bond mode is changed. | |
531 | cached_bond_mode = self.bondcmd.link_cache_get([ifname, 'linkinfo', Link.IFLA_BOND_MODE]) | |
532 | ifla_bond_mode = ifla_info_data.get(Link.IFLA_BOND_MODE) | |
533 | ||
534 | # bond-mode was changed or is not specified | |
535 | if ifla_bond_mode is not None: | |
536 | if ifla_bond_mode != cached_bond_mode: | |
537 | self.logger.info('%s: bond mode changed to %s: running ops on bond and slaves' | |
538 | % (ifname, ifla_bond_mode)) | |
539 | if is_link_up: | |
540 | netlink.link_set_updown(ifname, 'down') | |
541 | is_link_up = False | |
542 | ||
543 | for lower_dev in ifaceobj.lowerifaces: | |
544 | netlink.link_set_nomaster(lower_dev) | |
545 | ||
546 | self.bondcmd.cache_delete([ifname, 'linkinfo', 'slaves']) | |
547 | else: | |
548 | # bond-mode user config value is the current running(cached) value | |
549 | # no need to reset it again we can ignore this attribute | |
550 | del ifla_info_data[Link.IFLA_BOND_MODE] | |
551 | ||
552 | return is_link_up | |
553 | ||
554 | def create_or_set_bond_config(self, ifaceobj): | |
555 | ifname = ifaceobj.name | |
556 | link_exists = self.ipcmd.link_exists(ifname) | |
557 | is_link_up = self.ipcmd.is_link_up(ifname) if link_exists else False | |
558 | ifla_info_data = self.get_ifla_bond_attr_from_user_config(ifaceobj, link_exists) | |
559 | ||
560 | remove_delay_from_cache = self.check_updown_delay_nl(link_exists, ifaceobj, ifla_info_data) | |
561 | ||
562 | # if link exists: down link if specific attributes are specified | |
563 | if link_exists: | |
564 | # did bond-mode changed? | |
565 | is_link_up = self.should_update_bond_mode(ifaceobj, ifname, is_link_up, ifla_info_data) | |
566 | ||
567 | # if specific attributes need to be set we need to down the bond first | |
568 | if ifla_info_data and is_link_up: | |
569 | if self._should_down_bond(ifla_info_data): | |
570 | netlink.link_set_updown(ifname, 'down') | |
571 | is_link_up = False | |
572 | ||
573 | if link_exists and not ifla_info_data: | |
574 | # if the bond already exists and no attrs need to be set | |
575 | # ignore the netlink call | |
576 | self.logger.info('%s: already exists, no change detected' % ifname) | |
577 | else: | |
578 | try: | |
579 | netlink.link_add_set(kind='bond', ifname=ifname, ifla_info_data=ifla_info_data) | |
580 | except Exception as e: | |
581 | # defensive code | |
582 | # if anything happens, we try to set up the bond with the sysfs api | |
583 | self.logger.debug('%s: bond setup: %s' % (ifname, str(e))) | |
584 | self.create_or_set_bond_config_sysfs(ifaceobj, ifla_info_data) | |
585 | ||
586 | if remove_delay_from_cache: | |
587 | # making sure up/down delay attributes are set to 0 before caching | |
588 | # this can be removed when moving to a nllistener/live cache | |
589 | ifla_info_data[Link.IFLA_BOND_UPDELAY] = 0 | |
590 | ifla_info_data[Link.IFLA_BOND_DOWNDELAY] = 0 | |
591 | ||
592 | # if link_add doesn't raise we can update the cache, the future | |
593 | # netlink listener will update the cache based on the kernel response | |
594 | for key, value in ifla_info_data.items(): | |
595 | self.bondcmd.cache_update([ifname, 'linkinfo', key], value) | |
596 | ||
597 | if link_exists and ifla_info_data and not is_link_up: | |
598 | netlink.link_set_updown(ifname, 'up') | |
599 | ||
600 | def create_or_set_bond_config_sysfs(self, ifaceobj, ifla_info_data): | |
601 | if not self.ipcmd.link_exists(ifaceobj.name): | |
602 | self.bondcmd.create_bond(ifaceobj.name) | |
603 | self.bondcmd.bond_set_attrs_nl(ifaceobj.name, ifla_info_data) | |
604 | ||
605 | def _up(self, ifaceobj, ifaceobj_getfunc=None): | |
606 | try: | |
607 | self.create_or_set_bond_config(ifaceobj) | |
608 | self._add_slaves(ifaceobj, ifaceobj_getfunc) | |
609 | except Exception, e: | |
610 | self.log_error(str(e), ifaceobj) | |
611 | ||
612 | def _down(self, ifaceobj, ifaceobj_getfunc=None): | |
613 | try: | |
614 | netlink.link_del(ifaceobj.name) | |
615 | self.bondcmd.cache_delete([ifaceobj.name]) | |
616 | except Exception as e: | |
617 | self.log_warn('%s: %s' % (ifaceobj.name, str(e))) | |
618 | ||
619 | def _query_check_bond_slaves(self, ifaceobjcurr, attr, user_bond_slaves, running_bond_slaves): | |
620 | query = 1 | |
621 | ||
622 | if user_bond_slaves and running_bond_slaves: | |
623 | if not set(user_bond_slaves).symmetric_difference(running_bond_slaves): | |
624 | query = 0 | |
625 | ||
626 | # we want to display the same bond-slaves list as provided | |
627 | # in the interfaces file but if this list contains regexes or | |
628 | # globs, for now, we won't try to change it. | |
629 | if 'regex' in user_bond_slaves or 'glob' in user_bond_slaves: | |
630 | user_bond_slaves = running_bond_slaves | |
631 | else: | |
632 | ordered = [] | |
633 | for slave in user_bond_slaves: | |
634 | if slave in running_bond_slaves: | |
635 | ordered.append(slave) | |
636 | user_bond_slaves = ordered | |
637 | ifaceobjcurr.update_config_with_status(attr, ' '.join(user_bond_slaves) if user_bond_slaves else 'None', query) | |
638 | ||
639 | def _query_check(self, ifaceobj, ifaceobjcurr, ifaceobj_getfunc=None): | |
640 | if not self.bondcmd.bond_exists(ifaceobj.name): | |
641 | self.logger.debug('bond iface %s does not exist' % ifaceobj.name) | |
642 | return | |
643 | ||
644 | iface_attrs = self.dict_key_subset(ifaceobj.config, self.get_mod_attrs()) | |
645 | if not iface_attrs: | |
646 | return | |
647 | ||
648 | # remove bond-slaves and bond-ports from the list, | |
649 | # because there aren't any ifla_info_data netlink attr for slaves | |
650 | # an exception is raised when index is not found, so query_slaves will stay False | |
651 | query_slaves = False | |
652 | ||
653 | user_bond_slaves = None | |
654 | running_bond_slaves = None | |
655 | try: | |
656 | del iface_attrs[iface_attrs.index('bond-slaves')] | |
657 | ||
658 | # if user specified bond-slaves we need to display it | |
659 | query_slaves = True | |
660 | if not user_bond_slaves: | |
661 | user_bond_slaves = self._get_slave_list(ifaceobj) | |
662 | running_bond_slaves = self.bondcmd.bond_get_slaves(ifaceobj.name) | |
663 | ||
664 | self._query_check_bond_slaves(ifaceobjcurr, 'bond-slaves', user_bond_slaves, running_bond_slaves) | |
665 | except: | |
666 | pass | |
667 | try: | |
668 | del iface_attrs[iface_attrs.index('bond-ports')] | |
669 | ||
670 | # if user specified bond-ports we need to display it | |
671 | if not query_slaves and not user_bond_slaves: # if get_slave_list was already called for slaves | |
672 | user_bond_slaves = self._get_slave_list(ifaceobj) | |
673 | running_bond_slaves = self.bondcmd.bond_get_slaves(ifaceobj.name) | |
674 | ||
675 | self._query_check_bond_slaves(ifaceobjcurr, 'bond-ports', user_bond_slaves, running_bond_slaves) | |
676 | except: | |
677 | pass | |
678 | ||
679 | for attr in iface_attrs: | |
680 | nl_attr = self._bond_attr_netlink_map[attr] | |
681 | translate_func = self._bond_attr_ifquery_check_translate_func[nl_attr] | |
682 | current_config = self.bondcmd.link_cache_get([ifaceobj.name, 'linkinfo', nl_attr]) | |
683 | user_config = ifaceobj.get_attr_value_first(attr) | |
684 | ||
685 | if current_config == translate_func(user_config): | |
686 | ifaceobjcurr.update_config_with_status(attr, user_config, 0) | |
687 | else: | |
688 | ifaceobjcurr.update_config_with_status(attr, str(current_config), 1) | |
689 | ||
690 | @staticmethod | |
691 | def translate_nl_value_yesno(value): | |
692 | return 'yes' if value else 'no' | |
693 | ||
694 | @staticmethod | |
695 | def translate_nl_value_slowfast(value): | |
696 | return 'fast' if value else 'slow' | |
697 | ||
698 | def _query_running_attrs(self, bondname): | |
699 | bond_attrs = { | |
700 | 'bond-mode': Link.ifla_bond_mode_pretty_tbl.get(self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_MODE])), | |
701 | 'bond-miimon': self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_MIIMON]), | |
702 | 'bond-use-carrier': self.translate_nl_value_yesno(self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_USE_CARRIER])), | |
703 | 'bond-lacp-rate': self.translate_nl_value_slowfast(self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_AD_LACP_RATE])), | |
704 | 'bond-min-links': self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_MIN_LINKS]), | |
705 | 'bond-ad-actor-system': self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_AD_ACTOR_SYSTEM]), | |
706 | 'bond-ad-actor-sys-prio': self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_AD_ACTOR_SYS_PRIO]), | |
707 | 'bond-xmit-hash-policy': Link.ifla_bond_xmit_hash_policy_pretty_tbl.get(self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_XMIT_HASH_POLICY])), | |
708 | 'bond-lacp-bypass-allow': self.translate_nl_value_yesno(self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_AD_LACP_BYPASS])), | |
709 | 'bond-num-unsol-na': self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_NUM_PEER_NOTIF]), | |
710 | 'bond-num-grat-arp': self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_NUM_PEER_NOTIF]), | |
711 | 'bond-updelay': self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_UPDELAY]), | |
712 | 'bond-downdelay': self.bondcmd.link_cache_get([bondname, 'linkinfo', Link.IFLA_BOND_DOWNDELAY]) | |
713 | } | |
714 | slaves = self.bondcmd.bond_get_slaves(bondname) | |
715 | if slaves: | |
716 | bond_attrs['bond-slaves'] = slaves | |
717 | return bond_attrs | |
718 | ||
719 | def _query_running(self, ifaceobjrunning, ifaceobj_getfunc=None): | |
720 | if not self.bondcmd.bond_exists(ifaceobjrunning.name): | |
721 | return | |
722 | bond_attrs = self._query_running_attrs(ifaceobjrunning.name) | |
723 | if bond_attrs.get('bond-slaves'): | |
724 | bond_attrs['bond-slaves'] = ' '.join(bond_attrs.get('bond-slaves')) | |
725 | ||
726 | [ifaceobjrunning.update_config(k, str(v)) | |
727 | for k, v in bond_attrs.items() | |
728 | if v is not None] | |
729 | ||
730 | _run_ops = { | |
731 | 'pre-up': _up, | |
732 | 'post-down': _down, | |
733 | 'query-running': _query_running, | |
734 | 'query-checkcurr': _query_check | |
735 | } | |
736 | ||
737 | def get_ops(self): | |
738 | """ returns list of ops supported by this module """ | |
739 | return self._run_ops.keys() | |
740 | ||
741 | def _init_command_handlers(self): | |
742 | if not self.ipcmd: | |
743 | self.ipcmd = self.bondcmd = LinkUtils() | |
744 | ||
745 | def run(self, ifaceobj, operation, query_ifaceobj=None, | |
746 | ifaceobj_getfunc=None): | |
747 | """ run bond configuration on the interface object passed as argument | |
748 | ||
749 | Args: | |
750 | **ifaceobj** (object): iface object | |
751 | ||
752 | **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr', | |
753 | 'query-running' | |
754 | ||
755 | Kwargs: | |
756 | **query_ifaceobj** (object): query check ifaceobject. This is only | |
757 | valid when op is 'query-checkcurr'. It is an object same as | |
758 | ifaceobj, but contains running attribute values and its config | |
759 | status. The modules can use it to return queried running state | |
760 | of interfaces. status is success if the running state is same | |
761 | as user required state in ifaceobj. error otherwise. | |
762 | """ | |
763 | op_handler = self._run_ops.get(operation) | |
764 | if not op_handler: | |
765 | return | |
766 | if operation != 'query-running' and not self._is_bond(ifaceobj): | |
767 | return | |
768 | self._init_command_handlers() | |
769 | if operation == 'query-checkcurr': | |
770 | op_handler(self, ifaceobj, query_ifaceobj, | |
771 | ifaceobj_getfunc=ifaceobj_getfunc) | |
772 | else: | |
773 | op_handler(self, ifaceobj, ifaceobj_getfunc=ifaceobj_getfunc) |