1#!/usr/bin/python
2#
3# Copyright: Ansible Project
4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5
6from __future__ import absolute_import, division, print_function
7__metaclass__ = type
8
9ANSIBLE_METADATA = {'metadata_version': '1.1',
10                    'status': ['preview'],
11                    'supported_by': 'community'}
12
13DOCUMENTATION = """
14---
15module: onyx_pfc_interface
16version_added: "2.5"
17author: "Samer Deeb (@samerd)"
18short_description: Manage priority flow control on ONYX network devices
19description:
20  - This module provides declarative management of priority flow control (PFC)
21    on interfaces of Mellanox ONYX network devices.
22notes:
23  - Tested on ONYX 3.6.4000
24options:
25  name:
26    description:
27      - Name of the interface PFC should be configured on.
28  aggregate:
29    description: List of interfaces PFC should be configured on.
30  purge:
31    description:
32      - Purge interfaces not defined in the aggregate parameter.
33    type: bool
34    default: false
35  state:
36    description:
37      - State of the PFC configuration.
38    default: enabled
39    choices: ['enabled', 'disabled']
40"""
41
42EXAMPLES = """
43- name: configure PFC
44  onyx_pfc_interface:
45    name: Eth1/1
46    state: enabled
47"""
48
49RETURN = """
50commands:
51  description: The list of configuration mode commands to send to the device.
52  returned: always
53  type: list
54  sample:
55    - interface ethernet 1/17 dcb priority-flow-control mode on
56"""
57from copy import deepcopy
58import re
59
60from ansible.module_utils.basic import AnsibleModule
61from ansible.module_utils.network.common.utils import remove_default_spec
62from ansible.module_utils.six import iteritems
63
64from ansible.module_utils.network.onyx.onyx import BaseOnyxModule
65from ansible.module_utils.network.onyx.onyx import show_cmd
66
67
68class OnyxPfcInterfaceModule(BaseOnyxModule):
69    PFC_IF_REGEX = re.compile(
70        r"^(Eth\d+\/\d+)|(Eth\d+\/\d+\/\d+)|(Po\d+)|(Mpo\d+)$")
71
72    _purge = False
73
74    @classmethod
75    def _get_element_spec(cls):
76        return dict(
77            name=dict(type='str'),
78            state=dict(default='enabled',
79                       choices=['enabled', 'disabled']),
80        )
81
82    @classmethod
83    def _get_aggregate_spec(cls, element_spec):
84        aggregate_spec = deepcopy(element_spec)
85        aggregate_spec['name'] = dict(required=True)
86
87        # remove default in aggregate spec, to handle common arguments
88        remove_default_spec(aggregate_spec)
89        return aggregate_spec
90
91    def init_module(self):
92        """ module initialization
93        """
94        element_spec = self._get_element_spec()
95        aggregate_spec = self._get_aggregate_spec(element_spec)
96        argument_spec = dict(
97            aggregate=dict(type='list', elements='dict',
98                           options=aggregate_spec),
99            purge=dict(default=False, type='bool'),
100        )
101        argument_spec.update(element_spec)
102        required_one_of = [['name', 'aggregate']]
103        mutually_exclusive = [['name', 'aggregate']]
104        self._module = AnsibleModule(
105            argument_spec=argument_spec,
106            required_one_of=required_one_of,
107            mutually_exclusive=mutually_exclusive,
108            supports_check_mode=True)
109
110    def get_required_config(self):
111        self._required_config = list()
112        module_params = self._module.params
113        aggregate = module_params.get('aggregate')
114        self._purge = module_params.get('purge', False)
115        if aggregate:
116            for item in aggregate:
117                for key in item:
118                    if item.get(key) is None:
119                        item[key] = module_params[key]
120                self.validate_param_values(item, item)
121                req_item = item.copy()
122                self._required_config.append(req_item)
123        else:
124            params = {
125                'name': module_params['name'],
126                'state': module_params['state'],
127            }
128            self.validate_param_values(params)
129            self._required_config.append(params)
130
131    def _create_if_pfc_data(self, if_name, if_pfc_data):
132        state = self.get_config_attr(if_pfc_data, "PFC oper")
133        state = state.lower()
134        return dict(
135            name=if_name,
136            state=state)
137
138    def _get_pfc_config(self):
139        return show_cmd(self._module, "show dcb priority-flow-control")
140
141    def load_current_config(self):
142        # called in base class in run function
143        self._os_version = self._get_os_version()
144        self._current_config = dict()
145        pfc_config = self._get_pfc_config()
146        if not pfc_config:
147            return
148        if self._os_version >= self.ONYX_API_VERSION:
149            if len(pfc_config) >= 3:
150                pfc_config = pfc_config[2]
151            else:
152                pfc_config = dict()
153        else:
154            if 'Table 2' in pfc_config:
155                pfc_config = pfc_config['Table 2']
156
157        for if_name, if_pfc_data in iteritems(pfc_config):
158            match = self.PFC_IF_REGEX.match(if_name)
159            if not match:
160                continue
161            if if_pfc_data:
162                if_pfc_data = if_pfc_data[0]
163                self._current_config[if_name] = \
164                    self._create_if_pfc_data(if_name, if_pfc_data)
165
166    def _get_interface_cmd_name(self, if_name):
167        if if_name.startswith('Eth'):
168            return if_name.replace("Eth", "ethernet ")
169        if if_name.startswith('Po'):
170            return if_name.replace("Po", "port-channel ")
171        if if_name.startswith('Mpo'):
172            return if_name.replace("Mpo", "mlag-port-channel ")
173        self._module.fail_json(
174            msg='invalid interface name: %s' % if_name)
175
176    def _add_if_pfc_commands(self, if_name, req_state):
177        cmd_prefix = "interface %s " % self._get_interface_cmd_name(if_name)
178
179        if req_state == 'disabled':
180            pfc_cmd = 'no dcb priority-flow-control mode force'
181        else:
182            pfc_cmd = 'dcb priority-flow-control mode on force'
183        self._commands.append(cmd_prefix + pfc_cmd)
184
185    def _gen_pfc_commands(self, if_name, curr_conf, req_state):
186        curr_state = curr_conf.get('state', 'disabled')
187        if curr_state != req_state:
188            self._add_if_pfc_commands(if_name, req_state)
189
190    def generate_commands(self):
191        req_interfaces = set()
192        for req_conf in self._required_config:
193            req_state = req_conf['state']
194            if_name = req_conf['name']
195            if req_state == 'enabled':
196                req_interfaces.add(if_name)
197            curr_conf = self._current_config.get(if_name, {})
198            self._gen_pfc_commands(if_name, curr_conf, req_state)
199        if self._purge:
200            for if_name, curr_conf in iteritems(self._current_config):
201                if if_name not in req_interfaces:
202                    req_state = 'disabled'
203                    self._gen_pfc_commands(if_name, curr_conf, req_state)
204
205
206def main():
207    """ main entry point for module execution
208    """
209    OnyxPfcInterfaceModule.main()
210
211
212if __name__ == '__main__':
213    main()
214