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