]> git.proxmox.com Git - mirror_ifupdown2.git/blame - ifupdown2-addons/addons/mstpctl.py
Fix ifquery check for mstpctl-ports
[mirror_ifupdown2.git] / ifupdown2-addons / addons / mstpctl.py
CommitLineData
2c8c4ce7
RP
1#!/usr/bin/python
2#
3# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
4# Author: Roopa Prabhu, roopa@cumulusnetworks.com
5#
6
7from sets import Set
8from ifupdown.iface import *
9from ifupdownaddons.modulebase import moduleBase
10from ifupdownaddons.bridgeutils import brctl
11from ifupdownaddons.iproute2 import iproute2
12from ifupdownaddons.mstpctlutil import mstpctlutil
13import traceback
14
15class mstpctl(moduleBase):
16 """ ifupdown2 addon module to configure mstp attributes """
17
18 _modinfo = {'mhelp' : 'mstp configuration module for bridges',
19 'attrs' : {
20 'mstpctl-ports' :
21 {'help' : 'mstp ports',
22 'compat' : True},
23 'mstpctl-stp' :
24 {'help': 'bridge stp yes/no',
25 'compat' : True,
26 'default' : 'no'},
27 'mstpctl-treeprio' :
28 {'help': 'tree priority',
29 'default' : '32768',
30 'validrange' : ['0', '65535'],
31 'required' : False,
32 'example' : ['mstpctl-treeprio 32768']},
33 'mstpctl-ageing' :
34 {'help': 'ageing time',
35 'default' : '300',
36 'required' : False,
37 'example' : ['mstpctl-ageing 300']},
38 'mstpctl-maxage' :
39 { 'help' : 'max message age',
40 'default' : '20',
41 'required' : False,
42 'example' : ['mstpctl-maxage 20']},
43 'mstpctl-fdelay' :
44 { 'help' : 'set forwarding delay',
45 'default' : '15',
46 'required' : False,
47 'example' : ['mstpctl-fdelay 15']},
48 'mstpctl-maxhops' :
49 { 'help' : 'bridge max hops',
50 'default' : '15',
51 'required' : False,
52 'example' : ['mstpctl-maxhops 15']},
53 'mstpctl-txholdcount' :
54 { 'help' : 'bridge transmit holdcount',
55 'default' : '6',
56 'required' : False,
57 'example' : ['mstpctl-txholdcount 6']},
58 'mstpctl-forcevers' :
59 { 'help' : 'bridge force stp version',
60 'default' : 'rstp',
61 'required' : False,
62 'example' : ['mstpctl-forcevers rstp']},
63 'mstpctl-portpathcost' :
64 { 'help' : 'bridge port path cost',
65 'default' : '0',
66 'required' : False,
67 'example' : ['mstpctl-portpathcost swp1=0 swp2=1']},
68 'mstpctl-portadminage' :
69 { 'help' : 'bridge port admin age',
70 'default' : 'no',
71 'validvals' : ['yes', 'no'],
72 'required' : False,
73 'example' : ['mstpctl-portadminage swp1=no swp2=no']},
74 'mstpctl-portp2p' :
75 { 'help' : 'bridge port p2p detection mode',
76 'default' : 'no',
77 'validvals' : ['yes', 'no'],
78 'required' : False,
79 'example' : ['mstpctl-portp2p swp1=no swp2=no']},
80 'mstpctl-portrestrrole' :
81 { 'help' :
82 'enable/disable port ability to take root role of the port',
83 'default' : 'no',
84 'validvals' : ['yes', 'no'],
85 'required' : False,
86 'example' : ['mstpctl-portrestrrole swp1=no swp2=no']},
87 'mstpctl-portrestrtcn' :
88 { 'help' :
89 'enable/disable port ability to propagate received topology change notification of the port',
90 'default' : 'no',
91 'validvals' : ['yes', 'no'],
92 'required' : False,
93 'example' : ['mstpctl-portrestrtcn swp1=no swp2=no']},
94 'mstpctl-bpduguard' :
95 { 'help' :
96 'enable/disable bpduguard',
97 'default' : 'no',
98 'validvals' : ['yes', 'no'],
99 'required' : False,
100 'example' : ['mstpctl-bpduguard swp1=no swp2=no']},
101 'mstpctl-treeportprio' :
102 { 'help' :
103 'port priority for MSTI instance',
104 'default' : '128',
105 'validrange' : ['0', '240'],
106 'required' : False,
107 'example' : ['mstpctl-treeportprio swp1=128 swp2=128']},
108 'mstpctl-hello' :
109 { 'help' : 'set hello time',
110 'default' : '2',
111 'required' : False,
112 'example' : ['mstpctl-hello 2']},
113 'mstpctl-portnetwork' :
114 { 'help' : 'enable/disable bridge assurance capability for a port',
115 'validvals' : ['yes', 'no'],
116 'default' : 'no',
117 'required' : False,
118 'example' : ['mstpctl-portnetwork swp1=no swp2=no']},
119 'mstpctl-portadminedge' :
120 { 'help' : 'enable/disable initial edge state of the port',
121 'validvals' : ['yes', 'no'],
122 'default' : 'no',
123 'required' : False,
124 'example' : ['mstpctl-portadminedge swp1=no swp2=no']},
125 'mstpctl-portautoedge' :
126 { 'help' : 'enable/disable auto transition to/from edge state of the port',
127 'validvals' : ['yes', 'no'],
128 'default' : 'no',
129 'required' : False,
130 'example' : ['mstpctl-portautoedge swp1=yes swp2=yes']},
131 'mstpctl-treeportcost' :
132 { 'help' : 'port tree cost',
133 'required' : False},
134 'mstpctl-portbpdufilter' :
135 { 'help' : 'enable/disable bpdu filter on a port',
136 'validvals' : ['yes', 'no'],
137 'default' : 'no',
138 'required' : False,
139 'example' : ['mstpctl-portbpdufilter swp1=no swp2=no']},
140 }}
141
142 def __init__(self, *args, **kargs):
143 moduleBase.__init__(self, *args, **kargs)
144 self.ipcmd = None
145 self.brctlcmd = None
146 self.mstpctlcmd = None
147
148 def _is_bridge(self, ifaceobj):
149 if (ifaceobj.get_attr_value_first('mstpctl-ports') or
150 ifaceobj.get_attr_value_first('bridge-ports')):
151 return True
152 return False
153
154 def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None):
155 if not self._is_bridge(ifaceobj):
156 return None
157 return self.parse_port_list(ifaceobj.get_attr_value_first(
158 'mstpctl-ports'), ifacenames_all)
159
160 def get_dependent_ifacenames_running(self, ifaceobj):
161 self._init_command_handlers()
162 if (self.brctlcmd.bridge_exists(ifaceobj.name) and
163 not self.mstpctlcmd.mstpbridge_exists(ifaceobj.name)):
164 return None
165 return self.brctlcmd.get_bridge_ports(ifaceobj.name)
166
167 def _get_bridge_port_list(self, ifaceobj):
168
169 # port list is also available in the previously
170 # parsed dependent list. Use that if available, instead
171 # of parsing port expr again
172 port_list = ifaceobj.lowerifaces
173 if port_list:
174 ports = ifaceobj.get_attr_value_first('mstpctl-ports')
175 if ports:
176 return self.parse_port_list(ports)
177 else:
178 return None
179 else:
180 return port_list
181
182 def _add_ports(self, ifaceobj):
183 bridgeports = self._get_bridge_port_list(ifaceobj)
184
185 runningbridgeports = []
186 # Delete active ports not in the new port list
187 if not self.PERFMODE:
188 runningbridgeports = self.brctlcmd.get_bridge_ports(ifaceobj.name)
189 if runningbridgeports:
190 [self.ipcmd.link_set(bport, 'nomaster')
191 for bport in runningbridgeports
192 if not bridgeports or bport not in bridgeports]
193 else:
194 runningbridgeports = []
195 if not bridgeports:
196 return
197 err = 0
198 for bridgeport in Set(bridgeports).difference(Set(runningbridgeports)):
199 try:
200 if not self.DRYRUN and not self.ipcmd.link_exists(bridgeport):
201 self.log_warn('%s: bridge port %s does not exist'
202 %(ifaceobj.name, bridgeport))
203 err += 1
204 continue
205 self.ipcmd.link_set(bridgeport, 'master', ifaceobj.name)
206 self.write_file('/proc/sys/net/ipv6/conf/%s' %bridgeport +
207 '/disable_ipv6', '1')
208 self.ipcmd.addr_flush(bridgeport)
209 except Exception, e:
210 self.log_error(str(e))
211 if err:
212 self.log_error('error configuring bridge (missing ports)')
213
214 def _apply_bridge_settings(self, ifaceobj):
215 check = False if self.PERFMODE else True
216 try:
217 bridgeattrs = {k:v for k,v in
218 {'treeprio' :
219 ifaceobj.get_attr_value_first('mstpctl-treeprio'),
220 'ageing' :
221 ifaceobj.get_attr_value_first('mstpctl-ageing'),
222 'maxage' :
223 ifaceobj.get_attr_value_first('mstpctl-maxage'),
224 'fdelay' :
225 ifaceobj.get_attr_value_first('mstpctl-fdelay'),
226 'maxhops' :
227 ifaceobj.get_attr_value_first('mstpctl-maxhops'),
228 'txholdcount' :
229 ifaceobj.get_attr_value_first('mstpctl-txholdcount'),
230 'forcevers' :
231 ifaceobj.get_attr_value_first('mstpctl-forcevers'),
232 'hello' :
233 ifaceobj.get_attr_value_first('mstpctl-hello')
234 }.items() if v}
235
236 if bridgeattrs:
237 # set bridge attributes
238 for k,v in bridgeattrs.items():
239 if k == 'treeprio':
240 continue
241 try:
242 if v:
243 self.mstpctlcmd.set_bridge_attr(ifaceobj.name, k,
244 v, check)
245 except Exception, e:
246 self.logger.warn('%s' %str(e))
247 pass
248 if bridgeattrs.get('treeprio'):
249 try:
250 self.mstpctlcmd.set_bridge_treeprio(ifaceobj.name,
251 bridgeattrs['treeprio'], check)
252 except Exception, e:
253 self.logger.warn('%s' %str(e))
254 pass
255
256 # set bridge port attributes
257 for attrname in ['mstpctl-portpathcost', 'mstpctl-portadminedge',
258 'mstpctl-portp2p', 'mstpctl-portrestrrole',
259 'mstpctl-portrestrtcn', 'mstpctl-bpduguard',
260 'mstpctl-treeportprio', 'mstpctl-treeportcost',
261 'mstpctl-portnetwork', 'mstpctl-portbpdufilter']:
262 attrval = ifaceobj.get_attr_value_first(attrname)
263 if not attrval:
264 continue
265 dstattrname = attrname.split('-')[1]
266 portlist = self.parse_port_list(attrval)
267 if not portlist:
268 self.log_warn('%s: error parsing \'%s %s\''
269 %(ifaceobj.name, attrname, attrval))
270 continue
271 for p in portlist:
272 try:
273 (port, val) = p.split('=')
274 self.mstpctlcmd.set_bridgeport_attr(ifaceobj.name,
275 port, dstattrname, val, check)
276 except Exception, e:
277 self.log_warn('%s: error setting %s (%s)'
278 %(ifaceobj.name, attrname, str(e)))
279 except Exception, e:
280 self.log_warn(str(e))
281 pass
282
283 def _up(self, ifaceobj):
284 stp = None
285 try:
286 if ifaceobj.get_attr_value_first('mstpctl-ports'):
287 # If bridge ports specified with mstpctl attr, create the
288 # bridge and also add its ports
289 self.ipcmd.batch_start()
290 if not self.PERFMODE:
291 if not self.ipcmd.link_exists(ifaceobj.name):
292 self.ipcmd.link_create(ifaceobj.name, 'bridge')
293 else:
294 self.ipcmd.link_create(ifaceobj.name, 'bridge')
295 self._add_ports(ifaceobj)
296 self.ipcmd.batch_commit()
297 stp = ifaceobj.get_attr_value_first('mstpctl-stp')
298 if stp:
299 self.set_iface_attr(ifaceobj, 'mstpctl-stp',
300 self.brctlcmd.set_stp)
301 else:
302 stp = self.brctlcmd.get_stp(ifaceobj.name)
303 if (self.mstpctlcmd.is_mstpd_running() and
304 (stp == 'yes' or stp == 'on')):
305 self._apply_bridge_settings(ifaceobj)
306 except Exception, e:
307 self.log_error(str(e))
308
309 def _down(self, ifaceobj):
310 try:
311 if ifaceobj.get_attr_value_first('mstpctl-ports'):
312 # If bridge ports specified with mstpctl attr, delete the
313 # bridge
314 ports = self.brctlcmd.get_bridge_ports(ifaceobj.name)
315 if ports:
316 for p in ports:
317 proc_file = ('/proc/sys/net/ipv6/conf/%s' %p +
318 '/disable_ipv6')
319 self.write_file(proc_file, '0')
320 self.brctlcmd.delete_bridge(ifaceobj.name)
321 except Exception, e:
322 self.log_error(str(e))
323
324 def _query_running_attrs(self, ifaceobjrunning):
325 bridgeattrdict = {}
326
327 tmpbridgeattrdict = self.mstpctlcmd.get_bridge_attrs(ifaceobjrunning.name)
328 if not tmpbridgeattrdict:
329 return bridgeattrdict
330
331 for k,v in tmpbridgeattrdict.items():
332 if k == 'stp' or not v:
333 continue
334 if k == 'ports':
335 ports = v.keys()
336 continue
337 attrname = 'mstpctl-' + k
338 if v and v != self.get_mod_subattr(attrname, 'default'):
339 bridgeattrdict[attrname] = [v]
340
341 ports = self.brctlcmd.get_bridge_ports(ifaceobjrunning.name)
342 if ports:
343 portconfig = {'mstpctl-portnetwork' : '',
344 'mstpctl-portpathcost' : '',
345 'mstpctl-portadminedge' : '',
346 'mstpctl-portautoedge' : '',
347 'mstpctl-portp2p' : '',
348 'mstpctl-portrestrrole' : '',
349 'mstpctl-portrestrtcn' : '',
350 'mstpctl-bpduguard' : '',
351 'mstpctl-treeportprio' : '',
352 'mstpctl-treeportcost' : ''}
353
354 for p in ports:
355 v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name,
356 p, 'portnetwork')
357 if v and v != 'no':
358 portconfig['mstpctl-portnetwork'] += ' %s=%s' %(p, v)
359
360 # XXX: Can we really get path cost of a port ???
361 #v = self.mstpctlcmd.get_portpathcost(ifaceobjrunning.name, p)
362 #if v and v != self.get_mod_subattr('mstpctl-portpathcost',
363 # 'default'):
364 # portconfig['mstpctl-portpathcost'] += ' %s=%s' %(p, v)
365
366 v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name,
367 p, 'portadminedge')
368 if v and v != 'no':
369 portconfig['mstpctl-portadminedge'] += ' %s=%s' %(p, v)
370
371 v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name,
372 p, 'portp2p')
373 if v and v != 'no':
374 portconfig['mstpctl-portp2p'] += ' %s=%s' %(p, v)
375
376 v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name,
377 p, 'portrestrrole')
378 if v and v != 'no':
379 portconfig['mstpctl-portrestrrole'] += ' %s=%s' %(p, v)
380
381 v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name,
382 p, 'portrestrtcn')
383 if v and v != 'no':
384 portconfig['mstpctl-portrestrtcn'] += ' %s=%s' %(p, v)
385
386 v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name,
387 p, 'bpduguard')
388 if v and v != 'no':
389 portconfig['mstpctl-bpduguard'] += ' %s=%s' %(p, v)
390
391 # XXX: Can we really get path cost of a port ???
392 #v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name,
393 # p, 'treeprio')
394 #if v and v != self.get_mod_subattr('mstpctl-treeportprio',
395 # 'default'):
396 # portconfig['mstpctl-treeportprio'] += ' %s=%s' %(p, v)
397
398 #v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name,
399 # p, 'treecost')
400 #if v and v != self.get_mod_subattr('mstpctl-treeportcost',
401 # 'default'):
402 # portconfig['mstpctl-treeportcost'] += ' %s=%s' %(p, v)
403
404 bridgeattrdict.update({k : [v] for k, v in portconfig.items()
405 if v})
406 self.logger.debug(bridgeattrdict)
407 return bridgeattrdict
408
409 def _query_check(self, ifaceobj, ifaceobjcurr):
410 # list of attributes that are not supported currently
411 blacklistedattrs = ['mstpctl-portpathcost',
412 'mstpctl-treeportprio', 'mstpctl-treeportcost']
413 if not self.brctlcmd.bridge_exists(ifaceobj.name):
414 self.logger.debug('bridge %s does not exist' %ifaceobj.name)
415 ifaceobjcurr.status = ifaceStatus.NOTFOUND
416 return
417 ifaceattrs = self.dict_key_subset(ifaceobj.config,
418 self.get_mod_attrs())
419 if not ifaceattrs:
420 return
421 runningattrs = self.mstpctlcmd.get_bridge_attrs(ifaceobj.name)
422 if not runningattrs:
423 runningattrs = {}
424 for k in ifaceattrs:
425 # for all mstpctl options
426 if k in blacklistedattrs:
427 continue
428 # get the corresponding ifaceobj attr
429 v = ifaceobj.get_attr_value_first(k)
430 if not v:
431 continue
432
433 # Get the running attribute
434 rv = runningattrs.get(k[8:])
435 if k == 'mstpctl-stp':
436 # special case stp compare because it may
437 # contain more than one valid values
438 stp_on_vals = ['on', 'yes']
439 stp_off_vals = ['off']
440 rv = self.brctlcmd.get_stp(ifaceobj.name)
441 if ((v in stp_on_vals and rv in stp_on_vals) or
442 (v in stp_off_vals and rv in stp_off_vals)):
443 ifaceobjcurr.update_config_with_status('mstpctl-stp', v, 0)
444 else:
445 ifaceobjcurr.update_config_with_status('mstpctl-stp', v, 1)
446 continue
447
448 if k == 'mstpctl-ports':
449 # special case ports because it can contain regex or glob
450 # XXX: We get all info from mstputils, which means if
451 # mstpd is down, we will not be returning any bridge bridgeports
59bba4c9 452 running_port_list = self.brctlcmd.get_bridge_ports(ifaceobj.name)
2c8c4ce7
RP
453 bridge_port_list = self._get_bridge_port_list(ifaceobj)
454 if not running_port_list and not bridge_port_list:
455 continue
456 portliststatus = 1
457 if running_port_list and bridge_port_list:
458 difference = Set(running_port_list).symmetric_difference(
459 Set(bridge_port_list))
460 if not difference:
461 portliststatus = 0
462 ifaceobjcurr.update_config_with_status('mstpctl-ports',
463 ' '.join(running_port_list)
464 if running_port_list else '', portliststatus)
465 elif k[:12] == 'mstpctl-port' or k == 'mstpctl-bpduguard':
466 # Now, look at port attributes
467 # derive the mstpctlcmd attr name
468 #mstpctlcmdattrname = k[12:] if k[:12] == 'mstpctl-port' else k[8:]
469 mstpctlcmdattrname = k[8:]
470
471 # for port attributes, the attributes are in a list
472 # <portname>=<portattrvalue>
473 status = 0
474 currstr = ''
475 vlist = self.parse_port_list(v)
476 if not vlist:
477 continue
478 for vlistitem in vlist:
479 try:
480 (p, v) = vlistitem.split('=')
481 currv = self.mstpctlcmd.get_bridgeport_attr(
482 ifaceobj.name, p, mstpctlcmdattrname)
483 if currv:
484 currstr += ' %s=%s' %(p, currv)
485 else:
486 currstr += ' %s=%s' %(p, 'None')
487 if currv != v:
488 status = 1
489 except Exception, e:
490 self.log_warn(str(e))
491 pass
492 ifaceobjcurr.update_config_with_status(k, currstr, status)
493 elif not rv:
494 ifaceobjcurr.update_config_with_status(k, '', 1)
495 elif v != rv:
496 ifaceobjcurr.update_config_with_status(k, rv, 1)
497 else:
498 ifaceobjcurr.update_config_with_status(k, rv, 0)
499
500 def _query_running(self, ifaceobjrunning):
501 if not self.brctlcmd.bridge_exists(ifaceobjrunning.name):
502 return
503 if self.brctlcmd.get_stp(ifaceobjrunning.name) == 'no':
504 # This bridge does not run stp, return
505 return
506 # if userspace stp not set, return
507 if self.sysctl_get('net.bridge.bridge-stp-user-space') != '1':
508 return
509 # Check if mstp really knows about this bridge
510 if not self.mstpctlcmd.mstpbridge_exists(ifaceobjrunning.name):
511 return
512 ifaceobjrunning.update_config_dict(self._query_running_attrs(
513 ifaceobjrunning))
514
515 _run_ops = {'pre-up' : _up,
516 'post-down' : _down,
517 'query-checkcurr' : _query_check,
518 'query-running' : _query_running}
519
520 def get_ops(self):
521 """ returns list of ops supported by this module """
522 return self._run_ops.keys()
523
524 def _init_command_handlers(self):
525 flags = self.get_flags()
526 if not self.ipcmd:
527 self.ipcmd = iproute2(**flags)
528 if not self.brctlcmd:
529 self.brctlcmd = brctl(**flags)
530 if not self.mstpctlcmd:
531 self.mstpctlcmd = mstpctlutil(**flags)
532
533 def run(self, ifaceobj, operation, query_ifaceobj=None):
534 """ run mstp configuration on the interface object passed as argument
535
536 Args:
537 **ifaceobj** (object): iface object
538
539 **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr',
540 'query-running'
541 Kwargs:
542 **query_ifaceobj** (object): query check ifaceobject. This is only
543 valid when op is 'query-checkcurr'. It is an object same as
544 ifaceobj, but contains running attribute values and its config
545 status. The modules can use it to return queried running state
546 of interfaces. status is success if the running state is same
547 as user required state in ifaceobj. error otherwise.
548 """
549 op_handler = self._run_ops.get(operation)
550 if not op_handler:
551 return
552 if operation != 'query-running' and not self._is_bridge(ifaceobj):
553 return
554 self._init_command_handlers()
555 if operation == 'query-checkcurr':
556 op_handler(self, ifaceobj, query_ifaceobj)
557 else:
558 op_handler(self, ifaceobj)