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