1#!/usr/local/bin/python3.8
2from __future__ import (absolute_import, division, print_function)
3# Copyright 2019-2020 Fortinet, Inc.
4#
5# This program 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# This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
17
18__metaclass__ = type
19
20ANSIBLE_METADATA = {'status': ['preview'],
21                    'supported_by': 'community',
22                    'metadata_version': '1.1'}
23
24DOCUMENTATION = '''
25---
26module: fortios_system_tos_based_priority
27short_description: Configure Type of Service (ToS) based priority table to set network traffic priorities in Fortinet's FortiOS and FortiGate.
28description:
29    - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the
30      user to set and modify system feature and tos_based_priority category.
31      Examples include all parameters and values need to be adjusted to datasources before usage.
32      Tested with FOS v6.0.0
33version_added: "2.10"
34author:
35    - Link Zheng (@chillancezen)
36    - Jie Xue (@JieX19)
37    - Hongbin Lu (@fgtdev-hblu)
38    - Frank Shen (@frankshen01)
39    - Miguel Angel Munoz (@mamunozgonzalez)
40    - Nicolas Thomas (@thomnico)
41notes:
42    - Legacy fortiosapi has been deprecated, httpapi is the preferred way to run playbooks
43
44requirements:
45    - ansible>=2.9.0
46options:
47    access_token:
48        description:
49            - Token-based authentication.
50              Generated from GUI of Fortigate.
51        type: str
52        required: false
53    enable_log:
54        description:
55            - Enable/Disable logging for task.
56        type: bool
57        required: false
58        default: false
59    vdom:
60        description:
61            - Virtual domain, among those defined previously. A vdom is a
62              virtual instance of the FortiGate that can be configured and
63              used as a different unit.
64        type: str
65        default: root
66
67    state:
68        description:
69            - Indicates whether to create or remove the object.
70        type: str
71        required: true
72        choices:
73            - present
74            - absent
75    system_tos_based_priority:
76        description:
77            - Configure Type of Service (ToS) based priority table to set network traffic priorities.
78        default: null
79        type: dict
80        suboptions:
81            id:
82                description:
83                    - Item ID.
84                required: true
85                type: int
86            priority:
87                description:
88                    - ToS based priority level to low, medium or high (these priorities match firewall traffic shaping priorities) .
89                type: str
90                choices:
91                    - low
92                    - medium
93                    - high
94            tos:
95                description:
96                    - 'Value of the ToS byte in the IP datagram header (0-15, 8: minimize delay, 4: maximize throughput, 2: maximize reliability, 1: minimize
97                       monetary cost, and 0: ).'
98                type: int
99'''
100
101EXAMPLES = '''
102- hosts: fortigates
103  collections:
104    - fortinet.fortios
105  connection: httpapi
106  vars:
107   vdom: "root"
108   ansible_httpapi_use_ssl: yes
109   ansible_httpapi_validate_certs: no
110   ansible_httpapi_port: 443
111  tasks:
112  - name: Configure Type of Service (ToS) based priority table to set network traffic priorities.
113    fortios_system_tos_based_priority:
114      vdom:  "{{ vdom }}"
115      state: "present"
116      access_token: "<your_own_value>"
117      system_tos_based_priority:
118        id:  "3"
119        priority: "low"
120        tos: "5"
121
122'''
123
124RETURN = '''
125build:
126  description: Build number of the fortigate image
127  returned: always
128  type: str
129  sample: '1547'
130http_method:
131  description: Last method used to provision the content into FortiGate
132  returned: always
133  type: str
134  sample: 'PUT'
135http_status:
136  description: Last result given by FortiGate on last operation applied
137  returned: always
138  type: str
139  sample: "200"
140mkey:
141  description: Master key (id) used in the last call to FortiGate
142  returned: success
143  type: str
144  sample: "id"
145name:
146  description: Name of the table used to fulfill the request
147  returned: always
148  type: str
149  sample: "urlfilter"
150path:
151  description: Path of the table used to fulfill the request
152  returned: always
153  type: str
154  sample: "webfilter"
155revision:
156  description: Internal revision number
157  returned: always
158  type: str
159  sample: "17.0.2.10658"
160serial:
161  description: Serial number of the unit
162  returned: always
163  type: str
164  sample: "FGVMEVYYQT3AB5352"
165status:
166  description: Indication of the operation's result
167  returned: always
168  type: str
169  sample: "success"
170vdom:
171  description: Virtual domain used
172  returned: always
173  type: str
174  sample: "root"
175version:
176  description: Version of the FortiGate
177  returned: always
178  type: str
179  sample: "v5.6.3"
180
181'''
182from ansible.module_utils.basic import AnsibleModule
183from ansible.module_utils.connection import Connection
184from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
185from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
186from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
187from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
188from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
189from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
190from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
191
192
193def filter_system_tos_based_priority_data(json):
194    option_list = ['id', 'priority', 'tos']
195    dictionary = {}
196
197    for attribute in option_list:
198        if attribute in json and json[attribute] is not None:
199            dictionary[attribute] = json[attribute]
200
201    return dictionary
202
203
204def underscore_to_hyphen(data):
205    if isinstance(data, list):
206        for i, elem in enumerate(data):
207            data[i] = underscore_to_hyphen(elem)
208    elif isinstance(data, dict):
209        new_data = {}
210        for k, v in data.items():
211            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
212        data = new_data
213
214    return data
215
216
217def system_tos_based_priority(data, fos, check_mode=False):
218
219    vdom = data['vdom']
220
221    state = data['state']
222
223    system_tos_based_priority_data = data['system_tos_based_priority']
224    filtered_data = underscore_to_hyphen(filter_system_tos_based_priority_data(system_tos_based_priority_data))
225
226    # check_mode starts from here
227    if check_mode:
228        mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom)
229        current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey)
230        is_existed = current_data and current_data.get('http_status') == 200 \
231            and isinstance(current_data.get('results'), list) \
232            and len(current_data['results']) > 0
233
234        # 2. if it exists and the state is 'present' then compare current settings with desired
235        if state == 'present' or state is True:
236            if mkey is None:
237                return False, True, filtered_data
238
239            # if mkey exists then compare each other
240            # record exits and they're matched or not
241            if is_existed:
242                is_same = is_same_comparison(
243                    serialize(current_data['results'][0]), serialize(filtered_data))
244                return False, not is_same, filtered_data
245
246            # record does not exist
247            return False, True, filtered_data
248
249        if state == 'absent':
250            if mkey is None:
251                return False, False, filtered_data
252
253            if is_existed:
254                return False, True, filtered_data
255            return False, False, filtered_data
256
257        return True, False, {'reason: ': 'Must provide state parameter'}
258
259    if state == "present" or state is True:
260        return fos.set('system',
261                       'tos-based-priority',
262                       data=filtered_data,
263                       vdom=vdom)
264
265    elif state == "absent":
266        return fos.delete('system',
267                          'tos-based-priority',
268                          mkey=filtered_data['id'],
269                          vdom=vdom)
270    else:
271        fos._module.fail_json(msg='state must be present or absent!')
272
273
274def is_successful_status(status):
275    return status['status'] == "success" or \
276        status['http_method'] == "DELETE" and status['http_status'] == 404
277
278
279def fortios_system(data, fos, check_mode):
280
281    if data['system_tos_based_priority']:
282        resp = system_tos_based_priority(data, fos, check_mode)
283    else:
284        fos._module.fail_json(msg='missing task body: %s' % ('system_tos_based_priority'))
285    if check_mode:
286        return resp
287    return not is_successful_status(resp), \
288        resp['status'] == "success" and \
289        (resp['revision_changed'] if 'revision_changed' in resp else True), \
290        resp
291
292
293versioned_schema = {
294    "type": "list",
295    "children": {
296        "priority": {
297            "type": "string",
298            "options": [
299                {
300                    "value": "low",
301                    "revisions": {
302                        "v6.0.0": True,
303                        "v7.0.0": True,
304                        "v6.0.5": True,
305                        "v6.4.4": True,
306                        "v6.4.0": True,
307                        "v6.4.1": True,
308                        "v6.2.0": True,
309                        "v6.2.3": True,
310                        "v6.2.5": True,
311                        "v6.2.7": True,
312                        "v6.0.11": True
313                    }
314                },
315                {
316                    "value": "medium",
317                    "revisions": {
318                        "v6.0.0": True,
319                        "v7.0.0": True,
320                        "v6.0.5": True,
321                        "v6.4.4": True,
322                        "v6.4.0": True,
323                        "v6.4.1": True,
324                        "v6.2.0": True,
325                        "v6.2.3": True,
326                        "v6.2.5": True,
327                        "v6.2.7": True,
328                        "v6.0.11": True
329                    }
330                },
331                {
332                    "value": "high",
333                    "revisions": {
334                        "v6.0.0": True,
335                        "v7.0.0": True,
336                        "v6.0.5": True,
337                        "v6.4.4": True,
338                        "v6.4.0": True,
339                        "v6.4.1": True,
340                        "v6.2.0": True,
341                        "v6.2.3": True,
342                        "v6.2.5": True,
343                        "v6.2.7": True,
344                        "v6.0.11": True
345                    }
346                }
347            ],
348            "revisions": {
349                "v6.0.0": True,
350                "v7.0.0": True,
351                "v6.0.5": True,
352                "v6.4.4": True,
353                "v6.4.0": True,
354                "v6.4.1": True,
355                "v6.2.0": True,
356                "v6.2.3": True,
357                "v6.2.5": True,
358                "v6.2.7": True,
359                "v6.0.11": True
360            }
361        },
362        "tos": {
363            "type": "integer",
364            "revisions": {
365                "v6.0.0": True,
366                "v7.0.0": True,
367                "v6.0.5": True,
368                "v6.4.4": True,
369                "v6.4.0": True,
370                "v6.4.1": True,
371                "v6.2.0": True,
372                "v6.2.3": True,
373                "v6.2.5": True,
374                "v6.2.7": True,
375                "v6.0.11": True
376            }
377        },
378        "id": {
379            "type": "integer",
380            "revisions": {
381                "v6.0.0": True,
382                "v7.0.0": True,
383                "v6.0.5": True,
384                "v6.4.4": True,
385                "v6.4.0": True,
386                "v6.4.1": True,
387                "v6.2.0": True,
388                "v6.2.3": True,
389                "v6.2.5": True,
390                "v6.2.7": True,
391                "v6.0.11": True
392            }
393        }
394    },
395    "revisions": {
396        "v6.0.0": True,
397        "v7.0.0": True,
398        "v6.0.5": True,
399        "v6.4.4": True,
400        "v6.4.0": True,
401        "v6.4.1": True,
402        "v6.2.0": True,
403        "v6.2.3": True,
404        "v6.2.5": True,
405        "v6.2.7": True,
406        "v6.0.11": True
407    }
408}
409
410
411def main():
412    module_spec = schema_to_module_spec(versioned_schema)
413    mkeyname = 'id'
414    fields = {
415        "access_token": {"required": False, "type": "str", "no_log": True},
416        "enable_log": {"required": False, "type": bool},
417        "vdom": {"required": False, "type": "str", "default": "root"},
418        "state": {"required": True, "type": "str",
419                  "choices": ["present", "absent"]},
420        "system_tos_based_priority": {
421            "required": False, "type": "dict", "default": None,
422            "options": {
423            }
424        }
425    }
426    for attribute_name in module_spec['options']:
427        fields["system_tos_based_priority"]['options'][attribute_name] = module_spec['options'][attribute_name]
428        if mkeyname and mkeyname == attribute_name:
429            fields["system_tos_based_priority"]['options'][attribute_name]['required'] = True
430
431    check_legacy_fortiosapi()
432    module = AnsibleModule(argument_spec=fields,
433                           supports_check_mode=True)
434
435    versions_check_result = None
436    if module._socket_path:
437        connection = Connection(module._socket_path)
438        if 'access_token' in module.params:
439            connection.set_option('access_token', module.params['access_token'])
440
441        if 'enable_log' in module.params:
442            connection.set_option('enable_log', module.params['enable_log'])
443        else:
444            connection.set_option('enable_log', False)
445        fos = FortiOSHandler(connection, module, mkeyname)
446        versions_check_result = check_schema_versioning(fos, versioned_schema, "system_tos_based_priority")
447
448        is_error, has_changed, result = fortios_system(module.params, fos, module.check_mode)
449
450    else:
451        module.fail_json(**FAIL_SOCKET_MSG)
452
453    if versions_check_result and versions_check_result['matched'] is False:
454        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
455
456    if not is_error:
457        if versions_check_result and versions_check_result['matched'] is False:
458            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
459        else:
460            module.exit_json(changed=has_changed, meta=result)
461    else:
462        if versions_check_result and versions_check_result['matched'] is False:
463            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
464        else:
465            module.fail_json(msg="Error in repo", meta=result)
466
467
468if __name__ == '__main__':
469    main()
470