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