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