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_wireless_controller_hotspot20_h2qp_wan_metric
27short_description: Configure WAN metrics 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 wireless_controller_hotspot20 feature and h2qp_wan_metric 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    wireless_controller_hotspot20_h2qp_wan_metric:
76        description:
77            - Configure WAN metrics.
78        default: null
79        type: dict
80        suboptions:
81            downlink_load:
82                description:
83                    - Downlink load.
84                type: int
85            downlink_speed:
86                description:
87                    - Downlink speed (in kilobits/s).
88                type: int
89            link_at_capacity:
90                description:
91                    - Link at capacity.
92                type: str
93                choices:
94                    - enable
95                    - disable
96            link_status:
97                description:
98                    - Link status.
99                type: str
100                choices:
101                    - up
102                    - down
103                    - in-test
104            load_measurement_duration:
105                description:
106                    - Load measurement duration (in tenths of a second).
107                type: int
108            name:
109                description:
110                    - WAN metric name.
111                required: true
112                type: str
113            symmetric_wan_link:
114                description:
115                    - WAN link symmetry.
116                type: str
117                choices:
118                    - symmetric
119                    - asymmetric
120            uplink_load:
121                description:
122                    - Uplink load.
123                type: int
124            uplink_speed:
125                description:
126                    - Uplink speed (in kilobits/s).
127                type: int
128'''
129
130EXAMPLES = '''
131- hosts: fortigates
132  collections:
133    - fortinet.fortios
134  connection: httpapi
135  vars:
136   vdom: "root"
137   ansible_httpapi_use_ssl: yes
138   ansible_httpapi_validate_certs: no
139   ansible_httpapi_port: 443
140  tasks:
141  - name: Configure WAN metrics.
142    fortios_wireless_controller_hotspot20_h2qp_wan_metric:
143      vdom:  "{{ vdom }}"
144      state: "present"
145      access_token: "<your_own_value>"
146      wireless_controller_hotspot20_h2qp_wan_metric:
147        downlink_load: "3"
148        downlink_speed: "4"
149        link_at_capacity: "enable"
150        link_status: "up"
151        load_measurement_duration: "7"
152        name: "default_name_8"
153        symmetric_wan_link: "symmetric"
154        uplink_load: "10"
155        uplink_speed: "11"
156
157'''
158
159RETURN = '''
160build:
161  description: Build number of the fortigate image
162  returned: always
163  type: str
164  sample: '1547'
165http_method:
166  description: Last method used to provision the content into FortiGate
167  returned: always
168  type: str
169  sample: 'PUT'
170http_status:
171  description: Last result given by FortiGate on last operation applied
172  returned: always
173  type: str
174  sample: "200"
175mkey:
176  description: Master key (id) used in the last call to FortiGate
177  returned: success
178  type: str
179  sample: "id"
180name:
181  description: Name of the table used to fulfill the request
182  returned: always
183  type: str
184  sample: "urlfilter"
185path:
186  description: Path of the table used to fulfill the request
187  returned: always
188  type: str
189  sample: "webfilter"
190revision:
191  description: Internal revision number
192  returned: always
193  type: str
194  sample: "17.0.2.10658"
195serial:
196  description: Serial number of the unit
197  returned: always
198  type: str
199  sample: "FGVMEVYYQT3AB5352"
200status:
201  description: Indication of the operation's result
202  returned: always
203  type: str
204  sample: "success"
205vdom:
206  description: Virtual domain used
207  returned: always
208  type: str
209  sample: "root"
210version:
211  description: Version of the FortiGate
212  returned: always
213  type: str
214  sample: "v5.6.3"
215
216'''
217from ansible.module_utils.basic import AnsibleModule
218from ansible.module_utils.connection import Connection
219from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
220from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
221from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
222from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
223from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
224from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
225from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
226
227
228def filter_wireless_controller_hotspot20_h2qp_wan_metric_data(json):
229    option_list = ['downlink_load', 'downlink_speed', 'link_at_capacity',
230                   'link_status', 'load_measurement_duration', 'name',
231                   'symmetric_wan_link', 'uplink_load', 'uplink_speed']
232    dictionary = {}
233
234    for attribute in option_list:
235        if attribute in json and json[attribute] is not None:
236            dictionary[attribute] = json[attribute]
237
238    return dictionary
239
240
241def underscore_to_hyphen(data):
242    if isinstance(data, list):
243        for i, elem in enumerate(data):
244            data[i] = underscore_to_hyphen(elem)
245    elif isinstance(data, dict):
246        new_data = {}
247        for k, v in data.items():
248            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
249        data = new_data
250
251    return data
252
253
254def wireless_controller_hotspot20_h2qp_wan_metric(data, fos, check_mode=False):
255
256    vdom = data['vdom']
257
258    state = data['state']
259
260    wireless_controller_hotspot20_h2qp_wan_metric_data = data['wireless_controller_hotspot20_h2qp_wan_metric']
261    filtered_data = underscore_to_hyphen(filter_wireless_controller_hotspot20_h2qp_wan_metric_data(wireless_controller_hotspot20_h2qp_wan_metric_data))
262
263    # check_mode starts from here
264    if check_mode:
265        mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom)
266        current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey)
267        is_existed = current_data and current_data.get('http_status') == 200 \
268            and isinstance(current_data.get('results'), list) \
269            and len(current_data['results']) > 0
270
271        # 2. if it exists and the state is 'present' then compare current settings with desired
272        if state == 'present' or state is True:
273            if mkey is None:
274                return False, True, filtered_data
275
276            # if mkey exists then compare each other
277            # record exits and they're matched or not
278            if is_existed:
279                is_same = is_same_comparison(
280                    serialize(current_data['results'][0]), serialize(filtered_data))
281                return False, not is_same, filtered_data
282
283            # record does not exist
284            return False, True, filtered_data
285
286        if state == 'absent':
287            if mkey is None:
288                return False, False, filtered_data
289
290            if is_existed:
291                return False, True, filtered_data
292            return False, False, filtered_data
293
294        return True, False, {'reason: ': 'Must provide state parameter'}
295
296    if state == "present" or state is True:
297        return fos.set('wireless-controller.hotspot20',
298                       'h2qp-wan-metric',
299                       data=filtered_data,
300                       vdom=vdom)
301
302    elif state == "absent":
303        return fos.delete('wireless-controller.hotspot20',
304                          'h2qp-wan-metric',
305                          mkey=filtered_data['name'],
306                          vdom=vdom)
307    else:
308        fos._module.fail_json(msg='state must be present or absent!')
309
310
311def is_successful_status(status):
312    return status['status'] == "success" or \
313        status['http_method'] == "DELETE" and status['http_status'] == 404
314
315
316def fortios_wireless_controller_hotspot20(data, fos, check_mode):
317
318    if data['wireless_controller_hotspot20_h2qp_wan_metric']:
319        resp = wireless_controller_hotspot20_h2qp_wan_metric(data, fos, check_mode)
320    else:
321        fos._module.fail_json(msg='missing task body: %s' % ('wireless_controller_hotspot20_h2qp_wan_metric'))
322    if check_mode:
323        return resp
324    return not is_successful_status(resp), \
325        resp['status'] == "success" and \
326        (resp['revision_changed'] if 'revision_changed' in resp else True), \
327        resp
328
329
330versioned_schema = {
331    "type": "list",
332    "children": {
333        "uplink_speed": {
334            "type": "integer",
335            "revisions": {
336                "v6.0.0": True,
337                "v7.0.0": True,
338                "v6.0.5": True,
339                "v6.4.4": True,
340                "v6.4.0": True,
341                "v6.4.1": True,
342                "v6.2.0": True,
343                "v6.2.3": True,
344                "v6.2.5": True,
345                "v6.2.7": True,
346                "v6.0.11": True
347            }
348        },
349        "name": {
350            "type": "string",
351            "revisions": {
352                "v6.0.0": True,
353                "v7.0.0": True,
354                "v6.0.5": True,
355                "v6.4.4": True,
356                "v6.4.0": True,
357                "v6.4.1": True,
358                "v6.2.0": True,
359                "v6.2.3": True,
360                "v6.2.5": True,
361                "v6.2.7": True,
362                "v6.0.11": True
363            }
364        },
365        "link_status": {
366            "type": "string",
367            "options": [
368                {
369                    "value": "up",
370                    "revisions": {
371                        "v6.0.0": True,
372                        "v7.0.0": True,
373                        "v6.0.5": True,
374                        "v6.4.4": True,
375                        "v6.4.0": True,
376                        "v6.4.1": True,
377                        "v6.2.0": True,
378                        "v6.2.3": True,
379                        "v6.2.5": True,
380                        "v6.2.7": True,
381                        "v6.0.11": True
382                    }
383                },
384                {
385                    "value": "down",
386                    "revisions": {
387                        "v6.0.0": True,
388                        "v7.0.0": True,
389                        "v6.0.5": True,
390                        "v6.4.4": True,
391                        "v6.4.0": True,
392                        "v6.4.1": True,
393                        "v6.2.0": True,
394                        "v6.2.3": True,
395                        "v6.2.5": True,
396                        "v6.2.7": True,
397                        "v6.0.11": True
398                    }
399                },
400                {
401                    "value": "in-test",
402                    "revisions": {
403                        "v6.0.0": True,
404                        "v7.0.0": True,
405                        "v6.0.5": True,
406                        "v6.4.4": True,
407                        "v6.4.0": True,
408                        "v6.4.1": True,
409                        "v6.2.0": True,
410                        "v6.2.3": True,
411                        "v6.2.5": True,
412                        "v6.2.7": True,
413                        "v6.0.11": True
414                    }
415                }
416            ],
417            "revisions": {
418                "v6.0.0": True,
419                "v7.0.0": True,
420                "v6.0.5": True,
421                "v6.4.4": True,
422                "v6.4.0": True,
423                "v6.4.1": True,
424                "v6.2.0": True,
425                "v6.2.3": True,
426                "v6.2.5": True,
427                "v6.2.7": True,
428                "v6.0.11": True
429            }
430        },
431        "link_at_capacity": {
432            "type": "string",
433            "options": [
434                {
435                    "value": "enable",
436                    "revisions": {
437                        "v6.0.0": True,
438                        "v7.0.0": True,
439                        "v6.0.5": True,
440                        "v6.4.4": True,
441                        "v6.4.0": True,
442                        "v6.4.1": True,
443                        "v6.2.0": True,
444                        "v6.2.3": True,
445                        "v6.2.5": True,
446                        "v6.2.7": True,
447                        "v6.0.11": True
448                    }
449                },
450                {
451                    "value": "disable",
452                    "revisions": {
453                        "v6.0.0": True,
454                        "v7.0.0": True,
455                        "v6.0.5": True,
456                        "v6.4.4": True,
457                        "v6.4.0": True,
458                        "v6.4.1": True,
459                        "v6.2.0": True,
460                        "v6.2.3": True,
461                        "v6.2.5": True,
462                        "v6.2.7": True,
463                        "v6.0.11": True
464                    }
465                }
466            ],
467            "revisions": {
468                "v6.0.0": True,
469                "v7.0.0": True,
470                "v6.0.5": True,
471                "v6.4.4": True,
472                "v6.4.0": True,
473                "v6.4.1": True,
474                "v6.2.0": True,
475                "v6.2.3": True,
476                "v6.2.5": True,
477                "v6.2.7": True,
478                "v6.0.11": True
479            }
480        },
481        "uplink_load": {
482            "type": "integer",
483            "revisions": {
484                "v6.0.0": True,
485                "v7.0.0": True,
486                "v6.0.5": True,
487                "v6.4.4": True,
488                "v6.4.0": True,
489                "v6.4.1": True,
490                "v6.2.0": True,
491                "v6.2.3": True,
492                "v6.2.5": True,
493                "v6.2.7": True,
494                "v6.0.11": True
495            }
496        },
497        "downlink_speed": {
498            "type": "integer",
499            "revisions": {
500                "v6.0.0": True,
501                "v7.0.0": True,
502                "v6.0.5": True,
503                "v6.4.4": True,
504                "v6.4.0": True,
505                "v6.4.1": True,
506                "v6.2.0": True,
507                "v6.2.3": True,
508                "v6.2.5": True,
509                "v6.2.7": True,
510                "v6.0.11": True
511            }
512        },
513        "symmetric_wan_link": {
514            "type": "string",
515            "options": [
516                {
517                    "value": "symmetric",
518                    "revisions": {
519                        "v6.0.0": True,
520                        "v7.0.0": True,
521                        "v6.0.5": True,
522                        "v6.4.4": True,
523                        "v6.4.0": True,
524                        "v6.4.1": True,
525                        "v6.2.0": True,
526                        "v6.2.3": True,
527                        "v6.2.5": True,
528                        "v6.2.7": True,
529                        "v6.0.11": True
530                    }
531                },
532                {
533                    "value": "asymmetric",
534                    "revisions": {
535                        "v6.0.0": True,
536                        "v7.0.0": True,
537                        "v6.0.5": True,
538                        "v6.4.4": True,
539                        "v6.4.0": True,
540                        "v6.4.1": True,
541                        "v6.2.0": True,
542                        "v6.2.3": True,
543                        "v6.2.5": True,
544                        "v6.2.7": True,
545                        "v6.0.11": True
546                    }
547                }
548            ],
549            "revisions": {
550                "v6.0.0": True,
551                "v7.0.0": True,
552                "v6.0.5": True,
553                "v6.4.4": True,
554                "v6.4.0": True,
555                "v6.4.1": True,
556                "v6.2.0": True,
557                "v6.2.3": True,
558                "v6.2.5": True,
559                "v6.2.7": True,
560                "v6.0.11": True
561            }
562        },
563        "load_measurement_duration": {
564            "type": "integer",
565            "revisions": {
566                "v6.0.0": True,
567                "v7.0.0": True,
568                "v6.0.5": True,
569                "v6.4.4": True,
570                "v6.4.0": True,
571                "v6.4.1": True,
572                "v6.2.0": True,
573                "v6.2.3": True,
574                "v6.2.5": True,
575                "v6.2.7": True,
576                "v6.0.11": True
577            }
578        },
579        "downlink_load": {
580            "type": "integer",
581            "revisions": {
582                "v6.0.0": True,
583                "v7.0.0": True,
584                "v6.0.5": True,
585                "v6.4.4": True,
586                "v6.4.0": True,
587                "v6.4.1": True,
588                "v6.2.0": True,
589                "v6.2.3": True,
590                "v6.2.5": True,
591                "v6.2.7": True,
592                "v6.0.11": True
593            }
594        }
595    },
596    "revisions": {
597        "v6.0.0": True,
598        "v7.0.0": True,
599        "v6.0.5": True,
600        "v6.4.4": True,
601        "v6.4.0": True,
602        "v6.4.1": True,
603        "v6.2.0": True,
604        "v6.2.3": True,
605        "v6.2.5": True,
606        "v6.2.7": True,
607        "v6.0.11": True
608    }
609}
610
611
612def main():
613    module_spec = schema_to_module_spec(versioned_schema)
614    mkeyname = 'name'
615    fields = {
616        "access_token": {"required": False, "type": "str", "no_log": True},
617        "enable_log": {"required": False, "type": bool},
618        "vdom": {"required": False, "type": "str", "default": "root"},
619        "state": {"required": True, "type": "str",
620                  "choices": ["present", "absent"]},
621        "wireless_controller_hotspot20_h2qp_wan_metric": {
622            "required": False, "type": "dict", "default": None,
623            "options": {
624            }
625        }
626    }
627    for attribute_name in module_spec['options']:
628        fields["wireless_controller_hotspot20_h2qp_wan_metric"]['options'][attribute_name] = module_spec['options'][attribute_name]
629        if mkeyname and mkeyname == attribute_name:
630            fields["wireless_controller_hotspot20_h2qp_wan_metric"]['options'][attribute_name]['required'] = True
631
632    check_legacy_fortiosapi()
633    module = AnsibleModule(argument_spec=fields,
634                           supports_check_mode=True)
635
636    versions_check_result = None
637    if module._socket_path:
638        connection = Connection(module._socket_path)
639        if 'access_token' in module.params:
640            connection.set_option('access_token', module.params['access_token'])
641
642        if 'enable_log' in module.params:
643            connection.set_option('enable_log', module.params['enable_log'])
644        else:
645            connection.set_option('enable_log', False)
646        fos = FortiOSHandler(connection, module, mkeyname)
647        versions_check_result = check_schema_versioning(fos, versioned_schema, "wireless_controller_hotspot20_h2qp_wan_metric")
648
649        is_error, has_changed, result = fortios_wireless_controller_hotspot20(module.params, fos, module.check_mode)
650
651    else:
652        module.fail_json(**FAIL_SOCKET_MSG)
653
654    if versions_check_result and versions_check_result['matched'] is False:
655        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
656
657    if not is_error:
658        if versions_check_result and versions_check_result['matched'] is False:
659            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
660        else:
661            module.exit_json(changed=has_changed, meta=result)
662    else:
663        if versions_check_result and versions_check_result['matched'] is False:
664            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
665        else:
666            module.fail_json(msg="Error in repo", meta=result)
667
668
669if __name__ == '__main__':
670    main()
671