1#!/usr/local/bin/python3.8 2# 3# This file is part of Ansible 4# 5# Ansible is free software: you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation, either version 3 of the License, or 8# (at your option) any later version. 9# 10# Ansible is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with Ansible. If not, see <http://www.gnu.org/licenses/>. 17# 18 19from __future__ import (absolute_import, division, print_function) 20__metaclass__ = type 21 22DOCUMENTATION = ''' 23--- 24module: ce_mtu 25short_description: Manages MTU settings on HUAWEI CloudEngine switches. 26description: 27 - Manages MTU settings on HUAWEI CloudEngine switches. 28author: QijunPan (@QijunPan) 29notes: 30 - Either C(sysmtu) param is required or C(interface) AND C(mtu) params are req'd. 31 - C(state=absent) unconfigures a given MTU if that value is currently present. 32 - Recommended connection is C(network_cli). 33 - This module also works with C(local) connections for legacy playbooks. 34options: 35 interface: 36 description: 37 - Full name of interface, i.e. 40GE1/0/22. 38 mtu: 39 description: 40 - MTU for a specific interface. 41 The value is an integer ranging from 46 to 9600, in bytes. 42 jumbo_max: 43 description: 44 - Maximum frame size. The default value is 9216. 45 The value is an integer and expressed in bytes. The value range is 1536 to 12224 for the CE12800 46 and 1536 to 12288 for ToR switches. 47 jumbo_min: 48 description: 49 - Non-jumbo frame size threshold. The default value is 1518. 50 The value is an integer that ranges from 1518 to jumbo_max, in bytes. 51 state: 52 description: 53 - Specify desired state of the resource. 54 default: present 55 choices: ['present','absent'] 56''' 57 58EXAMPLES = ''' 59- name: Mtu test 60 hosts: cloudengine 61 connection: local 62 gather_facts: no 63 vars: 64 cli: 65 host: "{{ inventory_hostname }}" 66 port: "{{ ansible_ssh_port }}" 67 username: "{{ username }}" 68 password: "{{ password }}" 69 transport: cli 70 71 tasks: 72 73 - name: "Config jumboframe on 40GE1/0/22" 74 community.network.ce_mtu: 75 interface: 40GE1/0/22 76 jumbo_max: 9000 77 jumbo_min: 8000 78 provider: "{{ cli }}" 79 80 - name: "Config mtu on 40GE1/0/22 (routed interface)" 81 community.network.ce_mtu: 82 interface: 40GE1/0/22 83 mtu: 1600 84 provider: "{{ cli }}" 85 86 - name: "Config mtu on 40GE1/0/23 (switched interface)" 87 community.network.ce_mtu: 88 interface: 40GE1/0/22 89 mtu: 9216 90 provider: "{{ cli }}" 91 92 - name: "Config mtu and jumboframe on 40GE1/0/22 (routed interface)" 93 community.network.ce_mtu: 94 interface: 40GE1/0/22 95 mtu: 1601 96 jumbo_max: 9001 97 jumbo_min: 8001 98 provider: "{{ cli }}" 99 100 - name: "Unconfigure mtu and jumboframe on a given interface" 101 community.network.ce_mtu: 102 state: absent 103 interface: 40GE1/0/22 104 provider: "{{ cli }}" 105''' 106 107RETURN = ''' 108proposed: 109 description: k/v pairs of parameters passed into module 110 returned: always 111 type: dict 112 sample: {"mtu": "1700", "jumbo_max": "9000", jumbo_min: "8000"} 113existing: 114 description: k/v pairs of existing mtu/sysmtu on the interface/system 115 returned: always 116 type: dict 117 sample: {"mtu": "1600", "jumbo_max": "9216", "jumbo_min": "1518"} 118end_state: 119 description: k/v pairs of mtu/sysmtu values after module execution 120 returned: always 121 type: dict 122 sample: {"mtu": "1700", "jumbo_max": "9000", jumbo_min: "8000"} 123updates: 124 description: command sent to the device 125 returned: always 126 type: list 127 sample: ["interface 40GE1/0/23", "mtu 1700", "jumboframe enable 9000 8000"] 128changed: 129 description: check to see if a change was made on the device 130 returned: always 131 type: bool 132 sample: true 133''' 134 135import re 136import copy 137from ansible.module_utils.basic import AnsibleModule 138from ansible_collections.community.network.plugins.module_utils.network.cloudengine.ce import ce_argument_spec, load_config 139from ansible.module_utils.connection import exec_command 140 141 142def is_interface_support_setjumboframe(interface): 143 """is interface support set jumboframe""" 144 145 if interface is None: 146 return False 147 support_flag = False 148 if interface.upper().startswith('GE'): 149 support_flag = True 150 elif interface.upper().startswith('10GE'): 151 support_flag = True 152 elif interface.upper().startswith('25GE'): 153 support_flag = True 154 elif interface.upper().startswith('4X10GE'): 155 support_flag = True 156 elif interface.upper().startswith('40GE'): 157 support_flag = True 158 elif interface.upper().startswith('100GE'): 159 support_flag = True 160 else: 161 support_flag = False 162 return support_flag 163 164 165def get_interface_type(interface): 166 """Gets the type of interface, such as 10GE, ETH-TRUNK, VLANIF...""" 167 168 if interface is None: 169 return None 170 171 iftype = None 172 173 if interface.upper().startswith('GE'): 174 iftype = 'ge' 175 elif interface.upper().startswith('10GE'): 176 iftype = '10ge' 177 elif interface.upper().startswith('25GE'): 178 iftype = '25ge' 179 elif interface.upper().startswith('4X10GE'): 180 iftype = '4x10ge' 181 elif interface.upper().startswith('40GE'): 182 iftype = '40ge' 183 elif interface.upper().startswith('100GE'): 184 iftype = '100ge' 185 elif interface.upper().startswith('VLANIF'): 186 iftype = 'vlanif' 187 elif interface.upper().startswith('LOOPBACK'): 188 iftype = 'loopback' 189 elif interface.upper().startswith('METH'): 190 iftype = 'meth' 191 elif interface.upper().startswith('ETH-TRUNK'): 192 iftype = 'eth-trunk' 193 elif interface.upper().startswith('VBDIF'): 194 iftype = 'vbdif' 195 elif interface.upper().startswith('NVE'): 196 iftype = 'nve' 197 elif interface.upper().startswith('TUNNEL'): 198 iftype = 'tunnel' 199 elif interface.upper().startswith('ETHERNET'): 200 iftype = 'ethernet' 201 elif interface.upper().startswith('FCOE-PORT'): 202 iftype = 'fcoe-port' 203 elif interface.upper().startswith('FABRIC-PORT'): 204 iftype = 'fabric-port' 205 elif interface.upper().startswith('STACK-PORT'): 206 iftype = 'stack-Port' 207 elif interface.upper().startswith('NULL'): 208 iftype = 'null' 209 else: 210 return None 211 212 return iftype.lower() 213 214 215class Mtu(object): 216 """set mtu""" 217 218 def __init__(self, argument_spec): 219 self.spec = argument_spec 220 self.module = None 221 self.init_module() 222 223 # interface info 224 self.interface = self.module.params['interface'] 225 self.mtu = self.module.params['mtu'] 226 self.state = self.module.params['state'] 227 self.jbf_max = self.module.params['jumbo_max'] or None 228 self.jbf_min = self.module.params['jumbo_min'] or None 229 self.jbf_config = list() 230 self.jbf_cli = "" 231 self.commands = list() 232 233 # state 234 self.changed = False 235 self.updates_cmd = list() 236 self.results = dict() 237 self.proposed = dict() 238 self.existing = dict() 239 self.end_state = dict() 240 self.intf_info = dict() # one interface info 241 self.intf_type = None # loopback tunnel ... 242 243 def init_module(self): 244 """ init_module""" 245 246 self.module = AnsibleModule( 247 argument_spec=self.spec, supports_check_mode=True) 248 249 def get_config(self, flags=None): 250 """Retrieves the current config from the device or cache 251 """ 252 flags = [] if flags is None else flags 253 254 cmd = 'display current-configuration ' 255 cmd += ' '.join(flags) 256 cmd = cmd.strip() 257 258 rc, out, err = exec_command(self.module, cmd) 259 if rc != 0: 260 self.module.fail_json(msg=err) 261 cfg = str(out).strip() 262 263 return cfg 264 265 def get_interface_dict(self, ifname): 266 """ get one interface attributes dict.""" 267 intf_info = dict() 268 269 flags = list() 270 exp = r"| ignore-case section include ^#\s+interface %s\s+" % ifname.replace(" ", "") 271 flags.append(exp) 272 output = self.get_config(flags) 273 output_list = output.split('\n') 274 if output_list is None: 275 return intf_info 276 277 mtu = None 278 for config in output_list: 279 config = config.strip() 280 if config.startswith('mtu'): 281 mtu = re.findall(r'.*mtu\s*([0-9]*)', output)[0] 282 283 intf_info = dict(ifName=ifname, 284 ifMtu=mtu) 285 286 return intf_info 287 288 def prase_jumboframe_para(self, config_str): 289 """prase_jumboframe_para""" 290 291 interface_cli = "interface %s" % (self.interface.replace(" ", "").lower()) 292 if config_str.find(interface_cli) == -1: 293 self.module.fail_json(msg='Error: Interface does not exist.') 294 295 try: 296 npos1 = config_str.index('jumboframe enable') 297 except ValueError: 298 # return default vale 299 return [9216, 1518] 300 try: 301 npos2 = config_str.index('\n', npos1) 302 config_str_tmp = config_str[npos1:npos2] 303 except ValueError: 304 config_str_tmp = config_str[npos1:] 305 306 return re.findall(r'([0-9]+)', config_str_tmp) 307 308 def cli_load_config(self): 309 """load config by cli""" 310 311 if not self.module.check_mode: 312 if len(self.commands) > 1: 313 load_config(self.module, self.commands) 314 self.changed = True 315 316 def cli_add_command(self, command, undo=False): 317 """add command to self.update_cmd and self.commands""" 318 319 if undo and command.lower() not in ["quit", "return"]: 320 cmd = "undo " + command 321 else: 322 cmd = command 323 324 self.commands.append(cmd) # set to device 325 326 def get_jumboframe_config(self): 327 """ get_jumboframe_config""" 328 329 flags = list() 330 exp = r"| ignore-case section include ^#\s+interface %s\s+" % self.interface.replace(" ", "") 331 flags.append(exp) 332 output = self.get_config(flags) 333 output = output.replace('*', '').lower() 334 335 return self.prase_jumboframe_para(output) 336 337 def set_jumboframe(self): 338 """ set_jumboframe""" 339 340 if self.state == "present": 341 if not self.jbf_max and not self.jbf_min: 342 return 343 344 jbf_value = self.get_jumboframe_config() 345 self.jbf_config = copy.deepcopy(jbf_value) 346 if len(jbf_value) == 1: 347 jbf_value.append("1518") 348 self.jbf_config.append("1518") 349 if not self.jbf_max: 350 return 351 352 if (len(jbf_value) > 2) or (len(jbf_value) == 0): 353 self.module.fail_json( 354 msg='Error: Get jubmoframe config value num error.') 355 if self.jbf_min is None: 356 if jbf_value[0] == self.jbf_max: 357 return 358 else: 359 if (jbf_value[0] == self.jbf_max) \ 360 and (jbf_value[1] == self.jbf_min): 361 return 362 if jbf_value[0] != self.jbf_max: 363 jbf_value[0] = self.jbf_max 364 if (jbf_value[1] != self.jbf_min) and (self.jbf_min is not None): 365 jbf_value[1] = self.jbf_min 366 else: 367 jbf_value.pop(1) 368 else: 369 jbf_value = self.get_jumboframe_config() 370 self.jbf_config = copy.deepcopy(jbf_value) 371 if (jbf_value == [9216, 1518]): 372 return 373 jbf_value = [9216, 1518] 374 375 if len(jbf_value) == 2: 376 self.jbf_cli = "jumboframe enable %s %s" % ( 377 jbf_value[0], jbf_value[1]) 378 else: 379 self.jbf_cli = "jumboframe enable %s" % (jbf_value[0]) 380 self.cli_add_command(self.jbf_cli) 381 382 if self.state == "present": 383 if self.jbf_min: 384 self.updates_cmd.append( 385 "jumboframe enable %s %s" % (self.jbf_max, self.jbf_min)) 386 else: 387 self.updates_cmd.append("jumboframe enable %s" % (self.jbf_max)) 388 else: 389 self.updates_cmd.append("undo jumboframe enable") 390 391 return 392 393 def merge_interface(self, ifname, mtu): 394 """ Merge interface mtu.""" 395 396 xmlstr = '' 397 change = False 398 399 command = "interface %s" % ifname 400 self.cli_add_command(command) 401 402 if self.state == "present": 403 if mtu and self.intf_info["ifMtu"] != mtu: 404 command = "mtu %s" % mtu 405 self.cli_add_command(command) 406 self.updates_cmd.append("mtu %s" % mtu) 407 change = True 408 else: 409 if self.intf_info["ifMtu"] != '1500' and self.intf_info["ifMtu"]: 410 command = "mtu 1500" 411 self.cli_add_command(command) 412 self.updates_cmd.append("undo mtu") 413 change = True 414 415 return 416 417 def check_params(self): 418 """Check all input params""" 419 420 # interface type check 421 if self.interface: 422 self.intf_type = get_interface_type(self.interface) 423 if not self.intf_type: 424 self.module.fail_json( 425 msg='Error: Interface name of %s ' 426 'is error.' % self.interface) 427 428 if not self.intf_type: 429 self.module.fail_json( 430 msg='Error: Interface %s is error.') 431 432 # mtu check mtu 433 if self.mtu: 434 if not self.mtu.isdigit(): 435 self.module.fail_json(msg='Error: Mtu is invalid.') 436 # check mtu range 437 if int(self.mtu) < 46 or int(self.mtu) > 9600: 438 self.module.fail_json( 439 msg='Error: Mtu is not in the range from 46 to 9600.') 440 # get interface info 441 self.intf_info = self.get_interface_dict(self.interface) 442 if not self.intf_info: 443 self.module.fail_json(msg='Error: interface does not exist.') 444 445 # check interface can set jumbo frame 446 if self.state == 'present': 447 if self.jbf_max: 448 if not is_interface_support_setjumboframe(self.interface): 449 self.module.fail_json( 450 msg='Error: Interface %s does not support jumboframe set.' % self.interface) 451 if not self.jbf_max.isdigit(): 452 self.module.fail_json( 453 msg='Error: Max jumboframe is not digit.') 454 if (int(self.jbf_max) > 12288) or (int(self.jbf_max) < 1536): 455 self.module.fail_json( 456 msg='Error: Max jumboframe is between 1536 to 12288.') 457 458 if self.jbf_min: 459 if not self.jbf_min.isdigit(): 460 self.module.fail_json( 461 msg='Error: Min jumboframe is not digit.') 462 if not self.jbf_max: 463 self.module.fail_json( 464 msg='Error: please specify max jumboframe value.') 465 if (int(self.jbf_min) > int(self.jbf_max)) or (int(self.jbf_min) < 1518): 466 self.module.fail_json( 467 msg='Error: Min jumboframe is between ' 468 '1518 to jumboframe max value.') 469 470 if self.jbf_min is not None: 471 if self.jbf_max is None: 472 self.module.fail_json( 473 msg='Error: please input MAX jumboframe ' 474 'value.') 475 476 def get_proposed(self): 477 """ get_proposed""" 478 479 self.proposed['state'] = self.state 480 if self.interface: 481 self.proposed["interface"] = self.interface 482 483 if self.state == 'present': 484 if self.mtu: 485 self.proposed["mtu"] = self.mtu 486 if self.jbf_max: 487 if self.jbf_min: 488 self.proposed["jumboframe"] = "jumboframe enable %s %s" % ( 489 self.jbf_max, self.jbf_min) 490 else: 491 self.proposed[ 492 "jumboframe"] = "jumboframe enable %s %s" % (self.jbf_max, 1518) 493 494 def get_existing(self): 495 """ get_existing""" 496 497 if self.intf_info: 498 self.existing["interface"] = self.intf_info["ifName"] 499 self.existing["mtu"] = self.intf_info["ifMtu"] 500 501 if self.intf_info: 502 if not self.existing["interface"]: 503 self.existing["interface"] = self.interface 504 505 if len(self.jbf_config) != 2: 506 return 507 508 self.existing["jumboframe"] = "jumboframe enable %s %s" % ( 509 self.jbf_config[0], self.jbf_config[1]) 510 511 def get_end_state(self): 512 """ get_end_state""" 513 514 if self.intf_info: 515 end_info = self.get_interface_dict(self.interface) 516 if end_info: 517 self.end_state["interface"] = end_info["ifName"] 518 self.end_state["mtu"] = end_info["ifMtu"] 519 if self.intf_info: 520 if not self.end_state["interface"]: 521 self.end_state["interface"] = self.interface 522 523 if self.state == 'absent': 524 self.end_state["jumboframe"] = "jumboframe enable %s %s" % ( 525 9216, 1518) 526 elif not self.jbf_max and not self.jbf_min: 527 if len(self.jbf_config) != 2: 528 return 529 self.end_state["jumboframe"] = "jumboframe enable %s %s" % ( 530 self.jbf_config[0], self.jbf_config[1]) 531 elif self.jbf_min: 532 self.end_state["jumboframe"] = "jumboframe enable %s %s" % ( 533 self.jbf_max, self.jbf_min) 534 else: 535 self.end_state[ 536 "jumboframe"] = "jumboframe enable %s %s" % (self.jbf_max, 1518) 537 if self.end_state == self.existing: 538 self.changed = False 539 540 def work(self): 541 """worker""" 542 self.check_params() 543 544 self.get_proposed() 545 546 self.merge_interface(self.interface, self.mtu) 547 self.set_jumboframe() 548 self.cli_load_config() 549 550 self.get_existing() 551 self.get_end_state() 552 self.results['changed'] = self.changed 553 self.results['proposed'] = self.proposed 554 self.results['existing'] = self.existing 555 self.results['end_state'] = self.end_state 556 if self.changed: 557 self.results['updates'] = self.updates_cmd 558 else: 559 self.results['updates'] = list() 560 561 self.module.exit_json(**self.results) 562 563 564def main(): 565 """ main""" 566 567 argument_spec = dict( 568 interface=dict(required=True, type='str'), 569 mtu=dict(type='str'), 570 state=dict(choices=['absent', 'present'], 571 default='present', required=False), 572 jumbo_max=dict(type='str'), 573 jumbo_min=dict(type='str'), 574 ) 575 argument_spec.update(ce_argument_spec) 576 interface = Mtu(argument_spec) 577 interface.work() 578 579 580if __name__ == '__main__': 581 main() 582