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