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