Files from PR#60621, continue working on fortios_facts modules (#61405)

* Files from PR#60621, continue working on fortios_facts modules

* PR#61405 unit test module test_fortios_facts.py

* sanity fixed in test_fortios_facts unit test module
This commit is contained in:
Don Yao 2019-08-28 10:28:41 -04:00 committed by Nilashish Chakraborty
parent add33ccbdc
commit 7a1237781d
11 changed files with 613 additions and 0 deletions

View file

@ -0,0 +1,45 @@
from __future__ import (absolute_import, division, print_function)
# Copyright 2019 Fortinet, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
__metaclass__ = type
"""
The arg spec for the fortios monitor module.
"""
class FactsArgs(object):
""" The arg spec for the fortios monitor module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
"host": {"required": False, "type": "str"},
"username": {"required": False, "type": "str"},
"password": {"required": False, "type": "str", "no_log": True},
"vdom": {"required": False, "type": "str", "default": "root"},
"https": {"required": False, "type": "bool", "default": True},
"ssl_verify": {"required": False, "type": "bool", "default": False},
"gather_subset": {
"required": True, "type": "list", "elements": "dict",
"options": {
"fact": {"required": True, "type": "str"},
"filters": {"required": False, "type": "list", "elements": "dict"}
}
}
}

View file

@ -0,0 +1,28 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Fortinet, Inc.
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The arg spec for the fortios_facts module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class SystemArgs(object):
"""The arg spec for the fortios_facts module
"""
FACT_SYSTEM_SUBSETS = frozenset([
'system_current-admins_select',
'system_firmware_select',
'system_fortimanager_status',
'system_ha-checksums_select',
'system_interface_select',
'system_status_select',
'system_time_select',
])
def __init__(self, **kwargs):
pass

View file

@ -0,0 +1,92 @@
from __future__ import (absolute_import, division, print_function)
# Copyright 2019 Fortinet, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
__metaclass__ = type
"""
The facts class for fortios
this file validates each subset of monitor and selectively
calls the appropriate facts gathering and monitoring function
"""
from ansible.module_utils.network.fortios.argspec.facts.facts import FactsArgs
from ansible.module_utils.network.fortios.argspec.system.system import SystemArgs
from ansible.module_utils.network.common.facts.facts import FactsBase
from ansible.module_utils.network.fortios.facts.system.system import SystemFacts
class Facts(FactsBase):
""" The facts class for fortios
"""
FACT_SUBSETS = {
"system": SystemFacts
}
def __init__(self, module, fos=None, subset=None):
super(Facts, self).__init__(module)
self._fos = fos
self._subset = subset
def gen_runable(self, subsets, valid_subsets):
""" Generate the runable subset
:param module: The module instance
:param subsets: The provided subsets
:param valid_subsets: The valid subsets
:rtype: list
:returns: The runable subsets
"""
runable_subsets = []
FACT_DETAIL_SUBSETS = []
FACT_DETAIL_SUBSETS.extend(SystemArgs.FACT_SYSTEM_SUBSETS)
for subset in subsets:
if subset['fact'] not in FACT_DETAIL_SUBSETS:
self._module.fail_json(msg='Subset must be one of [%s], got %s' %
(', '.join(sorted([item for item in FACT_DETAIL_SUBSETS])), subset['fact']))
for valid_subset in frozenset(self.FACT_SUBSETS.keys()):
if subset['fact'].startswith(valid_subset):
runable_subsets.append((subset, valid_subset))
return runable_subsets
def get_network_legacy_facts(self, fact_legacy_obj_map, legacy_facts_type=None):
if not legacy_facts_type:
legacy_facts_type = self._gather_subset
runable_subsets = self.gen_runable(legacy_facts_type, frozenset(fact_legacy_obj_map.keys()))
if runable_subsets:
self.ansible_facts['ansible_net_gather_subset'] = []
instances = list()
for (subset, valid_subset) in runable_subsets:
instances.append(fact_legacy_obj_map[valid_subset](self._module, self._fos, subset))
for inst in instances:
inst.populate_facts(self._connection, self.ansible_facts)
def get_facts(self, facts_type=None, data=None):
""" Collect the facts for fortios
:param facts_type: List of facts types
:param data: previously collected conf
:rtype: dict
:return: the facts gathered
"""
self.get_network_legacy_facts(self.FACT_SUBSETS, facts_type)
return self.ansible_facts, self._warnings

View file

@ -0,0 +1,63 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Fortinet, Inc.
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The fortios system facts class
It is in this file the runtime information is collected from the device
for a given resource, parsed, and the facts tree is populated
based on the configuration.
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import re
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.fortios.argspec.system.system import SystemArgs
class SystemFacts(object):
""" The fortios system facts class
"""
def __init__(self, module, fos=None, subset=None, subspec='config', options='options'):
self._module = module
self._fos = fos
self._subset = subset
def populate_facts(self, connection, ansible_facts, data=None):
""" Populate the facts for system
:param connection: the device connection
:param ansible_facts: Facts dictionary
:rtype: dictionary
:returns: facts
"""
ansible_facts['ansible_network_resources'].pop('system', None)
facts = {}
if self._subset['fact'].startswith(tuple(SystemArgs.FACT_SYSTEM_SUBSETS)):
gather_method = getattr(self, self._subset['fact'].replace('-', '_'), self.system_fact)
resp = gather_method()
facts.update({self._subset['fact']: resp})
ansible_facts['ansible_network_resources'].update(facts)
return ansible_facts
def system_fact(self):
fos = self._fos
vdom = self._module.params['vdom']
return fos.monitor('system', self._subset['fact'][len('system_'):].replace('_', '/'), vdom=vdom)
def system_interface_select(self):
fos = self._fos
vdom = self._module.params['vdom']
query_string = '?vdom=' + vdom
system_interface_select_param = self._subset['filters']
if system_interface_select_param:
for filter in system_interface_select_param:
for key, val in filter.items():
if val:
query_string += '&' + str(key) + '=' + str(val)
return fos.monitor('system', self._subset['fact'][len('system_'):].replace('_', '/') + query_string, vdom=None)

View file

@ -0,0 +1,282 @@
#!/usr/bin/python
from __future__ import (absolute_import, division, print_function)
# Copyright 2019 Fortinet, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
__metaclass__ = type
ANSIBLE_METADATA = {'status': ['preview'],
'supported_by': 'community',
'metadata_version': '1.1'}
DOCUMENTATION = '''
---
module: fortios_facts
version_added: "2.9"
short_description: Get facts about fortios devices.
description:
- Collects facts from network devices running the fortios operating
system. This module places the facts gathered in the fact tree keyed by the
respective resource name. This facts module will only collect those
facts which user specified in playbook.
author:
- Don Yao (@fortinetps)
- Miguel Angel Munoz (@mamunozgonzalez)
- Nicolas Thomas (@thomnico)
notes:
- Support both legacy mode (local_action) and httpapi
- Legacy mode run as a local_action in your playbook, requires fortiosapi library developed by Fortinet
- httpapi mode is the new recommend way for network modules
requirements:
- fortiosapi>=0.9.8
options:
host:
description:
- FortiOS or FortiGate IP address.
type: str
required: false
username:
description:
- FortiOS or FortiGate username.
type: str
required: false
password:
description:
- FortiOS or FortiGate password.
type: str
default: ""
required: false
vdom:
description:
- Virtual domain, among those defined previously. A vdom is a
virtual instance of the FortiGate that can be configured and
used as a different unit.
type: str
default: root
required: false
https:
description:
- Indicates if the requests towards FortiGate must use HTTPS protocol.
type: bool
default: true
required: false
ssl_verify:
description:
- Ensures FortiGate certificate must be verified by a proper CA.
type: bool
default: false
required: false
gather_subset:
description:
- When supplied, this argument will restrict the facts collected
to a given subset. Possible values for this argument include
system_current-admins_select, system_firmware_select,
system_fortimanager_status, system_ha-checksums_select,
system_interface_select, system_status_select and system_time_select
type: list
elements: dict
required: true
suboptions:
fact:
description:
- Name of the facts to gather
type: str
required: true
filters:
description:
- Filters apply when gathering facts
type: list
elements: dict
required: false
'''
EXAMPLES = '''
- hosts: localhost
vars:
host: "192.168.122.40"
username: "admin"
password: ""
vdom: "root"
ssl_verify: "False"
tasks:
- name: gather basic system status facts
fortios_facts:
host: "{{ host }}"
username: "{{ username }}"
password: "{{ password }}"
vdom: "{{ vdom }}"
gather_subset:
- fact: 'system_status_select'
- name: gather all pysical interfaces status facts
fortios_facts:
host: "{{ host }}"
username: "{{ username }}"
password: "{{ password }}"
vdom: "{{ vdom }}"
gather_subset:
- fact: 'system_interface_select'
- name: gather gather all pysical and vlan interfaces status facts
fortios_facts:
host: "{{ host }}"
username: "{{ username }}"
password: "{{ password }}"
vdom: "{{ vdom }}"
gather_subset:
- fact: 'system_interface_select'
filters:
- include_vlan: true
- name: gather basic system info and physical interface port3 status facts
fortios_facts:
host: "{{ host }}"
username: "{{ username }}"
password: "{{ password }}"
vdom: "{{ vdom }}"
gather_subset:
- fact: 'system_status_select'
- fact: 'system_interface_select'
filters:
- interface_name: 'port3'
'''
RETURN = '''
build:
description: Build number of the fortigate image
returned: always
type: str
sample: '1547'
http_method:
description: Last method used to provision the content into FortiGate
returned: always
type: str
sample: 'GET'
name:
description: Name of the table used to fulfill the request
returned: always
type: str
sample: "firmware"
path:
description: Path of the table used to fulfill the request
returned: always
type: str
sample: "system"
revision:
description: Internal revision number
returned: always
type: str
sample: "17.0.2.10658"
serial:
description: Serial number of the unit
returned: always
type: str
sample: "FGVMEVYYQT3AB5352"
status:
description: Indication of the operation's result
returned: always
type: str
sample: "success"
vdom:
description: Virtual domain used
returned: always
type: str
sample: "root"
version:
description: Version of the FortiGate
returned: always
type: str
sample: "v5.6.3"
ansible_facts:
description: The list of fact subsets collected from the device
returned: always
type: dict
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.connection import Connection
from ansible.module_utils.network.fortios.fortios import FortiOSHandler
from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG
from ansible.module_utils.network.fortios.argspec.facts.facts import FactsArgs
from ansible.module_utils.network.fortios.facts.facts import Facts
def login(data, fos):
host = data['host']
username = data['username']
password = data['password']
ssl_verify = data['ssl_verify']
fos.debug('on')
if 'https' in data and not data['https']:
fos.https('off')
else:
fos.https('on')
fos.login(host, username, password, verify=ssl_verify)
def main():
""" Main entry point for AnsibleModule
"""
argument_spec = FactsArgs.argument_spec
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=False)
# legacy_mode refers to using fortiosapi instead of HTTPAPI
legacy_mode = 'host' in module.params and module.params['host'] is not None and \
'username' in module.params and module.params['username'] is not None and \
'password' in module.params and module.params['password'] is not None
if not legacy_mode:
if module._socket_path:
warnings = []
connection = Connection(module._socket_path)
module._connection = connection
fos = FortiOSHandler(connection)
result = Facts(module, fos).get_facts()
ansible_facts, additional_warnings = result
warnings.extend(additional_warnings)
module.exit_json(ansible_facts=ansible_facts, warnings=warnings)
else:
module.fail_json(**FAIL_SOCKET_MSG)
else:
try:
from fortiosapi import FortiOSAPI
except ImportError:
module.fail_json(msg="fortiosapi module is required")
warnings = []
fos = FortiOSAPI()
login(module.params, fos)
module._connection = fos
result = Facts(module, fos).get_facts()
ansible_facts, additional_warnings = result
warnings.extend(additional_warnings)
module.exit_json(ansible_facts=ansible_facts, warnings=warnings)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,103 @@
# Copyright 2019 Fortinet, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <https://www.gnu.org/licenses/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
import json
import pytest
from mock import ANY
from units.modules.utils import exit_json, fail_json
from units.compat import unittest
from units.compat.mock import patch
from ansible.module_utils import basic
from ansible.module_utils.network.fortios.fortios import FortiOSHandler
try:
from ansible.module_utils.network.fortios.facts.facts import Facts
from ansible.modules.network.fortios import fortios_facts
except ImportError:
pytest.skip("Could not load required modules for testing", allow_module_level=True)
@pytest.fixture(autouse=True)
def connection_mock(mocker):
connection_class_mock = mocker.patch('ansible.modules.network.fortios.fortios_facts.Connection')
return connection_class_mock
fos_instance = FortiOSHandler(connection_mock)
def test_facts_get(mocker):
monitor_method_result = {'status': 'success', 'http_method': 'GET', 'http_status': 200}
monitor_method_mock = mocker.patch('ansible.module_utils.network.fortios.fortios.FortiOSHandler.monitor', return_value=monitor_method_result)
mock_module = patch.multiple(basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json)
mock_module._connection = connection_mock
# test case 01, args with single gather_subset
args = {
'vdom': 'root',
'gather_subset': [
{'fact': 'system_status_select'},
]
}
mock_module.params = args
response, ignore = Facts(mock_module, fos_instance).get_facts()
monitor_method_mock.assert_called_with('system', 'status/select', vdom='root')
assert response['ansible_network_resources']['system_status_select']['status'] == 'success'
assert response['ansible_network_resources']['system_status_select']['http_status'] == 200
# test case 02, args with single gather_subset with filters
args = {
'vdom': 'root',
'gather_subset': [
{'fact': 'system_interface_select', 'filters': [{'include_vlan': 'true'}, {'interface_name': 'port3'}]},
]
}
mock_module.params = args
response, ignore = Facts(mock_module, fos_instance).get_facts()
monitor_method_mock.assert_called_with('system', 'interface/select?vdom=root&include_vlan=true&interface_name=port3', vdom=None)
assert response['ansible_network_resources']['system_interface_select']['status'] == 'success'
assert response['ansible_network_resources']['system_interface_select']['http_status'] == 200
# test case 03, args with multiple gather_subset
args = {
'vdom': 'root',
'gather_subset': [
{'fact': 'system_current-admins_select'},
{'fact': 'system_firmware_select'},
{'fact': 'system_fortimanager_status'},
{'fact': 'system_ha-checksums_select'},
]
}
mock_module.params = args
response, ignore = Facts(mock_module, fos_instance).get_facts()
monitor_method_mock.assert_any_call('system', 'current-admins/select', vdom='root')
monitor_method_mock.assert_any_call('system', 'firmware/select', vdom='root')
monitor_method_mock.assert_any_call('system', 'fortimanager/status', vdom='root')
monitor_method_mock.assert_any_call('system', 'ha-checksums/select', vdom='root')
assert response['ansible_network_resources']['system_ha-checksums_select']['status'] == 'success'
assert response['ansible_network_resources']['system_ha-checksums_select']['http_status'] == 200