]> git.proxmox.com Git - mirror_ifupdown2.git/blob - ifupdown2/addons/bond.py
addons: bond: keep link admin up after being removed from bond
[mirror_ifupdown2.git] / ifupdown2 / addons / bond.py
1 #!/usr/bin/env python3
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 try:
12 from ifupdown2.lib.addon import Addon
13 from ifupdown2.nlmanager.nlmanager import Link
14
15 from ifupdown2.ifupdown.iface import *
16 from ifupdown2.ifupdown.utils import utils
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
22 from ifupdown2.ifupdownaddons.modulebase import moduleBase
23 except (ImportError, ModuleNotFoundError):
24 from lib.addon import Addon
25 from nlmanager.nlmanager import Link
26
27 from ifupdown.iface import *
28 from ifupdown.utils import utils
29 from ifupdown.statemanager import statemanager_api as statemanager
30
31 from ifupdownaddons.modulebase import moduleBase
32
33 import ifupdown.policymanager as policymanager
34 import ifupdown.ifupdownflags as ifupdownflags
35
36 class bond(Addon, moduleBase):
37 """ ifupdown2 addon module to configure bond interfaces """
38
39 overrides_ifupdown_scripts = ['ifenslave', ]
40
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"]
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"]
174 },
175 "es-sys-mac": {
176 "help": "evpn-mh: system mac address",
177 "validvals": ["<mac>", ],
178 "example": ["es-sys-mac 00:00:00:00:00:42"],
179 }
180 }
181 }
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,
192 'es-sys-mac': Link.IFLA_BOND_AD_ACTOR_SYSTEM,
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,
199 'bond-downdelay': Link.IFLA_BOND_DOWNDELAY,
200 'bond-primary': Link.IFLA_BOND_PRIMARY,
201 'bond-primary-reselect': Link.IFLA_BOND_PRIMARY_RESELECT
202
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,
218 Link.IFLA_BOND_DOWNDELAY: int,
219 Link.IFLA_BOND_PRIMARY_RESELECT: lambda x: Link.ifla_bond_primary_reselect_tbl[x],
220 # Link.IFLA_BOND_PRIMARY: self.netlink.get_ifname is added in __init__()
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))),
240 ('es-sys-mac', Link.IFLA_BOND_AD_ACTOR_SYSTEM, str),
241 ('bond-ad-sys-mac-addr', Link.IFLA_BOND_AD_ACTOR_SYSTEM, str),
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])
244 # ('bond-primary', Link.IFLA_BOND_PRIMARY, self.cache.get_ifindex) added in __init__()
245 )
246
247 def __init__(self, *args, **kargs):
248 Addon.__init__(self)
249 moduleBase.__init__(self, *args, **kargs)
250
251 if not os.path.exists('/sys/class/net/bonding_masters'):
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))
256
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
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')
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
268 return ifaceobj.link_kind & ifaceLinkKind.BOND or self.get_bond_slaves(ifaceobj)
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
286 if ifaceobj.get_attr_value("es-sys-mac"):
287 ifaceobj.link_privflags |= ifaceLinkPrivFlags.ES_BOND
288
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):
295 return self.cache.get_slaves(ifaceobj.name)
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
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:
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
318 except Exception as e:
319 self.logger.info(str(e))
320
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
328 def _add_slaves(self, ifaceobj, runningslaves, ifaceobj_getfunc=None):
329 slaves = self._get_slave_list(ifaceobj)
330 if not slaves:
331 self.logger.debug('%s: no slaves found' %ifaceobj.name)
332 return
333
334 clag_bond = self._is_clag_bond(ifaceobj)
335
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:
343 if (not ifupdownflags.flags.PERFMODE and
344 not self.cache.link_exists(slave)):
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
350 if self.cache.link_is_up(slave):
351 self.netlink.link_down_force(slave)
352 link_up = True
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:
357 try:
358 self.netlink.link_set_protodown_on(slave)
359 except Exception as e:
360 self.logger.error('%s: %s' % (ifaceobj.name, str(e)))
361
362 self.enable_ipv6_if_prev_brport(slave)
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
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):
370 self.netlink.link_down_force(slave)
371 else:
372 self.netlink.link_up(slave)
373 except Exception as e:
374 self.logger.debug('%s: %s' % (ifaceobj.name, str(e)))
375 pass
376
377 if runningslaves:
378 for s in runningslaves:
379 # make sure that slaves are not in protodown since we are not in the clag-bond or es-bond case
380 if not clag_bond and not ifaceobj.link_privflags & ifaceLinkPrivFlags.ES_BOND and self.cache.get_link_protodown(s):
381 self.netlink.link_set_protodown_off(s)
382
383 if s not in slaves:
384 self.sysfs.bond_remove_slave(ifaceobj.name, s)
385 if clag_bond:
386 try:
387 self.netlink.link_set_protodown_off(s)
388 except Exception as e:
389 self.logger.error('%s: %s' % (ifaceobj.name, str(e)))
390
391 # ip link set $slave nomaster will set the slave admin down
392 # if the slave has an auto stanza, we should keep it admin up
393 # unless link-down yes is set
394 slave_class_auto = False
395 slave_link_down = False
396 for obj in ifaceobj_getfunc(s) or []:
397 if obj.auto:
398 slave_class_auto = True
399 if obj.link_privflags & ifaceLinkPrivFlags.KEEP_LINK_DOWN:
400 slave_link_down = True
401 if slave_class_auto and not slave_link_down:
402 self.netlink.link_up_force(s)
403 else:
404 # apply link-down config changes on running slaves
405 try:
406 link_up = self.cache.link_is_up(s)
407 config_link_down = (ifaceobj_getfunc(s)[0].link_privflags &
408 ifaceLinkPrivFlags.KEEP_LINK_DOWN)
409 if (config_link_down and link_up):
410 self.netlink.link_down_force(s)
411 elif (not config_link_down and not link_up):
412 self.netlink.link_up_force(s)
413 except Exception as e:
414 self.logger.warning('%s: %s' % (ifaceobj.name, str(e)))
415
416 def _check_updown_delay_log(self, ifaceobj, attr_name, value):
417 ifaceobj.status = ifaceStatus.ERROR
418 self.logger.error('%s: unable to set %s %s as MII link monitoring is '
419 'disabled' % (ifaceobj.name, attr_name, value))
420 # return False to notify syntax_check that an error has been logged
421 return False
422
423 def syntax_check_updown_delay(self, ifaceobj):
424 result = True
425 updelay = ifaceobj.get_attr_value_first('bond-updelay')
426 downdelay = ifaceobj.get_attr_value_first('bond-downdelay')
427
428 if not updelay and not downdelay:
429 return True
430
431 try:
432 miimon = int(ifaceobj.get_attr_value_first('bond-miimon'))
433 except Exception:
434 try:
435 miimon = int(policymanager.policymanager_api.get_iface_default(
436 module_name=self.__class__.__name__,
437 ifname=ifaceobj.name,
438 attr='bond-miimon'))
439 except Exception:
440 miimon = 0
441
442 if not miimon:
443 # self._check_updown_delay_log returns False no matter what
444 if updelay and int(updelay):
445 result = self._check_updown_delay_log(ifaceobj, 'bond-updelay', updelay)
446 if downdelay and int(downdelay):
447 result = self._check_updown_delay_log(ifaceobj, 'bond-downdelay', downdelay)
448
449 return result
450
451 _bond_updown_delay_nl_list = (
452 (Link.IFLA_BOND_UPDELAY, 'bond-updelay'),
453 (Link.IFLA_BOND_DOWNDELAY, 'bond-downdelay')
454 )
455
456 def check_updown_delay_nl(self, link_exists, ifaceobj, ifla_info_data):
457 """
458 IFLA_BOND_MIIMON
459 Specifies the time, in milliseconds, to wait before enabling a slave
460 after a link recovery has been detected. This option is only valid
461 for the miimon link monitor. The updelay value should be a multiple
462 of the miimon value; if not, it will be rounded down to the nearest
463 multiple. The default value is 0.
464
465 This ifla_bond_miimon code should be move to get_ifla_bond_attr_from_user_config
466 but we need to know if the operation was successful to update the cache accordingly
467 """
468 ifla_bond_miimon = ifla_info_data.get(Link.IFLA_BOND_MIIMON)
469 if link_exists and ifla_bond_miimon is None:
470 ifla_bond_miimon = self.cache.get_link_info_data_attribute(ifaceobj.name, Link.IFLA_BOND_MIIMON)
471
472 if ifla_bond_miimon == 0:
473 for nl_attr, attr_name in self._bond_updown_delay_nl_list:
474 delay = ifla_info_data.get(nl_attr)
475 # if up-down-delay exists we need to remove it, if non zero log error
476 if delay is not None:
477 if delay > 0:
478 self._check_updown_delay_log(ifaceobj, attr_name, delay)
479 del ifla_info_data[nl_attr]
480 return True
481 return False
482
483 _bond_lacp_attrs = (
484 (Link.IFLA_BOND_AD_LACP_RATE, 'bond-lacp-rate'),
485 (Link.IFLA_BOND_AD_LACP_BYPASS, 'bond-lacp-bypass')
486 )
487
488 def _check_bond_mode_user_config(self, ifname, link_exists, ifla_info_data):
489 ifla_bond_mode = ifla_info_data.get(Link.IFLA_BOND_MODE)
490 if ifla_bond_mode is None and link_exists:
491 ifla_bond_mode = self.cache.get_link_info_data_attribute(ifname, Link.IFLA_BOND_MODE)
492 # in this case the link already exists (we have a cached value):
493 # if IFLA_BOND_MODE is not present in ifla_info_data it means:
494 # - that bond-mode was present in the user config and didn't change
495 # - never was in the user config so bond mode should be the system default value
496 # - was removed from the stanza so we might have to reset it to default value
497 # nevertheless we need to add it back to the ifla_info_data dict to check
498 # if we need to reset the mode to system default
499 ifla_info_data[Link.IFLA_BOND_MODE] = ifla_bond_mode
500
501 if ifla_bond_mode == 4: # 802.3ad
502 min_links = ifla_info_data.get(Link.IFLA_BOND_MIN_LINKS)
503 if min_links is None:
504 min_links = self.cache.get_link_info_data_attribute(ifname, Link.IFLA_BOND_MIN_LINKS)
505 # get_min_links_nl may return None so we need to strictly check 0
506 if min_links == 0:
507 self.logger.warning('%s: attribute bond-min-links is set to \'0\'' % ifname)
508 else:
509 # IFLA_BOND_AD_LACP_RATE and IFLA_BOND_AD_LACP_BYPASS only for 802.3ad mode (4)
510 for nl_attr, attr_name in self._bond_lacp_attrs:
511 if nl_attr in ifla_info_data:
512 self.logger.info('%s: ignoring %s: only available for 802.3ad mode (4)' % (ifname, attr_name))
513 del ifla_info_data[nl_attr]
514
515 @staticmethod
516 def get_saved_ifaceobj(link_exists, ifname):
517 if link_exists:
518 old_config = statemanager.get_ifaceobjs(ifname)
519 if old_config:
520 return old_config[0]
521 return None
522
523 def get_ifla_bond_attr_from_user_config(self, ifaceobj, link_exists):
524 """
525 Potential issue: if a user load the bond driver with custom
526 default values (say bond-mode 3), ifupdown2 has no knowledge
527 of these default values.
528 At bond creation everything should work, bonds will be created
529 with mode 3 (even if not specified under the stanza).
530 But, for example: if the user specifies a value under bond-mode
531 and later on the user removes the bond-mode line from the stanza
532 we will detect it and reset to MODINFO: BOND-MODE: DEFAULT aka 0
533 which is not the real default value that the user may expect.
534 """
535 ifname = ifaceobj.name
536 ifla_info_data = OrderedDict()
537 old_config = self.get_saved_ifaceobj(link_exists, ifname)
538
539 # for each bond attribute we fetch the user configuration
540 # if no configuration is provided we look for a config in policy files
541 for attr_name, netlink_attr, func_ptr in self._bond_attr_set_list:
542 cached_value = None
543 user_config = ifaceobj.get_attr_value_first(attr_name)
544
545 if not user_config:
546 user_config = policymanager.policymanager_api.get_iface_default(
547 module_name=self.__class__.__name__,
548 ifname=ifname,
549 attr=attr_name)
550 if user_config:
551 self.logger.debug('%s: %s %s: extracted from policy files'
552 % (ifname, attr_name, user_config))
553
554 # no policy override, do we need to reset an attr to default value?
555 if not user_config and old_config and old_config.get_attr_value_first(attr_name):
556 # if the link already exists but the value is set
557 # (potentially removed from the stanza, we need to reset it to default)
558 # might not work for specific cases, see explanation at the top of this function :)
559 user_config = self.get_attr_default_value(attr_name)
560 if user_config:
561 self.logger.debug('%s: %s: removed from stanza, resetting to default value: %s'
562 % (ifname, attr_name, user_config))
563
564 if user_config:
565 try:
566 nl_value = func_ptr(user_config.lower())
567
568 if link_exists:
569 cached_value = self.cache.get_link_info_data_attribute(ifname, netlink_attr)
570
571 if link_exists and cached_value is None:
572 # the link already exists but we don't have any value
573 # cached for this attr, it probably means that the
574 # capability is not available on this system (i.e old kernel)
575 self.logger.debug('%s: ignoring %s %s: capability '
576 'probably not supported on this system'
577 % (ifname, attr_name, user_config))
578 continue
579 elif link_exists:
580 # there should be a cached value if the link already exists
581 if cached_value == nl_value:
582 # if the user value is already cached: continue
583 continue
584
585 # else: the link doesn't exist so we create the bond with
586 # all the user/policy defined values without extra checks
587 ifla_info_data[netlink_attr] = nl_value
588
589 if cached_value is not None:
590 self.logger.info('%s: set %s %s (cache %s)' % (ifname, attr_name, user_config, cached_value))
591 else:
592 self.logger.info('%s: set %s %s' % (ifname, attr_name, user_config))
593
594 except KeyError:
595 self.logger.warning('%s: invalid %s value %s' % (ifname, attr_name, user_config))
596
597 self._check_bond_mode_user_config(ifname, link_exists, ifla_info_data)
598 return ifla_info_data
599
600 _bond_down_nl_attributes_list = (
601 Link.IFLA_BOND_MODE,
602 Link.IFLA_BOND_XMIT_HASH_POLICY,
603 Link.IFLA_BOND_AD_LACP_RATE,
604 Link.IFLA_BOND_MIN_LINKS
605 )
606
607 def _should_down_bond(self, ifla_info_data):
608 for nl_attr in self._bond_down_nl_attributes_list:
609 if nl_attr in ifla_info_data:
610 return True
611 return False
612
613 def should_update_bond_mode(self, ifaceobj, ifname, is_link_up, ifla_info_data, bond_slaves):
614 # if bond-mode was changed the bond needs to be brought
615 # down and slaves un-slaved before bond mode is changed.
616 cached_bond_mode = self.cache.get_link_info_data_attribute(ifname, Link.IFLA_BOND_MODE)
617 ifla_bond_mode = ifla_info_data.get(Link.IFLA_BOND_MODE)
618
619 # bond-mode was changed or is not specified
620 if ifla_bond_mode is not None:
621 if ifla_bond_mode != cached_bond_mode:
622 self.logger.info('%s: bond mode changed to %s: running ops on bond and slaves'
623 % (ifname, ifla_bond_mode))
624 if is_link_up:
625 self.netlink.link_down(ifname)
626 is_link_up = False
627
628 for lower_dev in ifaceobj.lowerifaces:
629 self.netlink.link_set_nomaster(lower_dev)
630
631 # when unslaving a device from an ES bond we need to set
632 # protodown off
633 if ifaceobj.link_privflags & ifaceLinkPrivFlags.ES_BOND:
634 self.netlink.link_set_protodown_off(lower_dev)
635
636 try:
637 bond_slaves.remove(lower_dev)
638 except Exception:
639 pass
640
641 else:
642 # bond-mode user config value is the current running(cached) value
643 # no need to reset it again we can ignore this attribute
644 del ifla_info_data[Link.IFLA_BOND_MODE]
645
646 return is_link_up, bond_slaves
647
648 def create_or_set_bond_config(self, ifaceobj):
649 ifname = ifaceobj.name
650 link_exists, is_link_up = self.cache.link_exists_and_up(ifname)
651 ifla_info_data = self.get_ifla_bond_attr_from_user_config(ifaceobj, link_exists)
652
653 remove_delay_from_cache = self.check_updown_delay_nl(link_exists, ifaceobj, ifla_info_data)
654
655 # if link exists: down link if specific attributes are specified
656 if link_exists:
657 # did bond-mode changed?
658 is_link_up, bond_slaves = self.should_update_bond_mode(
659 ifaceobj,
660 ifname,
661 is_link_up,
662 ifla_info_data,
663 self.cache.get_slaves(ifname)
664 )
665
666 # if specific attributes need to be set we need to down the bond first
667 if ifla_info_data and is_link_up:
668 if self._should_down_bond(ifla_info_data):
669 self.netlink.link_down_force(ifname)
670 is_link_up = False
671 else:
672 bond_slaves = []
673
674 if link_exists and not ifla_info_data:
675 # if the bond already exists and no attrs need to be set
676 # ignore the netlink call
677 self.logger.info('%s: already exists, no change detected' % ifname)
678 else:
679 try:
680 self.netlink.link_add_bond_with_info_data(ifname, ifla_info_data)
681 except Exception as e:
682 # defensive code
683 # if anything happens, we try to set up the bond with the sysfs api
684 self.logger.debug('%s: bond setup: %s' % (ifname, str(e)))
685 self.create_or_set_bond_config_sysfs(ifaceobj, ifla_info_data)
686
687 if remove_delay_from_cache:
688 # making sure up/down delay attributes are set to 0 before caching
689 # this can be removed when moving to a nllistener/live cache
690 ifla_info_data[Link.IFLA_BOND_UPDELAY] = 0
691 ifla_info_data[Link.IFLA_BOND_DOWNDELAY] = 0
692
693 if link_exists and ifla_info_data and not is_link_up:
694 self.netlink.link_up_force(ifname)
695
696 return bond_slaves
697
698 def create_or_set_bond_config_sysfs(self, ifaceobj, ifla_info_data):
699 if len(ifaceobj.name) > 15:
700 self.log_error("%s: cannot create bond: interface name exceeds max length of 15" % ifaceobj.name, ifaceobj)
701 return
702
703 if not self.cache.link_exists(ifaceobj.name):
704 self.sysfs.bond_create(ifaceobj.name)
705 self.sysfs.bond_set_attrs_nl(ifaceobj.name, ifla_info_data)
706
707 def _up(self, ifaceobj, ifaceobj_getfunc=None):
708 try:
709 bond_slaves = self.create_or_set_bond_config(ifaceobj)
710 self._add_slaves(
711 ifaceobj,
712 bond_slaves,
713 ifaceobj_getfunc,
714 )
715 except Exception as e:
716 self.log_error(str(e), ifaceobj)
717
718 def _down(self, ifaceobj, ifaceobj_getfunc=None):
719 try:
720 self.netlink.link_del(ifaceobj.name)
721 except Exception as e:
722 self.log_warn('%s: %s' % (ifaceobj.name, str(e)))
723
724 def _query_check_bond_slaves(self, ifaceobjcurr, attr, user_bond_slaves, running_bond_slaves):
725 query = 1
726
727 if user_bond_slaves and running_bond_slaves:
728 if not set(user_bond_slaves).symmetric_difference(running_bond_slaves):
729 query = 0
730
731 # we want to display the same bond-slaves list as provided
732 # in the interfaces file but if this list contains regexes or
733 # globs, for now, we won't try to change it.
734 if 'regex' in user_bond_slaves or 'glob' in user_bond_slaves:
735 user_bond_slaves = running_bond_slaves
736 else:
737 ordered = []
738 for slave in user_bond_slaves:
739 if slave in running_bond_slaves:
740 ordered.append(slave)
741 user_bond_slaves = ordered
742 ifaceobjcurr.update_config_with_status(attr, ' '.join(user_bond_slaves) if user_bond_slaves else 'None', query)
743
744 def _query_check(self, ifaceobj, ifaceobjcurr, ifaceobj_getfunc=None):
745 if not self.cache.bond_exists(ifaceobj.name):
746 self.logger.debug('bond iface %s does not exist' % ifaceobj.name)
747 return
748
749 iface_attrs = self.dict_key_subset(ifaceobj.config, self.get_mod_attrs())
750 if not iface_attrs:
751 return
752
753 # remove bond-slaves and bond-ports from the list,
754 # because there aren't any ifla_info_data netlink attr for slaves
755 # an exception is raised when index is not found, so query_slaves will stay False
756 query_slaves = False
757
758 user_bond_slaves = None
759 running_bond_slaves = None
760 try:
761 del iface_attrs[iface_attrs.index('bond-slaves')]
762
763 # if user specified bond-slaves we need to display it
764 query_slaves = True
765 if not user_bond_slaves:
766 user_bond_slaves = self._get_slave_list(ifaceobj)
767 running_bond_slaves = self.cache.get_slaves(ifaceobj.name)
768
769 self._query_check_bond_slaves(ifaceobjcurr, 'bond-slaves', user_bond_slaves, running_bond_slaves)
770 except Exception:
771 pass
772 try:
773 del iface_attrs[iface_attrs.index('bond-ports')]
774
775 # if user specified bond-ports we need to display it
776 if not query_slaves and not user_bond_slaves: # if get_slave_list was already called for slaves
777 user_bond_slaves = self._get_slave_list(ifaceobj)
778 running_bond_slaves = self.cache.get_slaves(ifaceobj.name)
779
780 self._query_check_bond_slaves(ifaceobjcurr, 'bond-ports', user_bond_slaves, running_bond_slaves)
781 except Exception:
782 pass
783
784 for attr in iface_attrs:
785 nl_attr = self._bond_attr_netlink_map[attr]
786 translate_func = self._bond_attr_ifquery_check_translate_func[nl_attr]
787 current_config = self.cache.get_link_info_data_attribute(ifaceobj.name, nl_attr)
788 user_config = ifaceobj.get_attr_value_first(attr)
789
790 if current_config == translate_func(user_config):
791 ifaceobjcurr.update_config_with_status(attr, user_config, 0)
792 else:
793 ifaceobjcurr.update_config_with_status(attr, str(current_config), 1)
794
795 @staticmethod
796 def translate_nl_value_yesno(value):
797 return 'yes' if value else 'no'
798
799 @staticmethod
800 def translate_nl_value_slowfast(value):
801 return 'fast' if value else 'slow'
802
803 def _query_running_attrs(self, bondname):
804 cached_vxlan_ifla_info_data = self.cache.get_link_info_data(bondname)
805
806 bond_attrs = {
807 'bond-mode': Link.ifla_bond_mode_pretty_tbl.get(cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_MODE)),
808 'bond-miimon': cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_MIIMON),
809 'bond-use-carrier': self.translate_nl_value_yesno(cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_USE_CARRIER)),
810 'bond-lacp-rate': self.translate_nl_value_slowfast(cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_AD_LACP_RATE)),
811 'bond-min-links': cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_MIN_LINKS),
812 'bond-ad-actor-system': cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_AD_ACTOR_SYSTEM),
813 'es-sys-mac': cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_AD_ACTOR_SYSTEM),
814 'bond-ad-actor-sys-prio': cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_AD_ACTOR_SYS_PRIO),
815 '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)),
816 'bond-lacp-bypass-allow': self.translate_nl_value_yesno(cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_AD_LACP_BYPASS)),
817 'bond-num-unsol-na': cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_NUM_PEER_NOTIF),
818 'bond-num-grat-arp': cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_NUM_PEER_NOTIF),
819 'bond-updelay': cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_UPDELAY),
820 'bond-downdelay': cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_DOWNDELAY)
821 }
822
823 cached_bond_primary = cached_vxlan_ifla_info_data.get(Link.IFLA_BOND_PRIMARY)
824 if cached_bond_primary:
825 bond_attrs['bond-primary'] = self.cache.get_ifname(cached_bond_primary)
826
827 slaves = self.cache.get_slaves(bondname)
828 if slaves:
829 bond_attrs['bond-slaves'] = slaves
830 return bond_attrs
831
832 def _query_running(self, ifaceobjrunning, ifaceobj_getfunc=None):
833 if not self.cache.bond_exists(ifaceobjrunning.name):
834 return
835 bond_attrs = self._query_running_attrs(ifaceobjrunning.name)
836 if bond_attrs.get('bond-slaves'):
837 bond_attrs['bond-slaves'] = ' '.join(bond_attrs.get('bond-slaves'))
838
839 [ifaceobjrunning.update_config(k, str(v))
840 for k, v in list(bond_attrs.items())
841 if v is not None]
842
843 _run_ops = {
844 'pre-up': _up,
845 'post-down': _down,
846 'query-running': _query_running,
847 'query-checkcurr': _query_check
848 }
849
850 def get_ops(self):
851 """ returns list of ops supported by this module """
852 return list(self._run_ops.keys())
853
854 def run(self, ifaceobj, operation, query_ifaceobj=None,
855 ifaceobj_getfunc=None):
856 """ run bond configuration on the interface object passed as argument
857
858 Args:
859 **ifaceobj** (object): iface object
860
861 **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr',
862 'query-running'
863
864 Kwargs:
865 **query_ifaceobj** (object): query check ifaceobject. This is only
866 valid when op is 'query-checkcurr'. It is an object same as
867 ifaceobj, but contains running attribute values and its config
868 status. The modules can use it to return queried running state
869 of interfaces. status is success if the running state is same
870 as user required state in ifaceobj. error otherwise.
871 """
872 op_handler = self._run_ops.get(operation)
873 if not op_handler:
874 return
875 if operation != 'query-running' and not self._is_bond(ifaceobj):
876 return
877 if operation == 'query-checkcurr':
878 op_handler(self, ifaceobj, query_ifaceobj,
879 ifaceobj_getfunc=ifaceobj_getfunc)
880 else:
881 op_handler(self, ifaceobj, ifaceobj_getfunc=ifaceobj_getfunc)