Facts Refresh (2.4 roadmap) (#23012)

Facts Refresh (2.4 roadmap)

This commit implements most of the 2.4 roadmap 'Facts Refresh'
- move facts.py to facts/__init__.py
- move facts Distribution() to its own class
- add a facts/utils.py
- move get_file_content and get_uname_version to facts/utils.py
- move Facts() class from facts/__init__ to facts/facts.py
- mv get_file_lines to facts/utils.py
- mv Ohai()/Facter() class to facts/ohai.py and facter.py
- Start moving fact Hardware() classes to facts/hardware/*.py
- mv HPUX() hardware class to facts/hardware/hpux.py
- move SunOSHardware() fact class to facts/hardware/sunos.py
- move OpenBSDHardware() class to facts/hardware/openbsd.py
- mv FreeBsdHardware() and DragonFlyHardware() to facts/hardware/
- mv NetBSDHardware() to facts/hardware/netbsd.py
- mv Darwin() hardware class to facts/hardware/darwin.py
- pep8/etc cleanups on facts/hardware/*.py
- Mv network facts classes to facts/network/*.py
- mv Virtual fact classes to facts/virtual
- mv Hardware.get_sysctl to facts/sysctl.py:get_sysctl

- Also mv get_uname_version from facts/utils.py -> distribution.py
  since distribution.py is the only thing using it.

- add collector.py with new BaseFactCollector
- add a subclass for AnsibleFactCollector
- hook up dict key munging FactNamespaces
- add some test cases for testing the names of facts
- mv timeout stuff to facts.timeout

- rm ansible_facts()/get_all_facts() etc

- Instead of calling facts.ansible_facts(), fact collection
  api used by setup.py is now to create an AnsibleFactCollector()
  and call it's collect method.

- replace Facts.get_user_facts with UserFactCollector
- add a 'systems' facts package, mv UserFactCollector there
- mv get_dns_facts to DnsFactCollector
- mv get_env_facts to EnvFactCollector
- include the timeout length in exception message

- modules and module_utils that use AnsibleFactCollector
  can now theoretically set the 'valid_subsets'

  May be useful for network facts module that currently have
  to reimplement a good chunk of facts.py to get gather_subsets
  to work.

- get_local_facts -> system/LocalFactCollector
- get_date_time -> system/date_time.py
- get_fips_facts -> system/fips.py
- get_caps_facts() -> system/caps.py
- get_apparmor_facts -> system/apparmor.py
- get_selinux_facts -> system/selinux.py
- get_lsb_facts -> system/lsb.py
- get_service_mgr_facts -> system/service_mgr.py
- Facts.is_systemd_managed ->  system/service_mgr.py
- get_pkg_mgr_facts -> system/pkg_mgr.py
- Facts()._get_mount_size_facts() -> facts.utils.get_mount_size()

- add unit test for EnvFactCollector
- add a test case for minimal gather_subsets
- add test case for collect_ids
- Make gather_subset match existing behavior or '!all'

    If 'gather_subset' is provided as '!all', the existing behavior
    (in 2.2/2.3) is that means 'dont collect any facts except those
    from the Facts() class'. So 'skip everything except
    'apparmor', 'caps', 'date_time', 'env', 'fips', 'local', 'lsb',
    'pkg_mgr', 'python', 'selinux', 'service_mgr', 'user', 'platform', etc.

    The new facts setup was making '!all' mean no facts at all, since
    it can add/exclude at a finer granularity. Since that makes more
    sense for the ansible collector, and the set of minimal facts to
    collect is really more up to setup.py to decide we do just that.

    So if setup.py needs to always collect some gather_subset, even
    on !all, setup.py needs to have the that subset added to the
    list it passes as minimal_gather_subset.

    This should fix some intg tests that assume '!all' means that
    some facts are still collected (user info and env for example).

    If we want to make setup.py collect a more minimal set, we can do that.

- force facts_dicts.keys() to a list so py3 works
- split fact collector tests to test_collectors.py

- convert Facter(Facts) -> other/facter.py:FacterFactCollector

- add FactCollector.collect_with_namespace()

    regular .collect() will return a dict with the key names
    using the base names ('ip_address', 'service_mgr' etc)

    .collect_with_namespace() will return a dict where the key names
    have been transformed with the collectors namespace, if there is
    one. For most, this means a namespace that adds 'ansible_' to the
    start of the key name.

    For 'FacterFactCollector', the namespace transforms the key to
    'facter_*'.

- add test cases for collect_with_namespace

- move all the concrete 'which facts does setup.py' stuff to setup.py

    The caller of AnsibleFactCollector.from_gather_subset() needs to
    pass in the list of collector classes now.

- update system/setup.py to import all of the fact classes and pass
  in that list.
- split the Distribution fact class up a bit

    extracted the 'distro release' file handling (ie, linux
    boxes with /etc/release, /etc/os-release etc) into its
    own class.
- extract get_cmdline_facts -> cmdline.py
- extract get_public_ssh_host_keys -> system/ssh_pub_keys.py
- extract get_platform_facts -> system/platform.py

  platform.py may be a good candidate for further splitting.

- rm test for plain Facts() base class
- let the base class for Collector unit tests provide collected_facts

    some Collectors and/or their migrated Facts() subsclasses need
    to look at facts collected by other modules ('ansible_architecture'
    the main one...).

    Collector.collect() has the collected_facts arg for this, so add
    a class variable to BaseFactsTest so we can specify it.

- mv Ohai to other/ohai.py and convert to Collector
- update hardware/*.py to return facts (no side effects)

- mv AnsibleFactCollector to setup.py
- extra collector class gathering to module method in
  facts/__init__.py (collector_classes_from_gather_subset)
- add a CollectorMetaDataCollector collector used to provide
  the 'gather_setup' fact
- add unit test module for 'setup' module
  (test/units/modules/system/setup.py)

- Collector init now doesnt need a module, but collect does

    An instance of a FactCollector() isnt tied to a AnsibleModule
    instance, but the collect() method can be, so optionally pass
    in module to FactCollector.collect() (everywhere)

- add a default_collectors for list of default collectors

  import and use it from setup.py module

  eventually, would like to replace this with a plugin loader
  style class finder/loader

- unit tests for module_utils/facts/__init__.py
- add unit tests for ohai facts collector
- remove self.facts side effect on populate() in hardware/sunos.py
- convert OpenBSDHardware() to rm side effects on self.facts
- try to rm some self.facts side effects in Network()

    plumb in collected_facts from populate() where it is needed.

    stop passing collected_facts into Network() [via cached_facts=,
    where it eventually becomes self.facts]

- nothing provides Fact() cached_facts arg now, rm it

    Facts() should be internal only implementation so nothing
    should be using it.

    Of course, now someone will.

- add a Collector.name attr to build a map of name->_fact_ids

    To properly exclude a gather_subset spec like '!hardware', we
    need to know that 'hardware' also means 'devices', 'dmi', etc.
    Before, '!hardware' would remove the 'hardware' collector name
    but not 'devices'. Since both would end up in id_collector_map,
    we would still end up with the HardwareCollector in the collector
    list. End result being that '!hardware' wouldn't stop hardware
    from being collected.

    So we need to be able to build that map, so add the Collector.name
    attribute that is the primary name (like 'hardware') and let
    Collector._fact_ids be the other fact ids that a collector is
    responsible for.

    Construct the aliases_map of Collector.name -> set of _fact_ids
    in fact/__init__.py get_collector_names, and use it when we are
    populating the exclude set.

- refactor of distribution.py

    make the big OS_FAMILY literal a little easier to read
    Also keys can now be any string instead of python literals

    99% sure the test for 'KDE Neon' was wrong
    I don't see how/where it should or could get 'Neon' instead
    of 'KDE Neon' as provided in os-release NAME=

    Use 'distribution' string for key to OS_MAP

    ie, we dont need to make it a valid python label anymore so dont.

    move _has_dist_file to module as _file_exists
    easier to mock without mucking with os.path

    mv platform.system() calls to within get_distribution_facts() instead
    of Distribution() init.

- remove _json compat module

    The code in here was to support:

      -a 'json' python module that was not the standard one included
      with python since 2.6.

      - potentially fallback to simplejson if 'json' was not available.

    'json' is available for all supported python versions now so
    no longer needed.

- mv get_collector_names -> facts.collector
- mv collector_classes_from_gather_subset -> facts.collector
- mv collector tests from test_facts -> test_collector

- Use six's reduce() in sunos/netbsd hardware facts

- rm extraneous get_uname_version in utils

  only system/distribution.py uses it

- Remove Facts() subclass metaclass usage

  - using fact_id and a platform id for matching collectors

    gut most of Facts() subclasses

    rm Facts() subclasses with weird metaclass

    only add collectors that match the fact_ids and the platform_info
    to the list of collectors used.

    atm, a collectors platform_id will default to 'Generic', and
    any platform matches 'Generic'

    goal is to select collector classes including matching the
    systems platform in collector.py, instead of relying on
    metaclasses in hardware/*. To finish this, the various
    Facts() subclasses will need to be replaced entirely with
    Collector() subclasses.

    use collector classmethod platform_match() to match the platform

    This lets the particular class decide if it is compatible with
    a given platform_info. platform_info is a dict like obj, so it could be
    expanded in the future.

    Add a default platform_match to BaseFactCollector that matches
    platform_info['system'] == cls._platform

    They were needed previously to trigger a module
    load on all the collector classes when we import
    facts/hardare so that the Hardware() and related
    classes that used __new__ and find_all_subclasses()
    would work.

    Now that is done in collectors based on platform matching
    at runtime we dont need to do it py module import/parse
    time. So the non empty __init__.pys are no longer needed
    and their is a more flexible mechanism for selection
    platform specific stuff.

    facts/facts.py is no longer used, rm'ed

- if we dont find an implement class for gather spec.. just ignore it.

  Would be useful to add a warn to warn about this case.

- Fix SD-UX typo (should be HP-UX)

- Port fix for #21893 (0 sockets) to this branch

    This readds the change from 8ad182059d
    that got lost in merge/rebase

    Fixes #21893

- port sunos fact locale fix for #24542 to this branch

    based on e558ec19cd

    Fixes #24542

    Solaris fact fix (#24793)

    ensure locale for solaris fact gathering

    fixes issue with locale interfering with proper reading of decimals

- raise exceptions in the air like we just dont care.

    Pretty much ignore any not exit exception in facts
    collection. And add some test cases.

- added new selinux fact to clarify python lib

    the selinux fact is boolean false when the library is not installed,
    a dictionary/hash otherwise, but this is ambigous
    added new fact so we can eventually remove the type dichtomy and normalize it as a dict

    Re-add of devel commit 85c7a7b844 to
    the new code layout, since it got removed in merge/rebase
This commit is contained in:
Adrian Likins 2017-06-01 11:17:49 -04:00 committed by GitHub
commit 45a9f96774
83 changed files with 14846 additions and 4165 deletions

View file

@ -0,0 +1,61 @@
# base unit test classes for ansible/module_utils/facts/ tests
# -*- coding: utf-8 -*-
#
# Ansible 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.
#
# Ansible 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 <http://www.gnu.org/licenses/>.
#
# Make coding more python3-ish
from __future__ import (absolute_import, division)
__metaclass__ = type
from ansible.compat.tests import unittest
from ansible.compat.tests.mock import Mock
class BaseFactsTest(unittest.TestCase):
# just a base class, not an actual test
__test__ = False
gather_subset = ['all']
valid_subsets = None
fact_namespace = None
collector_class = None
# a dict ansible_facts. Some fact collectors depend on facts gathered by
# other collectors (like 'ansible_architecture' or 'ansible_system') which
# can be passed via the collected_facts arg to collect()
collected_facts = None
def _mock_module(self):
mock_module = Mock()
mock_module.params = {'gather_subset': self.gather_subset,
'gather_timeout': 5,
'filter': '*'}
mock_module.get_bin_path = Mock(return_value=None)
return mock_module
def test_collect(self):
module = self._mock_module()
fact_collector = self.collector_class()
facts_dict = fact_collector.collect(module=module, collected_facts=self.collected_facts)
self.assertIsInstance(facts_dict, dict)
return facts_dict
def test_collect_with_namespace(self):
module = self._mock_module()
fact_collector = self.collector_class()
facts_dict = fact_collector.collect_with_namespace(module=module,
collected_facts=self.collected_facts)
self.assertIsInstance(facts_dict, dict)
return facts_dict

View file

@ -0,0 +1,228 @@
# unit tests for ansible other facter fact collector
# -*- coding: utf-8 -*-
#
# Ansible 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.
#
# Ansible 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 <http://www.gnu.org/licenses/>.
#
# Make coding more python3-ish
from __future__ import (absolute_import, division)
__metaclass__ = type
from ansible.compat.tests.mock import Mock, patch
from .. base import BaseFactsTest
from ansible.module_utils.facts.other.facter import FacterFactCollector
facter_json_output = '''
{
"operatingsystemmajrelease": "25",
"hardwareisa": "x86_64",
"kernel": "Linux",
"path": "/home/testuser/src/ansible/bin:/home/testuser/src/ansible/test/runner:/home/testuser/perl5/bin:/home/testuser/perl5/bin:/home/testuser/bin:/home/testuser/.local/bin:/home/testuser/pythons/bin:/usr/lib64/qt-3.3/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/testuser/.cabal/bin:/home/testuser/gopath/bin:/home/testuser/.rvm/bin",
"memorysize": "15.36 GB",
"memoryfree": "4.88 GB",
"swapsize": "7.70 GB",
"swapfree": "6.75 GB",
"swapsize_mb": "7880.00",
"swapfree_mb": "6911.41",
"memorysize_mb": "15732.95",
"memoryfree_mb": "4997.68",
"lsbmajdistrelease": "25",
"macaddress": "02:42:ea:15:d8:84",
"id": "testuser",
"domain": "example.com",
"augeasversion": "1.7.0",
"os": {
"name": "Fedora",
"family": "RedHat",
"release": {
"major": "25",
"full": "25"
},
"lsb": {
"distcodename": "TwentyFive",
"distid": "Fedora",
"distdescription": "Fedora release 25 (Twenty Five)",
"release": ":core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch",
"distrelease": "25",
"majdistrelease": "25"
}
},
"processors": {
"models": [
"Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
"Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
"Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
"Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
"Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
"Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
"Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
"Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz"
],
"count": 8,
"physicalcount": 1
},
"architecture": "x86_64",
"hardwaremodel": "x86_64",
"operatingsystem": "Fedora",
"processor0": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
"processor1": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
"processor2": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
"processor3": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
"processor4": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
"processor5": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
"processor6": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
"processor7": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
"processorcount": 8,
"uptime_seconds": 1558090,
"fqdn": "myhostname.example.com",
"rubyversion": "2.3.3",
"gid": "testuser",
"physicalprocessorcount": 1,
"netmask": "255.255.0.0",
"uniqueid": "a8c01301",
"uptime_days": 18,
"interfaces": "docker0,em1,lo,vethf20ff12,virbr0,virbr1,virbr0_nic,virbr1_nic,wlp4s0",
"ipaddress_docker0": "172.17.0.1",
"macaddress_docker0": "02:42:ea:15:d8:84",
"netmask_docker0": "255.255.0.0",
"mtu_docker0": 1500,
"macaddress_em1": "3c:97:0e:e9:28:8e",
"mtu_em1": 1500,
"ipaddress_lo": "127.0.0.1",
"netmask_lo": "255.0.0.0",
"mtu_lo": 65536,
"macaddress_vethf20ff12": "ae:6e:2b:1e:a1:31",
"mtu_vethf20ff12": 1500,
"ipaddress_virbr0": "192.168.137.1",
"macaddress_virbr0": "52:54:00:ce:82:5e",
"netmask_virbr0": "255.255.255.0",
"mtu_virbr0": 1500,
"ipaddress_virbr1": "192.168.121.1",
"macaddress_virbr1": "52:54:00:b4:68:a9",
"netmask_virbr1": "255.255.255.0",
"mtu_virbr1": 1500,
"macaddress_virbr0_nic": "52:54:00:ce:82:5e",
"mtu_virbr0_nic": 1500,
"macaddress_virbr1_nic": "52:54:00:b4:68:a9",
"mtu_virbr1_nic": 1500,
"ipaddress_wlp4s0": "192.168.1.19",
"macaddress_wlp4s0": "5c:51:4f:e6:a8:e3",
"netmask_wlp4s0": "255.255.255.0",
"mtu_wlp4s0": 1500,
"virtual": "physical",
"is_virtual": false,
"partitions": {
"sda2": {
"size": "499091456"
},
"sda1": {
"uuid": "32caaec3-ef40-4691-a3b6-438c3f9bc1c0",
"size": "1024000",
"mount": "/boot"
}
},
"lsbdistcodename": "TwentyFive",
"lsbrelease": ":core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch", # noqa
"filesystems": "btrfs,ext2,ext3,ext4,xfs",
"system_uptime": {
"seconds": 1558090,
"hours": 432,
"days": 18,
"uptime": "18 days"
},
"ipaddress": "172.17.0.1",
"timezone": "EDT",
"ps": "ps -ef",
"rubyplatform": "x86_64-linux",
"rubysitedir": "/usr/local/share/ruby/site_ruby",
"uptime": "18 days",
"lsbdistrelease": "25",
"operatingsystemrelease": "25",
"facterversion": "2.4.3",
"kernelrelease": "4.9.14-200.fc25.x86_64",
"lsbdistdescription": "Fedora release 25 (Twenty Five)",
"network_docker0": "172.17.0.0",
"network_lo": "127.0.0.0",
"network_virbr0": "192.168.137.0",
"network_virbr1": "192.168.121.0",
"network_wlp4s0": "192.168.1.0",
"lsbdistid": "Fedora",
"selinux": true,
"selinux_enforced": false,
"selinux_policyversion": "30",
"selinux_current_mode": "permissive",
"selinux_config_mode": "permissive",
"selinux_config_policy": "targeted",
"hostname": "myhostname",
"osfamily": "RedHat",
"kernelmajversion": "4.9",
"blockdevice_sr0_size": 1073741312,
"blockdevice_sr0_vendor": "MATSHITA",
"blockdevice_sr0_model": "DVD-RAM UJ8E2",
"blockdevice_sda_size": 256060514304,
"blockdevice_sda_vendor": "ATA",
"blockdevice_sda_model": "SAMSUNG MZ7TD256",
"blockdevices": "sda,sr0",
"uptime_hours": 432,
"kernelversion": "4.9.14"
}
'''
class TestFacterCollector(BaseFactsTest):
__test__ = True
gather_subset = ['!all', 'facter']
valid_subsets = ['facter']
fact_namespace = 'ansible_facter'
collector_class = FacterFactCollector
def _mock_module(self):
mock_module = Mock()
mock_module.params = {'gather_subset': self.gather_subset,
'gather_timeout': 10,
'filter': '*'}
mock_module.get_bin_path = Mock(return_value='/not/actually/facter')
mock_module.run_command = Mock(return_value=(0, facter_json_output, ''))
return mock_module
@patch('ansible.module_utils.facts.other.facter.FacterFactCollector.get_facter_output')
def test_bogus_json(self, mock_get_facter_output):
module = self._mock_module()
# bogus json
mock_get_facter_output.return_value = '{'
fact_collector = self.collector_class()
facts_dict = fact_collector.collect(module=module)
self.assertIsInstance(facts_dict, dict)
self.assertEqual(facts_dict, {})
@patch('ansible.module_utils.facts.other.facter.FacterFactCollector.run_facter')
def test_facter_non_zero_return_code(self, mock_run_facter):
module = self._mock_module()
# bogus json
mock_run_facter.return_value = (1, '{}', '')
fact_collector = self.collector_class()
facts_dict = fact_collector.collect(module=module)
self.assertIsInstance(facts_dict, dict)
# This assumes no 'facter' entry at all is correct
self.assertNotIn('facter', facts_dict)
self.assertEqual(facts_dict, {})

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,108 @@
# unit tests for ansible system lsb fact collectors
# -*- coding: utf-8 -*-
#
# Ansible 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.
#
# Ansible 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 <http://www.gnu.org/licenses/>.
#
# Make coding more python3-ish
from __future__ import (absolute_import, division)
__metaclass__ = type
from ansible.compat.tests.mock import Mock, patch
from .. base import BaseFactsTest
from ansible.module_utils.facts.system.lsb import LSBFactCollector
lsb_release_a_fedora_output = '''
LSB Version: :core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch
Distributor ID: Fedora
Description: Fedora release 25 (Twenty Five)
Release: 25
Codename: TwentyFive
''' # noqa
# FIXME: a
etc_lsb_release_ubuntu14 = '''DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.3 LTS"
'''
etc_lsb_release_no_decimal = '''DISTRIB_ID=AwesomeOS
DISTRIB_RELEASE=11
DISTRIB_CODENAME=stonehenge
DISTRIB_DESCRIPTION="AwesomeÖS 11"
'''
class TestLSBFacts(BaseFactsTest):
__test__ = True
gather_subset = ['!all', 'lsb']
valid_subsets = ['lsb']
fact_namespace = 'ansible_lsb'
collector_class = LSBFactCollector
def _mock_module(self):
mock_module = Mock()
mock_module.params = {'gather_subset': self.gather_subset,
'gather_timeout': 10,
'filter': '*'}
mock_module.get_bin_path = Mock(return_value='/usr/bin/lsb_release')
mock_module.run_command = Mock(return_value=(0, lsb_release_a_fedora_output, ''))
return mock_module
def test_lsb_release_bin(self):
module = self._mock_module()
fact_collector = self.collector_class()
facts_dict = fact_collector.collect(module=module)
self.assertIsInstance(facts_dict, dict)
self.assertEqual(facts_dict['lsb']['release'], '25')
self.assertEqual(facts_dict['lsb']['id'], 'Fedora')
self.assertEqual(facts_dict['lsb']['description'], 'Fedora release 25 (Twenty Five)')
self.assertEqual(facts_dict['lsb']['codename'], 'TwentyFive')
self.assertEqual(facts_dict['lsb']['major_release'], '25')
def test_etc_lsb_release(self):
module = self._mock_module()
module.get_bin_path = Mock(return_value=None)
with patch('ansible.module_utils.facts.system.lsb.os.path.exists',
return_value=True):
with patch('ansible.module_utils.facts.system.lsb.get_file_lines',
return_value=etc_lsb_release_ubuntu14.splitlines()):
fact_collector = self.collector_class()
facts_dict = fact_collector.collect(module=module)
self.assertIsInstance(facts_dict, dict)
self.assertEqual(facts_dict['lsb']['release'], '14.04')
self.assertEqual(facts_dict['lsb']['id'], 'Ubuntu')
self.assertEqual(facts_dict['lsb']['description'], '"Ubuntu 14.04.3 LTS"')
self.assertEqual(facts_dict['lsb']['codename'], 'trusty')
def test_etc_lsb_release_no_decimal_release(self):
module = self._mock_module()
module.get_bin_path = Mock(return_value=None)
with patch('ansible.module_utils.facts.system.lsb.os.path.exists',
return_value=True):
with patch('ansible.module_utils.facts.system.lsb.get_file_lines',
return_value=etc_lsb_release_no_decimal.splitlines()):
fact_collector = self.collector_class()
facts_dict = fact_collector.collect(module=module)
self.assertIsInstance(facts_dict, dict)
self.assertEqual(facts_dict['lsb']['release'], '11')
self.assertEqual(facts_dict['lsb']['id'], 'AwesomeOS')
self.assertEqual(facts_dict['lsb']['description'], '"AwesomeÖS 11"')
self.assertEqual(facts_dict['lsb']['codename'], 'stonehenge')

View file

@ -0,0 +1,172 @@
# This file is part of Ansible
# -*- coding: utf-8 -*-
#
#
# Ansible 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.
#
# Ansible 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 <http://www.gnu.org/licenses/>.
#
# Make coding more python3-ish
from __future__ import (absolute_import, division)
__metaclass__ = type
# for testing
from ansible.compat.tests import unittest
from ansible.module_utils.facts import collector
from ansible.module_utils.facts import default_collectors
class TestGetCollectorNames(unittest.TestCase):
def test_none(self):
res = collector.get_collector_names()
self.assertIsInstance(res, set)
self.assertEqual(res, set([]))
def test_empty_sets(self):
res = collector.get_collector_names(valid_subsets=frozenset([]),
minimal_gather_subset=frozenset([]),
gather_subset=set([]))
self.assertIsInstance(res, set)
self.assertEqual(res, set([]))
def test_empty_valid_and_min_with_all_gather_subset(self):
res = collector.get_collector_names(valid_subsets=frozenset([]),
minimal_gather_subset=frozenset([]),
gather_subset=set(['all']))
self.assertIsInstance(res, set)
self.assertEqual(res, set([]))
def test_one_valid_with_all_gather_subset(self):
valid_subsets = frozenset(['my_fact'])
res = collector.get_collector_names(valid_subsets=valid_subsets,
minimal_gather_subset=frozenset([]),
gather_subset=set(['all']))
self.assertIsInstance(res, set)
self.assertEqual(res, set(['my_fact']))
def test_one_minimal_with_all_gather_subset(self):
my_fact = 'my_fact'
valid_subsets = frozenset([my_fact])
minimal_gather_subset = valid_subsets
res = collector.get_collector_names(valid_subsets=valid_subsets,
minimal_gather_subset=minimal_gather_subset,
gather_subset=set(['all']))
self.assertIsInstance(res, set)
self.assertEqual(res, set(['my_fact']))
def test_with_all_gather_subset(self):
valid_subsets = frozenset(['my_fact', 'something_else', 'whatever'])
minimal_gather_subset = frozenset(['my_fact'])
# even with '!all', the minimal_gather_subset should be returned
res = collector.get_collector_names(valid_subsets=valid_subsets,
minimal_gather_subset=minimal_gather_subset,
gather_subset=set(['all']))
self.assertIsInstance(res, set)
self.assertEqual(res, set(['my_fact', 'something_else', 'whatever']))
def test_one_minimal_with_not_all_gather_subset(self):
valid_subsets = frozenset(['my_fact', 'something_else', 'whatever'])
minimal_gather_subset = frozenset(['my_fact'])
# even with '!all', the minimal_gather_subset should be returned
res = collector.get_collector_names(valid_subsets=valid_subsets,
minimal_gather_subset=minimal_gather_subset,
gather_subset=set(['!all']))
self.assertIsInstance(res, set)
self.assertEqual(res, set(['my_fact']))
def test_gather_subset_excludes(self):
valid_subsets = frozenset(['my_fact', 'something_else', 'whatever'])
minimal_gather_subset = frozenset(['my_fact'])
# even with '!all', the minimal_gather_subset should be returned
res = collector.get_collector_names(valid_subsets=valid_subsets,
minimal_gather_subset=minimal_gather_subset,
gather_subset=set(['all', '!my_fact', '!whatever']))
self.assertIsInstance(res, set)
# my_facts is in minimal_gather_subset, so always returned
self.assertEqual(res, set(['my_fact', 'something_else']))
def test_gather_subset_excludes_ordering(self):
valid_subsets = frozenset(['my_fact', 'something_else', 'whatever'])
minimal_gather_subset = frozenset(['my_fact'])
res = collector.get_collector_names(valid_subsets=valid_subsets,
minimal_gather_subset=minimal_gather_subset,
gather_subset=set(['!all', 'whatever']))
self.assertIsInstance(res, set)
# excludes are higher precedence than includes, so !all excludes everything
# and then minimal_gather_subset is added. so '!all', 'other' == '!all'
self.assertEqual(res, set(['my_fact']))
def test_invaid_gather_subset(self):
valid_subsets = frozenset(['my_fact', 'something_else'])
minimal_gather_subset = frozenset(['my_fact'])
self.assertRaisesRegexp(TypeError,
'Bad subset .* given to Ansible.*allowed\:.*all,.*my_fact.*',
collector.get_collector_names,
valid_subsets=valid_subsets,
minimal_gather_subset=minimal_gather_subset,
gather_subset=set(['my_fact', 'not_a_valid_gather_subset']))
class TestCollectorClassesFromGatherSubset(unittest.TestCase):
def _classes(self,
all_collector_classes=None,
valid_subsets=None,
minimal_gather_subset=None,
gather_subset=None,
gather_timeout=None):
return collector.collector_classes_from_gather_subset(all_collector_classes=all_collector_classes,
valid_subsets=valid_subsets,
minimal_gather_subset=minimal_gather_subset,
gather_subset=gather_subset,
gather_timeout=gather_timeout)
def test_no_args(self):
res = self._classes()
self.assertIsInstance(res, list)
self.assertEqual(res, [])
def test(self):
res = self._classes(all_collector_classes=default_collectors.collectors,
gather_subset=set(['!all']))
self.assertIsInstance(res, list)
self.assertEqual(res, [])
def test_env(self):
res = self._classes(all_collector_classes=default_collectors.collectors,
gather_subset=set(['env']))
self.assertIsInstance(res, list)
self.assertEqual(res, [default_collectors.EnvFactCollector])
def test_collector_specified_multiple_times(self):
res = self._classes(all_collector_classes=default_collectors.collectors,
gather_subset=set(['platform', 'all', 'machine']))
self.assertIsInstance(res, list)
self.assertIn(default_collectors.PlatformFactCollector,
res)
def test_unknown_collector(self):
# something claims 'unknown_collector' is a valid gather_subset, but there is
# no FactCollector mapped to 'unknown_collector'
self.assertRaisesRegexp(TypeError,
'Bad subset.*unknown_collector.*given to Ansible.*allowed\:.*all,.*env.*',
self._classes,
all_collector_classes=default_collectors.collectors,
gather_subset=set(['env', 'unknown_collector']))

View file

@ -0,0 +1,330 @@
# unit tests for ansible fact collectors
# -*- coding: utf-8 -*-
#
# Ansible 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.
#
# Ansible 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 <http://www.gnu.org/licenses/>.
#
# Make coding more python3-ish
from __future__ import (absolute_import, division)
__metaclass__ = type
from ansible.compat.tests.mock import Mock, patch
from . base import BaseFactsTest
from ansible.module_utils.facts import collector
from ansible.module_utils.facts.system.apparmor import ApparmorFactCollector
from ansible.module_utils.facts.system.caps import SystemCapabilitiesFactCollector
from ansible.module_utils.facts.system.cmdline import CmdLineFactCollector
from ansible.module_utils.facts.system.distribution import DistributionFactCollector
from ansible.module_utils.facts.system.dns import DnsFactCollector
from ansible.module_utils.facts.system.env import EnvFactCollector
from ansible.module_utils.facts.system.fips import FipsFactCollector
from ansible.module_utils.facts.system.pkg_mgr import PkgMgrFactCollector
from ansible.module_utils.facts.system.platform import PlatformFactCollector
from ansible.module_utils.facts.system.python import PythonFactCollector
from ansible.module_utils.facts.system.selinux import SelinuxFactCollector
from ansible.module_utils.facts.system.service_mgr import ServiceMgrFactCollector
from ansible.module_utils.facts.system.ssh_pub_keys import SshPubKeyFactCollector
from ansible.module_utils.facts.system.user import UserFactCollector
from ansible.module_utils.facts.virtual.base import VirtualCollector
from ansible.module_utils.facts.network.base import NetworkCollector
from ansible.module_utils.facts.hardware.base import HardwareCollector
class CollectorException(Exception):
pass
class ExceptionThrowingCollector(collector.BaseFactCollector):
name = 'exc_throwing'
def __init__(self, collectors=None, namespace=None, exception=None):
super(ExceptionThrowingCollector, self).__init__(collectors, namespace)
self._exception = exception or CollectorException('collection failed')
def collect(self, module=None, collected_facts=None):
raise self._exception
class TestExceptionThrowingCollector(BaseFactsTest):
__test__ = True
gather_subset = ['exc_throwing']
valid_subsets = ['exc_throwing']
collector_class = ExceptionThrowingCollector
def test_collect(self):
module = self._mock_module()
fact_collector = self.collector_class()
self.assertRaises(CollectorException,
fact_collector.collect,
module=module,
collected_facts=self.collected_facts)
def test_collect_with_namespace(self):
module = self._mock_module()
fact_collector = self.collector_class()
self.assertRaises(CollectorException,
fact_collector.collect_with_namespace,
module=module,
collected_facts=self.collected_facts)
class TestApparmorFacts(BaseFactsTest):
__test__ = True
gather_subset = ['!all', 'apparmor']
valid_subsets = ['apparmor']
fact_namespace = 'ansible_apparmor'
collector_class = ApparmorFactCollector
def test_collect(self):
facts_dict = super(TestApparmorFacts, self).test_collect()
self.assertIn('status', facts_dict['apparmor'])
class TestCapsFacts(BaseFactsTest):
__test__ = True
gather_subset = ['!all', 'caps']
valid_subsets = ['caps']
fact_namespace = 'ansible_system_capabilities'
collector_class = SystemCapabilitiesFactCollector
def _mock_module(self):
mock_module = Mock()
mock_module.params = {'gather_subset': self.gather_subset,
'gather_timeout': 10,
'filter': '*'}
mock_module.get_bin_path = Mock(return_value='/usr/sbin/capsh')
mock_module.run_command = Mock(return_value=(0, 'Current: =ep', ''))
return mock_module
class TestCmdLineFacts(BaseFactsTest):
__test__ = True
gather_subset = ['!all', 'cmdline']
valid_subsets = ['cmdline']
fact_namespace = 'ansible_cmdline'
collector_class = CmdLineFactCollector
class TestDistributionFacts(BaseFactsTest):
__test__ = True
gather_subset = ['!all', 'distribution']
valid_subsets = ['distribution']
fact_namespace = 'ansible_distribution'
collector_class = DistributionFactCollector
class TestDnsFacts(BaseFactsTest):
__test__ = True
gather_subset = ['!all', 'dns']
valid_subsets = ['dns']
fact_namespace = 'ansible_dns'
collector_class = DnsFactCollector
class TestEnvFacts(BaseFactsTest):
__test__ = True
gather_subset = ['!all', 'env']
valid_subsets = ['env']
fact_namespace = 'ansible_env'
collector_class = EnvFactCollector
def test_collect(self):
facts_dict = super(TestEnvFacts, self).test_collect()
self.assertIn('HOME', facts_dict['env'])
class TestFipsFacts(BaseFactsTest):
__test__ = True
gather_subset = ['!all', 'fips']
valid_subsets = ['fips']
fact_namespace = 'ansible_fips'
collector_class = FipsFactCollector
class TestHardwareCollector(BaseFactsTest):
__test__ = True
gather_subset = ['!all', 'hardware']
valid_subsets = ['hardware']
fact_namespace = 'ansible_hardware'
collector_class = HardwareCollector
collected_facts = {'ansible_architecture': 'x86_64'}
class TestNetworkCollector(BaseFactsTest):
__test__ = True
gather_subset = ['!all', 'network']
valid_subsets = ['network']
fact_namespace = 'ansible_network'
collector_class = NetworkCollector
class TestPkgMgrFacts(BaseFactsTest):
__test__ = True
gather_subset = ['!all', 'pkg_mgr']
valid_subsets = ['pkg_mgr']
fact_namespace = 'ansible_pkgmgr'
collector_class = PkgMgrFactCollector
class TestPlatformFactCollector(BaseFactsTest):
__test__ = True
gather_subset = ['!all', 'platform']
valid_subsets = ['platform']
fact_namespace = 'ansible_platform'
collector_class = PlatformFactCollector
class TestPythonFactCollector(BaseFactsTest):
__test__ = True
gather_subset = ['!all', 'python']
valid_subsets = ['python']
fact_namespace = 'ansible_python'
collector_class = PythonFactCollector
class TestSelinuxFacts(BaseFactsTest):
__test__ = True
gather_subset = ['!all', 'selinux']
valid_subsets = ['selinux']
fact_namespace = 'ansible_selinux'
collector_class = SelinuxFactCollector
def test_no_selinux(self):
with patch('ansible.module_utils.facts.system.selinux.HAVE_SELINUX', False):
module = self._mock_module()
fact_collector = self.collector_class()
facts_dict = fact_collector.collect(module=module)
self.assertIsInstance(facts_dict, dict)
self.assertFalse(facts_dict['selinux'])
return facts_dict
class TestServiceMgrFacts(BaseFactsTest):
__test__ = True
gather_subset = ['!all', 'service_mgr']
valid_subsets = ['service_mgr']
fact_namespace = 'ansible_service_mgr'
collector_class = ServiceMgrFactCollector
# TODO: dedupe some of this test code
@patch('ansible.module_utils.facts.system.service_mgr.get_file_content', return_value=None)
def test_no_proc1(self, mock_gfc):
# no /proc/1/comm, ps returns non-0
# should fallback to 'service'
module = self._mock_module()
module.run_command = Mock(return_value=(1, '', 'wat'))
fact_collector = self.collector_class()
facts_dict = fact_collector.collect(module=module)
self.assertIsInstance(facts_dict, dict)
self.assertEqual(facts_dict['service_mgr'], 'service')
@patch('ansible.module_utils.facts.system.service_mgr.get_file_content', return_value=None)
def test_no_proc1_ps_random_init(self, mock_gfc):
# no /proc/1/comm, ps returns '/sbin/sys11' which we dont know
# should end up return 'sys11'
module = self._mock_module()
module.run_command = Mock(return_value=(0, '/sbin/sys11', ''))
fact_collector = self.collector_class()
facts_dict = fact_collector.collect(module=module)
self.assertIsInstance(facts_dict, dict)
self.assertEqual(facts_dict['service_mgr'], 'sys11')
@patch('ansible.module_utils.facts.system.service_mgr.get_file_content', return_value=None)
def test_clowncar(self, mock_gfc):
# no /proc/1/comm, ps fails, distro and system are clowncar
# should end up return 'sys11'
module = self._mock_module()
module.run_command = Mock(return_value=(1, '', ''))
collected_facts = {'distribution': 'clowncar',
'system': 'ClownCarOS'}
fact_collector = self.collector_class()
facts_dict = fact_collector.collect(module=module,
collected_facts=collected_facts)
self.assertIsInstance(facts_dict, dict)
self.assertEqual(facts_dict['service_mgr'], 'service')
# TODO: reenable these tests when we can mock more easily
# @patch('ansible.module_utils.facts.system.service_mgr.get_file_content', return_value=None)
# def test_sunos_fallback(self, mock_gfc):
# # no /proc/1/comm, ps fails, 'system' is SunOS
# # should end up return 'smf'?
# module = self._mock_module()
# # FIXME: the result here is a kluge to at least cover more of service_mgr.collect
# # TODO: remove
# # FIXME: have to force a pid for results here to get into any of the system/distro checks
# module.run_command = Mock(return_value=(1, ' 37 ', ''))
# collected_facts = {'system': 'SunOS'}
# fact_collector = self.collector_class(module=module)
# facts_dict = fact_collector.collect(collected_facts=collected_facts)
# print('facts_dict: %s' % facts_dict)
# self.assertIsInstance(facts_dict, dict)
# self.assertEqual(facts_dict['service_mgr'], 'smf')
# @patch('ansible.module_utils.facts.system.service_mgr.get_file_content', return_value=None)
# def test_aix_fallback(self, mock_gfc):
# # no /proc/1/comm, ps fails, 'system' is SunOS
# # should end up return 'smf'?
# module = self._mock_module()
# module.run_command = Mock(return_value=(1, '', ''))
# collected_facts = {'system': 'AIX'}
# fact_collector = self.collector_class(module=module)
# facts_dict = fact_collector.collect(collected_facts=collected_facts)
# print('facts_dict: %s' % facts_dict)
# self.assertIsInstance(facts_dict, dict)
# self.assertEqual(facts_dict['service_mgr'], 'src')
# @patch('ansible.module_utils.facts.system.service_mgr.get_file_content', return_value=None)
# def test_linux_fallback(self, mock_gfc):
# # no /proc/1/comm, ps fails, 'system' is SunOS
# # should end up return 'smf'?
# module = self._mock_module()
# module.run_command = Mock(return_value=(1, ' 37 ', ''))
# collected_facts = {'system': 'Linux'}
# fact_collector = self.collector_class(module=module)
# facts_dict = fact_collector.collect(collected_facts=collected_facts)
# print('facts_dict: %s' % facts_dict)
# self.assertIsInstance(facts_dict, dict)
# self.assertEqual(facts_dict['service_mgr'], 'sdfadf')
class TestSshPubKeyFactCollector(BaseFactsTest):
__test__ = True
gather_subset = ['!all', 'ssh_pub_keys']
valid_subsets = ['ssh_pub_keys']
fact_namespace = 'ansible_ssh_pub_leys'
collector_class = SshPubKeyFactCollector
class TestUserFactCollector(BaseFactsTest):
__test__ = True
gather_subset = ['!all', 'user']
valid_subsets = ['user']
fact_namespace = 'ansible_user'
collector_class = UserFactCollector
class TestVirtualFacts(BaseFactsTest):
__test__ = True
gather_subset = ['!all', 'virtual']
valid_subsets = ['virtual']
fact_namespace = 'ansible_virtual'
collector_class = VirtualCollector

View file

@ -1,4 +1,6 @@
# This file is part of Ansible
# -*- coding: utf-8 -*-
#
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -12,6 +14,7 @@
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
# Make coding more python3-ish
from __future__ import (absolute_import, division)
@ -19,26 +22,36 @@ __metaclass__ = type
import os
import pytest
# for testing
from ansible.compat.tests import unittest
from ansible.compat.tests.mock import Mock, patch
from ansible.module_utils import facts
from ansible.module_utils.facts import hardware
from ansible.module_utils.facts import network
from ansible.module_utils.facts import virtual
class BaseTestFactsPlatform(unittest.TestCase):
platform_id = 'Generic'
fact_class = facts.Hardware
fact_class = hardware.base.Hardware
collector_class = None
"""Verify that the automagic in Hardware.__new__ selects the right subclass."""
@patch('platform.system')
def test_new(self, mock_platform):
if not self.fact_class:
pytest.skip('This platform (%s) does not have a fact_class.' % self.platform_id)
mock_platform.return_value = self.platform_id
inst = self.fact_class(module=Mock(), load_on_init=False)
self.assertIsInstance(inst, self.fact_class)
self.assertEqual(inst.platform, self.platform_id)
def test_subclass(self):
if not self.fact_class:
pytest.skip('This platform (%s) does not have a fact_class.' % self.platform_id)
# 'Generic' will try to map to platform.system() that we are not mocking here
if self.platform_id == 'Generic':
return
@ -46,130 +59,179 @@ class BaseTestFactsPlatform(unittest.TestCase):
self.assertIsInstance(inst, self.fact_class)
self.assertEqual(inst.platform, self.platform_id)
def test_collector(self):
if not self.collector_class:
pytest.skip('This test class needs to be updated to specify collector_class')
inst = self.collector_class()
self.assertIsInstance(inst, self.collector_class)
self.assertEqual(inst._platform, self.platform_id)
class TestLinuxFactsPlatform(BaseTestFactsPlatform):
platform_id = 'Linux'
fact_class = facts.LinuxHardware
fact_class = hardware.linux.LinuxHardware
collector_class = hardware.linux.LinuxHardwareCollector
class TestHurdFactsPlatform(BaseTestFactsPlatform):
platform_id = 'GNU'
fact_class = hardware.hurd.HurdHardware
collector_class = hardware.hurd.HurdHardwareCollector
class TestSunOSHardware(BaseTestFactsPlatform):
platform_id = 'SunOS'
fact_class = facts.SunOSHardware
fact_class = hardware.sunos.SunOSHardware
collector_class = hardware.sunos.SunOSHardwareCollector
class TestOpenBSDHardware(BaseTestFactsPlatform):
platform_id = 'OpenBSD'
fact_class = facts.OpenBSDHardware
fact_class = hardware.openbsd.OpenBSDHardware
collector_class = hardware.openbsd.OpenBSDHardwareCollector
class TestFreeBSDHardware(BaseTestFactsPlatform):
platform_id = 'FreeBSD'
fact_class = facts.FreeBSDHardware
fact_class = hardware.freebsd.FreeBSDHardware
collector_class = hardware.freebsd.FreeBSDHardwareCollector
class TestDragonFlyHardware(BaseTestFactsPlatform):
platform_id = 'DragonFly'
fact_class = facts.DragonFlyHardware
fact_class = None
collector_class = hardware.dragonfly.DragonFlyHardwareCollector
class TestNetBSDHardware(BaseTestFactsPlatform):
platform_id = 'NetBSD'
fact_class = facts.NetBSDHardware
fact_class = hardware.netbsd.NetBSDHardware
collector_class = hardware.netbsd.NetBSDHardwareCollector
class TestAIXHardware(BaseTestFactsPlatform):
platform_id = 'AIX'
fact_class = facts.AIX
fact_class = hardware.aix.AIXHardware
collector_class = hardware.aix.AIXHardwareCollector
class TestHPUXHardware(BaseTestFactsPlatform):
platform_id = 'HP-UX'
fact_class = facts.HPUX
fact_class = hardware.hpux.HPUXHardware
collector_class = hardware.hpux.HPUXHardwareCollector
class TestDarwinHardware(BaseTestFactsPlatform):
platform_id = 'Darwin'
fact_class = facts.Darwin
fact_class = hardware.darwin.DarwinHardware
collector_class = hardware.darwin.DarwinHardwareCollector
class TestGenericNetwork(BaseTestFactsPlatform):
platform_id = 'Generic'
fact_class = facts.Network
fact_class = network.base.Network
class TestHurdPfinetNetwork(BaseTestFactsPlatform):
platform_id = 'GNU'
fact_class = network.hurd.HurdPfinetNetwork
collector_class = network.hurd.HurdNetworkCollector
class TestLinuxNetwork(BaseTestFactsPlatform):
platform_id = 'Generic'
fact_class = facts.Network
platform_id = 'Linux'
fact_class = network.linux.LinuxNetwork
collector_class = network.linux.LinuxNetworkCollector
class TestGenericBsdIfconfigNetwork(BaseTestFactsPlatform):
platform_id = 'Generic_BSD_Ifconfig'
fact_class = facts.GenericBsdIfconfigNetwork
fact_class = network.generic_bsd.GenericBsdIfconfigNetwork
collector_class = None
class TestHPUXNetwork(BaseTestFactsPlatform):
platform_id = 'HP-UX'
fact_class = facts.HPUXNetwork
fact_class = network.hpux.HPUXNetwork
collector_class = network.hpux.HPUXNetworkCollector
class TestDarwinNetwork(BaseTestFactsPlatform):
platform_id = 'Darwin'
fact_class = facts.DarwinNetwork
fact_class = network.darwin.DarwinNetwork
collector_class = network.darwin.DarwinNetworkCollector
class TestFreeBSDNetwork(BaseTestFactsPlatform):
platform_id = 'FreeBSD'
fact_class = facts.FreeBSDNetwork
fact_class = network.freebsd.FreeBSDNetwork
collector_class = network.freebsd.FreeBSDNetworkCollector
class TestDragonFlyNetwork(BaseTestFactsPlatform):
platform_id = 'DragonFly'
fact_class = facts.DragonFlyNetwork
fact_class = network.dragonfly.DragonFlyNetwork
collector_class = network.dragonfly.DragonFlyNetworkCollector
class TestAIXNetwork(BaseTestFactsPlatform):
platform_id = 'AIX'
fact_class = facts.AIXNetwork
fact_class = network.aix.AIXNetwork
collector_class = network.aix.AIXNetworkCollector
class TestNetBSDNetwork(BaseTestFactsPlatform):
platform_id = 'NetBSD'
fact_class = network.netbsd.NetBSDNetwork
collector_class = network.netbsd.NetBSDNetworkCollector
class TestOpenBSDNetwork(BaseTestFactsPlatform):
platform_id = 'OpenBSD'
fact_class = facts.OpenBSDNetwork
fact_class = network.openbsd.OpenBSDNetwork
collector_class = network.openbsd.OpenBSDNetworkCollector
class TestSunOSNetwork(BaseTestFactsPlatform):
platform_id = 'SunOS'
fact_class = facts.SunOSNetwork
fact_class = network.sunos.SunOSNetwork
collector_class = network.sunos.SunOSNetworkCollector
class TestLinuxVirtual(BaseTestFactsPlatform):
platform_id = 'Linux'
fact_class = facts.LinuxVirtual
fact_class = virtual.linux.LinuxVirtual
collector_class = virtual.linux.LinuxVirtualCollector
class TestFreeBSDVirtual(BaseTestFactsPlatform):
platform_id = 'FreeBSD'
fact_class = facts.FreeBSDNetwork
fact_class = virtual.freebsd.FreeBSDVirtual
collector_class = virtual.freebsd.FreeBSDVirtualCollector
class TestDragonFlyVirtual(BaseTestFactsPlatform):
platform_id = 'DragonFly'
fact_class = facts.DragonFlyNetwork
class TestNetBSDVirtual(BaseTestFactsPlatform):
platform_id = 'NetBSD'
fact_class = virtual.netbsd.NetBSDVirtual
collector_class = virtual.netbsd.NetBSDVirtualCollector
class TestOpenBSDVirtual(BaseTestFactsPlatform):
platform_id = 'OpenBSD'
fact_class = facts.OpenBSDVirtual
fact_class = virtual.openbsd.OpenBSDVirtual
collector_class = virtual.openbsd.OpenBSDVirtualCollector
class TestHPUXVirtual(BaseTestFactsPlatform):
platform_id = 'HP-UX'
fact_class = facts.HPUXVirtual
fact_class = virtual.hpux.HPUXVirtual
collector_class = virtual.hpux.HPUXVirtualCollector
class TestSunOSVirtual(BaseTestFactsPlatform):
platform_id = 'SunOS'
fact_class = facts.SunOSVirtual
fact_class = virtual.sunos.SunOSVirtual
collector_class = virtual.sunos.SunOSVirtualCollector
LSBLK_OUTPUT = b"""
@ -471,38 +533,38 @@ class TestFactsLinuxHardwareGetMountFacts(unittest.TestCase):
# The Hardware subclasses freakout if instaniated directly, so
# mock platform.system and inst Hardware() so we get a LinuxHardware()
# we can test.
@patch('ansible.module_utils.facts.LinuxHardware._mtab_entries', return_value=MTAB_ENTRIES)
@patch('ansible.module_utils.facts.LinuxHardware._find_bind_mounts', return_value=BIND_MOUNTS)
@patch('ansible.module_utils.facts.LinuxHardware._lsblk_uuid', return_value=LSBLK_UUIDS)
@patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._mtab_entries', return_value=MTAB_ENTRIES)
@patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._find_bind_mounts', return_value=BIND_MOUNTS)
@patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._lsblk_uuid', return_value=LSBLK_UUIDS)
def test_get_mount_facts(self,
mock_lsblk_uuid,
mock_find_bind_mounts,
mock_mtab_entries):
module = Mock()
# Returns a LinuxHardware-ish
lh = facts.LinuxHardware(module=module, load_on_init=False)
lh = hardware.linux.LinuxHardware(module=module, load_on_init=False)
# Nothing returned, just self.facts modified as a side effect
lh.get_mount_facts()
self.assertIsInstance(lh.facts, dict)
self.assertIn('mounts', lh.facts)
self.assertIsInstance(lh.facts['mounts'], list)
self.assertIsInstance(lh.facts['mounts'][0], dict)
mount_facts = lh.get_mount_facts()
self.assertIsInstance(mount_facts, dict)
self.assertIn('mounts', mount_facts)
self.assertIsInstance(mount_facts['mounts'], list)
self.assertIsInstance(mount_facts['mounts'][0], dict)
@patch('ansible.module_utils.facts.get_file_content', return_value=MTAB)
@patch('ansible.module_utils.facts.hardware.linux.get_file_content', return_value=MTAB)
def test_get_mtab_entries(self, mock_get_file_content):
module = Mock()
lh = facts.LinuxHardware(module=module, load_on_init=False)
lh = hardware.linux.LinuxHardware(module=module, load_on_init=False)
mtab_entries = lh._mtab_entries()
self.assertIsInstance(mtab_entries, list)
self.assertIsInstance(mtab_entries[0], list)
self.assertEqual(len(mtab_entries), 38)
@patch('ansible.module_utils.facts.LinuxHardware._run_findmnt', return_value=(0, FINDMNT_OUTPUT, ''))
@patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._run_findmnt', return_value=(0, FINDMNT_OUTPUT, ''))
def test_find_bind_mounts(self, mock_run_findmnt):
module = Mock()
lh = facts.LinuxHardware(module=module, load_on_init=False)
lh = hardware.linux.LinuxHardware(module=module, load_on_init=False)
bind_mounts = lh._find_bind_mounts()
# If bind_mounts becomes another seq type, feel free to change
@ -510,10 +572,10 @@ class TestFactsLinuxHardwareGetMountFacts(unittest.TestCase):
self.assertEqual(len(bind_mounts), 1)
self.assertIn('/not/a/real/bind_mount', bind_mounts)
@patch('ansible.module_utils.facts.LinuxHardware._run_findmnt', return_value=(37, '', ''))
@patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._run_findmnt', return_value=(37, '', ''))
def test_find_bind_mounts_non_zero(self, mock_run_findmnt):
module = Mock()
lh = facts.LinuxHardware(module=module, load_on_init=False)
lh = hardware.linux.LinuxHardware(module=module, load_on_init=False)
bind_mounts = lh._find_bind_mounts()
self.assertIsInstance(bind_mounts, set)
@ -522,48 +584,48 @@ class TestFactsLinuxHardwareGetMountFacts(unittest.TestCase):
def test_find_bind_mounts_no_findmnts(self):
module = Mock()
module.get_bin_path = Mock(return_value=None)
lh = facts.LinuxHardware(module=module, load_on_init=False)
lh = hardware.linux.LinuxHardware(module=module, load_on_init=False)
bind_mounts = lh._find_bind_mounts()
self.assertIsInstance(bind_mounts, set)
self.assertEqual(len(bind_mounts), 0)
@patch('ansible.module_utils.facts.LinuxHardware._run_lsblk', return_value=(0, LSBLK_OUTPUT, ''))
@patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._run_lsblk', return_value=(0, LSBLK_OUTPUT, ''))
def test_lsblk_uuid(self, mock_run_lsblk):
module = Mock()
lh = facts.LinuxHardware(module=module, load_on_init=False)
lh = hardware.linux.LinuxHardware(module=module, load_on_init=False)
lsblk_uuids = lh._lsblk_uuid()
self.assertIsInstance(lsblk_uuids, dict)
self.assertIn(b'/dev/loop9', lsblk_uuids)
self.assertIn(b'/dev/sda1', lsblk_uuids)
self.assertEquals(lsblk_uuids[b'/dev/sda1'], b'32caaec3-ef40-4691-a3b6-438c3f9bc1c0')
self.assertEqual(lsblk_uuids[b'/dev/sda1'], b'32caaec3-ef40-4691-a3b6-438c3f9bc1c0')
@patch('ansible.module_utils.facts.LinuxHardware._run_lsblk', return_value=(37, LSBLK_OUTPUT, ''))
@patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._run_lsblk', return_value=(37, LSBLK_OUTPUT, ''))
def test_lsblk_uuid_non_zero(self, mock_run_lsblk):
module = Mock()
lh = facts.LinuxHardware(module=module, load_on_init=False)
lh = hardware.linux.LinuxHardware(module=module, load_on_init=False)
lsblk_uuids = lh._lsblk_uuid()
self.assertIsInstance(lsblk_uuids, dict)
self.assertEquals(len(lsblk_uuids), 0)
self.assertEqual(len(lsblk_uuids), 0)
def test_lsblk_uuid_no_lsblk(self):
module = Mock()
module.get_bin_path = Mock(return_value=None)
lh = facts.LinuxHardware(module=module, load_on_init=False)
lh = hardware.linux.LinuxHardware(module=module, load_on_init=False)
lsblk_uuids = lh._lsblk_uuid()
self.assertIsInstance(lsblk_uuids, dict)
self.assertEquals(len(lsblk_uuids), 0)
self.assertEqual(len(lsblk_uuids), 0)
@patch('ansible.module_utils.facts.LinuxHardware._run_lsblk', return_value=(0, LSBLK_OUTPUT_2, ''))
@patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._run_lsblk', return_value=(0, LSBLK_OUTPUT_2, ''))
def test_lsblk_uuid_dev_with_space_in_name(self, mock_run_lsblk):
module = Mock()
lh = facts.LinuxHardware(module=module, load_on_init=False)
lh = hardware.linux.LinuxHardware(module=module, load_on_init=False)
lsblk_uuids = lh._lsblk_uuid()
self.assertIsInstance(lsblk_uuids, dict)
self.assertIn(b'/dev/loop0', lsblk_uuids)
self.assertIn(b'/dev/sda1', lsblk_uuids)
self.assertEquals(lsblk_uuids[b'/dev/mapper/an-example-mapper with a space in the name'], b'84639acb-013f-4d2f-9392-526a572b4373')
self.assertEquals(lsblk_uuids[b'/dev/sda1'], b'32caaec3-ef40-4691-a3b6-438c3f9bc1c0')
self.assertEqual(lsblk_uuids[b'/dev/mapper/an-example-mapper with a space in the name'], b'84639acb-013f-4d2f-9392-526a572b4373')
self.assertEqual(lsblk_uuids[b'/dev/sda1'], b'32caaec3-ef40-4691-a3b6-438c3f9bc1c0')

View file

@ -27,40 +27,40 @@ import pytest
from ansible.compat.tests import unittest
from ansible.compat.tests.mock import patch, MagicMock
from ansible.module_utils import facts
from ansible.module_utils.facts import timeout
@pytest.fixture
def set_gather_timeout_higher():
default_timeout = facts.GATHER_TIMEOUT
facts.GATHER_TIMEOUT = facts.DEFAULT_GATHER_TIMEOUT + 5
default_timeout = timeout.GATHER_TIMEOUT
timeout.GATHER_TIMEOUT = timeout.DEFAULT_GATHER_TIMEOUT + 5
yield
facts.GATHER_TIMEOUT = default_timeout
timeout.GATHER_TIMEOUT = default_timeout
@pytest.fixture
def set_gather_timeout_lower():
default_timeout = facts.GATHER_TIMEOUT
facts.GATHER_TIMEOUT = 2
default_timeout = timeout.GATHER_TIMEOUT
timeout.GATHER_TIMEOUT = 2
yield
facts.GATHER_TIMEOUT = default_timeout
timeout.GATHER_TIMEOUT = default_timeout
@facts.timeout
@timeout.timeout
def sleep_amount_implicit(amount):
# implicit refers to the lack of argument to the decorator
time.sleep(amount)
return 'Succeeded after {0} sec'.format(amount)
@facts.timeout(facts.DEFAULT_GATHER_TIMEOUT + 5)
@timeout.timeout(timeout.DEFAULT_GATHER_TIMEOUT + 5)
def sleep_amount_explicit_higher(amount):
# explicit refers to the argument to the decorator
time.sleep(amount)
return 'Succeeded after {0} sec'.format(amount)
@facts.timeout(2)
@timeout.timeout(2)
def sleep_amount_explicit_lower(amount):
# explicit refers to the argument to the decorator
time.sleep(amount)
@ -71,7 +71,7 @@ def test_defaults_still_within_bounds():
# If the default changes outside of these bounds, some of the tests will
# no longer test the right thing. Need to review and update the timeouts
# in the other tests if this fails
assert facts.DEFAULT_GATHER_TIMEOUT >= 4
assert timeout.DEFAULT_GATHER_TIMEOUT >= 4
def test_implicit_file_default_succeeds():
@ -81,32 +81,32 @@ def test_implicit_file_default_succeeds():
def test_implicit_file_default_timesout():
# sleep_time is greater than the default
sleep_time = facts.DEFAULT_GATHER_TIMEOUT + 1
with pytest.raises(facts.TimeoutError):
sleep_time = timeout.DEFAULT_GATHER_TIMEOUT + 1
with pytest.raises(timeout.TimeoutError):
assert sleep_amount_implicit(sleep_time) == '(Not expected to succeed)'
def test_implicit_file_overridden_succeeds(set_gather_timeout_higher):
# Set sleep_time greater than the default timeout and less than our new timeout
sleep_time = facts.DEFAULT_GATHER_TIMEOUT + 1
sleep_time = timeout.DEFAULT_GATHER_TIMEOUT + 1
assert sleep_amount_implicit(sleep_time) == 'Succeeded after {0} sec'.format(sleep_time)
def test_implicit_file_overridden_timesout(set_gather_timeout_lower):
# Set sleep_time greater than our new timeout but less than the default
sleep_time = 3
with pytest.raises(facts.TimeoutError):
with pytest.raises(timeout.TimeoutError):
assert sleep_amount_implicit(sleep_time) == '(Not expected to Succeed)'
def test_explicit_succeeds():
# Set sleep_time greater than the default timeout and less than our new timeout
sleep_time = facts.DEFAULT_GATHER_TIMEOUT + 1
sleep_time = timeout.DEFAULT_GATHER_TIMEOUT + 1
assert sleep_amount_explicit_higher(sleep_time) == 'Succeeded after {0} sec'.format(sleep_time)
def test_explicit_timeout():
# Set sleep_time greater than our new timeout but less than the default
sleep_time = 3
with pytest.raises(facts.TimeoutError):
with pytest.raises(timeout.TimeoutError):
assert sleep_amount_explicit_lower(sleep_time) == '(Not expected to succeed)'

View file

@ -18,7 +18,6 @@
from __future__ import (absolute_import, division)
__metaclass__ = type
import sys
# to work around basic.py reading stdin
import json
@ -27,12 +26,10 @@ import pytest
from units.mock.procenv import swap_stdin_and_argv
# for testing
from ansible.compat.tests import unittest
from ansible.compat.tests.mock import patch
# the module we are actually testing
import ansible.module_utils.facts as facts
# the module we are actually testing (sort of
from ansible.module_utils.facts.system.distribution import DistributionFactCollector
# to generate the testcase data, you can use the script gen_distribution_version_testcase.py in hacking/tests
TESTSETS = [
@ -485,7 +482,7 @@ VERSION_ID="12.04"
"name": "KDE neon 16.04",
"result": {
"distribution_release": "xenial",
"distribution": "Neon",
"distribution": "KDE neon",
"distribution_major_version": "16",
"os_family": "Debian",
"distribution_version": "16.04"
@ -512,6 +509,7 @@ DISTRIB_DESCRIPTION="CoreOS 976.0.0 (Coeur Rouge)"
""",
},
'platform.dist': ('', '', ''),
'platform.release': '',
'result': {
"distribution": "CoreOS",
"distribution_major_version": "NA",
@ -613,6 +611,7 @@ DISTRIB_DESCRIPTION="CoreOS 976.0.0 (Coeur Rouge)"
"",
""
],
# "platform.release": 'OmniOS',
"input": {
"/etc/release": (
" OmniOS v11 r151012\n Copyright 2014 OmniTI Computer Consulting, Inc. All rights reserved.\n Use is subject to license terms.\n\n"
@ -634,6 +633,7 @@ DISTRIB_DESCRIPTION="CoreOS 976.0.0 (Coeur Rouge)"
"",
""
],
"platform.release:": "",
"input": {
"/etc/release": (" Open Storage Appliance v3.1.6\n Copyright (c) 2014 Nexenta Systems, Inc. "
"All Rights Reserved.\n Copyright (c) 2011 Oracle. All Rights Reserved.\n "
@ -838,10 +838,10 @@ def test_distribution_version(testcase):
basic._ANSIBLE_ARGS = None
module = basic.AnsibleModule(argument_spec=dict())
_test_one_distribution(facts, module, testcase)
_test_one_distribution(module, testcase)
def _test_one_distribution(facts, module, testcase):
def _test_one_distribution(module, testcase):
"""run the test on one distribution testcase
* prepare some mock functions to get the testdata in
@ -863,27 +863,34 @@ def _test_one_distribution(facts, module, testcase):
def mock_get_uname_version(module):
return testcase.get('uname_v', None)
def mock_path_exists(fname):
return fname in testcase['input']
def mock_file_exists(fname, allow_empty=False):
if fname not in testcase['input']:
return False
def mock_path_getsize(fname):
if fname in testcase['input']:
# the len is not used, but why not be honest if you can be?
return len(testcase['input'][fname])
else:
return 0
if allow_empty:
return True
return bool(len(testcase['input'][fname]))
def mock_platform_system():
return testcase.get('platform.system', 'Linux')
@patch('ansible.module_utils.facts.get_file_content', mock_get_file_content)
@patch('ansible.module_utils.facts.get_uname_version', mock_get_uname_version)
@patch('os.path.exists', mock_path_exists)
@patch('os.path.getsize', mock_path_getsize)
def mock_platform_release():
return testcase.get('platform.release', '')
def mock_platform_version():
return testcase.get('platform.version', '')
@patch('ansible.module_utils.facts.system.distribution.get_file_content', mock_get_file_content)
@patch('ansible.module_utils.facts.system.distribution.get_uname_version', mock_get_uname_version)
@patch('ansible.module_utils.facts.system.distribution._file_exists', mock_file_exists)
@patch('platform.dist', lambda: testcase['platform.dist'])
@patch('platform.system', mock_platform_system)
@patch('platform.release', mock_platform_release)
@patch('platform.version', mock_platform_version)
def get_facts(testcase):
return facts.Facts(module).populate()
distro_collector = DistributionFactCollector()
res = distro_collector.collect(module)
return res
generated_facts = get_facts(testcase)