From 45a9f967749ec68e2077fe1d1d32dd37660ab376 Mon Sep 17 00:00:00 2001 From: Adrian Likins Date: Thu, 1 Jun 2017 11:17:49 -0400 Subject: [PATCH] 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 8ad182059d5a085e4f8ecfe8ae63bad14a7dc01c that got lost in merge/rebase Fixes #21893 - port sunos fact locale fix for #24542 to this branch based on e558ec19cd1fd9d00e299cf1938d4ca3cec85cda 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 85c7a7b844bf429fc9cc0ffce5dd9bf05ed47b5a to the new code layout, since it got removed in merge/rebase --- lib/ansible/executor/action_write_locks.py | 6 +- lib/ansible/module_utils/facts.py | 4058 ---------- lib/ansible/module_utils/facts/__init__.py | 0 lib/ansible/module_utils/facts/collector.py | 252 + .../module_utils/facts/default_collectors.py | 129 + .../module_utils/facts/hardware/__init__.py | 0 .../module_utils/facts/hardware/aix.py | 208 + .../module_utils/facts/hardware/base.py | 53 + .../module_utils/facts/hardware/darwin.py | 99 + .../module_utils/facts/hardware/dragonfly.py | 26 + .../module_utils/facts/hardware/freebsd.py | 195 + .../module_utils/facts/hardware/hpux.py | 161 + .../module_utils/facts/hardware/hurd.py | 53 + .../module_utils/facts/hardware/linux.py | 663 ++ .../module_utils/facts/hardware/netbsd.py | 164 + .../module_utils/facts/hardware/openbsd.py | 172 + .../module_utils/facts/hardware/sunos.py | 267 + lib/ansible/module_utils/facts/namespace.py | 39 + .../module_utils/facts/network/__init__.py | 0 lib/ansible/module_utils/facts/network/aix.py | 144 + .../module_utils/facts/network/base.py | 70 + .../module_utils/facts/network/darwin.py | 49 + .../module_utils/facts/network/dragonfly.py | 33 + .../module_utils/facts/network/freebsd.py | 33 + .../module_utils/facts/network/generic_bsd.py | 266 + .../module_utils/facts/network/hpux.py | 82 + .../module_utils/facts/network/hurd.py | 86 + .../module_utils/facts/network/linux.py | 311 + .../module_utils/facts/network/netbsd.py | 48 + .../module_utils/facts/network/openbsd.py | 42 + .../module_utils/facts/network/sunos.py | 116 + .../module_utils/facts/other/__init__.py | 0 .../module_utils/facts/other/facter.py | 85 + lib/ansible/module_utils/facts/other/ohai.py | 72 + lib/ansible/module_utils/facts/sysctl.py | 35 + .../module_utils/facts/system/__init__.py | 0 .../module_utils/facts/system/apparmor.py | 39 + lib/ansible/module_utils/facts/system/caps.py | 55 + .../module_utils/facts/system/cmdline.py | 50 + .../module_utils/facts/system/date_time.py | 59 + .../module_utils/facts/system/distribution.py | 579 ++ lib/ansible/module_utils/facts/system/dns.py | 67 + lib/ansible/module_utils/facts/system/env.py | 37 + lib/ansible/module_utils/facts/system/fips.py | 37 + .../module_utils/facts/system/local.py | 90 + lib/ansible/module_utils/facts/system/lsb.py | 101 + .../module_utils/facts/system/pkg_mgr.py | 73 + .../module_utils/facts/system/platform.py | 94 + .../module_utils/facts/system/python.py | 60 + .../module_utils/facts/system/selinux.py | 86 + .../module_utils/facts/system/service_mgr.py | 138 + .../module_utils/facts/system/ssh_pub_keys.py | 52 + lib/ansible/module_utils/facts/system/user.py | 50 + lib/ansible/module_utils/facts/timeout.py | 66 + lib/ansible/module_utils/facts/utils.py | 59 + .../module_utils/facts/virtual/__init__.py | 0 .../module_utils/facts/virtual/base.py | 70 + .../module_utils/facts/virtual/dragonfly.py | 25 + .../module_utils/facts/virtual/freebsd.py | 47 + .../module_utils/facts/virtual/hpux.py | 62 + .../module_utils/facts/virtual/linux.py | 228 + .../module_utils/facts/virtual/netbsd.py | 50 + .../module_utils/facts/virtual/openbsd.py | 64 + .../module_utils/facts/virtual/sunos.py | 120 + .../module_utils/facts/virtual/sysctl.py | 68 + lib/ansible/modules/system/setup.py | 142 +- test/integration/inventory | 2 +- .../targets/facts_d/tasks/main.yml | 2 + .../gathering_facts/test_gathering_facts.yml | 86 + test/sanity/pep8/legacy-files.txt | 2 - test/units/module_utils/facts/base.py | 61 + .../{ => facts}/fixtures/findmount_output.txt | 0 .../module_utils/facts/other/__init__.py | 0 .../module_utils/facts/other/test_facter.py | 228 + .../module_utils/facts/other/test_ohai.py | 6768 +++++++++++++++++ .../module_utils/facts/system/__init__.py | 0 .../module_utils/facts/system/test_lsb.py | 108 + .../module_utils/facts/test_collector.py | 172 + .../module_utils/facts/test_collectors.py | 330 + .../module_utils/{ => facts}/test_facts.py | 176 +- test/units/module_utils/facts/test_timeout.py | 34 +- .../module_utils/test_distribution_version.py | 49 +- test/units/modules/system/test_setup.py | 308 + 83 files changed, 14846 insertions(+), 4165 deletions(-) delete mode 100644 lib/ansible/module_utils/facts.py create mode 100644 lib/ansible/module_utils/facts/__init__.py create mode 100644 lib/ansible/module_utils/facts/collector.py create mode 100644 lib/ansible/module_utils/facts/default_collectors.py create mode 100644 lib/ansible/module_utils/facts/hardware/__init__.py create mode 100644 lib/ansible/module_utils/facts/hardware/aix.py create mode 100644 lib/ansible/module_utils/facts/hardware/base.py create mode 100644 lib/ansible/module_utils/facts/hardware/darwin.py create mode 100644 lib/ansible/module_utils/facts/hardware/dragonfly.py create mode 100644 lib/ansible/module_utils/facts/hardware/freebsd.py create mode 100644 lib/ansible/module_utils/facts/hardware/hpux.py create mode 100644 lib/ansible/module_utils/facts/hardware/hurd.py create mode 100644 lib/ansible/module_utils/facts/hardware/linux.py create mode 100644 lib/ansible/module_utils/facts/hardware/netbsd.py create mode 100644 lib/ansible/module_utils/facts/hardware/openbsd.py create mode 100644 lib/ansible/module_utils/facts/hardware/sunos.py create mode 100644 lib/ansible/module_utils/facts/namespace.py create mode 100644 lib/ansible/module_utils/facts/network/__init__.py create mode 100644 lib/ansible/module_utils/facts/network/aix.py create mode 100644 lib/ansible/module_utils/facts/network/base.py create mode 100644 lib/ansible/module_utils/facts/network/darwin.py create mode 100644 lib/ansible/module_utils/facts/network/dragonfly.py create mode 100644 lib/ansible/module_utils/facts/network/freebsd.py create mode 100644 lib/ansible/module_utils/facts/network/generic_bsd.py create mode 100644 lib/ansible/module_utils/facts/network/hpux.py create mode 100644 lib/ansible/module_utils/facts/network/hurd.py create mode 100644 lib/ansible/module_utils/facts/network/linux.py create mode 100644 lib/ansible/module_utils/facts/network/netbsd.py create mode 100644 lib/ansible/module_utils/facts/network/openbsd.py create mode 100644 lib/ansible/module_utils/facts/network/sunos.py create mode 100644 lib/ansible/module_utils/facts/other/__init__.py create mode 100644 lib/ansible/module_utils/facts/other/facter.py create mode 100644 lib/ansible/module_utils/facts/other/ohai.py create mode 100644 lib/ansible/module_utils/facts/sysctl.py create mode 100644 lib/ansible/module_utils/facts/system/__init__.py create mode 100644 lib/ansible/module_utils/facts/system/apparmor.py create mode 100644 lib/ansible/module_utils/facts/system/caps.py create mode 100644 lib/ansible/module_utils/facts/system/cmdline.py create mode 100644 lib/ansible/module_utils/facts/system/date_time.py create mode 100644 lib/ansible/module_utils/facts/system/distribution.py create mode 100644 lib/ansible/module_utils/facts/system/dns.py create mode 100644 lib/ansible/module_utils/facts/system/env.py create mode 100644 lib/ansible/module_utils/facts/system/fips.py create mode 100644 lib/ansible/module_utils/facts/system/local.py create mode 100644 lib/ansible/module_utils/facts/system/lsb.py create mode 100644 lib/ansible/module_utils/facts/system/pkg_mgr.py create mode 100644 lib/ansible/module_utils/facts/system/platform.py create mode 100644 lib/ansible/module_utils/facts/system/python.py create mode 100644 lib/ansible/module_utils/facts/system/selinux.py create mode 100644 lib/ansible/module_utils/facts/system/service_mgr.py create mode 100644 lib/ansible/module_utils/facts/system/ssh_pub_keys.py create mode 100644 lib/ansible/module_utils/facts/system/user.py create mode 100644 lib/ansible/module_utils/facts/timeout.py create mode 100644 lib/ansible/module_utils/facts/utils.py create mode 100644 lib/ansible/module_utils/facts/virtual/__init__.py create mode 100644 lib/ansible/module_utils/facts/virtual/base.py create mode 100644 lib/ansible/module_utils/facts/virtual/dragonfly.py create mode 100644 lib/ansible/module_utils/facts/virtual/freebsd.py create mode 100644 lib/ansible/module_utils/facts/virtual/hpux.py create mode 100644 lib/ansible/module_utils/facts/virtual/linux.py create mode 100644 lib/ansible/module_utils/facts/virtual/netbsd.py create mode 100644 lib/ansible/module_utils/facts/virtual/openbsd.py create mode 100644 lib/ansible/module_utils/facts/virtual/sunos.py create mode 100644 lib/ansible/module_utils/facts/virtual/sysctl.py create mode 100644 test/units/module_utils/facts/base.py rename test/units/module_utils/{ => facts}/fixtures/findmount_output.txt (100%) create mode 100644 test/units/module_utils/facts/other/__init__.py create mode 100644 test/units/module_utils/facts/other/test_facter.py create mode 100644 test/units/module_utils/facts/other/test_ohai.py create mode 100644 test/units/module_utils/facts/system/__init__.py create mode 100644 test/units/module_utils/facts/system/test_lsb.py create mode 100644 test/units/module_utils/facts/test_collector.py create mode 100644 test/units/module_utils/facts/test_collectors.py rename test/units/module_utils/{ => facts}/test_facts.py (71%) create mode 100644 test/units/modules/system/test_setup.py diff --git a/lib/ansible/executor/action_write_locks.py b/lib/ansible/executor/action_write_locks.py index 6f81b7e912c..b6486cb7d46 100644 --- a/lib/ansible/executor/action_write_locks.py +++ b/lib/ansible/executor/action_write_locks.py @@ -20,7 +20,8 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type from multiprocessing import Lock -from ansible.module_utils.facts import Facts + +from ansible.module_utils.facts.system.pkg_mgr import PKG_MGRS if 'action_write_locks' not in globals(): # Do not initialize this more than once because it seems to bash @@ -36,7 +37,8 @@ if 'action_write_locks' not in globals(): # These plugins are called directly by action plugins (not going through # a strategy). We precreate them here as an optimization - mods = set(p['name'] for p in Facts.PKG_MGRS) + mods = set(p['name'] for p in PKG_MGRS) + mods.update(('copy', 'file', 'setup', 'slurp', 'stat')) for mod_name in mods: action_write_locks[mod_name] = Lock() diff --git a/lib/ansible/module_utils/facts.py b/lib/ansible/module_utils/facts.py deleted file mode 100644 index 4d0de4f9c71..00000000000 --- a/lib/ansible/module_utils/facts.py +++ /dev/null @@ -1,4058 +0,0 @@ -# (c) 2012, Michael DeHaan -# -# This file is part of Ansible -# -# 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 . - -import os -import sys -import stat -import time -import shlex -import errno -import fnmatch -import glob -import platform -import re -import signal -import socket -import struct -import datetime -import getpass -import pwd - -from ansible.module_utils.basic import get_all_subclasses -from ansible.module_utils.six import PY3, iteritems -from ansible.module_utils.six.moves import configparser, StringIO, reduce -from ansible.module_utils._text import to_native, to_text - -try: - import selinux - HAVE_SELINUX=True -except ImportError: - HAVE_SELINUX=False - -try: - # Check if we have SSLContext support - from ssl import create_default_context, SSLContext - del create_default_context - del SSLContext - HAS_SSLCONTEXT = True -except ImportError: - HAS_SSLCONTEXT = False - -try: - import json - # Detect python-json which is incompatible and fallback to simplejson in - # that case - try: - json.loads - json.dumps - except AttributeError: - raise ImportError -except ImportError: - import simplejson as json - -# The distutils module is not shipped with SUNWPython on Solaris. -# It's in the SUNWPython-devel package which also contains development files -# that don't belong on production boxes. Since our Solaris code doesn't -# depend on LooseVersion, do not import it on Solaris. -if platform.system() != 'SunOS': - from distutils.version import LooseVersion - - -# -------------------------------------------------------------- -# timeout function to make sure some fact gathering -# steps do not exceed a time limit - -GATHER_TIMEOUT=None -DEFAULT_GATHER_TIMEOUT = 10 - -class TimeoutError(Exception): - pass - -def timeout(seconds=None, error_message="Timer expired"): - - def decorator(func): - def _handle_timeout(signum, frame): - raise TimeoutError(error_message) - - def wrapper(*args, **kwargs): - local_seconds = seconds # Make local var as we modify this every time it's invoked - if local_seconds is None: - local_seconds = globals().get('GATHER_TIMEOUT') or DEFAULT_GATHER_TIMEOUT - - signal.signal(signal.SIGALRM, _handle_timeout) - signal.alarm(local_seconds) - try: - result = func(*args, **kwargs) - finally: - signal.alarm(0) - return result - - return wrapper - - # If we were called as @timeout, then the first parameter will be the - # function we are to wrap instead of the number of seconds. Detect this - # and correct it by setting seconds to our sentinel value and return the - # inner decorator function manually wrapped around the function - if callable(seconds): - func = seconds - seconds = None - return decorator(func) - - # If we were called as @timeout([...]) then python itself will take - # care of wrapping the inner decorator around the function - - return decorator - -# -------------------------------------------------------------- - -class Facts(object): - """ - This class should only attempt to populate those facts that - are mostly generic to all systems. This includes platform facts, - service facts (e.g. ssh keys or selinux), and distribution facts. - Anything that requires extensive code or may have more than one - possible implementation to establish facts for a given topic should - subclass Facts. - """ - - # i86pc is a Solaris and derivatives-ism - _I386RE = re.compile(r'i([3456]86|86pc)') - # For the most part, we assume that platform.dist() will tell the truth. - # This is the fallback to handle unknowns or exceptions - SELINUX_MODE_DICT = { 1: 'enforcing', 0: 'permissive', -1: 'disabled' } - - # A list of dicts. If there is a platform with more than one - # package manager, put the preferred one last. If there is an - # ansible module, use that as the value for the 'name' key. - PKG_MGRS = [ { 'path' : '/usr/bin/yum', 'name' : 'yum' }, - { 'path' : '/usr/bin/dnf', 'name' : 'dnf' }, - { 'path' : '/usr/bin/apt-get', 'name' : 'apt' }, - { 'path' : '/usr/bin/zypper', 'name' : 'zypper' }, - { 'path' : '/usr/sbin/urpmi', 'name' : 'urpmi' }, - { 'path' : '/usr/bin/pacman', 'name' : 'pacman' }, - { 'path' : '/bin/opkg', 'name' : 'opkg' }, - { 'path' : '/usr/pkg/bin/pkgin', 'name' : 'pkgin' }, - { 'path' : '/opt/local/bin/pkgin', 'name' : 'pkgin' }, - { 'path' : '/opt/tools/bin/pkgin', 'name' : 'pkgin' }, - { 'path' : '/opt/local/bin/port', 'name' : 'macports' }, - { 'path' : '/usr/local/bin/brew', 'name' : 'homebrew' }, - { 'path' : '/sbin/apk', 'name' : 'apk' }, - { 'path' : '/usr/sbin/pkg', 'name' : 'pkgng' }, - { 'path' : '/usr/sbin/swlist', 'name' : 'SD-UX' }, - { 'path' : '/usr/bin/emerge', 'name' : 'portage' }, - { 'path' : '/usr/sbin/pkgadd', 'name' : 'svr4pkg' }, - { 'path' : '/usr/bin/pkg', 'name' : 'pkg5' }, - { 'path' : '/usr/bin/xbps-install','name' : 'xbps' }, - { 'path' : '/usr/local/sbin/pkg', 'name' : 'pkgng' }, - { 'path' : '/usr/bin/swupd', 'name' : 'swupd' }, - { 'path' : '/usr/sbin/sorcery', 'name' : 'sorcery' }, - ] - - def __init__(self, module, load_on_init=True, cached_facts=None): - - self.module = module - if not cached_facts: - self.facts = {} - else: - self.facts = cached_facts - ### TODO: Eventually, these should all get moved to populate(). But - # some of the values are currently being used by other subclasses (for - # instance, os_family and distribution). Have to sort out what to do - # about those first. - if load_on_init: - self.get_platform_facts() - self.facts.update(Distribution(module).populate()) - self.get_cmdline() - self.get_public_ssh_host_keys() - self.get_selinux_facts() - self.get_apparmor_facts() - self.get_caps_facts() - self.get_fips_facts() - self.get_pkg_mgr_facts() - self.get_service_mgr_facts() - self.get_lsb_facts() - self.get_date_time_facts() - self.get_user_facts() - self.get_local_facts() - self.get_env_facts() - self.get_dns_facts() - self.get_python_facts() - - - def populate(self): - return self.facts - - # Platform - # platform.system() can be Linux, Darwin, Java, or Windows - def get_platform_facts(self): - self.facts['system'] = platform.system() - self.facts['kernel'] = platform.release() - self.facts['machine'] = platform.machine() - self.facts['python_version'] = platform.python_version() - self.facts['fqdn'] = socket.getfqdn() - self.facts['hostname'] = platform.node().split('.')[0] - self.facts['nodename'] = platform.node() - self.facts['domain'] = '.'.join(self.facts['fqdn'].split('.')[1:]) - arch_bits = platform.architecture()[0] - self.facts['userspace_bits'] = arch_bits.replace('bit', '') - if self.facts['machine'] == 'x86_64': - self.facts['architecture'] = self.facts['machine'] - if self.facts['userspace_bits'] == '64': - self.facts['userspace_architecture'] = 'x86_64' - elif self.facts['userspace_bits'] == '32': - self.facts['userspace_architecture'] = 'i386' - elif Facts._I386RE.search(self.facts['machine']): - self.facts['architecture'] = 'i386' - if self.facts['userspace_bits'] == '64': - self.facts['userspace_architecture'] = 'x86_64' - elif self.facts['userspace_bits'] == '32': - self.facts['userspace_architecture'] = 'i386' - else: - self.facts['architecture'] = self.facts['machine'] - if self.facts['system'] == 'AIX': - # Attempt to use getconf to figure out architecture - # fall back to bootinfo if needed - getconf_bin = self.module.get_bin_path('getconf') - if getconf_bin: - rc, out, err = self.module.run_command([getconf_bin, 'MACHINE_ARCHITECTURE']) - data = out.splitlines() - self.facts['architecture'] = data[0] - else: - bootinfo_bin = self.module.get_bin_path('bootinfo') - rc, out, err = self.module.run_command([bootinfo_bin, '-p']) - data = out.splitlines() - self.facts['architecture'] = data[0] - elif self.facts['system'] == 'OpenBSD': - self.facts['architecture'] = platform.uname()[5] - machine_id = get_file_content("/var/lib/dbus/machine-id") or get_file_content("/etc/machine-id") - if machine_id: - machine_id = machine_id.splitlines()[0] - self.facts["machine_id"] = machine_id - - def get_local_facts(self): - - fact_path = self.module.params.get('fact_path', None) - if not fact_path or not os.path.exists(fact_path): - return - - local = {} - for fn in sorted(glob.glob(fact_path + '/*.fact')): - # where it will sit under local facts - fact_base = os.path.basename(fn).replace('.fact','') - if stat.S_IXUSR & os.stat(fn)[stat.ST_MODE]: - # run it - # try to read it as json first - # if that fails read it with ConfigParser - # if that fails, skip it - try: - rc, out, err = self.module.run_command(fn) - except UnicodeError: - fact = 'error loading fact - output of running %s was not utf-8' % fn - local[fact_base] = fact - self.facts['local'] = local - return - else: - out = get_file_content(fn, default='') - - # load raw json - fact = 'loading %s' % fact_base - try: - fact = json.loads(out) - except ValueError: - # load raw ini - cp = configparser.ConfigParser() - try: - cp.readfp(StringIO(out)) - except configparser.Error: - fact = "error loading fact - please check content" - else: - fact = {} - for sect in cp.sections(): - if sect not in fact: - fact[sect] = {} - for opt in cp.options(sect): - val = cp.get(sect, opt) - fact[sect][opt]=val - - local[fact_base] = fact - if not local: - return - self.facts['local'] = local - - def get_cmdline(self): - data = get_file_content('/proc/cmdline') - if data: - self.facts['cmdline'] = {} - try: - for piece in shlex.split(data): - item = piece.split('=', 1) - if len(item) == 1: - self.facts['cmdline'][item[0]] = True - else: - self.facts['cmdline'][item[0]] = item[1] - except ValueError: - pass - - def get_public_ssh_host_keys(self): - keytypes = ('dsa', 'rsa', 'ecdsa', 'ed25519') - - # list of directories to check for ssh keys - # used in the order listed here, the first one with keys is used - keydirs = ['/etc/ssh', '/etc/openssh', '/etc'] - - for keydir in keydirs: - for type_ in keytypes: - factname = 'ssh_host_key_%s_public' % type_ - if factname in self.facts: - # a previous keydir was already successful, stop looking - # for keys - return - key_filename = '%s/ssh_host_%s_key.pub' % (keydir, type_) - keydata = get_file_content(key_filename) - if keydata is not None: - self.facts[factname] = keydata.split()[1] - - def get_pkg_mgr_facts(self): - if self.facts['system'] == 'OpenBSD': - self.facts['pkg_mgr'] = 'openbsd_pkg' - else: - self.facts['pkg_mgr'] = 'unknown' - for pkg in Facts.PKG_MGRS: - if os.path.isfile(pkg['path']): - self.facts['pkg_mgr'] = pkg['name'] - - def get_service_mgr_facts(self): - #TODO: detect more custom init setups like bootscripts, dmd, s6, Epoch, etc - # also other OSs other than linux might need to check across several possible candidates - - # Mapping of proc_1 values to more useful names - proc_1_map = { - 'procd': 'openwrt_init', - 'runit-init': 'runit', - 'svscan': 'svc', - 'openrc-init': 'openrc', - } - - # try various forms of querying pid 1 - proc_1 = get_file_content('/proc/1/comm') - if proc_1 is None: - rc, proc_1, err = self.module.run_command("ps -p 1 -o comm|tail -n 1", use_unsafe_shell=True) - # If the output of the command starts with what looks like a PID, then the 'ps' command - # probably didn't work the way we wanted, probably because it's busybox - if re.match(r' *[0-9]+ ', proc_1): - proc_1 = None - - # The ps command above may return "COMMAND" if the user cannot read /proc, e.g. with grsecurity - if proc_1 == "COMMAND\n": - proc_1 = None - - if proc_1 is not None: - proc_1 = os.path.basename(proc_1) - proc_1 = to_native(proc_1) - proc_1 = proc_1.strip() - - if proc_1 is not None and (proc_1 == 'init' or proc_1.endswith('sh')): - # many systems return init, so this cannot be trusted, if it ends in 'sh' it probalby is a shell in a container - proc_1 = None - - # if not init/None it should be an identifiable or custom init, so we are done! - if proc_1 is not None: - # Lookup proc_1 value in map and use proc_1 value itself if no match - self.facts['service_mgr'] = proc_1_map.get(proc_1, proc_1) - - # start with the easy ones - elif self.facts['distribution'] == 'MacOSX': - #FIXME: find way to query executable, version matching is not ideal - if LooseVersion(platform.mac_ver()[0]) >= LooseVersion('10.4'): - self.facts['service_mgr'] = 'launchd' - else: - self.facts['service_mgr'] = 'systemstarter' - elif 'BSD' in self.facts['system'] or self.facts['system'] in ['Bitrig', 'DragonFly']: - #FIXME: we might want to break out to individual BSDs or 'rc' - self.facts['service_mgr'] = 'bsdinit' - elif self.facts['system'] == 'AIX': - self.facts['service_mgr'] = 'src' - elif self.facts['system'] == 'SunOS': - self.facts['service_mgr'] = 'smf' - elif self.facts['distribution'] == 'OpenWrt': - self.facts['service_mgr'] = 'openwrt_init' - elif self.facts['system'] == 'Linux': - if self.is_systemd_managed(): - self.facts['service_mgr'] = 'systemd' - elif self.module.get_bin_path('initctl') and os.path.exists("/etc/init/"): - self.facts['service_mgr'] = 'upstart' - elif os.path.exists('/sbin/openrc'): - self.facts['service_mgr'] = 'openrc' - elif os.path.exists('/etc/init.d/'): - self.facts['service_mgr'] = 'sysvinit' - - if not self.facts.get('service_mgr', False): - # if we cannot detect, fallback to generic 'service' - self.facts['service_mgr'] = 'service' - - def get_lsb_facts(self): - lsb_path = self.module.get_bin_path('lsb_release') - if lsb_path: - rc, out, err = self.module.run_command([lsb_path, "-a"], errors='surrogate_then_replace') - if rc == 0: - self.facts['lsb'] = {} - for line in out.splitlines(): - if len(line) < 1 or ':' not in line: - continue - value = line.split(':', 1)[1].strip() - if 'LSB Version:' in line: - self.facts['lsb']['release'] = value - elif 'Distributor ID:' in line: - self.facts['lsb']['id'] = value - elif 'Description:' in line: - self.facts['lsb']['description'] = value - elif 'Release:' in line: - self.facts['lsb']['release'] = value - elif 'Codename:' in line: - self.facts['lsb']['codename'] = value - elif lsb_path is None and os.path.exists('/etc/lsb-release'): - self.facts['lsb'] = {} - for line in get_file_lines('/etc/lsb-release'): - value = line.split('=',1)[1].strip() - if 'DISTRIB_ID' in line: - self.facts['lsb']['id'] = value - elif 'DISTRIB_RELEASE' in line: - self.facts['lsb']['release'] = value - elif 'DISTRIB_DESCRIPTION' in line: - self.facts['lsb']['description'] = value - elif 'DISTRIB_CODENAME' in line: - self.facts['lsb']['codename'] = value - - if 'lsb' in self.facts and 'release' in self.facts['lsb']: - self.facts['lsb']['major_release'] = self.facts['lsb']['release'].split('.')[0] - - def get_selinux_facts(self): - if not HAVE_SELINUX: - self.facts['selinux'] = False - self.facts['selinux_python_present'] = False - return - self.facts['selinux'] = {} - self.facts['selinux_python_present'] = True - if not selinux.is_selinux_enabled(): - self.facts['selinux']['status'] = 'disabled' - else: - self.facts['selinux']['status'] = 'enabled' - try: - self.facts['selinux']['policyvers'] = selinux.security_policyvers() - except (AttributeError,OSError): - self.facts['selinux']['policyvers'] = 'unknown' - try: - (rc, configmode) = selinux.selinux_getenforcemode() - if rc == 0: - self.facts['selinux']['config_mode'] = Facts.SELINUX_MODE_DICT.get(configmode, 'unknown') - else: - self.facts['selinux']['config_mode'] = 'unknown' - except (AttributeError,OSError): - self.facts['selinux']['config_mode'] = 'unknown' - try: - mode = selinux.security_getenforce() - self.facts['selinux']['mode'] = Facts.SELINUX_MODE_DICT.get(mode, 'unknown') - except (AttributeError,OSError): - self.facts['selinux']['mode'] = 'unknown' - try: - (rc, policytype) = selinux.selinux_getpolicytype() - if rc == 0: - self.facts['selinux']['type'] = policytype - else: - self.facts['selinux']['type'] = 'unknown' - except (AttributeError,OSError): - self.facts['selinux']['type'] = 'unknown' - - def get_apparmor_facts(self): - self.facts['apparmor'] = {} - if os.path.exists('/sys/kernel/security/apparmor'): - self.facts['apparmor']['status'] = 'enabled' - else: - self.facts['apparmor']['status'] = 'disabled' - - def get_caps_facts(self): - capsh_path = self.module.get_bin_path('capsh') - if capsh_path: - rc, out, err = self.module.run_command([capsh_path, "--print"], errors='surrogate_then_replace') - enforced_caps = [] - enforced = 'NA' - for line in out.splitlines(): - if len(line) < 1: - continue - if line.startswith('Current:'): - if line.split(':')[1].strip() == '=ep': - enforced = 'False' - else: - enforced = 'True' - enforced_caps = [i.strip() for i in line.split('=')[1].split(',')] - - self.facts['system_capabilities_enforced'] = enforced - self.facts['system_capabilities'] = enforced_caps - - - def get_fips_facts(self): - self.facts['fips'] = False - data = get_file_content('/proc/sys/crypto/fips_enabled') - if data and data == '1': - self.facts['fips'] = True - - - def get_date_time_facts(self): - self.facts['date_time'] = {} - - now = datetime.datetime.now() - self.facts['date_time']['year'] = now.strftime('%Y') - self.facts['date_time']['month'] = now.strftime('%m') - self.facts['date_time']['weekday'] = now.strftime('%A') - self.facts['date_time']['weekday_number'] = now.strftime('%w') - self.facts['date_time']['weeknumber'] = now.strftime('%W') - self.facts['date_time']['day'] = now.strftime('%d') - self.facts['date_time']['hour'] = now.strftime('%H') - self.facts['date_time']['minute'] = now.strftime('%M') - self.facts['date_time']['second'] = now.strftime('%S') - self.facts['date_time']['epoch'] = now.strftime('%s') - if self.facts['date_time']['epoch'] == '' or self.facts['date_time']['epoch'][0] == '%': - self.facts['date_time']['epoch'] = str(int(time.time())) - self.facts['date_time']['date'] = now.strftime('%Y-%m-%d') - self.facts['date_time']['time'] = now.strftime('%H:%M:%S') - self.facts['date_time']['iso8601_micro'] = now.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ") - self.facts['date_time']['iso8601'] = now.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ") - self.facts['date_time']['iso8601_basic'] = now.strftime("%Y%m%dT%H%M%S%f") - self.facts['date_time']['iso8601_basic_short'] = now.strftime("%Y%m%dT%H%M%S") - self.facts['date_time']['tz'] = time.strftime("%Z") - self.facts['date_time']['tz_offset'] = time.strftime("%z") - - def is_systemd_managed(self): - # tools must be installed - if self.module.get_bin_path('systemctl'): - - # this should show if systemd is the boot init system, if checking init faild to mark as systemd - # these mirror systemd's own sd_boot test http://www.freedesktop.org/software/systemd/man/sd_booted.html - for canary in ["/run/systemd/system/", "/dev/.run/systemd/", "/dev/.systemd/"]: - if os.path.exists(canary): - return True - return False - - # User - def get_user_facts(self): - self.facts['user_id'] = getpass.getuser() - pwent = pwd.getpwnam(getpass.getuser()) - self.facts['user_uid'] = pwent.pw_uid - self.facts['user_gid'] = pwent.pw_gid - self.facts['user_gecos'] = pwent.pw_gecos - self.facts['user_dir'] = pwent.pw_dir - self.facts['user_shell'] = pwent.pw_shell - self.facts['real_user_id'] = os.getuid() - self.facts['effective_user_id'] = os.geteuid() - self.facts['real_group_id'] = os.getgid() - self.facts['effective_group_id'] = os.getgid() - - def get_env_facts(self): - self.facts['env'] = {} - for k,v in iteritems(os.environ): - self.facts['env'][k] = v - - def get_dns_facts(self): - self.facts['dns'] = {} - for line in get_file_content('/etc/resolv.conf', '').splitlines(): - if line.startswith('#') or line.startswith(';') or line.strip() == '': - continue - tokens = line.split() - if len(tokens) == 0: - continue - if tokens[0] == 'nameserver': - if not 'nameservers' in self.facts['dns']: - self.facts['dns']['nameservers'] = [] - for nameserver in tokens[1:]: - self.facts['dns']['nameservers'].append(nameserver) - elif tokens[0] == 'domain': - if len(tokens) > 1: - self.facts['dns']['domain'] = tokens[1] - elif tokens[0] == 'search': - self.facts['dns']['search'] = [] - for suffix in tokens[1:]: - self.facts['dns']['search'].append(suffix) - elif tokens[0] == 'sortlist': - self.facts['dns']['sortlist'] = [] - for address in tokens[1:]: - self.facts['dns']['sortlist'].append(address) - elif tokens[0] == 'options': - self.facts['dns']['options'] = {} - if len(tokens) > 1: - for option in tokens[1:]: - option_tokens = option.split(':', 1) - if len(option_tokens) == 0: - continue - val = len(option_tokens) == 2 and option_tokens[1] or True - self.facts['dns']['options'][option_tokens[0]] = val - - def _get_mount_size_facts(self, mountpoint): - size_total = None - size_available = None - try: - statvfs_result = os.statvfs(mountpoint) - size_total = statvfs_result.f_frsize * statvfs_result.f_blocks - size_available = statvfs_result.f_frsize * (statvfs_result.f_bavail) - except OSError: - pass - return size_total, size_available - - def get_python_facts(self): - self.facts['python'] = { - 'version': { - 'major': sys.version_info[0], - 'minor': sys.version_info[1], - 'micro': sys.version_info[2], - 'releaselevel': sys.version_info[3], - 'serial': sys.version_info[4] - }, - 'version_info': list(sys.version_info), - 'executable': sys.executable, - 'has_sslcontext': HAS_SSLCONTEXT - } - try: - self.facts['python']['type'] = sys.subversion[0] - except AttributeError: - try: - self.facts['python']['type'] = sys.implementation.name - except AttributeError: - self.facts['python']['type'] = None - - -class Distribution(object): - """ - This subclass of Facts fills the distribution, distribution_version and distribution_release variables - - To do so it checks the existence and content of typical files in /etc containing distribution information - - This is unit tested. Please extend the tests to cover all distributions if you have them available. - """ - - # every distribution name mentioned here, must have one of - # - allowempty == True - # - be listed in SEARCH_STRING - # - have a function get_distribution_DISTNAME implemented - OSDIST_LIST = ( - {'path': '/etc/oracle-release', 'name': 'OracleLinux'}, - {'path': '/etc/slackware-version', 'name': 'Slackware'}, - {'path': '/etc/redhat-release', 'name': 'RedHat'}, - {'path': '/etc/vmware-release', 'name': 'VMwareESX', 'allowempty': True}, - {'path': '/etc/openwrt_release', 'name': 'OpenWrt'}, - {'path': '/etc/system-release', 'name': 'Amazon'}, - {'path': '/etc/alpine-release', 'name': 'Alpine'}, - {'path': '/etc/arch-release', 'name': 'Archlinux', 'allowempty': True}, - {'path': '/etc/os-release', 'name': 'SuSE'}, - {'path': '/etc/SuSE-release', 'name': 'SuSE'}, - {'path': '/etc/gentoo-release', 'name': 'Gentoo'}, - {'path': '/etc/os-release', 'name': 'Debian'}, - {'path': '/etc/lsb-release', 'name': 'Mandriva'}, - {'path': '/etc/altlinux-release', 'name': 'Altlinux'}, - {'path': '/etc/sourcemage-release', 'name': 'SMGL'}, - {'path': '/etc/os-release', 'name': 'NA'}, - {'path': '/etc/coreos/update.conf', 'name': 'Coreos'}, - {'path': '/usr/lib/os-release', 'name': 'ClearLinux'}, - ) - - SEARCH_STRING = { - 'OracleLinux': 'Oracle Linux', - 'RedHat': 'Red Hat', - 'Altlinux': 'ALT Linux', - 'ClearLinux': 'Clear Linux', - 'SMGL': 'Source Mage GNU/Linux', - } - - # A list with OS Family members - OS_FAMILY = dict( - RedHat = 'RedHat', Fedora = 'RedHat', CentOS = 'RedHat', Scientific = 'RedHat', - SLC = 'RedHat', Ascendos = 'RedHat', CloudLinux = 'RedHat', PSBM = 'RedHat', - OracleLinux = 'RedHat', OVS = 'RedHat', OEL = 'RedHat', Amazon = 'RedHat', Virtuozzo = 'RedHat', - XenServer = 'RedHat', Ubuntu = 'Debian', Debian = 'Debian', Raspbian = 'Debian', Slackware = 'Slackware', SLES = 'Suse', - SLED = 'Suse', openSUSE = 'Suse', openSUSE_Tumbleweed = 'Suse', SuSE = 'Suse', SLES_SAP = 'Suse', SUSE_LINUX = 'Suse', Gentoo = 'Gentoo', - Funtoo = 'Gentoo', Archlinux = 'Archlinux', Manjaro = 'Archlinux', Mandriva = 'Mandrake', Mandrake = 'Mandrake', Altlinux = 'Altlinux', SMGL = 'SMGL', - Solaris = 'Solaris', Nexenta = 'Solaris', OmniOS = 'Solaris', OpenIndiana = 'Solaris', - SmartOS = 'Solaris', AIX = 'AIX', Alpine = 'Alpine', MacOSX = 'Darwin', - FreeBSD = 'FreeBSD', HPUX = 'HP-UX', openSUSE_Leap = 'Suse', Neon = 'Debian' - ) - - def __init__(self, module): - self.system = platform.system() - self.facts = {} - self.module = module - - def populate(self): - self.get_distribution_facts() - return self.facts - - def get_distribution_facts(self): - # The platform module provides information about the running - # system/distribution. Use this as a baseline and fix buggy systems - # afterwards - self.facts['distribution'] = self.system - self.facts['distribution_release'] = platform.release() - self.facts['distribution_version'] = platform.version() - systems_implemented = ('AIX', 'HP-UX', 'Darwin', 'FreeBSD', 'OpenBSD', 'SunOS', 'DragonFly', 'NetBSD') - - self.facts['distribution'] = self.system - - if self.system in systems_implemented: - cleanedname = self.system.replace('-','') - distfunc = getattr(self, 'get_distribution_'+cleanedname) - distfunc() - elif self.system == 'Linux': - # try to find out which linux distribution this is - dist = platform.dist() - self.facts['distribution'] = dist[0].capitalize() or 'NA' - self.facts['distribution_version'] = dist[1] or 'NA' - self.facts['distribution_major_version'] = dist[1].split('.')[0] or 'NA' - self.facts['distribution_release'] = dist[2] or 'NA' - # Try to handle the exceptions now ... - # self.facts['distribution_debug'] = [] - for ddict in self.OSDIST_LIST: - name = ddict['name'] - path = ddict['path'] - - if not os.path.exists(path): - continue - # if allowempty is set, we only check for file existance but not content - if 'allowempty' in ddict and ddict['allowempty']: - self.facts['distribution'] = name - break - if os.path.getsize(path) == 0: - continue - - data = get_file_content(path) - if name in self.SEARCH_STRING: - # look for the distribution string in the data and replace according to RELEASE_NAME_MAP - # only the distribution name is set, the version is assumed to be correct from platform.dist() - if self.SEARCH_STRING[name] in data: - # this sets distribution=RedHat if 'Red Hat' shows up in data - self.facts['distribution'] = name - else: - # this sets distribution to what's in the data, e.g. CentOS, Scientific, ... - self.facts['distribution'] = data.split()[0] - break - else: - # call a dedicated function for parsing the file content - try: - distfunc = getattr(self, 'get_distribution_' + name) - parsed = distfunc(name, data, path) - if parsed is None or parsed: - # distfunc return False if parsing failed - # break only if parsing was succesful - # otherwise continue with other distributions - break - except AttributeError: - # this should never happen, but if it does fail quitely and not with a traceback - pass - - - - # to debug multiple matching release files, one can use: - # self.facts['distribution_debug'].append({path + ' ' + name: - # (parsed, - # self.facts['distribution'], - # self.facts['distribution_version'], - # self.facts['distribution_release'], - # )}) - - self.facts['os_family'] = self.facts['distribution'] - distro = self.facts['distribution'].replace(' ', '_') - if distro in self.OS_FAMILY: - self.facts['os_family'] = self.OS_FAMILY[distro] - - def get_distribution_AIX(self): - rc, out, err = self.module.run_command("/usr/bin/oslevel") - data = out.split('.') - self.facts['distribution_version'] = data[0] - self.facts['distribution_release'] = data[1] - - def get_distribution_HPUX(self): - rc, out, err = self.module.run_command("/usr/sbin/swlist |egrep 'HPUX.*OE.*[AB].[0-9]+\.[0-9]+'", use_unsafe_shell=True) - data = re.search('HPUX.*OE.*([AB].[0-9]+\.[0-9]+)\.([0-9]+).*', out) - if data: - self.facts['distribution_version'] = data.groups()[0] - self.facts['distribution_release'] = data.groups()[1] - - def get_distribution_Darwin(self): - self.facts['distribution'] = 'MacOSX' - rc, out, err = self.module.run_command("/usr/bin/sw_vers -productVersion") - data = out.split()[-1] - self.facts['distribution_version'] = data - - def get_distribution_FreeBSD(self): - self.facts['distribution_release'] = platform.release() - data = re.search('(\d+)\.(\d+)-RELEASE.*', self.facts['distribution_release']) - if data: - self.facts['distribution_major_version'] = data.group(1) - self.facts['distribution_version'] = '%s.%s' % (data.group(1), data.group(2)) - - def get_distribution_OpenBSD(self): - self.facts['distribution_version'] = platform.release() - rc, out, err = self.module.run_command("/sbin/sysctl -n kern.version") - match = re.match('OpenBSD\s[0-9]+.[0-9]+-(\S+)\s.*', out) - if match: - self.facts['distribution_release'] = match.groups()[0] - else: - self.facts['distribution_release'] = 'release' - - def get_distribution_DragonFly(self): - pass - - def get_distribution_NetBSD(self): - self.facts['distribution_major_version'] = self.facts['distribution_release'].split('.')[0] - - def get_distribution_Slackware(self, name, data, path): - if 'Slackware' not in data: - return False # TODO: remove - self.facts['distribution'] = name - version = re.findall('\w+[.]\w+', data) - if version: - self.facts['distribution_version'] = version[0] - - def get_distribution_Amazon(self, name, data, path): - if 'Amazon' not in data: - return False # TODO: remove - self.facts['distribution'] = 'Amazon' - self.facts['distribution_version'] = data.split()[-1] - - def get_distribution_OpenWrt(self, name, data, path): - if 'OpenWrt' not in data: - return False # TODO: remove - self.facts['distribution'] = name - version = re.search('DISTRIB_RELEASE="(.*)"', data) - if version: - self.facts['distribution_version'] = version.groups()[0] - release = re.search('DISTRIB_CODENAME="(.*)"', data) - if release: - self.facts['distribution_release'] = release.groups()[0] - - def get_distribution_Alpine(self, name, data, path): - self.facts['distribution'] = 'Alpine' - self.facts['distribution_version'] = data - - def get_distribution_SMGL(self): - self.facts['distribution'] = 'Source Mage GNU/Linux' - - def get_distribution_SunOS(self): - data = get_file_content('/etc/release').splitlines()[0] - if 'Solaris' in data: - ora_prefix = '' - if 'Oracle Solaris' in data: - data = data.replace('Oracle ','') - ora_prefix = 'Oracle ' - self.facts['distribution'] = data.split()[0] - self.facts['distribution_version'] = data.split()[1] - self.facts['distribution_release'] = ora_prefix + data - return - - uname_v = get_uname_version(self.module) - distribution_version = None - if 'SmartOS' in data: - self.facts['distribution'] = 'SmartOS' - if os.path.exists('/etc/product'): - product_data = dict([l.split(': ', 1) for l in get_file_content('/etc/product').splitlines() if ': ' in l]) - if 'Image' in product_data: - distribution_version = product_data.get('Image').split()[-1] - elif 'OpenIndiana' in data: - self.facts['distribution'] = 'OpenIndiana' - elif 'OmniOS' in data: - self.facts['distribution'] = 'OmniOS' - distribution_version = data.split()[-1] - elif uname_v is not None and 'NexentaOS_' in uname_v: - self.facts['distribution'] = 'Nexenta' - distribution_version = data.split()[-1].lstrip('v') - - if self.facts['distribution'] in ('SmartOS', 'OpenIndiana', 'OmniOS', 'Nexenta'): - self.facts['distribution_release'] = data.strip() - if distribution_version is not None: - self.facts['distribution_version'] = distribution_version - elif uname_v is not None: - self.facts['distribution_version'] = uname_v.splitlines()[0].strip() - return - - return False # TODO: remove if tested without this - - def get_distribution_SuSE(self, name, data, path): - if 'suse' not in data.lower(): - return False # TODO: remove if tested without this - if path == '/etc/os-release': - for line in data.splitlines(): - distribution = re.search("^NAME=(.*)", line) - if distribution: - self.facts['distribution'] = distribution.group(1).strip('"') - # example pattern are 13.04 13.0 13 - distribution_version = re.search('^VERSION_ID="?([0-9]+\.?[0-9]*)"?', line) - if distribution_version: - self.facts['distribution_version'] = distribution_version.group(1) - if 'open' in data.lower(): - release = re.search('^VERSION_ID="?[0-9]+\.?([0-9]*)"?', line) - if release: - self.facts['distribution_release'] = release.groups()[0] - elif 'enterprise' in data.lower() and 'VERSION_ID' in line: - # SLES doesn't got funny release names - release = re.search('^VERSION_ID="?[0-9]+\.?([0-9]*)"?', line) - if release.group(1): - release = release.group(1) - else: - release = "0" # no minor number, so it is the first release - self.facts['distribution_release'] = release - elif path == '/etc/SuSE-release': - if 'open' in data.lower(): - data = data.splitlines() - distdata = get_file_content(path).splitlines()[0] - self.facts['distribution'] = distdata.split()[0] - for line in data: - release = re.search('CODENAME *= *([^\n]+)', line) - if release: - self.facts['distribution_release'] = release.groups()[0].strip() - elif 'enterprise' in data.lower(): - lines = data.splitlines() - distribution = lines[0].split()[0] - if "Server" in data: - self.facts['distribution'] = "SLES" - elif "Desktop" in data: - self.facts['distribution'] = "SLED" - for line in lines: - release = re.search('PATCHLEVEL = ([0-9]+)', line) # SLES doesn't got funny release names - if release: - self.facts['distribution_release'] = release.group(1) - self.facts['distribution_version'] = self.facts['distribution_version'] + '.' + release.group(1) - - def get_distribution_Debian(self, name, data, path): - if 'Debian' in data or 'Raspbian' in data: - self.facts['distribution'] = 'Debian' - release = re.search("PRETTY_NAME=[^(]+ \(?([^)]+?)\)", data) - if release: - self.facts['distribution_release'] = release.groups()[0] - - # Last resort: try to find release from tzdata as either lsb is missing or this is very old debian - if self.facts['distribution_release'] == 'NA' and 'Debian' in data: - dpkg_cmd = self.module.get_bin_path('dpkg') - if dpkg_cmd: - cmd = "%s --status tzdata|grep Provides|cut -f2 -d'-'" % dpkg_cmd - rc, out, err = self.module.run_command(cmd) - if rc == 0: - self.facts['distribution_release'] = out.strip() - elif 'Ubuntu' in data: - self.facts['distribution'] = 'Ubuntu' - # nothing else to do, Ubuntu gets correct info from python functions - else: - return False - - def get_distribution_Mandriva(self, name, data, path): - if 'Mandriva' in data: - self.facts['distribution'] = 'Mandriva' - version = re.search('DISTRIB_RELEASE="(.*)"', data) - if version: - self.facts['distribution_version'] = version.groups()[0] - release = re.search('DISTRIB_CODENAME="(.*)"', data) - if release: - self.facts['distribution_release'] = release.groups()[0] - self.facts['distribution'] = name - else: - return False - - def get_distribution_NA(self, name, data, path): - for line in data.splitlines(): - distribution = re.search("^NAME=(.*)", line) - if distribution and self.facts['distribution'] == 'NA': - self.facts['distribution'] = distribution.group(1).strip('"') - version = re.search("^VERSION=(.*)", line) - if version and self.facts['distribution_version'] == 'NA': - self.facts['distribution_version'] = version.group(1).strip('"') - - def get_distribution_Coreos(self, name, data, path): - if self.facts['distribution'].lower() == 'coreos': - if not data: - # include fix from #15230, #15228 - return - release = re.search("^GROUP=(.*)", data) - if release: - self.facts['distribution_release'] = release.group(1).strip('"') - else: - return False # TODO: remove if tested without this - - -class Hardware(Facts): - """ - This is a generic Hardware subclass of Facts. This should be further - subclassed to implement per platform. If you subclass this, it - should define: - - memfree_mb - - memtotal_mb - - swapfree_mb - - swaptotal_mb - - processor (a list) - - processor_cores - - processor_count - - All subclasses MUST define platform. - """ - platform = 'Generic' - - def __new__(cls, *arguments, **keyword): - # When Hardware is created, it chooses a subclass to create instead. - # This check prevents the subclass from then trying to find a subclass - # and create that. - if cls is not Hardware: - return super(Hardware, cls).__new__(cls) - - subclass = cls - for sc in get_all_subclasses(Hardware): - if sc.platform == platform.system(): - subclass = sc - if PY3: - return super(cls, subclass).__new__(subclass) - else: - return super(cls, subclass).__new__(subclass, *arguments, **keyword) - - def populate(self): - return self.facts - - def get_sysctl(self, prefixes): - sysctl_cmd = self.module.get_bin_path('sysctl') - cmd = [sysctl_cmd] - cmd.extend(prefixes) - rc, out, err = self.module.run_command(cmd) - if rc != 0: - return dict() - sysctl = dict() - for line in out.splitlines(): - if not line: - continue - (key, value) = re.split('\s?=\s?|: ', line, maxsplit=1) - sysctl[key] = value.strip() - return sysctl - - - -class LinuxHardware(Hardware): - """ - Linux-specific subclass of Hardware. Defines memory and CPU facts: - - memfree_mb - - memtotal_mb - - swapfree_mb - - swaptotal_mb - - processor (a list) - - processor_cores - - processor_count - - In addition, it also defines number of DMI facts and device facts. - """ - - platform = 'Linux' - - # Originally only had these four as toplevelfacts - ORIGINAL_MEMORY_FACTS = frozenset(('MemTotal', 'SwapTotal', 'MemFree', 'SwapFree')) - # Now we have all of these in a dict structure - MEMORY_FACTS = ORIGINAL_MEMORY_FACTS.union(('Buffers', 'Cached', 'SwapCached')) - - # regex used against findmnt output to detect bind mounts - BIND_MOUNT_RE = re.compile(r'.*\]') - - # regex used against mtab content to find entries that are bind mounts - MTAB_BIND_MOUNT_RE = re.compile(r'.*bind.*"') - - def populate(self): - self.get_cpu_facts() - self.get_memory_facts() - self.get_dmi_facts() - self.get_device_facts() - self.get_uptime_facts() - self.get_lvm_facts() - try: - self.get_mount_facts() - except TimeoutError: - pass - return self.facts - - def get_memory_facts(self): - if not os.access("/proc/meminfo", os.R_OK): - return - - memstats = {} - for line in get_file_lines("/proc/meminfo"): - data = line.split(":", 1) - key = data[0] - if key in self.ORIGINAL_MEMORY_FACTS: - val = data[1].strip().split(' ')[0] - self.facts["%s_mb" % key.lower()] = int(val) // 1024 - - if key in self.MEMORY_FACTS: - val = data[1].strip().split(' ')[0] - memstats[key.lower()] = int(val) // 1024 - - if None not in (memstats.get('memtotal'), memstats.get('memfree')): - memstats['real:used'] = memstats['memtotal'] - memstats['memfree'] - if None not in (memstats.get('cached'), memstats.get('memfree'), memstats.get('buffers')): - memstats['nocache:free'] = memstats['cached'] + memstats['memfree'] + memstats['buffers'] - if None not in (memstats.get('memtotal'), memstats.get('nocache:free')): - memstats['nocache:used'] = memstats['memtotal'] - memstats['nocache:free'] - if None not in (memstats.get('swaptotal'), memstats.get('swapfree')): - memstats['swap:used'] = memstats['swaptotal'] - memstats['swapfree'] - - self.facts['memory_mb'] = { - 'real' : { - 'total': memstats.get('memtotal'), - 'used': memstats.get('real:used'), - 'free': memstats.get('memfree'), - }, - 'nocache' : { - 'free': memstats.get('nocache:free'), - 'used': memstats.get('nocache:used'), - }, - 'swap' : { - 'total': memstats.get('swaptotal'), - 'free': memstats.get('swapfree'), - 'used': memstats.get('swap:used'), - 'cached': memstats.get('swapcached'), - }, - } - - def get_cpu_facts(self): - i = 0 - vendor_id_occurrence = 0 - model_name_occurrence = 0 - physid = 0 - coreid = 0 - sockets = {} - cores = {} - - xen = False - xen_paravirt = False - try: - if os.path.exists('/proc/xen'): - xen = True - else: - for line in get_file_lines('/sys/hypervisor/type'): - if line.strip() == 'xen': - xen = True - # Only interested in the first line - break - except IOError: - pass - - if not os.access("/proc/cpuinfo", os.R_OK): - return - self.facts['processor'] = [] - for line in get_file_lines('/proc/cpuinfo'): - data = line.split(":", 1) - key = data[0].strip() - - if xen: - if key == 'flags': - # Check for vme cpu flag, Xen paravirt does not expose this. - # Need to detect Xen paravirt because it exposes cpuinfo - # differently than Xen HVM or KVM and causes reporting of - # only a single cpu core. - if 'vme' not in data: - xen_paravirt = True - - # model name is for Intel arch, Processor (mind the uppercase P) - # works for some ARM devices, like the Sheevaplug. - if key in ['model name', 'Processor', 'vendor_id', 'cpu', 'Vendor']: - if 'processor' not in self.facts: - self.facts['processor'] = [] - self.facts['processor'].append(data[1].strip()) - if key == 'vendor_id': - vendor_id_occurrence += 1 - if key == 'model name': - model_name_occurrence += 1 - i += 1 - elif key == 'physical id': - physid = data[1].strip() - if physid not in sockets: - sockets[physid] = 1 - elif key == 'core id': - coreid = data[1].strip() - if coreid not in sockets: - cores[coreid] = 1 - elif key == 'cpu cores': - sockets[physid] = int(data[1].strip()) - elif key == 'siblings': - cores[coreid] = int(data[1].strip()) - elif key == '# processors': - self.facts['processor_cores'] = int(data[1].strip()) - - # Skip for platforms without vendor_id/model_name in cpuinfo (e.g ppc64le) - if vendor_id_occurrence > 0: - if vendor_id_occurrence == model_name_occurrence: - i = vendor_id_occurrence - - if self.facts['architecture'] != 's390x': - if xen_paravirt: - self.facts['processor_count'] = i - self.facts['processor_cores'] = i - self.facts['processor_threads_per_core'] = 1 - self.facts['processor_vcpus'] = i - else: - if sockets: - self.facts['processor_count'] = len(sockets) - else: - self.facts['processor_count'] = i - - socket_values = list(sockets.values()) - if socket_values and socket_values[0]: - self.facts['processor_cores'] = socket_values[0] - else: - self.facts['processor_cores'] = 1 - - core_values = list(cores.values()) - if core_values: - self.facts['processor_threads_per_core'] = core_values[0] // self.facts['processor_cores'] - else: - self.facts['processor_threads_per_core'] = 1 // self.facts['processor_cores'] - - self.facts['processor_vcpus'] = (self.facts['processor_threads_per_core'] * - self.facts['processor_count'] * self.facts['processor_cores']) - - def get_dmi_facts(self): - ''' learn dmi facts from system - - Try /sys first for dmi related facts. - If that is not available, fall back to dmidecode executable ''' - - if os.path.exists('/sys/devices/virtual/dmi/id/product_name'): - # Use kernel DMI info, if available - - # DMI SPEC -- http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.0.pdf - FORM_FACTOR = [ "Unknown", "Other", "Unknown", "Desktop", - "Low Profile Desktop", "Pizza Box", "Mini Tower", "Tower", - "Portable", "Laptop", "Notebook", "Hand Held", "Docking Station", - "All In One", "Sub Notebook", "Space-saving", "Lunch Box", - "Main Server Chassis", "Expansion Chassis", "Sub Chassis", - "Bus Expansion Chassis", "Peripheral Chassis", "RAID Chassis", - "Rack Mount Chassis", "Sealed-case PC", "Multi-system", - "CompactPCI", "AdvancedTCA", "Blade" ] - - DMI_DICT = { - 'bios_date': '/sys/devices/virtual/dmi/id/bios_date', - 'bios_version': '/sys/devices/virtual/dmi/id/bios_version', - 'form_factor': '/sys/devices/virtual/dmi/id/chassis_type', - 'product_name': '/sys/devices/virtual/dmi/id/product_name', - 'product_serial': '/sys/devices/virtual/dmi/id/product_serial', - 'product_uuid': '/sys/devices/virtual/dmi/id/product_uuid', - 'product_version': '/sys/devices/virtual/dmi/id/product_version', - 'system_vendor': '/sys/devices/virtual/dmi/id/sys_vendor' - } - - for (key,path) in DMI_DICT.items(): - data = get_file_content(path) - if data is not None: - if key == 'form_factor': - try: - self.facts['form_factor'] = FORM_FACTOR[int(data)] - except IndexError: - self.facts['form_factor'] = 'unknown (%s)' % data - else: - self.facts[key] = data - else: - self.facts[key] = 'NA' - - else: - # Fall back to using dmidecode, if available - dmi_bin = self.module.get_bin_path('dmidecode') - DMI_DICT = { - 'bios_date': 'bios-release-date', - 'bios_version': 'bios-version', - 'form_factor': 'chassis-type', - 'product_name': 'system-product-name', - 'product_serial': 'system-serial-number', - 'product_uuid': 'system-uuid', - 'product_version': 'system-version', - 'system_vendor': 'system-manufacturer' - } - for (k, v) in DMI_DICT.items(): - if dmi_bin is not None: - (rc, out, err) = self.module.run_command('%s -s %s' % (dmi_bin, v)) - if rc == 0: - # Strip out commented lines (specific dmidecode output) - thisvalue = ''.join([ line for line in out.splitlines() if not line.startswith('#') ]) - try: - json.dumps(thisvalue) - except UnicodeDecodeError: - thisvalue = "NA" - - self.facts[k] = thisvalue - else: - self.facts[k] = 'NA' - else: - self.facts[k] = 'NA' - - def _run_lsblk(self, lsblk_path): - # call lsblk and collect all uuids - # --exclude 2 makes lsblk ignore floppy disks, which are slower to answer than typical timeouts - # this uses the linux major device number - # for details see https://www.kernel.org/doc/Documentation/devices.txt - args = ['--list', '--noheadings', '--paths', '--output', 'NAME,UUID', '--exclude', '2'] - cmd = [lsblk_path] + args - rc, out, err = self.module.run_command(cmd) - return rc, out, err - - def _lsblk_uuid(self): - uuids = {} - lsblk_path = self.module.get_bin_path("lsblk") - if not lsblk_path: - return uuids - - rc, out, err = self._run_lsblk(lsblk_path) - if rc != 0: - return uuids - - # each line will be in format: - # - # /dev/sda1 32caaec3-ef40-4691-a3b6-438c3f9bc1c0 - for lsblk_line in out.splitlines(): - if not lsblk_line: - continue - - line = lsblk_line.strip() - fields = line.rsplit(None, 1) - - if len(fields) < 2: - continue - - device_name, uuid = fields[0].strip(), fields[1].strip() - if device_name in uuids: - continue - uuids[device_name] = uuid - - return uuids - - def _run_findmnt(self, findmnt_path): - args = ['--list', '--noheadings', '--notruncate'] - cmd = [findmnt_path] + args - rc, out, err = self.module.run_command(cmd, errors='surrogate_then_replace') - return rc, out, err - - def _find_bind_mounts(self): - bind_mounts = set() - findmnt_path = self.module.get_bin_path("findmnt") - if not findmnt_path: - return bind_mounts - - rc, out, err = self._run_findmnt(findmnt_path) - if rc != 0: - return bind_mounts - - # find bind mounts, in case /etc/mtab is a symlink to /proc/mounts - for line in out.splitlines(): - fields = line.split() - # fields[0] is the TARGET, fields[1] is the SOURCE - if len(fields) < 2: - continue - - # bind mounts will have a [/directory_name] in the SOURCE column - if self.BIND_MOUNT_RE.match(fields[1]): - bind_mounts.add(fields[0]) - - return bind_mounts - - def _mtab_entries(self): - mtab_file = '/etc/mtab' - if not os.path.exists(mtab_file): - mtab_file = '/proc/mounts' - - mtab = get_file_content(mtab_file, '') - mtab_entries = [] - for line in mtab.splitlines(): - fields = line.split() - if len(fields) < 4: - continue - mtab_entries.append(fields) - return mtab_entries - - @timeout() - def get_mount_facts(self): - self.facts['mounts'] = [] - - bind_mounts = self._find_bind_mounts() - uuids = self._lsblk_uuid() - mtab_entries = self._mtab_entries() - - mounts = [] - for fields in mtab_entries: - device, mount, fstype, options = fields[0], fields[1], fields[2], fields[3] - - if not device.startswith('/') and ':/' not in device: - continue - - if fstype == 'none': - continue - - size_total, size_available = self._get_mount_size_facts(mount) - - if mount in bind_mounts: - # only add if not already there, we might have a plain /etc/mtab - if not self.MTAB_BIND_MOUNT_RE.match(options): - options += ",bind" - - mount_info = {'mount': mount, - 'device': device, - 'fstype': fstype, - 'options': options, - # statvfs data - 'size_total': size_total, - 'size_available': size_available, - 'uuid': uuids.get(device, 'N/A')} - - mounts.append(mount_info) - - self.facts['mounts'] = mounts - - def get_holders(self, block_dev_dict, sysdir): - block_dev_dict['holders'] = [] - if os.path.isdir(sysdir + "/holders"): - for folder in os.listdir(sysdir + "/holders"): - if not folder.startswith("dm-"): - continue - name = get_file_content(sysdir + "/holders/" + folder + "/dm/name") - if name: - block_dev_dict['holders'].append(name) - else: - block_dev_dict['holders'].append(folder) - - def get_device_facts(self): - self.facts['devices'] = {} - lspci = self.module.get_bin_path('lspci') - if lspci: - rc, pcidata, err = self.module.run_command([lspci, '-D'], errors='surrogate_then_replace') - else: - pcidata = None - - try: - block_devs = os.listdir("/sys/block") - except OSError: - return - - devs_wwn = {} - try: - devs_by_id = os.listdir("/dev/disk/by-id") - except OSError: - pass - else: - for link_name in devs_by_id: - if link_name.startswith("wwn-"): - try: - wwn_link = os.readlink(os.path.join("/dev/disk/by-id", link_name)) - except OSError: - continue - devs_wwn[os.path.basename(wwn_link)] = link_name[4:] - - for block in block_devs: - virtual = 1 - sysfs_no_links = 0 - try: - path = os.readlink(os.path.join("/sys/block/", block)) - except OSError: - e = sys.exc_info()[1] - if e.errno == errno.EINVAL: - path = block - sysfs_no_links = 1 - else: - continue - if "virtual" in path: - continue - sysdir = os.path.join("/sys/block", path) - if sysfs_no_links == 1: - for folder in os.listdir(sysdir): - if "device" in folder: - virtual = 0 - break - if virtual: - continue - d = {} - diskname = os.path.basename(sysdir) - for key in ['vendor', 'model', 'sas_address', 'sas_device_handle']: - d[key] = get_file_content(sysdir + "/device/" + key) - - sg_inq = self.module.get_bin_path('sg_inq') - - if sg_inq: - device = "/dev/%s" % (block) - rc, drivedata, err = self.module.run_command([sg_inq, device]) - if rc == 0: - serial = re.search("Unit serial number:\s+(\w+)", drivedata) - if serial: - d['serial'] = serial.group(1) - - for key in ['vendor', 'model']: - d[key] = get_file_content(sysdir + "/device/" + key) - - for key,test in [ ('removable','/removable'), \ - ('support_discard','/queue/discard_granularity'), - ]: - d[key] = get_file_content(sysdir + test) - - if diskname in devs_wwn: - d['wwn'] = devs_wwn[diskname] - - d['partitions'] = {} - for folder in os.listdir(sysdir): - m = re.search("(" + diskname + "\d+)", folder) - if m: - part = {} - partname = m.group(1) - part_sysdir = sysdir + "/" + partname - - part['start'] = get_file_content(part_sysdir + "/start",0) - part['sectors'] = get_file_content(part_sysdir + "/size",0) - part['sectorsize'] = get_file_content(part_sysdir + "/queue/logical_block_size") - if not part['sectorsize']: - part['sectorsize'] = get_file_content(part_sysdir + "/queue/hw_sector_size",512) - part['size'] = self.module.pretty_bytes((float(part['sectors']) * float(part['sectorsize']))) - part['uuid'] = get_partition_uuid(partname) - self.get_holders(part, part_sysdir) - - d['partitions'][partname] = part - - d['rotational'] = get_file_content(sysdir + "/queue/rotational") - d['scheduler_mode'] = "" - scheduler = get_file_content(sysdir + "/queue/scheduler") - if scheduler is not None: - m = re.match(".*?(\[(.*)\])", scheduler) - if m: - d['scheduler_mode'] = m.group(2) - - d['sectors'] = get_file_content(sysdir + "/size") - if not d['sectors']: - d['sectors'] = 0 - d['sectorsize'] = get_file_content(sysdir + "/queue/logical_block_size") - if not d['sectorsize']: - d['sectorsize'] = get_file_content(sysdir + "/queue/hw_sector_size",512) - d['size'] = self.module.pretty_bytes(float(d['sectors']) * float(d['sectorsize'])) - - d['host'] = "" - - # domains are numbered (0 to ffff), bus (0 to ff), slot (0 to 1f), and function (0 to 7). - m = re.match(".+/([a-f0-9]{4}:[a-f0-9]{2}:[0|1][a-f0-9]\.[0-7])/", sysdir) - if m and pcidata: - pciid = m.group(1) - did = re.escape(pciid) - m = re.search("^" + did + "\s(.*)$", pcidata, re.MULTILINE) - if m: - d['host'] = m.group(1) - - self.get_holders(d, sysdir) - - self.facts['devices'][diskname] = d - - def get_uptime_facts(self): - uptime_file_content = get_file_content('/proc/uptime') - if uptime_file_content: - uptime_seconds_string = uptime_file_content.split(' ')[0] - self.facts['uptime_seconds'] = int(float(uptime_seconds_string)) - - def _find_mapper_device_name(self, dm_device): - dm_prefix = '/dev/dm-' - mapper_device = dm_device - if dm_device.startswith(dm_prefix): - dmsetup_cmd = self.module.get_bin_path('dmsetup', True) - mapper_prefix = '/dev/mapper/' - rc, dm_name, err = self.module.run_command("%s info -C --noheadings -o name %s" % (dmsetup_cmd, dm_device)) - if rc == 0: - mapper_device = mapper_prefix + dm_name.rstrip() - return mapper_device - - def get_lvm_facts(self): - """ Get LVM Facts if running as root and lvm utils are available """ - - if os.getuid() == 0 and self.module.get_bin_path('vgs'): - lvm_util_options = '--noheadings --nosuffix --units g' - - vgs_path = self.module.get_bin_path('vgs') - #vgs fields: VG #PV #LV #SN Attr VSize VFree - vgs={} - if vgs_path: - rc, vg_lines, err = self.module.run_command( '%s %s' % (vgs_path, lvm_util_options)) - for vg_line in vg_lines.splitlines(): - items = vg_line.split() - vgs[items[0]] = {'size_g':items[-2], - 'free_g':items[-1], - 'num_lvs': items[2], - 'num_pvs': items[1]} - - lvs_path = self.module.get_bin_path('lvs') - #lvs fields: - #LV VG Attr LSize Pool Origin Data% Move Log Copy% Convert - lvs = {} - if lvs_path: - rc, lv_lines, err = self.module.run_command( '%s %s' % (lvs_path, lvm_util_options)) - for lv_line in lv_lines.splitlines(): - items = lv_line.split() - lvs[items[0]] = {'size_g': items[3], 'vg': items[1]} - - - pvs_path = self.module.get_bin_path('pvs') - #pvs fields: PV VG #Fmt #Attr PSize PFree - pvs = {} - if pvs_path: - rc, pv_lines, err = self.module.run_command( '%s %s' % (pvs_path, lvm_util_options)) - for pv_line in pv_lines.splitlines(): - items = pv_line.split() - pvs[self._find_mapper_device_name(items[0])] = { - 'size_g': items[4], - 'free_g': items[5], - 'vg': items[1]} - - self.facts['lvm'] = {'lvs': lvs, 'vgs': vgs, 'pvs': pvs} - - -class SunOSHardware(Hardware): - """ - In addition to the generic memory and cpu facts, this also sets - swap_reserved_mb and swap_allocated_mb that is available from *swap -s*. - """ - platform = 'SunOS' - - def populate(self): - self.module.run_command_environ_update = {'LANG': 'C', 'LC_ALL': 'C', 'LC_NUMERIC': 'C'} - self.get_cpu_facts() - self.get_memory_facts() - self.get_dmi_facts() - self.get_device_facts() - self.get_uptime_facts() - try: - self.get_mount_facts() - except TimeoutError: - pass - return self.facts - - def get_cpu_facts(self): - physid = 0 - sockets = {} - rc, out, err = self.module.run_command("/usr/bin/kstat cpu_info") - self.facts['processor'] = [] - for line in out.splitlines(): - if len(line) < 1: - continue - data = line.split(None, 1) - key = data[0].strip() - # "brand" works on Solaris 10 & 11. "implementation" for Solaris 9. - if key == 'module:': - brand = '' - elif key == 'brand': - brand = data[1].strip() - elif key == 'clock_MHz': - clock_mhz = data[1].strip() - elif key == 'implementation': - processor = brand or data[1].strip() - # Add clock speed to description for SPARC CPU - if self.facts['machine'] != 'i86pc': - processor += " @ " + clock_mhz + "MHz" - if 'processor' not in self.facts: - self.facts['processor'] = [] - self.facts['processor'].append(processor) - elif key == 'chip_id': - physid = data[1].strip() - if physid not in sockets: - sockets[physid] = 1 - else: - sockets[physid] += 1 - # Counting cores on Solaris can be complicated. - # https://blogs.oracle.com/mandalika/entry/solaris_show_me_the_cpu - # Treat 'processor_count' as physical sockets and 'processor_cores' as - # virtual CPUs visisble to Solaris. Not a true count of cores for modern SPARC as - # these processors have: sockets -> cores -> threads/virtual CPU. - if len(sockets) > 0: - self.facts['processor_count'] = len(sockets) - self.facts['processor_cores'] = reduce(lambda x, y: x + y, sockets.values()) - else: - self.facts['processor_cores'] = 'NA' - self.facts['processor_count'] = len(self.facts['processor']) - - def get_memory_facts(self): - rc, out, err = self.module.run_command(["/usr/sbin/prtconf"]) - for line in out.splitlines(): - if 'Memory size' in line: - self.facts['memtotal_mb'] = int(line.split()[2]) - rc, out, err = self.module.run_command("/usr/sbin/swap -s") - allocated = int(out.split()[1][:-1]) - reserved = int(out.split()[5][:-1]) - used = int(out.split()[8][:-1]) - free = int(out.split()[10][:-1]) - self.facts['swapfree_mb'] = free // 1024 - self.facts['swaptotal_mb'] = (free + used) // 1024 - self.facts['swap_allocated_mb'] = allocated // 1024 - self.facts['swap_reserved_mb'] = reserved // 1024 - - @timeout() - def get_mount_facts(self): - self.facts['mounts'] = [] - # For a detailed format description see mnttab(4) - # special mount_point fstype options time - fstab = get_file_content('/etc/mnttab') - if fstab: - for line in fstab.splitlines(): - fields = line.split('\t') - size_total, size_available = self._get_mount_size_facts(fields[1]) - self.facts['mounts'].append({ - 'mount': fields[1], - 'device': fields[0], - 'fstype' : fields[2], - 'options': fields[3], - 'time': fields[4], - 'size_total': size_total, - 'size_available': size_available - }) - - def get_dmi_facts(self): - uname_path = self.module.get_bin_path("prtdiag") - rc, out, err = self.module.run_command(uname_path) - """ - rc returns 1 - """ - if out: - system_conf = out.split('\n')[0] - found = re.search(r'(\w+\sEnterprise\s\w+)',system_conf) - if found: - self.facts['product_name'] = found.group(1) - - def get_device_facts(self): - # Device facts are derived for sdderr kstats. This code does not use the - # full output, but rather queries for specific stats. - # Example output: - # sderr:0:sd0,err:Hard Errors 0 - # sderr:0:sd0,err:Illegal Request 6 - # sderr:0:sd0,err:Media Error 0 - # sderr:0:sd0,err:Predictive Failure Analysis 0 - # sderr:0:sd0,err:Product VBOX HARDDISK 9 - # sderr:0:sd0,err:Revision 1.0 - # sderr:0:sd0,err:Serial No VB0ad2ec4d-074a - # sderr:0:sd0,err:Size 53687091200 - # sderr:0:sd0,err:Soft Errors 0 - # sderr:0:sd0,err:Transport Errors 0 - # sderr:0:sd0,err:Vendor ATA - - self.facts['devices'] = {} - - disk_stats = { - 'Product': 'product', - 'Revision': 'revision', - 'Serial No': 'serial', - 'Size': 'size', - 'Vendor': 'vendor', - 'Hard Errors': 'hard_errors', - 'Soft Errors': 'soft_errors', - 'Transport Errors': 'transport_errors', - 'Media Error': 'media_errors', - 'Predictive Failure Analysis': 'predictive_failure_analysis', - 'Illegal Request': 'illegal_request', - } - - cmd = ['/usr/bin/kstat', '-p'] - - for ds in disk_stats: - cmd.append('sderr:::%s' % ds) - - d = {} - rc, out, err = self.module.run_command(cmd) - if rc != 0: - return dict() - - sd_instances = frozenset(line.split(':')[1] for line in out.split('\n') if line.startswith('sderr')) - for instance in sd_instances: - lines = (line for line in out.split('\n') if ':' in line and line.split(':')[1] == instance) - for line in lines: - text, value = line.split('\t') - stat = text.split(':')[3] - - if stat == 'Size': - d[disk_stats.get(stat)] = self.module.pretty_bytes(float(value)) - else: - d[disk_stats.get(stat)] = value.rstrip() - - diskname = 'sd' + instance - self.facts['devices'][diskname] = d - d = {} - - def get_uptime_facts(self): - # On Solaris, unix:0:system_misc:snaptime is created shortly after machine boots up - # and displays tiem in seconds. This is much easier than using uptime as we would - # need to have a parsing procedure for translating from human-readable to machine-readable - # format. - # Example output: - # unix:0:system_misc:snaptime 1175.410463590 - rc, out, err = self.module.run_command('/usr/bin/kstat -p unix:0:system_misc:snaptime') - - if rc != 0: - return - - self.facts['uptime_seconds'] = int(float(out.split('\t')[1])) - - -class OpenBSDHardware(Hardware): - """ - OpenBSD-specific subclass of Hardware. Defines memory, CPU and device facts: - - memfree_mb - - memtotal_mb - - swapfree_mb - - swaptotal_mb - - processor (a list) - - processor_cores - - processor_count - - processor_speed - - In addition, it also defines number of DMI facts and device facts. - """ - platform = 'OpenBSD' - - def populate(self): - self.sysctl = self.get_sysctl(['hw']) - self.get_memory_facts() - self.get_processor_facts() - self.get_device_facts() - try: - self.get_mount_facts() - except TimeoutError: - pass - self.get_dmi_facts() - return self.facts - - @timeout() - def get_mount_facts(self): - self.facts['mounts'] = [] - fstab = get_file_content('/etc/fstab') - if fstab: - for line in fstab.splitlines(): - if line.startswith('#') or line.strip() == '': - continue - fields = re.sub(r'\s+',' ', line).split() - if fields[1] == 'none' or fields[3] == 'xx': - continue - size_total, size_available = self._get_mount_size_facts(fields[1]) - self.facts['mounts'].append({ - 'mount': fields[1], - 'device': fields[0], - 'fstype' : fields[2], - 'options': fields[3], - 'size_total': size_total, - 'size_available': size_available - }) - - - def get_memory_facts(self): - # Get free memory. vmstat output looks like: - # procs memory page disks traps cpu - # r b w avm fre flt re pi po fr sr wd0 fd0 int sys cs us sy id - # 0 0 0 47512 28160 51 0 0 0 0 0 1 0 116 89 17 0 1 99 - rc, out, err = self.module.run_command("/usr/bin/vmstat") - if rc == 0: - self.facts['memfree_mb'] = int(out.splitlines()[-1].split()[4]) // 1024 - self.facts['memtotal_mb'] = int(self.sysctl['hw.usermem']) // 1024 // 1024 - - # Get swapctl info. swapctl output looks like: - # total: 69268 1K-blocks allocated, 0 used, 69268 available - # And for older OpenBSD: - # total: 69268k bytes allocated = 0k used, 69268k available - rc, out, err = self.module.run_command("/sbin/swapctl -sk") - if rc == 0: - swaptrans = { ord(u'k'): None, ord(u'm'): None, ord(u'g'): None} - data = to_text(out, errors='surrogate_or_strict').split() - self.facts['swapfree_mb'] = int(data[-2].translate(swaptrans)) // 1024 - self.facts['swaptotal_mb'] = int(data[1].translate(swaptrans)) // 1024 - - def get_processor_facts(self): - processor = [] - for i in range(int(self.sysctl['hw.ncpu'])): - processor.append(self.sysctl['hw.model']) - - self.facts['processor'] = processor - # The following is partly a lie because there is no reliable way to - # determine the number of physical CPUs in the system. We can only - # query the number of logical CPUs, which hides the number of cores. - # On amd64/i386 we could try to inspect the smt/core/package lines in - # dmesg, however even those have proven to be unreliable. - # So take a shortcut and report the logical number of processors in - # 'processor_count' and 'processor_cores' and leave it at that. - self.facts['processor_count'] = self.sysctl['hw.ncpu'] - self.facts['processor_cores'] = self.sysctl['hw.ncpu'] - - def get_device_facts(self): - devices = [] - devices.extend(self.sysctl['hw.disknames'].split(',')) - self.facts['devices'] = devices - - def get_dmi_facts(self): - # We don't use dmidecode(1) here because: - # - it would add dependency on an external package - # - dmidecode(1) can only be ran as root - # So instead we rely on sysctl(8) to provide us the information on a - # best-effort basis. As a bonus we also get facts on non-amd64/i386 - # platforms this way. - sysctl_to_dmi = { - 'hw.product': 'product_name', - 'hw.version': 'product_version', - 'hw.uuid': 'product_uuid', - 'hw.serialno': 'product_serial', - 'hw.vendor': 'system_vendor', - } - - for mib in sysctl_to_dmi: - if mib in self.sysctl: - self.facts[sysctl_to_dmi[mib]] = self.sysctl[mib] - -class FreeBSDHardware(Hardware): - """ - FreeBSD-specific subclass of Hardware. Defines memory and CPU facts: - - memfree_mb - - memtotal_mb - - swapfree_mb - - swaptotal_mb - - processor (a list) - - processor_cores - - processor_count - - devices - """ - platform = 'FreeBSD' - DMESG_BOOT = '/var/run/dmesg.boot' - - def populate(self): - self.get_cpu_facts() - self.get_memory_facts() - self.get_dmi_facts() - self.get_device_facts() - try: - self.get_mount_facts() - except TimeoutError: - pass - return self.facts - - def get_cpu_facts(self): - self.facts['processor'] = [] - rc, out, err = self.module.run_command("/sbin/sysctl -n hw.ncpu") - self.facts['processor_count'] = out.strip() - - dmesg_boot = get_file_content(FreeBSDHardware.DMESG_BOOT) - if not dmesg_boot: - rc, dmesg_boot, err = self.module.run_command("/sbin/dmesg") - for line in dmesg_boot.splitlines(): - if 'CPU:' in line: - cpu = re.sub(r'CPU:\s+', r"", line) - self.facts['processor'].append(cpu.strip()) - if 'Logical CPUs per core' in line: - self.facts['processor_cores'] = line.split()[4] - - - def get_memory_facts(self): - rc, out, err = self.module.run_command("/sbin/sysctl vm.stats") - for line in out.splitlines(): - data = line.split() - if 'vm.stats.vm.v_page_size' in line: - pagesize = int(data[1]) - if 'vm.stats.vm.v_page_count' in line: - pagecount = int(data[1]) - if 'vm.stats.vm.v_free_count' in line: - freecount = int(data[1]) - self.facts['memtotal_mb'] = pagesize * pagecount // 1024 // 1024 - self.facts['memfree_mb'] = pagesize * freecount // 1024 // 1024 - # Get swapinfo. swapinfo output looks like: - # Device 1M-blocks Used Avail Capacity - # /dev/ada0p3 314368 0 314368 0% - # - rc, out, err = self.module.run_command("/usr/sbin/swapinfo -k") - lines = out.splitlines() - if len(lines[-1]) == 0: - lines.pop() - data = lines[-1].split() - if data[0] != 'Device': - self.facts['swaptotal_mb'] = int(data[1]) // 1024 - self.facts['swapfree_mb'] = int(data[3]) // 1024 - - @timeout() - def get_mount_facts(self): - self.facts['mounts'] = [] - fstab = get_file_content('/etc/fstab') - if fstab: - for line in fstab.splitlines(): - if line.startswith('#') or line.strip() == '': - continue - fields = re.sub(r'\s+',' ',line).split() - size_total, size_available = self._get_mount_size_facts(fields[1]) - self.facts['mounts'].append({ - 'mount': fields[1], - 'device': fields[0], - 'fstype': fields[2], - 'options': fields[3], - 'size_total': size_total, - 'size_available': size_available - }) - - def get_device_facts(self): - sysdir = '/dev' - self.facts['devices'] = {} - drives = re.compile('(ada?\d+|da\d+|a?cd\d+)') #TODO: rc, disks, err = self.module.run_command("/sbin/sysctl kern.disks") - slices = re.compile('(ada?\d+s\d+\w*|da\d+s\d+\w*)') - if os.path.isdir(sysdir): - dirlist = sorted(os.listdir(sysdir)) - for device in dirlist: - d = drives.match(device) - if d: - self.facts['devices'][d.group(1)] = [] - s = slices.match(device) - if s: - self.facts['devices'][d.group(1)].append(s.group(1)) - - def get_dmi_facts(self): - ''' learn dmi facts from system - - Use dmidecode executable if available''' - - # Fall back to using dmidecode, if available - dmi_bin = self.module.get_bin_path('dmidecode') - DMI_DICT = dict( - bios_date='bios-release-date', - bios_version='bios-version', - form_factor='chassis-type', - product_name='system-product-name', - product_serial='system-serial-number', - product_uuid='system-uuid', - product_version='system-version', - system_vendor='system-manufacturer' - ) - for (k, v) in DMI_DICT.items(): - if dmi_bin is not None: - (rc, out, err) = self.module.run_command('%s -s %s' % (dmi_bin, v)) - if rc == 0: - # Strip out commented lines (specific dmidecode output) - self.facts[k] = ''.join([line for line in out.splitlines() if not line.startswith('#') ]) - try: - json.dumps(self.facts[k]) - except UnicodeDecodeError: - self.facts[k] = 'NA' - else: - self.facts[k] = 'NA' - else: - self.facts[k] = 'NA' - - -class DragonFlyHardware(FreeBSDHardware): - platform = 'DragonFly' - - -class NetBSDHardware(Hardware): - """ - NetBSD-specific subclass of Hardware. Defines memory and CPU facts: - - memfree_mb - - memtotal_mb - - swapfree_mb - - swaptotal_mb - - processor (a list) - - processor_cores - - processor_count - - devices - """ - platform = 'NetBSD' - MEMORY_FACTS = ['MemTotal', 'SwapTotal', 'MemFree', 'SwapFree'] - - def populate(self): - self.sysctl = self.get_sysctl(['machdep']) - self.get_cpu_facts() - self.get_memory_facts() - try: - self.get_mount_facts() - except TimeoutError: - pass - self.get_dmi_facts() - return self.facts - - def get_cpu_facts(self): - - i = 0 - physid = 0 - sockets = {} - if not os.access("/proc/cpuinfo", os.R_OK): - return - self.facts['processor'] = [] - for line in get_file_lines("/proc/cpuinfo"): - data = line.split(":", 1) - key = data[0].strip() - # model name is for Intel arch, Processor (mind the uppercase P) - # works for some ARM devices, like the Sheevaplug. - if key == 'model name' or key == 'Processor': - if 'processor' not in self.facts: - self.facts['processor'] = [] - self.facts['processor'].append(data[1].strip()) - i += 1 - elif key == 'physical id': - physid = data[1].strip() - if physid not in sockets: - sockets[physid] = 1 - elif key == 'cpu cores': - sockets[physid] = int(data[1].strip()) - if len(sockets) > 0: - self.facts['processor_count'] = len(sockets) - self.facts['processor_cores'] = reduce(lambda x, y: x + y, sockets.values()) - else: - self.facts['processor_count'] = i - self.facts['processor_cores'] = 'NA' - - def get_memory_facts(self): - if not os.access("/proc/meminfo", os.R_OK): - return - for line in get_file_lines("/proc/meminfo"): - data = line.split(":", 1) - key = data[0] - if key in NetBSDHardware.MEMORY_FACTS: - val = data[1].strip().split(' ')[0] - self.facts["%s_mb" % key.lower()] = int(val) // 1024 - - @timeout() - def get_mount_facts(self): - self.facts['mounts'] = [] - fstab = get_file_content('/etc/fstab') - if fstab: - for line in fstab.splitlines(): - if line.startswith('#') or line.strip() == '': - continue - fields = re.sub(r'\s+',' ',line).split() - size_total, size_available = self._get_mount_size_facts(fields[1]) - self.facts['mounts'].append({ - 'mount': fields[1], - 'device': fields[0], - 'fstype' : fields[2], - 'options': fields[3], - 'size_total': size_total, - 'size_available': size_available - }) - - def get_dmi_facts(self): - # We don't use dmidecode(1) here because: - # - it would add dependency on an external package - # - dmidecode(1) can only be ran as root - # So instead we rely on sysctl(8) to provide us the information on a - # best-effort basis. As a bonus we also get facts on non-amd64/i386 - # platforms this way. - sysctl_to_dmi = { - 'machdep.dmi.system-product': 'product_name', - 'machdep.dmi.system-version': 'product_version', - 'machdep.dmi.system-uuid': 'product_uuid', - 'machdep.dmi.system-serial': 'product_serial', - 'machdep.dmi.system-vendor': 'system_vendor', - } - - for mib in sysctl_to_dmi: - if mib in self.sysctl: - self.facts[sysctl_to_dmi[mib]] = self.sysctl[mib] - -class AIX(Hardware): - """ - AIX-specific subclass of Hardware. Defines memory and CPU facts: - - memfree_mb - - memtotal_mb - - swapfree_mb - - swaptotal_mb - - processor (a list) - - processor_cores - - processor_count - """ - platform = 'AIX' - - def populate(self): - self.get_cpu_facts() - self.get_memory_facts() - self.get_dmi_facts() - self.get_vgs_facts() - self.get_mount_facts() - return self.facts - - def get_cpu_facts(self): - self.facts['processor'] = [] - - - rc, out, err = self.module.run_command("/usr/sbin/lsdev -Cc processor") - if out: - i = 0 - for line in out.splitlines(): - - if 'Available' in line: - if i == 0: - data = line.split(' ') - cpudev = data[0] - - i += 1 - self.facts['processor_count'] = int(i) - - rc, out, err = self.module.run_command("/usr/sbin/lsattr -El " + cpudev + " -a type") - - data = out.split(' ') - self.facts['processor'] = data[1] - - rc, out, err = self.module.run_command("/usr/sbin/lsattr -El " + cpudev + " -a smt_threads") - - data = out.split(' ') - self.facts['processor_cores'] = int(data[1]) - - def get_memory_facts(self): - pagesize = 4096 - rc, out, err = self.module.run_command("/usr/bin/vmstat -v") - for line in out.splitlines(): - data = line.split() - if 'memory pages' in line: - pagecount = int(data[0]) - if 'free pages' in line: - freecount = int(data[0]) - self.facts['memtotal_mb'] = pagesize * pagecount // 1024 // 1024 - self.facts['memfree_mb'] = pagesize * freecount // 1024 // 1024 - # Get swapinfo. swapinfo output looks like: - # Device 1M-blocks Used Avail Capacity - # /dev/ada0p3 314368 0 314368 0% - # - rc, out, err = self.module.run_command("/usr/sbin/lsps -s") - if out: - lines = out.splitlines() - data = lines[1].split() - swaptotal_mb = int(data[0].rstrip('MB')) - percused = int(data[1].rstrip('%')) - self.facts['swaptotal_mb'] = swaptotal_mb - self.facts['swapfree_mb'] = int(swaptotal_mb * ( 100 - percused ) / 100) - - def get_dmi_facts(self): - rc, out, err = self.module.run_command("/usr/sbin/lsattr -El sys0 -a fwversion") - data = out.split() - self.facts['firmware_version'] = data[1].strip('IBM,') - lsconf_path = self.module.get_bin_path("lsconf") - if lsconf_path: - rc, out, err = self.module.run_command(lsconf_path) - if rc == 0 and out: - for line in out.splitlines(): - data = line.split(':') - if 'Machine Serial Number' in line: - self.facts['product_serial'] = data[1].strip() - if 'LPAR Info' in line: - self.facts['lpar_info'] = data[1].strip() - if 'System Model' in line: - self.facts['product_name'] = data[1].strip() - def get_vgs_facts(self): - """ - Get vg and pv Facts - rootvg: - PV_NAME PV STATE TOTAL PPs FREE PPs FREE DISTRIBUTION - hdisk0 active 546 0 00..00..00..00..00 - hdisk1 active 546 113 00..00..00..21..92 - realsyncvg: - PV_NAME PV STATE TOTAL PPs FREE PPs FREE DISTRIBUTION - hdisk74 active 1999 6 00..00..00..00..06 - testvg: - PV_NAME PV STATE TOTAL PPs FREE PPs FREE DISTRIBUTION - hdisk105 active 999 838 200..39..199..200..200 - hdisk106 active 999 599 200..00..00..199..200 - """ - - lsvg_path = self.module.get_bin_path("lsvg") - xargs_path = self.module.get_bin_path("xargs") - cmd = "%s | %s %s -p" % (lsvg_path ,xargs_path,lsvg_path) - if lsvg_path and xargs_path: - rc, out, err = self.module.run_command(cmd,use_unsafe_shell=True) - if rc == 0 and out: - self.facts['vgs']= {} - for m in re.finditer(r'(\S+):\n.*FREE DISTRIBUTION(\n(\S+)\s+(\w+)\s+(\d+)\s+(\d+).*)+', out): - self.facts['vgs'][m.group(1)] = [] - pp_size = 0 - cmd = "%s %s" % (lsvg_path,m.group(1)) - rc, out, err = self.module.run_command(cmd) - if rc == 0 and out: - pp_size = re.search(r'PP SIZE:\s+(\d+\s+\S+)',out).group(1) - for n in re.finditer(r'(\S+)\s+(\w+)\s+(\d+)\s+(\d+).*',m.group(0)): - pv_info = { 'pv_name': n.group(1), - 'pv_state': n.group(2), - 'total_pps': n.group(3), - 'free_pps': n.group(4), - 'pp_size': pp_size - } - self.facts['vgs'][m.group(1)].append(pv_info) - - - def get_mount_facts(self): - self.facts['mounts'] = [] - # AIX does not have mtab but mount command is only source of info (or to use - # api calls to get same info) - mount_path = self.module.get_bin_path('mount') - rc, mount_out, err = self.module.run_command(mount_path) - if mount_out: - for line in mount_out.split('\n'): - fields = line.split() - if len(fields) != 0 and fields[0] != 'node' and fields[0][0] != '-' and re.match('^/.*|^[a-zA-Z].*|^[0-9].*', fields[0]): - if re.match('^/', fields[0]): - # normal mount - self.facts['mounts'].append({'mount': fields[1], - 'device': fields[0], - 'fstype' : fields[2], - 'options': fields[6], - 'time': '%s %s %s' % ( fields[3], fields[4], fields[5])}) - else: - # nfs or cifs based mount - # in case of nfs if no mount options are provided on command line - # add into fields empty string... - if len(fields) < 8: - fields.append("") - self.facts['mounts'].append({'mount': fields[2], - 'device': '%s:%s' % (fields[0], fields[1]), - 'fstype' : fields[3], - 'options': fields[7], - 'time': '%s %s %s' % ( fields[4], fields[5], fields[6])}) - -class HPUX(Hardware): - """ - HP-UX-specific subclass of Hardware. Defines memory and CPU facts: - - memfree_mb - - memtotal_mb - - swapfree_mb - - swaptotal_mb - - processor - - processor_cores - - processor_count - - model - - firmware - """ - - platform = 'HP-UX' - - def populate(self): - self.get_cpu_facts() - self.get_memory_facts() - self.get_hw_facts() - return self.facts - - def get_cpu_facts(self): - if self.facts['architecture'] == '9000/800': - rc, out, err = self.module.run_command("ioscan -FkCprocessor | wc -l", use_unsafe_shell=True) - self.facts['processor_count'] = int(out.strip()) - #Working with machinfo mess - elif self.facts['architecture'] == 'ia64': - if self.facts['distribution_version'] == "B.11.23": - rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo | grep 'Number of CPUs'", use_unsafe_shell=True) - self.facts['processor_count'] = int(out.strip().split('=')[1]) - rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo | grep 'processor family'", use_unsafe_shell=True) - self.facts['processor'] = re.search('.*(Intel.*)', out).groups()[0].strip() - rc, out, err = self.module.run_command("ioscan -FkCprocessor | wc -l", use_unsafe_shell=True) - self.facts['processor_cores'] = int(out.strip()) - if self.facts['distribution_version'] == "B.11.31": - #if machinfo return cores strings release B.11.31 > 1204 - rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo | grep core | wc -l", use_unsafe_shell=True) - if out.strip()== '0': - rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo | grep Intel", use_unsafe_shell=True) - self.facts['processor_count'] = int(out.strip().split(" ")[0]) - #If hyperthreading is active divide cores by 2 - rc, out, err = self.module.run_command("/usr/sbin/psrset | grep LCPU", use_unsafe_shell=True) - data = re.sub(' +',' ',out).strip().split(' ') - if len(data) == 1: - hyperthreading = 'OFF' - else: - hyperthreading = data[1] - rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo | grep logical", use_unsafe_shell=True) - data = out.strip().split(" ") - if hyperthreading == 'ON': - self.facts['processor_cores'] = int(data[0])/2 - else: - if len(data) == 1: - self.facts['processor_cores'] = self.facts['processor_count'] - else: - self.facts['processor_cores'] = int(data[0]) - rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo | grep Intel |cut -d' ' -f4-", use_unsafe_shell=True) - self.facts['processor'] = out.strip() - else: - rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo | egrep 'socket[s]?$' | tail -1", use_unsafe_shell=True) - self.facts['processor_count'] = int(out.strip().split(" ")[0]) - rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo | grep -e '[0-9] core' | tail -1", use_unsafe_shell=True) - self.facts['processor_cores'] = int(out.strip().split(" ")[0]) - rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo | grep Intel", use_unsafe_shell=True) - self.facts['processor'] = out.strip() - - def get_memory_facts(self): - pagesize = 4096 - rc, out, err = self.module.run_command("/usr/bin/vmstat | tail -1", use_unsafe_shell=True) - data = int(re.sub(' +',' ',out).split(' ')[5].strip()) - self.facts['memfree_mb'] = pagesize * data // 1024 // 1024 - if self.facts['architecture'] == '9000/800': - try: - rc, out, err = self.module.run_command("grep Physical /var/adm/syslog/syslog.log") - data = re.search('.*Physical: ([0-9]*) Kbytes.*',out).groups()[0].strip() - self.facts['memtotal_mb'] = int(data) // 1024 - except AttributeError: - #For systems where memory details aren't sent to syslog or the log has rotated, use parsed - #adb output. Unfortunately /dev/kmem doesn't have world-read, so this only works as root. - if os.access("/dev/kmem", os.R_OK): - rc, out, err = self.module.run_command("echo 'phys_mem_pages/D' | adb -k /stand/vmunix /dev/kmem | tail -1 | awk '{print $2}'", - use_unsafe_shell=True) - if not err: - data = out - self.facts['memtotal_mb'] = int(data) / 256 - else: - rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo | grep Memory", use_unsafe_shell=True) - data = re.search('Memory[\ :=]*([0-9]*).*MB.*',out).groups()[0].strip() - self.facts['memtotal_mb'] = int(data) - rc, out, err = self.module.run_command("/usr/sbin/swapinfo -m -d -f -q") - self.facts['swaptotal_mb'] = int(out.strip()) - rc, out, err = self.module.run_command("/usr/sbin/swapinfo -m -d -f | egrep '^dev|^fs'", use_unsafe_shell=True) - swap = 0 - for line in out.strip().splitlines(): - swap += int(re.sub(' +',' ',line).split(' ')[3].strip()) - self.facts['swapfree_mb'] = swap - - def get_hw_facts(self): - rc, out, err = self.module.run_command("model") - self.facts['model'] = out.strip() - if self.facts['architecture'] == 'ia64': - separator = ':' - if self.facts['distribution_version'] == "B.11.23": - separator = '=' - rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo |grep -i 'Firmware revision' | grep -v BMC", use_unsafe_shell=True) - self.facts['firmware_version'] = out.split(separator)[1].strip() - rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo |grep -i 'Machine serial number' ",use_unsafe_shell=True) - if rc == 0 and out: - self.facts['product_serial'] = out.split(separator)[1].strip() - -class Darwin(Hardware): - """ - Darwin-specific subclass of Hardware. Defines memory and CPU facts: - - processor - - processor_cores - - memtotal_mb - - memfree_mb - - model - - osversion - - osrevision - """ - platform = 'Darwin' - - def populate(self): - self.sysctl = self.get_sysctl(['hw','machdep','kern']) - self.get_mac_facts() - self.get_cpu_facts() - self.get_memory_facts() - return self.facts - - def get_system_profile(self): - rc, out, err = self.module.run_command(["/usr/sbin/system_profiler", "SPHardwareDataType"]) - if rc != 0: - return dict() - system_profile = dict() - for line in out.splitlines(): - if ': ' in line: - (key, value) = line.split(': ', 1) - system_profile[key.strip()] = ' '.join(value.strip().split()) - return system_profile - - def get_mac_facts(self): - rc, out, err = self.module.run_command("sysctl hw.model") - if rc == 0: - self.facts['model'] = out.splitlines()[-1].split()[1] - self.facts['osversion'] = self.sysctl['kern.osversion'] - self.facts['osrevision'] = self.sysctl['kern.osrevision'] - - def get_cpu_facts(self): - if 'machdep.cpu.brand_string' in self.sysctl: # Intel - self.facts['processor'] = self.sysctl['machdep.cpu.brand_string'] - self.facts['processor_cores'] = self.sysctl['machdep.cpu.core_count'] - else: # PowerPC - system_profile = self.get_system_profile() - self.facts['processor'] = '%s @ %s' % (system_profile['Processor Name'], system_profile['Processor Speed']) - self.facts['processor_cores'] = self.sysctl['hw.physicalcpu'] - - def get_memory_facts(self): - self.facts['memtotal_mb'] = int(self.sysctl['hw.memsize']) // 1024 // 1024 - - rc, out, err = self.module.run_command("sysctl hw.usermem") - if rc == 0: - self.facts['memfree_mb'] = int(out.splitlines()[-1].split()[1]) // 1024 // 1024 - -class HurdHardware(LinuxHardware): - """ - GNU Hurd specific subclass of Hardware. Define memory and mount facts - based on procfs compatibility translator mimicking the interface of - the Linux kernel. - """ - - platform = 'GNU' - - def populate(self): - self.get_uptime_facts() - self.get_memory_facts() - try: - self.get_mount_facts() - except TimeoutError: - pass - return self.facts - -class Network(Facts): - """ - This is a generic Network subclass of Facts. This should be further - subclassed to implement per platform. If you subclass this, - you must define: - - interfaces (a list of interface names) - - interface_ dictionary of ipv4, ipv6, and mac address information. - - All subclasses MUST define platform. - """ - platform = 'Generic' - - IPV6_SCOPE = { '0' : 'global', - '10' : 'host', - '20' : 'link', - '40' : 'admin', - '50' : 'site', - '80' : 'organization' } - - def __new__(cls, *arguments, **keyword): - # When Network is created, it chooses a subclass to create instead. - # This check prevents the subclass from then trying to find a subclass - # and create that. - if cls is not Network: - return super(Network, cls).__new__(cls) - - subclass = cls - for sc in get_all_subclasses(Network): - if sc.platform == platform.system(): - subclass = sc - if PY3: - return super(cls, subclass).__new__(subclass) - else: - return super(cls, subclass).__new__(subclass, *arguments, **keyword) - - def populate(self): - return self.facts - -class LinuxNetwork(Network): - """ - This is a Linux-specific subclass of Network. It defines - - interfaces (a list of interface names) - - interface_ dictionary of ipv4, ipv6, and mac address information. - - all_ipv4_addresses and all_ipv6_addresses: lists of all configured addresses. - - ipv4_address and ipv6_address: the first non-local address for each family. - """ - platform = 'Linux' - INTERFACE_TYPE = { - '1': 'ether', - '32': 'infiniband', - '512': 'ppp', - '772': 'loopback', - '65534': 'tunnel', - } - - def populate(self): - ip_path = self.module.get_bin_path('ip') - if ip_path is None: - return self.facts - default_ipv4, default_ipv6 = self.get_default_interfaces(ip_path) - interfaces, ips = self.get_interfaces_info(ip_path, default_ipv4, default_ipv6) - self.facts['interfaces'] = interfaces.keys() - for iface in interfaces: - self.facts[iface] = interfaces[iface] - self.facts['default_ipv4'] = default_ipv4 - self.facts['default_ipv6'] = default_ipv6 - self.facts['all_ipv4_addresses'] = ips['all_ipv4_addresses'] - self.facts['all_ipv6_addresses'] = ips['all_ipv6_addresses'] - return self.facts - - def get_default_interfaces(self, ip_path): - # Use the commands: - # ip -4 route get 8.8.8.8 -> Google public DNS - # ip -6 route get 2404:6800:400a:800::1012 -> ipv6.google.com - # to find out the default outgoing interface, address, and gateway - command = dict( - v4 = [ip_path, '-4', 'route', 'get', '8.8.8.8'], - v6 = [ip_path, '-6', 'route', 'get', '2404:6800:400a:800::1012'] - ) - interface = dict(v4 = {}, v6 = {}) - for v in 'v4', 'v6': - if (v == 'v6' and self.facts['os_family'] == 'RedHat' and - self.facts['distribution_version'].startswith('4.')): - continue - if v == 'v6' and not socket.has_ipv6: - continue - rc, out, err = self.module.run_command(command[v], errors='surrogate_then_replace') - if not out: - # v6 routing may result in - # RTNETLINK answers: Invalid argument - continue - words = out.splitlines()[0].split() - # A valid output starts with the queried address on the first line - if len(words) > 0 and words[0] == command[v][-1]: - for i in range(len(words) - 1): - if words[i] == 'dev': - interface[v]['interface'] = words[i+1] - elif words[i] == 'src': - interface[v]['address'] = words[i+1] - elif words[i] == 'via' and words[i+1] != command[v][-1]: - interface[v]['gateway'] = words[i+1] - return interface['v4'], interface['v6'] - - def get_interfaces_info(self, ip_path, default_ipv4, default_ipv6): - interfaces = {} - ips = dict( - all_ipv4_addresses = [], - all_ipv6_addresses = [], - ) - - for path in glob.glob('/sys/class/net/*'): - if not os.path.isdir(path): - continue - device = os.path.basename(path) - interfaces[device] = { 'device': device } - if os.path.exists(os.path.join(path, 'address')): - macaddress = get_file_content(os.path.join(path, 'address'), default='') - if macaddress and macaddress != '00:00:00:00:00:00': - interfaces[device]['macaddress'] = macaddress - if os.path.exists(os.path.join(path, 'mtu')): - interfaces[device]['mtu'] = int(get_file_content(os.path.join(path, 'mtu'))) - if os.path.exists(os.path.join(path, 'operstate')): - interfaces[device]['active'] = get_file_content(os.path.join(path, 'operstate')) != 'down' - if os.path.exists(os.path.join(path, 'device','driver', 'module')): - interfaces[device]['module'] = os.path.basename(os.path.realpath(os.path.join(path, 'device', 'driver', 'module'))) - if os.path.exists(os.path.join(path, 'type')): - _type = get_file_content(os.path.join(path, 'type')) - interfaces[device]['type'] = self.INTERFACE_TYPE.get(_type, 'unknown') - if os.path.exists(os.path.join(path, 'bridge')): - interfaces[device]['type'] = 'bridge' - interfaces[device]['interfaces'] = [ os.path.basename(b) for b in glob.glob(os.path.join(path, 'brif', '*')) ] - if os.path.exists(os.path.join(path, 'bridge', 'bridge_id')): - interfaces[device]['id'] = get_file_content(os.path.join(path, 'bridge', 'bridge_id'), default='') - if os.path.exists(os.path.join(path, 'bridge', 'stp_state')): - interfaces[device]['stp'] = get_file_content(os.path.join(path, 'bridge', 'stp_state')) == '1' - if os.path.exists(os.path.join(path, 'bonding')): - interfaces[device]['type'] = 'bonding' - interfaces[device]['slaves'] = get_file_content(os.path.join(path, 'bonding', 'slaves'), default='').split() - interfaces[device]['mode'] = get_file_content(os.path.join(path, 'bonding', 'mode'), default='').split()[0] - interfaces[device]['miimon'] = get_file_content(os.path.join(path, 'bonding', 'miimon'), default='').split()[0] - interfaces[device]['lacp_rate'] = get_file_content(os.path.join(path, 'bonding', 'lacp_rate'), default='').split()[0] - primary = get_file_content(os.path.join(path, 'bonding', 'primary')) - if primary: - interfaces[device]['primary'] = primary - path = os.path.join(path, 'bonding', 'all_slaves_active') - if os.path.exists(path): - interfaces[device]['all_slaves_active'] = get_file_content(path) == '1' - if os.path.exists(os.path.join(path, 'bonding_slave')): - interfaces[device]['perm_macaddress'] = get_file_content(os.path.join(path, 'bonding_slave', 'perm_hwaddr'), default='') - if os.path.exists(os.path.join(path,'device')): - interfaces[device]['pciid'] = os.path.basename(os.readlink(os.path.join(path,'device'))) - if os.path.exists(os.path.join(path, 'speed')): - speed = get_file_content(os.path.join(path, 'speed')) - if speed is not None: - interfaces[device]['speed'] = int(speed) - - # Check whether an interface is in promiscuous mode - if os.path.exists(os.path.join(path,'flags')): - promisc_mode = False - # The second byte indicates whether the interface is in promiscuous mode. - # 1 = promisc - # 0 = no promisc - data = int(get_file_content(os.path.join(path, 'flags')),16) - promisc_mode = (data & 0x0100 > 0) - interfaces[device]['promisc'] = promisc_mode - - def parse_ip_output(output, secondary=False): - for line in output.splitlines(): - if not line: - continue - words = line.split() - broadcast = '' - if words[0] == 'inet': - if '/' in words[1]: - address, netmask_length = words[1].split('/') - if len(words) > 3: - broadcast = words[3] - else: - # pointopoint interfaces do not have a prefix - address = words[1] - netmask_length = "32" - address_bin = struct.unpack('!L', socket.inet_aton(address))[0] - netmask_bin = (1<<32) - (1<<32>>int(netmask_length)) - netmask = socket.inet_ntoa(struct.pack('!L', netmask_bin)) - network = socket.inet_ntoa(struct.pack('!L', address_bin & netmask_bin)) - iface = words[-1] - if iface != device: - interfaces[iface] = {} - if not secondary and "ipv4" not in interfaces[iface]: - interfaces[iface]['ipv4'] = {'address': address, - 'broadcast': broadcast, - 'netmask': netmask, - 'network': network} - else: - if "ipv4_secondaries" not in interfaces[iface]: - interfaces[iface]["ipv4_secondaries"] = [] - interfaces[iface]["ipv4_secondaries"].append({ - 'address': address, - 'broadcast': broadcast, - 'netmask': netmask, - 'network': network, - }) - - # add this secondary IP to the main device - if secondary: - if "ipv4_secondaries" not in interfaces[device]: - interfaces[device]["ipv4_secondaries"] = [] - interfaces[device]["ipv4_secondaries"].append({ - 'address': address, - 'broadcast': broadcast, - 'netmask': netmask, - 'network': network, - }) - - # If this is the default address, update default_ipv4 - if 'address' in default_ipv4 and default_ipv4['address'] == address: - default_ipv4['broadcast'] = broadcast - default_ipv4['netmask'] = netmask - default_ipv4['network'] = network - default_ipv4['macaddress'] = macaddress - default_ipv4['mtu'] = interfaces[device]['mtu'] - default_ipv4['type'] = interfaces[device].get("type", "unknown") - default_ipv4['alias'] = words[-1] - if not address.startswith('127.'): - ips['all_ipv4_addresses'].append(address) - elif words[0] == 'inet6': - if 'peer' == words[2]: - address = words[1] - _, prefix = words[3].split('/') - scope = words[5] - else: - address, prefix = words[1].split('/') - scope = words[3] - if 'ipv6' not in interfaces[device]: - interfaces[device]['ipv6'] = [] - interfaces[device]['ipv6'].append({ - 'address' : address, - 'prefix' : prefix, - 'scope' : scope - }) - # If this is the default address, update default_ipv6 - if 'address' in default_ipv6 and default_ipv6['address'] == address: - default_ipv6['prefix'] = prefix - default_ipv6['scope'] = scope - default_ipv6['macaddress'] = macaddress - default_ipv6['mtu'] = interfaces[device]['mtu'] - default_ipv6['type'] = interfaces[device].get("type", "unknown") - if not address == '::1': - ips['all_ipv6_addresses'].append(address) - - ip_path = self.module.get_bin_path("ip") - - args = [ip_path, 'addr', 'show', 'primary', device] - rc, primary_data, stderr = self.module.run_command(args, errors='surrogate_then_replace') - - args = [ip_path, 'addr', 'show', 'secondary', device] - rc, secondary_data, stderr = self.module.run_command(args, errors='surrogate_then_replace') - - parse_ip_output(primary_data) - parse_ip_output(secondary_data, secondary=True) - - interfaces[device].update(self.get_ethtool_data(device)) - - # replace : by _ in interface name since they are hard to use in template - new_interfaces = {} - for i in interfaces: - if ':' in i: - new_interfaces[i.replace(':','_')] = interfaces[i] - else: - new_interfaces[i] = interfaces[i] - return new_interfaces, ips - - def get_ethtool_data(self, device): - - data = {} - ethtool_path = self.module.get_bin_path("ethtool") - if ethtool_path: - args = [ethtool_path, '-k', device] - rc, stdout, stderr = self.module.run_command(args, errors='surrogate_then_replace') - if rc == 0: - features = {} - for line in stdout.strip().splitlines(): - if not line or line.endswith(":"): - continue - key,value = line.split(": ") - if not value: - continue - features[key.strip().replace('-','_')] = value.strip() - data['features'] = features - - args = [ethtool_path, '-T', device] - rc, stdout, stderr = self.module.run_command(args, errors='surrogate_then_replace') - if rc == 0: - data['timestamping'] = [m.lower() for m in re.findall('SOF_TIMESTAMPING_(\w+)', stdout)] - data['hw_timestamp_filters'] = [m.lower() for m in re.findall('HWTSTAMP_FILTER_(\w+)', stdout)] - m = re.search('PTP Hardware Clock: (\d+)', stdout) - if m: - data['phc_index'] = int(m.groups()[0]) - - return data - - -class GenericBsdIfconfigNetwork(Network): - """ - This is a generic BSD subclass of Network using the ifconfig command. - It defines - - interfaces (a list of interface names) - - interface_ dictionary of ipv4, ipv6, and mac address information. - - all_ipv4_addresses and all_ipv6_addresses: lists of all configured addresses. - """ - platform = 'Generic_BSD_Ifconfig' - - def populate(self): - - ifconfig_path = self.module.get_bin_path('ifconfig') - - if ifconfig_path is None: - return self.facts - route_path = self.module.get_bin_path('route') - - if route_path is None: - return self.facts - - default_ipv4, default_ipv6 = self.get_default_interfaces(route_path) - interfaces, ips = self.get_interfaces_info(ifconfig_path) - self.detect_type_media(interfaces) - self.merge_default_interface(default_ipv4, interfaces, 'ipv4') - self.merge_default_interface(default_ipv6, interfaces, 'ipv6') - self.facts['interfaces'] = interfaces.keys() - - for iface in interfaces: - self.facts[iface] = interfaces[iface] - - self.facts['default_ipv4'] = default_ipv4 - self.facts['default_ipv6'] = default_ipv6 - self.facts['all_ipv4_addresses'] = ips['all_ipv4_addresses'] - self.facts['all_ipv6_addresses'] = ips['all_ipv6_addresses'] - - return self.facts - - def detect_type_media(self, interfaces): - for iface in interfaces: - if 'media' in interfaces[iface]: - if 'ether' in interfaces[iface]['media'].lower(): - interfaces[iface]['type'] = 'ether' - - def get_default_interfaces(self, route_path): - - # Use the commands: - # route -n get 8.8.8.8 -> Google public DNS - # route -n get -inet6 2404:6800:400a:800::1012 -> ipv6.google.com - # to find out the default outgoing interface, address, and gateway - - command = dict( - v4 = [route_path, '-n', 'get', '8.8.8.8'], - v6 = [route_path, '-n', 'get', '-inet6', '2404:6800:400a:800::1012'] - ) - - interface = dict(v4 = {}, v6 = {}) - - for v in 'v4', 'v6': - - if v == 'v6' and not socket.has_ipv6: - continue - rc, out, err = self.module.run_command(command[v]) - if not out: - # v6 routing may result in - # RTNETLINK answers: Invalid argument - continue - for line in out.splitlines(): - words = line.split() - # Collect output from route command - if len(words) > 1: - if words[0] == 'interface:': - interface[v]['interface'] = words[1] - if words[0] == 'gateway:': - interface[v]['gateway'] = words[1] - - return interface['v4'], interface['v6'] - - def get_interfaces_info(self, ifconfig_path, ifconfig_options='-a'): - interfaces = {} - current_if = {} - ips = dict( - all_ipv4_addresses = [], - all_ipv6_addresses = [], - ) - # FreeBSD, DragonflyBSD, NetBSD, OpenBSD and OS X all implicitly add '-a' - # when running the command 'ifconfig'. - # Solaris must explicitly run the command 'ifconfig -a'. - rc, out, err = self.module.run_command([ifconfig_path, ifconfig_options]) - - for line in out.splitlines(): - - if line: - words = line.split() - - if words[0] == 'pass': - continue - elif re.match('^\S', line) and len(words) > 3: - current_if = self.parse_interface_line(words) - interfaces[ current_if['device'] ] = current_if - elif words[0].startswith('options='): - self.parse_options_line(words, current_if, ips) - elif words[0] == 'nd6': - self.parse_nd6_line(words, current_if, ips) - elif words[0] == 'ether': - self.parse_ether_line(words, current_if, ips) - elif words[0] == 'media:': - self.parse_media_line(words, current_if, ips) - elif words[0] == 'status:': - self.parse_status_line(words, current_if, ips) - elif words[0] == 'lladdr': - self.parse_lladdr_line(words, current_if, ips) - elif words[0] == 'inet': - self.parse_inet_line(words, current_if, ips) - elif words[0] == 'inet6': - self.parse_inet6_line(words, current_if, ips) - elif words[0] == 'tunnel': - self.parse_tunnel_line(words, current_if, ips) - else: - self.parse_unknown_line(words, current_if, ips) - - return interfaces, ips - - def parse_interface_line(self, words): - device = words[0][0:-1] - current_if = {'device': device, 'ipv4': [], 'ipv6': [], 'type': 'unknown'} - current_if['flags'] = self.get_options(words[1]) - if 'LOOPBACK' in current_if['flags']: - current_if['type'] = 'loopback' - current_if['macaddress'] = 'unknown' # will be overwritten later - - if len(words) >= 5 : # Newer FreeBSD versions - current_if['metric'] = words[3] - current_if['mtu'] = words[5] - else: - current_if['mtu'] = words[3] - - return current_if - - def parse_options_line(self, words, current_if, ips): - # Mac has options like this... - current_if['options'] = self.get_options(words[0]) - - def parse_nd6_line(self, words, current_if, ips): - # FreeBSD has options like this... - current_if['options'] = self.get_options(words[1]) - - def parse_ether_line(self, words, current_if, ips): - current_if['macaddress'] = words[1] - current_if['type'] = 'ether' - - def parse_media_line(self, words, current_if, ips): - # not sure if this is useful - we also drop information - current_if['media'] = words[1] - if len(words) > 2: - current_if['media_select'] = words[2] - if len(words) > 3: - current_if['media_type'] = words[3][1:] - if len(words) > 4: - current_if['media_options'] = self.get_options(words[4]) - - def parse_status_line(self, words, current_if, ips): - current_if['status'] = words[1] - - def parse_lladdr_line(self, words, current_if, ips): - current_if['lladdr'] = words[1] - - def parse_inet_line(self, words, current_if, ips): - # netbsd show aliases like this - # lo0: flags=8049 mtu 33184 - # inet 127.0.0.1 netmask 0xff000000 - # inet alias 127.1.1.1 netmask 0xff000000 - if words[1] == 'alias': - del words[1] - address = {'address': words[1]} - # deal with hex netmask - if re.match('([0-9a-f]){8}', words[3]) and len(words[3]) == 8: - words[3] = '0x' + words[3] - if words[3].startswith('0x'): - address['netmask'] = socket.inet_ntoa(struct.pack('!L', int(words[3], base=16))) - else: - # otherwise assume this is a dotted quad - address['netmask'] = words[3] - # calculate the network - address_bin = struct.unpack('!L', socket.inet_aton(address['address']))[0] - netmask_bin = struct.unpack('!L', socket.inet_aton(address['netmask']))[0] - address['network'] = socket.inet_ntoa(struct.pack('!L', address_bin & netmask_bin)) - # broadcast may be given or we need to calculate - if len(words) > 5: - address['broadcast'] = words[5] - else: - address['broadcast'] = socket.inet_ntoa(struct.pack('!L', address_bin | (~netmask_bin & 0xffffffff))) - # add to our list of addresses - if not words[1].startswith('127.'): - ips['all_ipv4_addresses'].append(address['address']) - current_if['ipv4'].append(address) - - def parse_inet6_line(self, words, current_if, ips): - address = {'address': words[1]} - if (len(words) >= 4) and (words[2] == 'prefixlen'): - address['prefix'] = words[3] - if (len(words) >= 6) and (words[4] == 'scopeid'): - address['scope'] = words[5] - localhost6 = ['::1', '::1/128', 'fe80::1%lo0'] - if address['address'] not in localhost6: - ips['all_ipv6_addresses'].append(address['address']) - current_if['ipv6'].append(address) - - def parse_tunnel_line(self, words, current_if, ips): - current_if['type'] = 'tunnel' - - def parse_unknown_line(self, words, current_if, ips): - # we are going to ignore unknown lines here - this may be - # a bad idea - but you can override it in your subclass - pass - - def get_options(self, option_string): - start = option_string.find('<') + 1 - end = option_string.rfind('>') - if (start > 0) and (end > 0) and (end > start + 1): - option_csv = option_string[start:end] - return option_csv.split(',') - else: - return [] - - def merge_default_interface(self, defaults, interfaces, ip_type): - if 'interface' not in defaults: - return - if not defaults['interface'] in interfaces: - return - ifinfo = interfaces[defaults['interface']] - # copy all the interface values across except addresses - for item in ifinfo: - if item != 'ipv4' and item != 'ipv6': - defaults[item] = ifinfo[item] - if len(ifinfo[ip_type]) > 0: - for item in ifinfo[ip_type][0]: - defaults[item] = ifinfo[ip_type][0][item] - - -class HPUXNetwork(Network): - """ - HP-UX-specifig subclass of Network. Defines networking facts: - - default_interface - - interfaces (a list of interface names) - - interface_ dictionary of ipv4 address information. - """ - platform = 'HP-UX' - - def populate(self): - netstat_path = self.module.get_bin_path('netstat') - if netstat_path is None: - return self.facts - self.get_default_interfaces() - interfaces = self.get_interfaces_info() - self.facts['interfaces'] = interfaces.keys() - for iface in interfaces: - self.facts[iface] = interfaces[iface] - return self.facts - - def get_default_interfaces(self): - rc, out, err = self.module.run_command("/usr/bin/netstat -nr") - lines = out.splitlines() - for line in lines: - words = line.split() - if len(words) > 1: - if words[0] == 'default': - self.facts['default_interface'] = words[4] - self.facts['default_gateway'] = words[1] - - def get_interfaces_info(self): - interfaces = {} - rc, out, err = self.module.run_command("/usr/bin/netstat -ni") - lines = out.splitlines() - for line in lines: - words = line.split() - for i in range(len(words) - 1): - if words[i][:3] == 'lan': - device = words[i] - interfaces[device] = { 'device': device } - address = words[i+3] - interfaces[device]['ipv4'] = { 'address': address } - network = words[i+2] - interfaces[device]['ipv4'] = { 'network': network, - 'interface': device, - 'address': address } - return interfaces - -class DarwinNetwork(GenericBsdIfconfigNetwork): - """ - This is the Mac OS X/Darwin Network Class. - It uses the GenericBsdIfconfigNetwork unchanged - """ - platform = 'Darwin' - - # media line is different to the default FreeBSD one - def parse_media_line(self, words, current_if, ips): - # not sure if this is useful - we also drop information - current_if['media'] = 'Unknown' # Mac does not give us this - current_if['media_select'] = words[1] - if len(words) > 2: - # MacOSX sets the media to '' for bridge interface - # and parsing splits this into two words; this if/else helps - if words[1] == '': - current_if['media_select'] = 'Unknown' - current_if['media_type'] = 'unknown type' - else: - current_if['media_type'] = words[2][1:-1] - if len(words) > 3: - current_if['media_options'] = self.get_options(words[3]) - - -class FreeBSDNetwork(GenericBsdIfconfigNetwork): - """ - This is the FreeBSD Network Class. - It uses the GenericBsdIfconfigNetwork unchanged. - """ - platform = 'FreeBSD' - - -class DragonFlyNetwork(GenericBsdIfconfigNetwork): - """ - This is the DragonFly Network Class. - It uses the GenericBsdIfconfigNetwork unchanged. - """ - platform = 'DragonFly' - - -class AIXNetwork(GenericBsdIfconfigNetwork): - """ - This is the AIX Network Class. - It uses the GenericBsdIfconfigNetwork unchanged. - """ - platform = 'AIX' - - def get_default_interfaces(self, route_path): - netstat_path = self.module.get_bin_path('netstat') - - rc, out, err = self.module.run_command([netstat_path, '-nr']) - - interface = dict(v4 = {}, v6 = {}) - - lines = out.splitlines() - for line in lines: - words = line.split() - if len(words) > 1 and words[0] == 'default': - if '.' in words[1]: - interface['v4']['gateway'] = words[1] - interface['v4']['interface'] = words[5] - elif ':' in words[1]: - interface['v6']['gateway'] = words[1] - interface['v6']['interface'] = words[5] - - return interface['v4'], interface['v6'] - - # AIX 'ifconfig -a' does not have three words in the interface line - def get_interfaces_info(self, ifconfig_path, ifconfig_options='-a'): - interfaces = {} - current_if = {} - ips = dict( - all_ipv4_addresses = [], - all_ipv6_addresses = [], - ) - - uname_rc = None - uname_out = None - uname_err = None - uname_path = self.module.get_bin_path('uname') - if uname_path: - uname_rc, uname_out, uname_err = self.module.run_command([uname_path, '-W']) - - rc, out, err = self.module.run_command([ifconfig_path, ifconfig_options]) - - for line in out.splitlines(): - - if line: - words = line.split() - - # only this condition differs from GenericBsdIfconfigNetwork - if re.match('^\w*\d*:', line): - current_if = self.parse_interface_line(words) - interfaces[ current_if['device'] ] = current_if - elif words[0].startswith('options='): - self.parse_options_line(words, current_if, ips) - elif words[0] == 'nd6': - self.parse_nd6_line(words, current_if, ips) - elif words[0] == 'ether': - self.parse_ether_line(words, current_if, ips) - elif words[0] == 'media:': - self.parse_media_line(words, current_if, ips) - elif words[0] == 'status:': - self.parse_status_line(words, current_if, ips) - elif words[0] == 'lladdr': - self.parse_lladdr_line(words, current_if, ips) - elif words[0] == 'inet': - self.parse_inet_line(words, current_if, ips) - elif words[0] == 'inet6': - self.parse_inet6_line(words, current_if, ips) - else: - self.parse_unknown_line(words, current_if, ips) - - # don't bother with wpars it does not work - # zero means not in wpar - if not uname_rc and uname_out.split()[0] == '0': - - if current_if['macaddress'] == 'unknown' and re.match('^en', current_if['device']): - entstat_path = self.module.get_bin_path('entstat') - if entstat_path: - rc, out, err = self.module.run_command([entstat_path, current_if['device'] ]) - if rc != 0: - break - for line in out.splitlines(): - if not line: - pass - buff = re.match('^Hardware Address: (.*)', line) - if buff: - current_if['macaddress'] = buff.group(1) - - buff = re.match('^Device Type:', line) - if buff and re.match('.*Ethernet', line): - current_if['type'] = 'ether' - - # device must have mtu attribute in ODM - if 'mtu' not in current_if: - lsattr_path = self.module.get_bin_path('lsattr') - if lsattr_path: - rc, out, err = self.module.run_command([lsattr_path,'-El', current_if['device'] ]) - if rc != 0: - break - for line in out.splitlines(): - if line: - words = line.split() - if words[0] == 'mtu': - current_if['mtu'] = words[1] - return interfaces, ips - - # AIX 'ifconfig -a' does not inform about MTU, so remove current_if['mtu'] here - def parse_interface_line(self, words): - device = words[0][0:-1] - current_if = {'device': device, 'ipv4': [], 'ipv6': [], 'type': 'unknown'} - current_if['flags'] = self.get_options(words[1]) - current_if['macaddress'] = 'unknown' # will be overwritten later - return current_if - -class OpenBSDNetwork(GenericBsdIfconfigNetwork): - """ - This is the OpenBSD Network Class. - It uses the GenericBsdIfconfigNetwork. - """ - platform = 'OpenBSD' - - # OpenBSD 'ifconfig -a' does not have information about aliases - def get_interfaces_info(self, ifconfig_path, ifconfig_options='-aA'): - return super(OpenBSDNetwork, self).get_interfaces_info(ifconfig_path, ifconfig_options) - - # Return macaddress instead of lladdr - def parse_lladdr_line(self, words, current_if, ips): - current_if['macaddress'] = words[1] - current_if['type'] = 'ether' - -class NetBSDNetwork(GenericBsdIfconfigNetwork): - """ - This is the NetBSD Network Class. - It uses the GenericBsdIfconfigNetwork - """ - platform = 'NetBSD' - - def parse_media_line(self, words, current_if, ips): - # example of line: - # $ ifconfig - # ne0: flags=8863 mtu 1500 - # ec_capabilities=1 - # ec_enabled=0 - # address: 00:20:91:45:00:78 - # media: Ethernet 10baseT full-duplex - # inet 192.168.156.29 netmask 0xffffff00 broadcast 192.168.156.255 - current_if['media'] = words[1] - if len(words) > 2: - current_if['media_type'] = words[2] - if len(words) > 3: - current_if['media_options'] = words[3].split(',') - - -class SunOSNetwork(GenericBsdIfconfigNetwork): - """ - This is the SunOS Network Class. - It uses the GenericBsdIfconfigNetwork. - - Solaris can have different FLAGS and MTU for IPv4 and IPv6 on the same interface - so these facts have been moved inside the 'ipv4' and 'ipv6' lists. - """ - platform = 'SunOS' - - # Solaris 'ifconfig -a' will print interfaces twice, once for IPv4 and again for IPv6. - # MTU and FLAGS also may differ between IPv4 and IPv6 on the same interface. - # 'parse_interface_line()' checks for previously seen interfaces before defining - # 'current_if' so that IPv6 facts don't clobber IPv4 facts (or vice versa). - def get_interfaces_info(self, ifconfig_path): - interfaces = {} - current_if = {} - ips = dict( - all_ipv4_addresses = [], - all_ipv6_addresses = [], - ) - rc, out, err = self.module.run_command([ifconfig_path, '-a']) - - for line in out.splitlines(): - - if line: - words = line.split() - - if re.match('^\S', line) and len(words) > 3: - current_if = self.parse_interface_line(words, current_if, interfaces) - interfaces[ current_if['device'] ] = current_if - elif words[0].startswith('options='): - self.parse_options_line(words, current_if, ips) - elif words[0] == 'nd6': - self.parse_nd6_line(words, current_if, ips) - elif words[0] == 'ether': - self.parse_ether_line(words, current_if, ips) - elif words[0] == 'media:': - self.parse_media_line(words, current_if, ips) - elif words[0] == 'status:': - self.parse_status_line(words, current_if, ips) - elif words[0] == 'lladdr': - self.parse_lladdr_line(words, current_if, ips) - elif words[0] == 'inet': - self.parse_inet_line(words, current_if, ips) - elif words[0] == 'inet6': - self.parse_inet6_line(words, current_if, ips) - else: - self.parse_unknown_line(words, current_if, ips) - - # 'parse_interface_line' and 'parse_inet*_line' leave two dicts in the - # ipv4/ipv6 lists which is ugly and hard to read. - # This quick hack merges the dictionaries. Purely cosmetic. - for iface in interfaces: - for v in 'ipv4', 'ipv6': - combined_facts = {} - for facts in interfaces[iface][v]: - combined_facts.update(facts) - if len(combined_facts.keys()) > 0: - interfaces[iface][v] = [combined_facts] - - return interfaces, ips - - def parse_interface_line(self, words, current_if, interfaces): - device = words[0][0:-1] - if device not in interfaces: - current_if = {'device': device, 'ipv4': [], 'ipv6': [], 'type': 'unknown'} - else: - current_if = interfaces[device] - flags = self.get_options(words[1]) - v = 'ipv4' - if 'IPv6' in flags: - v = 'ipv6' - if 'LOOPBACK' in flags: - current_if['type'] = 'loopback' - current_if[v].append({'flags': flags, 'mtu': words[3]}) - current_if['macaddress'] = 'unknown' # will be overwritten later - return current_if - - # Solaris displays single digit octets in MAC addresses e.g. 0:1:2:d:e:f - # Add leading zero to each octet where needed. - def parse_ether_line(self, words, current_if, ips): - macaddress = '' - for octet in words[1].split(':'): - octet = ('0' + octet)[-2:None] - macaddress += (octet + ':') - current_if['macaddress'] = macaddress[0:-1] - -class HurdPfinetNetwork(Network): - """ - This is a GNU Hurd specific subclass of Network. It use fsysopts to - get the ip address and support only pfinet. - """ - platform = 'GNU' - _socket_dir = '/servers/socket/' - - def populate(self): - fsysopts_path = self.module.get_bin_path('fsysopts') - if fsysopts_path is None: - return self.facts - socket_path = None - for l in ('inet', 'inet6'): - link = os.path.join(self._socket_dir, l) - if os.path.exists(link): - socket_path = link - break - - if socket_path: - rc, out, err = self.module.run_command([fsysopts_path, '-L', socket_path]) - self.facts['interfaces'] = [] - for i in out.split(): - if '=' in i and i.startswith('--'): - k,v = i.split('=',1) - # remove '--' - k = k[2:] - if k == 'interface': - # remove /dev/ from /dev/eth0 - v = v[5:] - self.facts['interfaces'].append(v) - self.facts[v] = { - 'active': True, - 'device': v, - 'ipv4': {}, - 'ipv6': [], - } - current_if = v - elif k == 'address': - self.facts[current_if]['ipv4']['address'] = v - elif k == 'netmask': - self.facts[current_if]['ipv4']['netmask'] = v - elif k == 'address6': - address,prefix = v.split('/') - self.facts[current_if]['ipv6'].append({ - 'address': address, - 'prefix': prefix, - }) - - return self.facts - - -class Virtual(Facts): - """ - This is a generic Virtual subclass of Facts. This should be further - subclassed to implement per platform. If you subclass this, - you should define: - - virtualization_type - - virtualization_role - - container (e.g. solaris zones, freebsd jails, linux containers) - - All subclasses MUST define platform. - """ - - def __new__(cls, *arguments, **keyword): - # When Virtual is created, it chooses a subclass to create instead. - # This check prevents the subclass from then trying to find a subclass - # and create that. - if cls is not Virtual: - return super(Virtual, cls).__new__(cls) - - subclass = cls - for sc in get_all_subclasses(Virtual): - if sc.platform == platform.system(): - subclass = sc - - if PY3: - return super(cls, subclass).__new__(subclass) - else: - return super(cls, subclass).__new__(subclass, *arguments, **keyword) - - def populate(self): - self.get_virtual_facts() - return self.facts - - def get_virtual_facts(self): - self.facts['virtualization_type'] = '' - self.facts['virtualization_role'] = '' - -class LinuxVirtual(Virtual): - """ - This is a Linux-specific subclass of Virtual. It defines - - virtualization_type - - virtualization_role - """ - platform = 'Linux' - - # For more information, check: http://people.redhat.com/~rjones/virt-what/ - def get_virtual_facts(self): - # lxc/docker - if os.path.exists('/proc/1/cgroup'): - for line in get_file_lines('/proc/1/cgroup'): - if re.search(r'/docker(/|-[0-9a-f]+\.scope)', line): - self.facts['virtualization_type'] = 'docker' - self.facts['virtualization_role'] = 'guest' - return - if re.search('/lxc/', line) or re.search('/machine.slice/machine-lxc', line): - self.facts['virtualization_type'] = 'lxc' - self.facts['virtualization_role'] = 'guest' - return - - # lxc does not always appear in cgroups anymore but sets 'container=lxc' environment var, requires root privs - if os.path.exists('/proc/1/environ'): - for line in get_file_lines('/proc/1/environ'): - if re.search('container=lxc', line): - self.facts['virtualization_type'] = 'lxc' - self.facts['virtualization_role'] = 'guest' - return - - if os.path.exists('/proc/vz'): - self.facts['virtualization_type'] = 'openvz' - if os.path.exists('/proc/bc'): - self.facts['virtualization_role'] = 'host' - else: - self.facts['virtualization_role'] = 'guest' - return - - systemd_container = get_file_content('/run/systemd/container') - if systemd_container: - self.facts['virtualization_type'] = systemd_container - self.facts['virtualization_role'] = 'guest' - return - - if os.path.exists("/proc/xen"): - self.facts['virtualization_type'] = 'xen' - self.facts['virtualization_role'] = 'guest' - try: - for line in get_file_lines('/proc/xen/capabilities'): - if "control_d" in line: - self.facts['virtualization_role'] = 'host' - except IOError: - pass - return - - product_name = get_file_content('/sys/devices/virtual/dmi/id/product_name') - - if product_name in ['KVM', 'Bochs']: - self.facts['virtualization_type'] = 'kvm' - self.facts['virtualization_role'] = 'guest' - return - - if product_name == 'RHEV Hypervisor': - self.facts['virtualization_type'] = 'RHEV' - self.facts['virtualization_role'] = 'guest' - return - - if product_name == 'VMware Virtual Platform': - self.facts['virtualization_type'] = 'VMware' - self.facts['virtualization_role'] = 'guest' - return - - if product_name == 'OpenStack Nova': - self.facts['virtualization_type'] = 'openstack' - self.facts['virtualization_role'] = 'guest' - return - - bios_vendor = get_file_content('/sys/devices/virtual/dmi/id/bios_vendor') - - if bios_vendor == 'Xen': - self.facts['virtualization_type'] = 'xen' - self.facts['virtualization_role'] = 'guest' - return - - if bios_vendor == 'innotek GmbH': - self.facts['virtualization_type'] = 'virtualbox' - self.facts['virtualization_role'] = 'guest' - return - - sys_vendor = get_file_content('/sys/devices/virtual/dmi/id/sys_vendor') - - # FIXME: This does also match hyperv - if sys_vendor == 'Microsoft Corporation': - self.facts['virtualization_type'] = 'VirtualPC' - self.facts['virtualization_role'] = 'guest' - return - - if sys_vendor == 'Parallels Software International Inc.': - self.facts['virtualization_type'] = 'parallels' - self.facts['virtualization_role'] = 'guest' - return - - if sys_vendor == 'QEMU': - self.facts['virtualization_type'] = 'kvm' - self.facts['virtualization_role'] = 'guest' - return - - if sys_vendor == 'oVirt': - self.facts['virtualization_type'] = 'kvm' - self.facts['virtualization_role'] = 'guest' - return - - if sys_vendor == 'OpenStack Foundation': - self.facts['virtualization_type'] = 'openstack' - self.facts['virtualization_role'] = 'guest' - return - - if os.path.exists('/proc/self/status'): - for line in get_file_lines('/proc/self/status'): - if re.match('^VxID: \d+', line): - self.facts['virtualization_type'] = 'linux_vserver' - if re.match('^VxID: 0', line): - self.facts['virtualization_role'] = 'host' - else: - self.facts['virtualization_role'] = 'guest' - return - - if os.path.exists('/proc/cpuinfo'): - for line in get_file_lines('/proc/cpuinfo'): - if re.match('^model name.*QEMU Virtual CPU', line): - self.facts['virtualization_type'] = 'kvm' - elif re.match('^vendor_id.*User Mode Linux', line): - self.facts['virtualization_type'] = 'uml' - elif re.match('^model name.*UML', line): - self.facts['virtualization_type'] = 'uml' - elif re.match('^vendor_id.*PowerVM Lx86', line): - self.facts['virtualization_type'] = 'powervm_lx86' - elif re.match('^vendor_id.*IBM/S390', line): - self.facts['virtualization_type'] = 'PR/SM' - lscpu = self.module.get_bin_path('lscpu') - if lscpu: - rc, out, err = self.module.run_command(["lscpu"]) - if rc == 0: - for line in out.splitlines(): - data = line.split(":", 1) - key = data[0].strip() - if key == 'Hypervisor': - self.facts['virtualization_type'] = data[1].strip() - else: - self.facts['virtualization_type'] = 'ibm_systemz' - else: - continue - if self.facts['virtualization_type'] == 'PR/SM': - self.facts['virtualization_role'] = 'LPAR' - else: - self.facts['virtualization_role'] = 'guest' - return - - # Beware that we can have both kvm and virtualbox running on a single system - if os.path.exists("/proc/modules") and os.access('/proc/modules', os.R_OK): - modules = [] - for line in get_file_lines("/proc/modules"): - data = line.split(" ", 1) - modules.append(data[0]) - - if 'kvm' in modules: - - if os.path.isdir('/rhev/'): - - # Check whether this is a RHEV hypervisor (is vdsm running ?) - for f in glob.glob('/proc/[0-9]*/comm'): - try: - if open(f).read().rstrip() == 'vdsm': - self.facts['virtualization_type'] = 'RHEV' - break - except: - pass - else: - self.facts['virtualization_type'] = 'kvm' - - else: - self.facts['virtualization_type'] = 'kvm' - self.facts['virtualization_role'] = 'host' - return - - if 'vboxdrv' in modules: - self.facts['virtualization_type'] = 'virtualbox' - self.facts['virtualization_role'] = 'host' - return - - # If none of the above matches, return 'NA' for virtualization_type - # and virtualization_role. This allows for proper grouping. - self.facts['virtualization_type'] = 'NA' - self.facts['virtualization_role'] = 'NA' - return - -class VirtualSysctlDetectionMixin(object): - def detect_sysctl(self): - self.sysctl_path = self.module.get_bin_path('sysctl') - - def detect_virt_product(self, key): - self.detect_sysctl() - if self.sysctl_path: - rc, out, err = self.module.run_command("%s -n %s" % (self.sysctl_path, key)) - if rc == 0: - if re.match('(KVM|Bochs|SmartDC).*', out): - self.facts['virtualization_type'] = 'kvm' - self.facts['virtualization_role'] = 'guest' - elif re.match('.*VMware.*', out): - self.facts['virtualization_type'] = 'VMware' - self.facts['virtualization_role'] = 'guest' - elif out.rstrip() == 'VirtualBox': - self.facts['virtualization_type'] = 'virtualbox' - self.facts['virtualization_role'] = 'guest' - elif out.rstrip() == 'HVM domU': - self.facts['virtualization_type'] = 'xen' - self.facts['virtualization_role'] = 'guest' - elif out.rstrip() == 'Parallels': - self.facts['virtualization_type'] = 'parallels' - self.facts['virtualization_role'] = 'guest' - elif out.rstrip() == 'RHEV Hypervisor': - self.facts['virtualization_type'] = 'RHEV' - self.facts['virtualization_role'] = 'guest' - - def detect_virt_vendor(self, key): - self.detect_sysctl() - if self.sysctl_path: - rc, out, err = self.module.run_command("%s -n %s" % (self.sysctl_path, key)) - if rc == 0: - if out.rstrip() == 'QEMU': - self.facts['virtualization_type'] = 'kvm' - self.facts['virtualization_role'] = 'guest' - if out.rstrip() == 'OpenBSD': - self.facts['virtualization_type'] = 'vmm' - self.facts['virtualization_role'] = 'guest' - - -class FreeBSDVirtual(Virtual): - """ - This is a FreeBSD-specific subclass of Virtual. It defines - - virtualization_type - - virtualization_role - """ - platform = 'FreeBSD' - - def get_virtual_facts(self): - - # Set empty values as default - self.facts['virtualization_type'] = '' - self.facts['virtualization_role'] = '' - - if os.path.exists('/dev/xen/xenstore'): - self.facts['virtualization_type'] = 'xen' - self.facts['virtualization_role'] = 'guest' - -class DragonFlyVirtual(FreeBSDVirtual): - platform = 'DragonFly' - -class OpenBSDVirtual(Virtual, VirtualSysctlDetectionMixin): - """ - This is a OpenBSD-specific subclass of Virtual. It defines - - virtualization_type - - virtualization_role - """ - platform = 'OpenBSD' - DMESG_BOOT = '/var/run/dmesg.boot' - - def get_virtual_facts(self): - - # Set empty values as default - self.facts['virtualization_type'] = '' - self.facts['virtualization_role'] = '' - - self.detect_virt_product('hw.product') - if self.facts['virtualization_type'] == '': - self.detect_virt_vendor('hw.vendor') - - # Check the dmesg if vmm(4) attached, indicating the host is - # capable of virtualization. - dmesg_boot = get_file_content(OpenBSDVirtual.DMESG_BOOT) - for line in dmesg_boot.splitlines(): - match = re.match('^vmm0 at mainbus0: (SVM/RVI|VMX/EPT)$', line) - if match: - self.facts['virtualization_type'] = 'vmm' - self.facts['virtualization_role'] = 'host' - -class NetBSDVirtual(Virtual, VirtualSysctlDetectionMixin): - platform = 'NetBSD' - - def get_virtual_facts(self): - # Set empty values as default - self.facts['virtualization_type'] = '' - self.facts['virtualization_role'] = '' - - self.detect_virt_product('machdep.dmi.system-product') - if self.facts['virtualization_type'] == '': - self.detect_virt_vendor('machdep.dmi.system-vendor') - - if os.path.exists('/dev/xencons'): - self.facts['virtualization_type'] = 'xen' - self.facts['virtualization_role'] = 'guest' - - -class HPUXVirtual(Virtual): - """ - This is a HP-UX specific subclass of Virtual. It defines - - virtualization_type - - virtualization_role - """ - platform = 'HP-UX' - - def get_virtual_facts(self): - if os.path.exists('/usr/sbin/vecheck'): - rc, out, err = self.module.run_command("/usr/sbin/vecheck") - if rc == 0: - self.facts['virtualization_type'] = 'guest' - self.facts['virtualization_role'] = 'HP vPar' - if os.path.exists('/opt/hpvm/bin/hpvminfo'): - rc, out, err = self.module.run_command("/opt/hpvm/bin/hpvminfo") - if rc == 0 and re.match('.*Running.*HPVM vPar.*', out): - self.facts['virtualization_type'] = 'guest' - self.facts['virtualization_role'] = 'HPVM vPar' - elif rc == 0 and re.match('.*Running.*HPVM guest.*', out): - self.facts['virtualization_type'] = 'guest' - self.facts['virtualization_role'] = 'HPVM IVM' - elif rc == 0 and re.match('.*Running.*HPVM host.*', out): - self.facts['virtualization_type'] = 'host' - self.facts['virtualization_role'] = 'HPVM' - if os.path.exists('/usr/sbin/parstatus'): - rc, out, err = self.module.run_command("/usr/sbin/parstatus") - if rc == 0: - self.facts['virtualization_type'] = 'guest' - self.facts['virtualization_role'] = 'HP nPar' - - -class SunOSVirtual(Virtual): - """ - This is a SunOS-specific subclass of Virtual. It defines - - virtualization_type - - virtualization_role - - container - """ - platform = 'SunOS' - - def get_virtual_facts(self): - - # Check if it's a zone - - zonename = self.module.get_bin_path('zonename') - if zonename: - rc, out, err = self.module.run_command(zonename) - if rc == 0 and out.rstrip() != "global": - self.facts['container'] = 'zone' - # Check if it's a branded zone (i.e. Solaris 8/9 zone) - if os.path.isdir('/.SUNWnative'): - self.facts['container'] = 'zone' - # If it's a zone check if we can detect if our global zone is itself virtualized. - # Relies on the "guest tools" (e.g. vmware tools) to be installed - if 'container' in self.facts and self.facts['container'] == 'zone': - modinfo = self.module.get_bin_path('modinfo') - if modinfo: - rc, out, err = self.module.run_command(modinfo) - if rc == 0: - for line in out.splitlines(): - if 'VMware' in line: - self.facts['virtualization_type'] = 'vmware' - self.facts['virtualization_role'] = 'guest' - if 'VirtualBox' in line: - self.facts['virtualization_type'] = 'virtualbox' - self.facts['virtualization_role'] = 'guest' - - if os.path.exists('/proc/vz'): - self.facts['virtualization_type'] = 'virtuozzo' - self.facts['virtualization_role'] = 'guest' - - # Detect domaining on Sparc hardware - virtinfo = self.module.get_bin_path('virtinfo') - if virtinfo: - # The output of virtinfo is different whether we are on a machine with logical - # domains ('LDoms') on a T-series or domains ('Domains') on a M-series. Try LDoms first. - rc, out, err = self.module.run_command("/usr/sbin/virtinfo -p") - # The output contains multiple lines with different keys like this: - # DOMAINROLE|impl=LDoms|control=false|io=false|service=false|root=false - # The output may also be not formatted and the returncode is set to 0 regardless of the error condition: - # virtinfo can only be run from the global zone - if rc == 0: - try: - for line in out.splitlines(): - fields = line.split('|') - if( fields[0] == 'DOMAINROLE' and fields[1] == 'impl=LDoms' ): - self.facts['virtualization_type'] = 'ldom' - self.facts['virtualization_role'] = 'guest' - hostfeatures = [] - for field in fields[2:]: - arg = field.split('=') - if( arg[1] == 'true' ): - hostfeatures.append(arg[0]) - if( len(hostfeatures) > 0 ): - self.facts['virtualization_role'] = 'host (' + ','.join(hostfeatures) + ')' - except ValueError: - pass - - else: - smbios = self.module.get_bin_path('smbios') - if not smbios: - return - rc, out, err = self.module.run_command(smbios) - if rc == 0: - for line in out.splitlines(): - if 'VMware' in line: - self.facts['virtualization_type'] = 'vmware' - self.facts['virtualization_role'] = 'guest' - elif 'Parallels' in line: - self.facts['virtualization_type'] = 'parallels' - self.facts['virtualization_role'] = 'guest' - elif 'VirtualBox' in line: - self.facts['virtualization_type'] = 'virtualbox' - self.facts['virtualization_role'] = 'guest' - elif 'HVM domU' in line: - self.facts['virtualization_type'] = 'xen' - self.facts['virtualization_role'] = 'guest' - elif 'KVM' in line: - self.facts['virtualization_type'] = 'kvm' - self.facts['virtualization_role'] = 'guest' - -class Ohai(Facts): - """ - This is a subclass of Facts for including information gathered from Ohai. - """ - - def populate(self): - self.run_ohai() - return self.facts - - def run_ohai(self): - ohai_path = self.module.get_bin_path('ohai') - if ohai_path is None: - return - rc, out, err = self.module.run_command(ohai_path) - try: - self.facts.update(json.loads(out)) - except: - pass - -class Facter(Facts): - """ - This is a subclass of Facts for including information gathered from Facter. - """ - def populate(self): - self.run_facter() - return self.facts - - def run_facter(self): - facter_path = self.module.get_bin_path('facter', opt_dirs=['/opt/puppetlabs/bin']) - cfacter_path = self.module.get_bin_path('cfacter', opt_dirs=['/opt/puppetlabs/bin']) - # Prefer to use cfacter if available - if cfacter_path is not None: - facter_path = cfacter_path - - if facter_path is None: - return - - # if facter is installed, and we can use --json because - # ruby-json is ALSO installed, include facter data in the JSON - rc, out, err = self.module.run_command(facter_path + " --puppet --json") - try: - self.facts = json.loads(out) - except: - pass - - -def get_file_content(path, default=None, strip=True): - data = default - if os.path.exists(path) and os.access(path, os.R_OK): - try: - try: - datafile = open(path) - data = datafile.read() - if strip: - data = data.strip() - if len(data) == 0: - data = default - finally: - datafile.close() - except: - # ignore errors as some jails/containers might have readable permissions but not allow reads to proc - # done in 2 blocks for 2.4 compat - pass - return data - -def get_uname_version(module): - rc, out, err = module.run_command(['uname', '-v']) - if rc == 0: - return out - return None - -def get_partition_uuid(partname): - try: - uuids = os.listdir("/dev/disk/by-uuid") - except OSError: - return - - for uuid in uuids: - dev = os.path.realpath("/dev/disk/by-uuid/" + uuid) - if dev == ("/dev/" + partname): - return uuid - - return None - -def get_file_lines(path): - '''get list of lines from file''' - data = get_file_content(path) - if data: - ret = data.splitlines() - else: - ret = [] - return ret - -def ansible_facts(module, gather_subset): - facts = {} - facts['gather_subset'] = list(gather_subset) - facts.update(Facts(module).populate()) - for subset in gather_subset: - facts.update(FACT_SUBSETS[subset](module, - load_on_init=False, - cached_facts=facts).populate()) - return facts - -def get_all_facts(module): - - setup_options = dict(module_setup=True) - - # Retrieve module parameters - gather_subset = module.params['gather_subset'] - - global GATHER_TIMEOUT - GATHER_TIMEOUT = module.params['gather_timeout'] - - # Retrieve all facts elements - additional_subsets = set() - exclude_subsets = set() - for subset in gather_subset: - if subset == 'all': - additional_subsets.update(VALID_SUBSETS) - continue - if subset.startswith('!'): - subset = subset[1:] - if subset == 'all': - exclude_subsets.update(VALID_SUBSETS) - continue - exclude = True - else: - exclude = False - - if subset not in VALID_SUBSETS: - raise TypeError("Bad subset '%s' given to Ansible. gather_subset options allowed: all, %s" % (subset, ", ".join(FACT_SUBSETS.keys()))) - - if exclude: - exclude_subsets.add(subset) - else: - additional_subsets.add(subset) - - if not additional_subsets: - additional_subsets.update(VALID_SUBSETS) - - additional_subsets.difference_update(exclude_subsets) - - # facter and ohai are given a different prefix than other subsets - if 'facter' in additional_subsets: - additional_subsets.difference_update(('facter',)) - facter_ds = FACT_SUBSETS['facter'](module, load_on_init=False).populate() - if facter_ds: - for (k, v) in facter_ds.items(): - setup_options['facter_%s' % k.replace('-', '_')] = v - - if 'ohai' in additional_subsets: - additional_subsets.difference_update(('ohai',)) - ohai_ds = FACT_SUBSETS['ohai'](module, load_on_init=False).populate() - if ohai_ds: - for (k, v) in ohai_ds.items(): - setup_options['ohai_%s' % k.replace('-', '_')] = v - - facts = ansible_facts(module, additional_subsets) - - for (k, v) in facts.items(): - setup_options["ansible_%s" % k.replace('-', '_')] = v - - setup_result = { 'ansible_facts': {} } - - for (k,v) in setup_options.items(): - if module.params['filter'] == '*' or fnmatch.fnmatch(k, module.params['filter']): - setup_result['ansible_facts'][k] = v - - return setup_result - -# Allowed fact subset for gather_subset options and what classes they use -# Note: have to define this at the bottom as it references classes defined earlier in this file -FACT_SUBSETS = dict( - hardware=Hardware, - network=Network, - virtual=Virtual, - ohai=Ohai, - facter=Facter, -) -VALID_SUBSETS = frozenset(FACT_SUBSETS.keys()) diff --git a/lib/ansible/module_utils/facts/__init__.py b/lib/ansible/module_utils/facts/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/facts/collector.py b/lib/ansible/module_utils/facts/collector.py new file mode 100644 index 00000000000..d55a9f0254b --- /dev/null +++ b/lib/ansible/module_utils/facts/collector.py @@ -0,0 +1,252 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from collections import defaultdict + +import platform + +from ansible.module_utils.facts import timeout + + +class BaseFactCollector: + _fact_ids = set() + + _platform = 'Generic' + name = None + + def __init__(self, collectors=None, namespace=None): + '''Base class for things that collect facts. + + 'collectors' is an optional list of other FactCollectors for composing.''' + self.collectors = collectors or [] + + # self.namespace is a object with a 'transform' method that transforms + # the name to indicate the namespace (ie, adds a prefix or suffix). + self.namespace = namespace + + self.fact_ids = set([self.name]) + self.fact_ids.update(self._fact_ids) + + @classmethod + def platform_match(cls, platform_info): + if platform_info.get('system', None) == cls._platform: + return cls + return None + + def _transform_name(self, key_name): + if self.namespace: + return self.namespace.transform(key_name) + return key_name + + def _transform_dict_keys(self, fact_dict): + '''update a dicts keys to use new names as transformed by self._transform_name''' + + for old_key in list(fact_dict.keys()): + new_key = self._transform_name(old_key) + # pop the item by old_key and replace it using new_key + fact_dict[new_key] = fact_dict.pop(old_key) + return fact_dict + + # TODO/MAYBE: rename to 'collect' and add 'collect_without_namespace' + def collect_with_namespace(self, module=None, collected_facts=None): + # collect, then transform the key names if needed + facts_dict = self.collect(module=module, collected_facts=collected_facts) + if self.namespace: + facts_dict = self._transform_dict_keys(facts_dict) + return facts_dict + + def collect(self, module=None, collected_facts=None): + '''do the fact collection + + 'collected_facts' is a object (a dict, likely) that holds all previously + facts. This is intended to be used if a FactCollector needs to reference + another fact (for ex, the system arch) and should not be modified (usually). + + Returns a dict of facts. + + ''' + facts_dict = {} + return facts_dict + + +def get_collector_names(valid_subsets=None, + minimal_gather_subset=None, + gather_subset=None, + aliases_map=None, + platform_info=None): + '''return a set of FactCollector names based on gather_subset spec. + + gather_subset is a spec describing which facts to gather. + valid_subsets is a frozenset of potential matches for gather_subset ('all', 'network') etc + minimal_gather_subsets is a frozenset of matches to always use, even for gather_subset='!all' + ''' + + # Retrieve module parameters + gather_subset = gather_subset or ['all'] + + # the list of everything that 'all' expands to + valid_subsets = valid_subsets or frozenset() + + # if provided, minimal_gather_subset is always added, even after all negations + minimal_gather_subset = minimal_gather_subset or frozenset() + + aliases_map = aliases_map or defaultdict(set) + + # Retrieve all facts elements + additional_subsets = set() + exclude_subsets = set() + + for subset in gather_subset: + subset_id = subset + + if subset_id == 'all': + additional_subsets.update(valid_subsets) + continue + if subset_id.startswith('!'): + subset = subset[1:] + if subset == 'all': + exclude_subsets.update(valid_subsets) + continue + exclude = True + else: + exclude = False + + if exclude: + # include 'devices', 'dmi' etc for '!hardware' + exclude_subsets.update(aliases_map.get(subset, set())) + exclude_subsets.add(subset) + else: + # NOTE: this only considers adding an unknown gather subsetup an error. Asking to + # exclude an unknown gather subset is ignored. + if subset_id not in valid_subsets: + raise TypeError("Bad subset '%s' given to Ansible. gather_subset options allowed: all, %s" % + (subset, ", ".join(sorted(valid_subsets)))) + + additional_subsets.add(subset) + + if not additional_subsets: + additional_subsets.update(valid_subsets) + + additional_subsets.difference_update(exclude_subsets) + + additional_subsets.update(minimal_gather_subset) + + return additional_subsets + + +def find_collectors_for_platform(all_collector_classes, compat_platforms): + found_collectors = set() + found_collectors_names = set() + + # start from specific platform, then try generic + for compat_platform in compat_platforms: + platform_match = None + for all_collector_class in all_collector_classes: + + # ask the class if it is compatible with the platform info + platform_match = all_collector_class.platform_match(compat_platform) + + if not platform_match: + continue + + primary_name = all_collector_class.name + + if primary_name not in found_collectors_names: + found_collectors.add(all_collector_class) + found_collectors_names.add(all_collector_class.name) + + return found_collectors + + +def build_fact_id_to_collector_map(collectors_for_platform): + fact_id_to_collector_map = defaultdict(list) + aliases_map = defaultdict(set) + + for collector_class in collectors_for_platform: + primary_name = collector_class.name + + fact_id_to_collector_map[primary_name].append(collector_class) + + for fact_id in collector_class._fact_ids: + fact_id_to_collector_map[fact_id].append(collector_class) + aliases_map[primary_name].add(fact_id) + + return fact_id_to_collector_map, aliases_map + + +def collector_classes_from_gather_subset(all_collector_classes=None, + valid_subsets=None, + minimal_gather_subset=None, + gather_subset=None, + gather_timeout=None, + platform_info=None): + '''return a list of collector classes that match the args''' + + # use gather_name etc to get the list of collectors + + all_collector_classes = all_collector_classes or [] + + minimal_gather_subset = minimal_gather_subset or frozenset() + + platform_info = platform_info or {'system': platform.system()} + + gather_timeout = gather_timeout or timeout.DEFAULT_GATHER_TIMEOUT + + # tweak the modules GATHER_TIMEOUT + timeout.GATHER_TIMEOUT = gather_timeout + + valid_subsets = valid_subsets or frozenset() + + # maps alias names like 'hardware' to the list of names that are part of hardware + # like 'devices' and 'dmi' + aliases_map = defaultdict(set) + + compat_platforms = [platform_info, {'system': 'Generic'}] + + collectors_for_platform = find_collectors_for_platform(all_collector_classes, compat_platforms) + + # all_facts_subsets maps the subset name ('hardware') to the class that provides it. + + # TODO: name collisions here? are there facts with the same name as a gather_subset (all, network, hardware, virtual, ohai, facter) + all_fact_subsets, aliases_map = build_fact_id_to_collector_map(collectors_for_platform) + + all_valid_subsets = frozenset(all_fact_subsets.keys()) + + # expand any fact_id/collectorname/gather_subset term ('all', 'env', etc) to the list of names that represents + collector_names = get_collector_names(valid_subsets=all_valid_subsets, + minimal_gather_subset=minimal_gather_subset, + gather_subset=gather_subset, + aliases_map=aliases_map, + platform_info=platform_info) + + # TODO: can be a set() + seen_collector_classes = [] + + selected_collector_classes = [] + + for collector_name in collector_names: + collector_classes = all_fact_subsets.get(collector_name, []) + + # TODO? log/warn if we dont find an implementation of a fact_id? + + for collector_class in collector_classes: + if collector_class not in seen_collector_classes: + selected_collector_classes.append(collector_class) + seen_collector_classes.append(collector_class) + + return selected_collector_classes diff --git a/lib/ansible/module_utils/facts/default_collectors.py b/lib/ansible/module_utils/facts/default_collectors.py new file mode 100644 index 00000000000..7a5caedbb70 --- /dev/null +++ b/lib/ansible/module_utils/facts/default_collectors.py @@ -0,0 +1,129 @@ +# +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +from ansible.module_utils.facts.other.facter import FacterFactCollector +from ansible.module_utils.facts.other.ohai import OhaiFactCollector + +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.date_time import DateTimeFactCollector +from ansible.module_utils.facts.system.env import EnvFactCollector +from ansible.module_utils.facts.system.dns import DnsFactCollector +from ansible.module_utils.facts.system.fips import FipsFactCollector +from ansible.module_utils.facts.system.local import LocalFactCollector +from ansible.module_utils.facts.system.lsb import LSBFactCollector +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.hardware.base import HardwareCollector +from ansible.module_utils.facts.hardware.aix import AIXHardwareCollector +from ansible.module_utils.facts.hardware.darwin import DarwinHardwareCollector +from ansible.module_utils.facts.hardware.dragonfly import DragonFlyHardwareCollector +from ansible.module_utils.facts.hardware.freebsd import FreeBSDHardwareCollector +from ansible.module_utils.facts.hardware.hpux import HPUXHardwareCollector +from ansible.module_utils.facts.hardware.hurd import HurdHardwareCollector +from ansible.module_utils.facts.hardware.linux import LinuxHardwareCollector +from ansible.module_utils.facts.hardware.netbsd import NetBSDHardwareCollector +from ansible.module_utils.facts.hardware.openbsd import OpenBSDHardwareCollector +from ansible.module_utils.facts.hardware.sunos import SunOSHardwareCollector + +from ansible.module_utils.facts.network.base import NetworkCollector +from ansible.module_utils.facts.network.aix import AIXNetworkCollector +from ansible.module_utils.facts.network.darwin import DarwinNetworkCollector +from ansible.module_utils.facts.network.dragonfly import DragonFlyNetworkCollector +from ansible.module_utils.facts.network.freebsd import FreeBSDNetworkCollector +from ansible.module_utils.facts.network.hpux import HPUXNetworkCollector +from ansible.module_utils.facts.network.hurd import HurdNetworkCollector +from ansible.module_utils.facts.network.linux import LinuxNetworkCollector +from ansible.module_utils.facts.network.netbsd import NetBSDNetworkCollector +from ansible.module_utils.facts.network.openbsd import OpenBSDNetworkCollector +from ansible.module_utils.facts.network.sunos import SunOSNetworkCollector + +from ansible.module_utils.facts.virtual.base import VirtualCollector +from ansible.module_utils.facts.virtual.dragonfly import DragonFlyVirtualCollector +from ansible.module_utils.facts.virtual.freebsd import FreeBSDVirtualCollector +from ansible.module_utils.facts.virtual.hpux import HPUXVirtualCollector +from ansible.module_utils.facts.virtual.linux import LinuxVirtualCollector +from ansible.module_utils.facts.virtual.netbsd import NetBSDVirtualCollector +from ansible.module_utils.facts.virtual.openbsd import OpenBSDVirtualCollector +from ansible.module_utils.facts.virtual.sunos import SunOSVirtualCollector + +# TODO: make config driven +collectors = [ApparmorFactCollector, + CmdLineFactCollector, + DateTimeFactCollector, + DistributionFactCollector, + DnsFactCollector, + EnvFactCollector, + FipsFactCollector, + + HardwareCollector, + AIXHardwareCollector, + DarwinHardwareCollector, + DragonFlyHardwareCollector, + FreeBSDHardwareCollector, + HPUXHardwareCollector, + HurdHardwareCollector, + LinuxHardwareCollector, + NetBSDHardwareCollector, + OpenBSDHardwareCollector, + SunOSHardwareCollector, + LocalFactCollector, + LSBFactCollector, + + NetworkCollector, + AIXNetworkCollector, + DarwinNetworkCollector, + DragonFlyNetworkCollector, + FreeBSDNetworkCollector, + HPUXNetworkCollector, + HurdNetworkCollector, + LinuxNetworkCollector, + NetBSDNetworkCollector, + OpenBSDNetworkCollector, + SunOSNetworkCollector, + + PkgMgrFactCollector, + PlatformFactCollector, + PythonFactCollector, + SelinuxFactCollector, + ServiceMgrFactCollector, + SshPubKeyFactCollector, + SystemCapabilitiesFactCollector, + UserFactCollector, + + VirtualCollector, + DragonFlyVirtualCollector, + FreeBSDVirtualCollector, + LinuxVirtualCollector, + OpenBSDVirtualCollector, + NetBSDVirtualCollector, + SunOSVirtualCollector, + HPUXVirtualCollector] + +external_collectors = [FacterFactCollector, + OhaiFactCollector] diff --git a/lib/ansible/module_utils/facts/hardware/__init__.py b/lib/ansible/module_utils/facts/hardware/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/facts/hardware/aix.py b/lib/ansible/module_utils/facts/hardware/aix.py new file mode 100644 index 00000000000..eb228ffcf51 --- /dev/null +++ b/lib/ansible/module_utils/facts/hardware/aix.py @@ -0,0 +1,208 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from ansible.module_utils.facts.hardware.base import Hardware, HardwareCollector + + +class AIXHardware(Hardware): + """ + AIX-specific subclass of Hardware. Defines memory and CPU facts: + - memfree_mb + - memtotal_mb + - swapfree_mb + - swaptotal_mb + - processor (a list) + - processor_cores + - processor_count + """ + platform = 'AIX' + + def populate(self, collected_facts=None): + hardware_facts = {} + + cpu_facts = self.get_cpu_facts() + memory_facts = self.get_memory_facts() + dmi_facts = self.get_dmi_facts() + vgs_facts = self.get_vgs_facts() + mount_facts = self.get_mount_facts() + + hardware_facts.update(cpu_facts) + hardware_facts.update(memory_facts) + hardware_facts.update(dmi_facts) + hardware_facts.update(vgs_facts) + hardware_facts.update(mount_facts) + + return hardware_facts + + def get_cpu_facts(self): + cpu_facts = {} + cpu_facts['processor'] = [] + + rc, out, err = self.module.run_command("/usr/sbin/lsdev -Cc processor") + if out: + i = 0 + for line in out.splitlines(): + + if 'Available' in line: + if i == 0: + data = line.split(' ') + cpudev = data[0] + + i += 1 + cpu_facts['processor_count'] = int(i) + + rc, out, err = self.module.run_command("/usr/sbin/lsattr -El " + cpudev + " -a type") + + data = out.split(' ') + cpu_facts['processor'] = data[1] + + rc, out, err = self.module.run_command("/usr/sbin/lsattr -El " + cpudev + " -a smt_threads") + + data = out.split(' ') + cpu_facts['processor_cores'] = int(data[1]) + + return cpu_facts + + def get_memory_facts(self): + memory_facts = {} + pagesize = 4096 + rc, out, err = self.module.run_command("/usr/bin/vmstat -v") + for line in out.splitlines(): + data = line.split() + if 'memory pages' in line: + pagecount = int(data[0]) + if 'free pages' in line: + freecount = int(data[0]) + memory_facts['memtotal_mb'] = pagesize * pagecount // 1024 // 1024 + memory_facts['memfree_mb'] = pagesize * freecount // 1024 // 1024 + # Get swapinfo. swapinfo output looks like: + # Device 1M-blocks Used Avail Capacity + # /dev/ada0p3 314368 0 314368 0% + # + rc, out, err = self.module.run_command("/usr/sbin/lsps -s") + if out: + lines = out.splitlines() + data = lines[1].split() + swaptotal_mb = int(data[0].rstrip('MB')) + percused = int(data[1].rstrip('%')) + memory_facts['swaptotal_mb'] = swaptotal_mb + memory_facts['swapfree_mb'] = int(swaptotal_mb * (100 - percused) / 100) + + return memory_facts + + def get_dmi_facts(self): + dmi_facts = {} + + rc, out, err = self.module.run_command("/usr/sbin/lsattr -El sys0 -a fwversion") + data = out.split() + dmi_facts['firmware_version'] = data[1].strip('IBM,') + lsconf_path = self.module.get_bin_path("lsconf") + if lsconf_path: + rc, out, err = self.module.run_command(lsconf_path) + if rc == 0 and out: + for line in out.splitlines(): + data = line.split(':') + if 'Machine Serial Number' in line: + dmi_facts['product_serial'] = data[1].strip() + if 'LPAR Info' in line: + dmi_facts['lpar_info'] = data[1].strip() + if 'System Model' in line: + dmi_facts['product_name'] = data[1].strip() + return dmi_facts + + def get_vgs_facts(self): + """ + Get vg and pv Facts + rootvg: + PV_NAME PV STATE TOTAL PPs FREE PPs FREE DISTRIBUTION + hdisk0 active 546 0 00..00..00..00..00 + hdisk1 active 546 113 00..00..00..21..92 + realsyncvg: + PV_NAME PV STATE TOTAL PPs FREE PPs FREE DISTRIBUTION + hdisk74 active 1999 6 00..00..00..00..06 + testvg: + PV_NAME PV STATE TOTAL PPs FREE PPs FREE DISTRIBUTION + hdisk105 active 999 838 200..39..199..200..200 + hdisk106 active 999 599 200..00..00..199..200 + """ + + vgs_facts = {} + lsvg_path = self.module.get_bin_path("lsvg") + xargs_path = self.module.get_bin_path("xargs") + cmd = "%s | %s %s -p" % (lsvg_path, xargs_path, lsvg_path) + if lsvg_path and xargs_path: + rc, out, err = self.module.run_command(cmd, use_unsafe_shell=True) + if rc == 0 and out: + vgs_facts['vgs'] = {} + for m in re.finditer(r'(\S+):\n.*FREE DISTRIBUTION(\n(\S+)\s+(\w+)\s+(\d+)\s+(\d+).*)+', out): + vgs_facts['vgs'][m.group(1)] = [] + pp_size = 0 + cmd = "%s %s" % (lsvg_path, m.group(1)) + rc, out, err = self.module.run_command(cmd) + if rc == 0 and out: + pp_size = re.search(r'PP SIZE:\s+(\d+\s+\S+)', out).group(1) + for n in re.finditer(r'(\S+)\s+(\w+)\s+(\d+)\s+(\d+).*', m.group(0)): + pv_info = {'pv_name': n.group(1), + 'pv_state': n.group(2), + 'total_pps': n.group(3), + 'free_pps': n.group(4), + 'pp_size': pp_size + } + vgs_facts['vgs'][m.group(1)].append(pv_info) + + return vgs_facts + + def get_mount_facts(self): + mount_facts = {} + + mount_facts['mounts'] = [] + # AIX does not have mtab but mount command is only source of info (or to use + # api calls to get same info) + mount_path = self.module.get_bin_path('mount') + rc, mount_out, err = self.module.run_command(mount_path) + if mount_out: + for line in mount_out.split('\n'): + fields = line.split() + if len(fields) != 0 and fields[0] != 'node' and fields[0][0] != '-' and re.match('^/.*|^[a-zA-Z].*|^[0-9].*', fields[0]): + if re.match('^/', fields[0]): + # normal mount + mount_facts['mounts'].append({'mount': fields[1], + 'device': fields[0], + 'fstype': fields[2], + 'options': fields[6], + 'time': '%s %s %s' % (fields[3], fields[4], fields[5])}) + else: + # nfs or cifs based mount + # in case of nfs if no mount options are provided on command line + # add into fields empty string... + if len(fields) < 8: + fields.append("") + + mount_facts['mounts'].append({'mount': fields[2], + 'device': '%s:%s' % (fields[0], fields[1]), + 'fstype': fields[3], + 'options': fields[7], + 'time': '%s %s %s' % (fields[4], fields[5], fields[6])}) + return mount_facts + + +class AIXHardwareCollector(HardwareCollector): + _platform = 'AIX' + _fact_class = AIXHardware diff --git a/lib/ansible/module_utils/facts/hardware/base.py b/lib/ansible/module_utils/facts/hardware/base.py new file mode 100644 index 00000000000..8f6e4524950 --- /dev/null +++ b/lib/ansible/module_utils/facts/hardware/base.py @@ -0,0 +1,53 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils.facts.collector import BaseFactCollector + + +class Hardware: + platform = 'Generic' + + # FIXME: remove load_on_init when we can + def __init__(self, module, load_on_init=False): + self.module = module + + def populate(self, collected_facts=None): + return {} + + +class HardwareCollector(BaseFactCollector): + name = 'hardware' + _fact_ids = set(['processor', + 'processor_cores', + 'processor_count', + # TODO: mounts isnt exactly hardware + 'mounts', + 'devices']) + _fact_class = Hardware + + def collect(self, module=None, collected_facts=None): + collected_facts = collected_facts or {} + if not module: + return {} + + # Network munges cached_facts by side effect, so give it a copy + facts_obj = self._fact_class(module) + + facts_dict = facts_obj.populate(collected_facts=collected_facts) + + return facts_dict diff --git a/lib/ansible/module_utils/facts/hardware/darwin.py b/lib/ansible/module_utils/facts/hardware/darwin.py new file mode 100644 index 00000000000..c99e459353f --- /dev/null +++ b/lib/ansible/module_utils/facts/hardware/darwin.py @@ -0,0 +1,99 @@ +# This file is part of Ansible +# +# 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 . + + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils.facts.hardware.base import Hardware, HardwareCollector + +from ansible.module_utils.facts.sysctl import get_sysctl + + +class DarwinHardware(Hardware): + """ + Darwin-specific subclass of Hardware. Defines memory and CPU facts: + - processor + - processor_cores + - memtotal_mb + - memfree_mb + - model + - osversion + - osrevision + """ + platform = 'Darwin' + + def populate(self, collected_facts=None): + hardware_facts = {} + + self.sysctl = get_sysctl(self.module, ['hw', 'machdep', 'kern']) + mac_facts = self.get_mac_facts() + cpu_facts = self.get_cpu_facts() + memory_facts = self.get_memory_facts() + + hardware_facts.update(mac_facts) + hardware_facts.update(cpu_facts) + hardware_facts.update(memory_facts) + + return hardware_facts + + def get_system_profile(self): + rc, out, err = self.module.run_command(["/usr/sbin/system_profiler", "SPHardwareDataType"]) + if rc != 0: + return dict() + system_profile = dict() + for line in out.splitlines(): + if ': ' in line: + (key, value) = line.split(': ', 1) + system_profile[key.strip()] = ' '.join(value.strip().split()) + return system_profile + + def get_mac_facts(self): + mac_facts = {} + rc, out, err = self.module.run_command("sysctl hw.model") + if rc == 0: + mac_facts['model'] = out.splitlines()[-1].split()[1] + mac_facts['osversion'] = self.sysctl['kern.osversion'] + mac_facts['osrevision'] = self.sysctl['kern.osrevision'] + + return mac_facts + + def get_cpu_facts(self): + cpu_facts = {} + if 'machdep.cpu.brand_string' in self.sysctl: # Intel + cpu_facts['processor'] = self.sysctl['machdep.cpu.brand_string'] + cpu_facts['processor_cores'] = self.sysctl['machdep.cpu.core_count'] + else: # PowerPC + system_profile = self.get_system_profile() + cpu_facts['processor'] = '%s @ %s' % (system_profile['Processor Name'], system_profile['Processor Speed']) + cpu_facts['processor_cores'] = self.sysctl['hw.physicalcpu'] + + return cpu_facts + + def get_memory_facts(self): + memory_facts = {} + + memory_facts['memtotal_mb'] = int(self.sysctl['hw.memsize']) // 1024 // 1024 + + rc, out, err = self.module.run_command("sysctl hw.usermem") + if rc == 0: + memory_facts['memfree_mb'] = int(out.splitlines()[-1].split()[1]) // 1024 // 1024 + + return memory_facts + + +class DarwinHardwareCollector(HardwareCollector): + _fact_class = DarwinHardware + _platform = 'Darwin' diff --git a/lib/ansible/module_utils/facts/hardware/dragonfly.py b/lib/ansible/module_utils/facts/hardware/dragonfly.py new file mode 100644 index 00000000000..ea24151fdb7 --- /dev/null +++ b/lib/ansible/module_utils/facts/hardware/dragonfly.py @@ -0,0 +1,26 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils.facts.hardware.base import HardwareCollector +from ansible.module_utils.facts.hardware.freebsd import FreeBSDHardware + + +class DragonFlyHardwareCollector(HardwareCollector): + # Note: This uses the freebsd fact class, there is no dragonfly hardware fact class + _fact_class = FreeBSDHardware + _platform = 'DragonFly' diff --git a/lib/ansible/module_utils/facts/hardware/freebsd.py b/lib/ansible/module_utils/facts/hardware/freebsd.py new file mode 100644 index 00000000000..609a13f339a --- /dev/null +++ b/lib/ansible/module_utils/facts/hardware/freebsd.py @@ -0,0 +1,195 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +import re + +from ansible.module_utils.facts.hardware.base import Hardware, HardwareCollector +from ansible.module_utils.facts.timeout import TimeoutError, timeout + +from ansible.module_utils.facts.utils import get_file_content, get_mount_size + + +class FreeBSDHardware(Hardware): + """ + FreeBSD-specific subclass of Hardware. Defines memory and CPU facts: + - memfree_mb + - memtotal_mb + - swapfree_mb + - swaptotal_mb + - processor (a list) + - processor_cores + - processor_count + - devices + """ + platform = 'FreeBSD' + DMESG_BOOT = '/var/run/dmesg.boot' + + def populate(self, collected_facts=None): + hardware_facts = {} + + cpu_facts = self.get_cpu_facts() + memory_facts = self.get_memory_facts() + dmi_facts = self.get_dmi_facts() + device_facts = self.get_device_facts() + + mount_facts = {} + try: + mount_facts = self.get_mount_facts() + except TimeoutError: + pass + + hardware_facts.update(cpu_facts) + hardware_facts.update(memory_facts) + hardware_facts.update(dmi_facts) + hardware_facts.update(device_facts) + hardware_facts.update(mount_facts) + + return hardware_facts + + def get_cpu_facts(self): + cpu_facts = {} + cpu_facts['processor'] = [] + rc, out, err = self.module.run_command("/sbin/sysctl -n hw.ncpu") + cpu_facts['processor_count'] = out.strip() + + dmesg_boot = get_file_content(FreeBSDHardware.DMESG_BOOT) + if not dmesg_boot: + rc, dmesg_boot, err = self.module.run_command("/sbin/dmesg") + for line in dmesg_boot.splitlines(): + if 'CPU:' in line: + cpu = re.sub(r'CPU:\s+', r"", line) + cpu_facts['processor'].append(cpu.strip()) + if 'Logical CPUs per core' in line: + cpu_facts['processor_cores'] = line.split()[4] + + return cpu_facts + + def get_memory_facts(self): + memory_facts = {} + + rc, out, err = self.module.run_command("/sbin/sysctl vm.stats") + for line in out.splitlines(): + data = line.split() + if 'vm.stats.vm.v_page_size' in line: + pagesize = int(data[1]) + if 'vm.stats.vm.v_page_count' in line: + pagecount = int(data[1]) + if 'vm.stats.vm.v_free_count' in line: + freecount = int(data[1]) + memory_facts['memtotal_mb'] = pagesize * pagecount // 1024 // 1024 + memory_facts['memfree_mb'] = pagesize * freecount // 1024 // 1024 + # Get swapinfo. swapinfo output looks like: + # Device 1M-blocks Used Avail Capacity + # /dev/ada0p3 314368 0 314368 0% + # + rc, out, err = self.module.run_command("/usr/sbin/swapinfo -k") + lines = out.splitlines() + if len(lines[-1]) == 0: + lines.pop() + data = lines[-1].split() + if data[0] != 'Device': + memory_facts['swaptotal_mb'] = int(data[1]) // 1024 + memory_facts['swapfree_mb'] = int(data[3]) // 1024 + + return memory_facts + + @timeout() + def get_mount_facts(self): + mount_facts = {} + + mount_facts['mounts'] = [] + fstab = get_file_content('/etc/fstab') + if fstab: + for line in fstab.splitlines(): + if line.startswith('#') or line.strip() == '': + continue + fields = re.sub(r'\s+', ' ', line).split() + size_total, size_available = get_mount_size(fields[1]) + mount_facts['mounts'].append({ + 'mount': fields[1], + 'device': fields[0], + 'fstype': fields[2], + 'options': fields[3], + 'size_total': size_total, + 'size_available': size_available + }) + + return mount_facts + + def get_device_facts(self): + device_facts = {} + + sysdir = '/dev' + device_facts['devices'] = {} + drives = re.compile('(ada?\d+|da\d+|a?cd\d+)') # TODO: rc, disks, err = self.module.run_command("/sbin/sysctl kern.disks") + slices = re.compile('(ada?\d+s\d+\w*|da\d+s\d+\w*)') + if os.path.isdir(sysdir): + dirlist = sorted(os.listdir(sysdir)) + for device in dirlist: + d = drives.match(device) + if d: + device_facts['devices'][d.group(1)] = [] + s = slices.match(device) + if s: + device_facts['devices'][d.group(1)].append(s.group(1)) + + return device_facts + + def get_dmi_facts(self): + ''' learn dmi facts from system + + Use dmidecode executable if available''' + + dmi_facts = {} + + # Fall back to using dmidecode, if available + dmi_bin = self.module.get_bin_path('dmidecode') + DMI_DICT = dict( + bios_date='bios-release-date', + bios_version='bios-version', + form_factor='chassis-type', + product_name='system-product-name', + product_serial='system-serial-number', + product_uuid='system-uuid', + product_version='system-version', + system_vendor='system-manufacturer' + ) + for (k, v) in DMI_DICT.items(): + if dmi_bin is not None: + (rc, out, err) = self.module.run_command('%s -s %s' % (dmi_bin, v)) + if rc == 0: + # Strip out commented lines (specific dmidecode output) + # FIXME: why add the fact and then test if it is json? + dmi_facts[k] = ''.join([line for line in out.splitlines() if not line.startswith('#')]) + try: + json.dumps(dmi_facts[k]) + except UnicodeDecodeError: + dmi_facts[k] = 'NA' + else: + dmi_facts[k] = 'NA' + else: + dmi_facts[k] = 'NA' + + return dmi_facts + + +class FreeBSDHardwareCollector(HardwareCollector): + _fact_class = FreeBSDHardware + _platform = 'FreeBSD' diff --git a/lib/ansible/module_utils/facts/hardware/hpux.py b/lib/ansible/module_utils/facts/hardware/hpux.py new file mode 100644 index 00000000000..24daa1948c8 --- /dev/null +++ b/lib/ansible/module_utils/facts/hardware/hpux.py @@ -0,0 +1,161 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import re + +from ansible.module_utils.facts.hardware.base import Hardware, HardwareCollector + + +class HPUXHardware(Hardware): + """ + HP-UX-specific subclass of Hardware. Defines memory and CPU facts: + - memfree_mb + - memtotal_mb + - swapfree_mb + - swaptotal_mb + - processor + - processor_cores + - processor_count + - model + - firmware + """ + + platform = 'HP-UX' + + def populate(self, collected_facts=None): + hardware_facts = {} + + cpu_facts = self.get_cpu_facts(collected_facts=collected_facts) + memory_facts = self.get_memory_facts() + hw_facts = self.get_hw_facts() + + hardware_facts.update(cpu_facts) + hardware_facts.update(memory_facts) + hardware_facts.update(hw_facts) + + return hardware_facts + + def get_cpu_facts(self, collected_facts=None): + cpu_facts = {} + collected_facts = collected_facts or {} + + if collected_facts.get('ansible_architecture') == '9000/800': + rc, out, err = self.module.run_command("ioscan -FkCprocessor | wc -l", use_unsafe_shell=True) + cpu_facts['processor_count'] = int(out.strip()) + # Working with machinfo mess + elif collected_facts.get('ansible_architecture') == 'ia64': + if collected_facts.get('ansible_distribution_version') == "B.11.23": + rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo | grep 'Number of CPUs'", use_unsafe_shell=True) + cpu_facts['processor_count'] = int(out.strip().split('=')[1]) + rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo | grep 'processor family'", use_unsafe_shell=True) + cpu_facts['processor'] = re.search('.*(Intel.*)', out).groups()[0].strip() + rc, out, err = self.module.run_command("ioscan -FkCprocessor | wc -l", use_unsafe_shell=True) + cpu_facts['processor_cores'] = int(out.strip()) + if collected_facts.get('ansible_distribution_version') == "B.11.31": + # if machinfo return cores strings release B.11.31 > 1204 + rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo | grep core | wc -l", use_unsafe_shell=True) + if out.strip() == '0': + rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo | grep Intel", use_unsafe_shell=True) + cpu_facts['processor_count'] = int(out.strip().split(" ")[0]) + # If hyperthreading is active divide cores by 2 + rc, out, err = self.module.run_command("/usr/sbin/psrset | grep LCPU", use_unsafe_shell=True) + data = re.sub(' +', ' ', out).strip().split(' ') + if len(data) == 1: + hyperthreading = 'OFF' + else: + hyperthreading = data[1] + rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo | grep logical", use_unsafe_shell=True) + data = out.strip().split(" ") + if hyperthreading == 'ON': + cpu_facts['processor_cores'] = int(data[0]) / 2 + else: + if len(data) == 1: + cpu_facts['processor_cores'] = cpu_facts['processor_count'] + else: + cpu_facts['processor_cores'] = int(data[0]) + rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo | grep Intel |cut -d' ' -f4-", use_unsafe_shell=True) + cpu_facts['processor'] = out.strip() + else: + rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo | egrep 'socket[s]?$' | tail -1", use_unsafe_shell=True) + cpu_facts['processor_count'] = int(out.strip().split(" ")[0]) + rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo | grep -e '[0-9] core' | tail -1", use_unsafe_shell=True) + cpu_facts['processor_cores'] = int(out.strip().split(" ")[0]) + rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo | grep Intel", use_unsafe_shell=True) + cpu_facts['processor'] = out.strip() + + return cpu_facts + + def get_memory_facts(self, collected_facts=None): + memory_facts = {} + collected_facts = collected_facts or {} + + pagesize = 4096 + rc, out, err = self.module.run_command("/usr/bin/vmstat | tail -1", use_unsafe_shell=True) + data = int(re.sub(' +', ' ', out).split(' ')[5].strip()) + memory_facts['memfree_mb'] = pagesize * data // 1024 // 1024 + if collected_facts.get('ansible_architecture') == '9000/800': + try: + rc, out, err = self.module.run_command("grep Physical /var/adm/syslog/syslog.log") + data = re.search('.*Physical: ([0-9]*) Kbytes.*', out).groups()[0].strip() + memory_facts['memtotal_mb'] = int(data) // 1024 + except AttributeError: + # For systems where memory details aren't sent to syslog or the log has rotated, use parsed + # adb output. Unfortunately /dev/kmem doesn't have world-read, so this only works as root. + if os.access("/dev/kmem", os.R_OK): + rc, out, err = self.module.run_command("echo 'phys_mem_pages/D' | adb -k /stand/vmunix /dev/kmem | tail -1 | awk '{print $2}'", + use_unsafe_shell=True) + if not err: + data = out + memory_facts['memtotal_mb'] = int(data) / 256 + else: + rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo | grep Memory", use_unsafe_shell=True) + data = re.search('Memory[\ :=]*([0-9]*).*MB.*', out).groups()[0].strip() + memory_facts['memtotal_mb'] = int(data) + rc, out, err = self.module.run_command("/usr/sbin/swapinfo -m -d -f -q") + memory_facts['swaptotal_mb'] = int(out.strip()) + rc, out, err = self.module.run_command("/usr/sbin/swapinfo -m -d -f | egrep '^dev|^fs'", use_unsafe_shell=True) + swap = 0 + for line in out.strip().splitlines(): + swap += int(re.sub(' +', ' ', line).split(' ')[3].strip()) + memory_facts['swapfree_mb'] = swap + + return memory_facts + + def get_hw_facts(self, collected_facts=None): + hw_facts = {} + collected_facts = collected_facts or {} + + rc, out, err = self.module.run_command("model") + hw_facts['model'] = out.strip() + if collected_facts.get('ansible_architecture') == 'ia64': + separator = ':' + if collected_facts.get('ansible_distribution_version') == "B.11.23": + separator = '=' + rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo |grep -i 'Firmware revision' | grep -v BMC", use_unsafe_shell=True) + hw_facts['firmware_version'] = out.split(separator)[1].strip() + rc, out, err = self.module.run_command("/usr/contrib/bin/machinfo |grep -i 'Machine serial number' ", use_unsafe_shell=True) + if rc == 0 and out: + hw_facts['product_serial'] = out.split(separator)[1].strip() + + return hw_facts + + +class HPUXHardwareCollector(HardwareCollector): + _fact_class = HPUXHardware + _platform = 'HP-UX' diff --git a/lib/ansible/module_utils/facts/hardware/hurd.py b/lib/ansible/module_utils/facts/hardware/hurd.py new file mode 100644 index 00000000000..306e13c1354 --- /dev/null +++ b/lib/ansible/module_utils/facts/hardware/hurd.py @@ -0,0 +1,53 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils.facts.timeout import TimeoutError +from ansible.module_utils.facts.hardware.base import HardwareCollector +from ansible.module_utils.facts.hardware.linux import LinuxHardware + + +class HurdHardware(LinuxHardware): + """ + GNU Hurd specific subclass of Hardware. Define memory and mount facts + based on procfs compatibility translator mimicking the interface of + the Linux kernel. + """ + + platform = 'GNU' + + def populate(self, collected_facts=None): + hardware_facts = {} + uptime_facts = self.get_uptime_facts() + memory_facts = self.get_memory_facts() + + mount_facts = {} + try: + mount_facts = self.get_mount_facts() + except TimeoutError: + pass + + hardware_facts.update(uptime_facts) + hardware_facts.update(memory_facts) + hardware_facts.update(mount_facts) + + return hardware_facts + + +class HurdHardwareCollector(HardwareCollector): + _fact_class = HurdHardware + _platform = 'GNU' diff --git a/lib/ansible/module_utils/facts/hardware/linux.py b/lib/ansible/module_utils/facts/hardware/linux.py new file mode 100644 index 00000000000..33851e199c6 --- /dev/null +++ b/lib/ansible/module_utils/facts/hardware/linux.py @@ -0,0 +1,663 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import errno +import json +import os +import re +import sys + +from ansible.module_utils.basic import bytes_to_human + +from ansible.module_utils.facts.hardware.base import Hardware, HardwareCollector +from ansible.module_utils.facts.utils import get_file_content, get_file_lines, get_mount_size + +# import this as a module to ensure we get the same module isntance +from ansible.module_utils.facts import timeout + + +def get_partition_uuid(partname): + try: + uuids = os.listdir("/dev/disk/by-uuid") + except OSError: + return + + for uuid in uuids: + dev = os.path.realpath("/dev/disk/by-uuid/" + uuid) + if dev == ("/dev/" + partname): + return uuid + + return None + + +class LinuxHardware(Hardware): + """ + Linux-specific subclass of Hardware. Defines memory and CPU facts: + - memfree_mb + - memtotal_mb + - swapfree_mb + - swaptotal_mb + - processor (a list) + - processor_cores + - processor_count + + In addition, it also defines number of DMI facts and device facts. + """ + + platform = 'Linux' + + # Originally only had these four as toplevelfacts + ORIGINAL_MEMORY_FACTS = frozenset(('MemTotal', 'SwapTotal', 'MemFree', 'SwapFree')) + # Now we have all of these in a dict structure + MEMORY_FACTS = ORIGINAL_MEMORY_FACTS.union(('Buffers', 'Cached', 'SwapCached')) + + # regex used against findmnt output to detect bind mounts + BIND_MOUNT_RE = re.compile(r'.*\]') + + # regex used against mtab content to find entries that are bind mounts + MTAB_BIND_MOUNT_RE = re.compile(r'.*bind.*"') + + def populate(self, collected_facts=None): + hardware_facts = {} + + cpu_facts = self.get_cpu_facts(collected_facts=collected_facts) + memory_facts = self.get_memory_facts() + dmi_facts = self.get_dmi_facts() + device_facts = self.get_device_facts() + uptime_facts = self.get_uptime_facts() + lvm_facts = self.get_lvm_facts() + + mount_facts = {} + try: + mount_facts = self.get_mount_facts() + except timeout.TimeoutError: + pass + + hardware_facts.update(cpu_facts) + hardware_facts.update(memory_facts) + hardware_facts.update(dmi_facts) + hardware_facts.update(device_facts) + hardware_facts.update(uptime_facts) + hardware_facts.update(lvm_facts) + hardware_facts.update(mount_facts) + + return hardware_facts + + def get_memory_facts(self): + memory_facts = {} + if not os.access("/proc/meminfo", os.R_OK): + return memory_facts + + memstats = {} + for line in get_file_lines("/proc/meminfo"): + data = line.split(":", 1) + key = data[0] + if key in self.ORIGINAL_MEMORY_FACTS: + val = data[1].strip().split(' ')[0] + memory_facts["%s_mb" % key.lower()] = int(val) // 1024 + + if key in self.MEMORY_FACTS: + val = data[1].strip().split(' ')[0] + memstats[key.lower()] = int(val) // 1024 + + if None not in (memstats.get('memtotal'), memstats.get('memfree')): + memstats['real:used'] = memstats['memtotal'] - memstats['memfree'] + if None not in (memstats.get('cached'), memstats.get('memfree'), memstats.get('buffers')): + memstats['nocache:free'] = memstats['cached'] + memstats['memfree'] + memstats['buffers'] + if None not in (memstats.get('memtotal'), memstats.get('nocache:free')): + memstats['nocache:used'] = memstats['memtotal'] - memstats['nocache:free'] + if None not in (memstats.get('swaptotal'), memstats.get('swapfree')): + memstats['swap:used'] = memstats['swaptotal'] - memstats['swapfree'] + + memory_facts['memory_mb'] = { + 'real': { + 'total': memstats.get('memtotal'), + 'used': memstats.get('real:used'), + 'free': memstats.get('memfree'), + }, + 'nocache': { + 'free': memstats.get('nocache:free'), + 'used': memstats.get('nocache:used'), + }, + 'swap': { + 'total': memstats.get('swaptotal'), + 'free': memstats.get('swapfree'), + 'used': memstats.get('swap:used'), + 'cached': memstats.get('swapcached'), + }, + } + + return memory_facts + + def get_cpu_facts(self, collected_facts=None): + cpu_facts = {} + collected_facts = collected_facts or {} + + i = 0 + vendor_id_occurrence = 0 + model_name_occurrence = 0 + physid = 0 + coreid = 0 + sockets = {} + cores = {} + + xen = False + xen_paravirt = False + try: + if os.path.exists('/proc/xen'): + xen = True + else: + for line in get_file_lines('/sys/hypervisor/type'): + if line.strip() == 'xen': + xen = True + # Only interested in the first line + break + except IOError: + pass + + if not os.access("/proc/cpuinfo", os.R_OK): + return cpu_facts + + cpu_facts['processor'] = [] + for line in get_file_lines('/proc/cpuinfo'): + data = line.split(":", 1) + key = data[0].strip() + + if xen: + if key == 'flags': + # Check for vme cpu flag, Xen paravirt does not expose this. + # Need to detect Xen paravirt because it exposes cpuinfo + # differently than Xen HVM or KVM and causes reporting of + # only a single cpu core. + if 'vme' not in data: + xen_paravirt = True + + # model name is for Intel arch, Processor (mind the uppercase P) + # works for some ARM devices, like the Sheevaplug. + if key in ['model name', 'Processor', 'vendor_id', 'cpu', 'Vendor']: + if 'processor' not in cpu_facts: + cpu_facts['processor'] = [] + cpu_facts['processor'].append(data[1].strip()) + if key == 'vendor_id': + vendor_id_occurrence += 1 + if key == 'model name': + model_name_occurrence += 1 + i += 1 + elif key == 'physical id': + physid = data[1].strip() + if physid not in sockets: + sockets[physid] = 1 + elif key == 'core id': + coreid = data[1].strip() + if coreid not in sockets: + cores[coreid] = 1 + elif key == 'cpu cores': + sockets[physid] = int(data[1].strip()) + elif key == 'siblings': + cores[coreid] = int(data[1].strip()) + elif key == '# processors': + cpu_facts['processor_cores'] = int(data[1].strip()) + + # Skip for platforms without vendor_id/model_name in cpuinfo (e.g ppc64le) + if vendor_id_occurrence > 0: + if vendor_id_occurrence == model_name_occurrence: + i = vendor_id_occurrence + + # FIXME + if collected_facts.get('ansible_architecture') != 's390x': + if xen_paravirt: + cpu_facts['processor_count'] = i + cpu_facts['processor_cores'] = i + cpu_facts['processor_threads_per_core'] = 1 + cpu_facts['processor_vcpus'] = i + else: + if sockets: + cpu_facts['processor_count'] = len(sockets) + else: + cpu_facts['processor_count'] = i + + socket_values = list(sockets.values()) + if socket_values and socket_values[0]: + cpu_facts['processor_cores'] = socket_values[0] + else: + cpu_facts['processor_cores'] = 1 + + core_values = list(cores.values()) + if core_values: + cpu_facts['processor_threads_per_core'] = core_values[0] // cpu_facts['processor_cores'] + else: + cpu_facts['processor_threads_per_core'] = 1 // cpu_facts['processor_cores'] + + cpu_facts['processor_vcpus'] = (cpu_facts['processor_threads_per_core'] * + cpu_facts['processor_count'] * cpu_facts['processor_cores']) + + return cpu_facts + + def get_dmi_facts(self): + ''' learn dmi facts from system + + Try /sys first for dmi related facts. + If that is not available, fall back to dmidecode executable ''' + + dmi_facts = {} + + if os.path.exists('/sys/devices/virtual/dmi/id/product_name'): + # Use kernel DMI info, if available + + # DMI SPEC -- http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.0.pdf + FORM_FACTOR = ["Unknown", "Other", "Unknown", "Desktop", + "Low Profile Desktop", "Pizza Box", "Mini Tower", "Tower", + "Portable", "Laptop", "Notebook", "Hand Held", "Docking Station", + "All In One", "Sub Notebook", "Space-saving", "Lunch Box", + "Main Server Chassis", "Expansion Chassis", "Sub Chassis", + "Bus Expansion Chassis", "Peripheral Chassis", "RAID Chassis", + "Rack Mount Chassis", "Sealed-case PC", "Multi-system", + "CompactPCI", "AdvancedTCA", "Blade"] + + DMI_DICT = { + 'bios_date': '/sys/devices/virtual/dmi/id/bios_date', + 'bios_version': '/sys/devices/virtual/dmi/id/bios_version', + 'form_factor': '/sys/devices/virtual/dmi/id/chassis_type', + 'product_name': '/sys/devices/virtual/dmi/id/product_name', + 'product_serial': '/sys/devices/virtual/dmi/id/product_serial', + 'product_uuid': '/sys/devices/virtual/dmi/id/product_uuid', + 'product_version': '/sys/devices/virtual/dmi/id/product_version', + 'system_vendor': '/sys/devices/virtual/dmi/id/sys_vendor' + } + + for (key, path) in DMI_DICT.items(): + data = get_file_content(path) + if data is not None: + if key == 'form_factor': + try: + dmi_facts['form_factor'] = FORM_FACTOR[int(data)] + except IndexError: + dmi_facts['form_factor'] = 'unknown (%s)' % data + else: + dmi_facts[key] = data + else: + dmi_facts[key] = 'NA' + + else: + # Fall back to using dmidecode, if available + dmi_bin = self.module.get_bin_path('dmidecode') + DMI_DICT = { + 'bios_date': 'bios-release-date', + 'bios_version': 'bios-version', + 'form_factor': 'chassis-type', + 'product_name': 'system-product-name', + 'product_serial': 'system-serial-number', + 'product_uuid': 'system-uuid', + 'product_version': 'system-version', + 'system_vendor': 'system-manufacturer' + } + for (k, v) in DMI_DICT.items(): + if dmi_bin is not None: + (rc, out, err) = self.module.run_command('%s -s %s' % (dmi_bin, v)) + if rc == 0: + # Strip out commented lines (specific dmidecode output) + thisvalue = ''.join([line for line in out.splitlines() if not line.startswith('#')]) + try: + json.dumps(thisvalue) + except UnicodeDecodeError: + thisvalue = "NA" + + dmi_facts[k] = thisvalue + else: + dmi_facts[k] = 'NA' + else: + dmi_facts[k] = 'NA' + + return dmi_facts + + def _run_lsblk(self, lsblk_path): + # call lsblk and collect all uuids + # --exclude 2 makes lsblk ignore floppy disks, which are slower to answer than typical timeouts + # this uses the linux major device number + # for details see https://www.kernel.org/doc/Documentation/devices.txt + args = ['--list', '--noheadings', '--paths', '--output', 'NAME,UUID', '--exclude', '2'] + cmd = [lsblk_path] + args + rc, out, err = self.module.run_command(cmd) + return rc, out, err + + def _lsblk_uuid(self): + uuids = {} + lsblk_path = self.module.get_bin_path("lsblk") + if not lsblk_path: + return uuids + + rc, out, err = self._run_lsblk(lsblk_path) + if rc != 0: + return uuids + + # each line will be in format: + # + # /dev/sda1 32caaec3-ef40-4691-a3b6-438c3f9bc1c0 + for lsblk_line in out.splitlines(): + if not lsblk_line: + continue + + line = lsblk_line.strip() + fields = line.rsplit(None, 1) + + if len(fields) < 2: + continue + + device_name, uuid = fields[0].strip(), fields[1].strip() + if device_name in uuids: + continue + uuids[device_name] = uuid + + return uuids + + def _run_findmnt(self, findmnt_path): + args = ['--list', '--noheadings', '--notruncate'] + cmd = [findmnt_path] + args + rc, out, err = self.module.run_command(cmd, errors='surrogate_then_replace') + return rc, out, err + + def _find_bind_mounts(self): + bind_mounts = set() + findmnt_path = self.module.get_bin_path("findmnt") + if not findmnt_path: + return bind_mounts + + rc, out, err = self._run_findmnt(findmnt_path) + if rc != 0: + return bind_mounts + + # find bind mounts, in case /etc/mtab is a symlink to /proc/mounts + for line in out.splitlines(): + fields = line.split() + # fields[0] is the TARGET, fields[1] is the SOURCE + if len(fields) < 2: + continue + + # bind mounts will have a [/directory_name] in the SOURCE column + if self.BIND_MOUNT_RE.match(fields[1]): + bind_mounts.add(fields[0]) + + return bind_mounts + + def _mtab_entries(self): + mtab_file = '/etc/mtab' + if not os.path.exists(mtab_file): + mtab_file = '/proc/mounts' + + mtab = get_file_content(mtab_file, '') + mtab_entries = [] + for line in mtab.splitlines(): + fields = line.split() + if len(fields) < 4: + continue + mtab_entries.append(fields) + return mtab_entries + + @timeout.timeout() + def get_mount_facts(self): + mount_facts = {} + + mount_facts['mounts'] = [] + + bind_mounts = self._find_bind_mounts() + uuids = self._lsblk_uuid() + mtab_entries = self._mtab_entries() + + mounts = [] + for fields in mtab_entries: + device, mount, fstype, options = fields[0], fields[1], fields[2], fields[3] + + if not device.startswith('/') and ':/' not in device: + continue + + if fstype == 'none': + continue + + size_total, size_available = get_mount_size(mount) + + if mount in bind_mounts: + # only add if not already there, we might have a plain /etc/mtab + if not self.MTAB_BIND_MOUNT_RE.match(options): + options += ",bind" + + mount_info = {'mount': mount, + 'device': device, + 'fstype': fstype, + 'options': options, + # statvfs data + 'size_total': size_total, + 'size_available': size_available, + 'uuid': uuids.get(device, 'N/A')} + + mounts.append(mount_info) + + mount_facts['mounts'] = mounts + + return mount_facts + + def get_holders(self, block_dev_dict, sysdir): + block_dev_dict['holders'] = [] + if os.path.isdir(sysdir + "/holders"): + for folder in os.listdir(sysdir + "/holders"): + if not folder.startswith("dm-"): + continue + name = get_file_content(sysdir + "/holders/" + folder + "/dm/name") + if name: + block_dev_dict['holders'].append(name) + else: + block_dev_dict['holders'].append(folder) + + def get_device_facts(self): + device_facts = {} + + device_facts['devices'] = {} + lspci = self.module.get_bin_path('lspci') + if lspci: + rc, pcidata, err = self.module.run_command([lspci, '-D'], errors='surrogate_then_replace') + else: + pcidata = None + + try: + block_devs = os.listdir("/sys/block") + except OSError: + return device_facts + + devs_wwn = {} + try: + devs_by_id = os.listdir("/dev/disk/by-id") + except OSError: + pass + else: + for link_name in devs_by_id: + if link_name.startswith("wwn-"): + try: + wwn_link = os.readlink(os.path.join("/dev/disk/by-id", link_name)) + except OSError: + continue + devs_wwn[os.path.basename(wwn_link)] = link_name[4:] + + for block in block_devs: + virtual = 1 + sysfs_no_links = 0 + try: + path = os.readlink(os.path.join("/sys/block/", block)) + except OSError: + e = sys.exc_info()[1] + if e.errno == errno.EINVAL: + path = block + sysfs_no_links = 1 + else: + continue + if "virtual" in path: + continue + sysdir = os.path.join("/sys/block", path) + if sysfs_no_links == 1: + for folder in os.listdir(sysdir): + if "device" in folder: + virtual = 0 + break + if virtual: + continue + d = {} + diskname = os.path.basename(sysdir) + for key in ['vendor', 'model', 'sas_address', 'sas_device_handle']: + d[key] = get_file_content(sysdir + "/device/" + key) + + sg_inq = self.module.get_bin_path('sg_inq') + + if sg_inq: + device = "/dev/%s" % (block) + rc, drivedata, err = self.module.run_command([sg_inq, device]) + if rc == 0: + serial = re.search("Unit serial number:\s+(\w+)", drivedata) + if serial: + d['serial'] = serial.group(1) + + for key in ['vendor', 'model']: + d[key] = get_file_content(sysdir + "/device/" + key) + + for key, test in [('removable', '/removable'), + ('support_discard', '/queue/discard_granularity'), + ]: + d[key] = get_file_content(sysdir + test) + + if diskname in devs_wwn: + d['wwn'] = devs_wwn[diskname] + + d['partitions'] = {} + for folder in os.listdir(sysdir): + m = re.search("(" + diskname + "\d+)", folder) + if m: + part = {} + partname = m.group(1) + part_sysdir = sysdir + "/" + partname + + part['start'] = get_file_content(part_sysdir + "/start", 0) + part['sectors'] = get_file_content(part_sysdir + "/size", 0) + part['sectorsize'] = get_file_content(part_sysdir + "/queue/logical_block_size") + if not part['sectorsize']: + part['sectorsize'] = get_file_content(part_sysdir + "/queue/hw_sector_size", 512) + part['size'] = bytes_to_human((float(part['sectors']) * float(part['sectorsize']))) + part['uuid'] = get_partition_uuid(partname) + self.get_holders(part, part_sysdir) + + d['partitions'][partname] = part + + d['rotational'] = get_file_content(sysdir + "/queue/rotational") + d['scheduler_mode'] = "" + scheduler = get_file_content(sysdir + "/queue/scheduler") + if scheduler is not None: + m = re.match(".*?(\[(.*)\])", scheduler) + if m: + d['scheduler_mode'] = m.group(2) + + d['sectors'] = get_file_content(sysdir + "/size") + if not d['sectors']: + d['sectors'] = 0 + d['sectorsize'] = get_file_content(sysdir + "/queue/logical_block_size") + if not d['sectorsize']: + d['sectorsize'] = get_file_content(sysdir + "/queue/hw_sector_size", 512) + d['size'] = bytes_to_human(float(d['sectors']) * float(d['sectorsize'])) + + d['host'] = "" + + # domains are numbered (0 to ffff), bus (0 to ff), slot (0 to 1f), and function (0 to 7). + m = re.match(".+/([a-f0-9]{4}:[a-f0-9]{2}:[0|1][a-f0-9]\.[0-7])/", sysdir) + if m and pcidata: + pciid = m.group(1) + did = re.escape(pciid) + m = re.search("^" + did + "\s(.*)$", pcidata, re.MULTILINE) + if m: + d['host'] = m.group(1) + + self.get_holders(d, sysdir) + + device_facts['devices'][diskname] = d + + return device_facts + + def get_uptime_facts(self): + uptime_facts = {} + uptime_file_content = get_file_content('/proc/uptime') + if uptime_file_content: + uptime_seconds_string = uptime_file_content.split(' ')[0] + uptime_facts['uptime_seconds'] = int(float(uptime_seconds_string)) + + return uptime_facts + + def _find_mapper_device_name(self, dm_device): + dm_prefix = '/dev/dm-' + mapper_device = dm_device + if dm_device.startswith(dm_prefix): + dmsetup_cmd = self.module.get_bin_path('dmsetup', True) + mapper_prefix = '/dev/mapper/' + rc, dm_name, err = self.module.run_command("%s info -C --noheadings -o name %s" % (dmsetup_cmd, dm_device)) + if rc == 0: + mapper_device = mapper_prefix + dm_name.rstrip() + return mapper_device + + def get_lvm_facts(self): + """ Get LVM Facts if running as root and lvm utils are available """ + + lvm_facts = {} + + if os.getuid() == 0 and self.module.get_bin_path('vgs'): + lvm_util_options = '--noheadings --nosuffix --units g' + + vgs_path = self.module.get_bin_path('vgs') + # vgs fields: VG #PV #LV #SN Attr VSize VFree + vgs = {} + if vgs_path: + rc, vg_lines, err = self.module.run_command('%s %s' % (vgs_path, lvm_util_options)) + for vg_line in vg_lines.splitlines(): + items = vg_line.split() + vgs[items[0]] = {'size_g': items[-2], + 'free_g': items[-1], + 'num_lvs': items[2], + 'num_pvs': items[1]} + + lvs_path = self.module.get_bin_path('lvs') + # lvs fields: + # LV VG Attr LSize Pool Origin Data% Move Log Copy% Convert + lvs = {} + if lvs_path: + rc, lv_lines, err = self.module.run_command('%s %s' % (lvs_path, lvm_util_options)) + for lv_line in lv_lines.splitlines(): + items = lv_line.split() + lvs[items[0]] = {'size_g': items[3], 'vg': items[1]} + + pvs_path = self.module.get_bin_path('pvs') + # pvs fields: PV VG #Fmt #Attr PSize PFree + pvs = {} + if pvs_path: + rc, pv_lines, err = self.module.run_command('%s %s' % (pvs_path, lvm_util_options)) + for pv_line in pv_lines.splitlines(): + items = pv_line.split() + pvs[self._find_mapper_device_name(items[0])] = { + 'size_g': items[4], + 'free_g': items[5], + 'vg': items[1]} + + lvm_facts['lvm'] = {'lvs': lvs, 'vgs': vgs, 'pvs': pvs} + + return lvm_facts + + +class LinuxHardwareCollector(HardwareCollector): + _platform = 'Linux' + _fact_class = LinuxHardware diff --git a/lib/ansible/module_utils/facts/hardware/netbsd.py b/lib/ansible/module_utils/facts/hardware/netbsd.py new file mode 100644 index 00000000000..3b58794bf88 --- /dev/null +++ b/lib/ansible/module_utils/facts/hardware/netbsd.py @@ -0,0 +1,164 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import re + +from ansible.module_utils.six.moves import reduce + +from ansible.module_utils.facts.hardware.base import Hardware, HardwareCollector +from ansible.module_utils.facts.timeout import TimeoutError, timeout + +from ansible.module_utils.facts.utils import get_file_content, get_file_lines, get_mount_size +from ansible.module_utils.facts.sysctl import get_sysctl + + +class NetBSDHardware(Hardware): + """ + NetBSD-specific subclass of Hardware. Defines memory and CPU facts: + - memfree_mb + - memtotal_mb + - swapfree_mb + - swaptotal_mb + - processor (a list) + - processor_cores + - processor_count + - devices + """ + platform = 'NetBSD' + MEMORY_FACTS = ['MemTotal', 'SwapTotal', 'MemFree', 'SwapFree'] + + def populate(self, collected_facts=None): + hardware_facts = {} + self.sysctl = get_sysctl(self.module, ['machdep']) + cpu_facts = self.get_cpu_facts() + memory_facts = self.get_memory_facts() + + mount_facts = {} + try: + mount_facts = self.get_mount_facts() + except TimeoutError: + pass + + dmi_facts = self.get_dmi_facts() + + hardware_facts.update(cpu_facts) + hardware_facts.update(memory_facts) + hardware_facts.update(mount_facts) + hardware_facts.update(dmi_facts) + + return hardware_facts + + def get_cpu_facts(self): + cpu_facts = {} + + i = 0 + physid = 0 + sockets = {} + if not os.access("/proc/cpuinfo", os.R_OK): + return cpu_facts + cpu_facts['processor'] = [] + for line in get_file_lines("/proc/cpuinfo"): + data = line.split(":", 1) + key = data[0].strip() + # model name is for Intel arch, Processor (mind the uppercase P) + # works for some ARM devices, like the Sheevaplug. + if key == 'model name' or key == 'Processor': + if 'processor' not in cpu_facts: + cpu_facts['processor'] = [] + cpu_facts['processor'].append(data[1].strip()) + i += 1 + elif key == 'physical id': + physid = data[1].strip() + if physid not in sockets: + sockets[physid] = 1 + elif key == 'cpu cores': + sockets[physid] = int(data[1].strip()) + if len(sockets) > 0: + cpu_facts['processor_count'] = len(sockets) + cpu_facts['processor_cores'] = reduce(lambda x, y: x + y, sockets.values()) + else: + cpu_facts['processor_count'] = i + cpu_facts['processor_cores'] = 'NA' + + return cpu_facts + + def get_memory_facts(self): + memory_facts = {} + if not os.access("/proc/meminfo", os.R_OK): + return memory_facts + for line in get_file_lines("/proc/meminfo"): + data = line.split(":", 1) + key = data[0] + if key in NetBSDHardware.MEMORY_FACTS: + val = data[1].strip().split(' ')[0] + memory_facts["%s_mb" % key.lower()] = int(val) // 1024 + + return memory_facts + + @timeout() + def get_mount_facts(self): + mount_facts = {} + + mount_facts['mounts'] = [] + fstab = get_file_content('/etc/fstab') + + if not fstab: + return mount_facts + + for line in fstab.splitlines(): + if line.startswith('#') or line.strip() == '': + continue + fields = re.sub(r'\s+', ' ', line).split() + size_total, size_available = get_mount_size(fields[1]) + mount_facts['mounts'].append({ + 'mount': fields[1], + 'device': fields[0], + 'fstype': fields[2], + 'options': fields[3], + 'size_total': size_total, + 'size_available': size_available + }) + return mount_facts + + def get_dmi_facts(self): + dmi_facts = {} + # We don't use dmidecode(1) here because: + # - it would add dependency on an external package + # - dmidecode(1) can only be ran as root + # So instead we rely on sysctl(8) to provide us the information on a + # best-effort basis. As a bonus we also get facts on non-amd64/i386 + # platforms this way. + sysctl_to_dmi = { + 'machdep.dmi.system-product': 'product_name', + 'machdep.dmi.system-version': 'product_version', + 'machdep.dmi.system-uuid': 'product_uuid', + 'machdep.dmi.system-serial': 'product_serial', + 'machdep.dmi.system-vendor': 'system_vendor', + } + + for mib in sysctl_to_dmi: + if mib in self.sysctl: + dmi_facts[sysctl_to_dmi[mib]] = self.sysctl[mib] + + return dmi_facts + + +class NetBSDHardwareCollector(HardwareCollector): + _fact_class = NetBSDHardware + _platform = 'NetBSD' diff --git a/lib/ansible/module_utils/facts/hardware/openbsd.py b/lib/ansible/module_utils/facts/hardware/openbsd.py new file mode 100644 index 00000000000..04577e4dc82 --- /dev/null +++ b/lib/ansible/module_utils/facts/hardware/openbsd.py @@ -0,0 +1,172 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from ansible.module_utils._text import to_text + +from ansible.module_utils.facts.hardware.base import Hardware, HardwareCollector +from ansible.module_utils.facts import timeout + +from ansible.module_utils.facts.utils import get_file_content, get_mount_size +from ansible.module_utils.facts.sysctl import get_sysctl + + +class OpenBSDHardware(Hardware): + """ + OpenBSD-specific subclass of Hardware. Defines memory, CPU and device facts: + - memfree_mb + - memtotal_mb + - swapfree_mb + - swaptotal_mb + - processor (a list) + - processor_cores + - processor_count + - processor_speed + + In addition, it also defines number of DMI facts and device facts. + """ + platform = 'OpenBSD' + + def populate(self, collected_facts=None): + hardware_facts = {} + self.sysctl = get_sysctl(self.module, ['hw']) + + # TODO: change name + cpu_facts = self.get_processor_facts() + memory_facts = self.get_memory_facts() + device_facts = self.get_device_facts() + dmi_facts = self.get_dmi_facts() + + mount_facts = {} + try: + mount_facts = self.get_mount_facts() + except timeout.TimeoutError: + pass + + hardware_facts.update(cpu_facts) + hardware_facts.update(memory_facts) + hardware_facts.update(dmi_facts) + hardware_facts.update(device_facts) + hardware_facts.update(mount_facts) + + return hardware_facts + + @timeout.timeout() + def get_mount_facts(self): + mount_facts = {} + + mount_facts['mounts'] = [] + fstab = get_file_content('/etc/fstab') + if fstab: + for line in fstab.splitlines(): + if line.startswith('#') or line.strip() == '': + continue + fields = re.sub(r'\s+', ' ', line).split() + if fields[1] == 'none' or fields[3] == 'xx': + continue + size_total, size_available = get_mount_size(fields[1]) + mount_facts['mounts'].append({ + 'mount': fields[1], + 'device': fields[0], + 'fstype': fields[2], + 'options': fields[3], + 'size_total': size_total, + 'size_available': size_available + }) + return mount_facts + + def get_memory_facts(self): + memory_facts = {} + # Get free memory. vmstat output looks like: + # procs memory page disks traps cpu + # r b w avm fre flt re pi po fr sr wd0 fd0 int sys cs us sy id + # 0 0 0 47512 28160 51 0 0 0 0 0 1 0 116 89 17 0 1 99 + rc, out, err = self.module.run_command("/usr/bin/vmstat") + if rc == 0: + memory_facts['memfree_mb'] = int(out.splitlines()[-1].split()[4]) // 1024 + memory_facts['memtotal_mb'] = int(self.sysctl['hw.usermem']) // 1024 // 1024 + + # Get swapctl info. swapctl output looks like: + # total: 69268 1K-blocks allocated, 0 used, 69268 available + # And for older OpenBSD: + # total: 69268k bytes allocated = 0k used, 69268k available + rc, out, err = self.module.run_command("/sbin/swapctl -sk") + if rc == 0: + swaptrans = {ord(u'k'): None, + ord(u'm'): None, + ord(u'g'): None} + data = to_text(out, errors='surrogate_or_strict').split() + memory_facts['swapfree_mb'] = int(data[-2].translate(swaptrans)) // 1024 + memory_facts['swaptotal_mb'] = int(data[1].translate(swaptrans)) // 1024 + + return memory_facts + + def get_processor_facts(self): + cpu_facts = {} + processor = [] + for i in range(int(self.sysctl['hw.ncpu'])): + processor.append(self.sysctl['hw.model']) + + cpu_facts['processor'] = processor + # The following is partly a lie because there is no reliable way to + # determine the number of physical CPUs in the system. We can only + # query the number of logical CPUs, which hides the number of cores. + # On amd64/i386 we could try to inspect the smt/core/package lines in + # dmesg, however even those have proven to be unreliable. + # So take a shortcut and report the logical number of processors in + # 'processor_count' and 'processor_cores' and leave it at that. + cpu_facts['processor_count'] = self.sysctl['hw.ncpu'] + cpu_facts['processor_cores'] = self.sysctl['hw.ncpu'] + + return cpu_facts + + def get_device_facts(self): + device_facts = {} + devices = [] + devices.extend(self.sysctl['hw.disknames'].split(',')) + device_facts['devices'] = devices + + return device_facts + + def get_dmi_facts(self): + dmi_facts = {} + # We don't use dmidecode(1) here because: + # - it would add dependency on an external package + # - dmidecode(1) can only be ran as root + # So instead we rely on sysctl(8) to provide us the information on a + # best-effort basis. As a bonus we also get facts on non-amd64/i386 + # platforms this way. + sysctl_to_dmi = { + 'hw.product': 'product_name', + 'hw.version': 'product_version', + 'hw.uuid': 'product_uuid', + 'hw.serialno': 'product_serial', + 'hw.vendor': 'system_vendor', + } + + for mib in sysctl_to_dmi: + if mib in self.sysctl: + dmi_facts[sysctl_to_dmi[mib]] = self.sysctl[mib] + + return dmi_facts + + +class OpenBSDHardwareCollector(HardwareCollector): + _fact_class = OpenBSDHardware + _platform = 'OpenBSD' diff --git a/lib/ansible/module_utils/facts/hardware/sunos.py b/lib/ansible/module_utils/facts/hardware/sunos.py new file mode 100644 index 00000000000..595e100a88a --- /dev/null +++ b/lib/ansible/module_utils/facts/hardware/sunos.py @@ -0,0 +1,267 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from ansible.module_utils.six.moves import reduce + +from ansible.module_utils.basic import bytes_to_human + +from ansible.module_utils.facts.utils import get_file_content, get_mount_size + +from ansible.module_utils.facts.hardware.base import Hardware, HardwareCollector +from ansible.module_utils.facts import timeout + + +class SunOSHardware(Hardware): + """ + In addition to the generic memory and cpu facts, this also sets + swap_reserved_mb and swap_allocated_mb that is available from *swap -s*. + """ + platform = 'SunOS' + + def populate(self, collected_facts=None): + hardware_facts = {} + + # FIXME: could pass to run_command(environ_update), but it also tweaks the env + # of the parent process instead of altering an env provided to Popen() + # Use C locale for hardware collection helpers to avoid locale specific number formatting (#24542) + self.module.run_command_environ_update = {'LANG': 'C', 'LC_ALL': 'C', 'LC_NUMERIC': 'C'} + + cpu_facts = self.get_cpu_facts() + memory_facts = self.get_memory_facts() + dmi_facts = self.get_dmi_facts() + device_facts = self.get_device_facts() + uptime_facts = self.get_uptime_facts() + + mount_facts = {} + try: + mount_facts = self.get_mount_facts() + except timeout.TimeoutError: + pass + + hardware_facts.update(cpu_facts) + hardware_facts.update(memory_facts) + hardware_facts.update(dmi_facts) + hardware_facts.update(device_facts) + hardware_facts.update(uptime_facts) + hardware_facts.update(mount_facts) + + return hardware_facts + + def get_cpu_facts(self, collected_facts=None): + physid = 0 + sockets = {} + + cpu_facts = {} + collected_facts = collected_facts or {} + + rc, out, err = self.module.run_command("/usr/bin/kstat cpu_info") + + cpu_facts['processor'] = [] + + for line in out.splitlines(): + if len(line) < 1: + continue + + data = line.split(None, 1) + key = data[0].strip() + + # "brand" works on Solaris 10 & 11. "implementation" for Solaris 9. + if key == 'module:': + brand = '' + elif key == 'brand': + brand = data[1].strip() + elif key == 'clock_MHz': + clock_mhz = data[1].strip() + elif key == 'implementation': + processor = brand or data[1].strip() + # Add clock speed to description for SPARC CPU + # FIXME + if collected_facts.get('ansible_machine') != 'i86pc': + processor += " @ " + clock_mhz + "MHz" + if 'ansible_processor' not in collected_facts: + cpu_facts['processor'] = [] + cpu_facts['processor'].append(processor) + elif key == 'chip_id': + physid = data[1].strip() + if physid not in sockets: + sockets[physid] = 1 + else: + sockets[physid] += 1 + + # Counting cores on Solaris can be complicated. + # https://blogs.oracle.com/mandalika/entry/solaris_show_me_the_cpu + # Treat 'processor_count' as physical sockets and 'processor_cores' as + # virtual CPUs visisble to Solaris. Not a true count of cores for modern SPARC as + # these processors have: sockets -> cores -> threads/virtual CPU. + if len(sockets) > 0: + cpu_facts['processor_count'] = len(sockets) + cpu_facts['processor_cores'] = reduce(lambda x, y: x + y, sockets.values()) + else: + cpu_facts['processor_cores'] = 'NA' + cpu_facts['processor_count'] = len(cpu_facts['processor']) + + return cpu_facts + + def get_memory_facts(self): + memory_facts = {} + + rc, out, err = self.module.run_command(["/usr/sbin/prtconf"]) + + for line in out.splitlines(): + if 'Memory size' in line: + memory_facts['memtotal_mb'] = int(line.split()[2]) + + rc, out, err = self.module.run_command("/usr/sbin/swap -s") + + allocated = int(out.split()[1][:-1]) + reserved = int(out.split()[5][:-1]) + used = int(out.split()[8][:-1]) + free = int(out.split()[10][:-1]) + + memory_facts['swapfree_mb'] = free // 1024 + memory_facts['swaptotal_mb'] = (free + used) // 1024 + memory_facts['swap_allocated_mb'] = allocated // 1024 + memory_facts['swap_reserved_mb'] = reserved // 1024 + + return memory_facts + + @timeout.timeout() + def get_mount_facts(self): + mount_facts = {} + mount_facts['mounts'] = [] + + # For a detailed format description see mnttab(4) + # special mount_point fstype options time + fstab = get_file_content('/etc/mnttab') + + if fstab: + for line in fstab.splitlines(): + fields = line.split('\t') + size_total, size_available = get_mount_size(fields[1]) + mount_facts['mounts'].append({ + 'mount': fields[1], + 'device': fields[0], + 'fstype': fields[2], + 'options': fields[3], + 'time': fields[4], + 'size_total': size_total, + 'size_available': size_available + }) + + return mount_facts + + def get_dmi_facts(self): + dmi_facts = {} + + uname_path = self.module.get_bin_path("prtdiag") + rc, out, err = self.module.run_command(uname_path) + """ + rc returns 1 + """ + if out: + system_conf = out.split('\n')[0] + found = re.search(r'(\w+\sEnterprise\s\w+)', system_conf) + + if found: + dmi_facts['product_name'] = found.group(1) + + return dmi_facts + + def get_device_facts(self): + # Device facts are derived for sdderr kstats. This code does not use the + # full output, but rather queries for specific stats. + # Example output: + # sderr:0:sd0,err:Hard Errors 0 + # sderr:0:sd0,err:Illegal Request 6 + # sderr:0:sd0,err:Media Error 0 + # sderr:0:sd0,err:Predictive Failure Analysis 0 + # sderr:0:sd0,err:Product VBOX HARDDISK 9 + # sderr:0:sd0,err:Revision 1.0 + # sderr:0:sd0,err:Serial No VB0ad2ec4d-074a + # sderr:0:sd0,err:Size 53687091200 + # sderr:0:sd0,err:Soft Errors 0 + # sderr:0:sd0,err:Transport Errors 0 + # sderr:0:sd0,err:Vendor ATA + + device_facts = {} + + disk_stats = { + 'Product': 'product', + 'Revision': 'revision', + 'Serial No': 'serial', + 'Size': 'size', + 'Vendor': 'vendor', + 'Hard Errors': 'hard_errors', + 'Soft Errors': 'soft_errors', + 'Transport Errors': 'transport_errors', + 'Media Error': 'media_errors', + 'Predictive Failure Analysis': 'predictive_failure_analysis', + 'Illegal Request': 'illegal_request', + } + + cmd = ['/usr/bin/kstat', '-p'] + + for ds in disk_stats: + cmd.append('sderr:::%s' % ds) + + d = {} + rc, out, err = self.module.run_command(cmd) + if rc != 0: + return device_facts + + sd_instances = frozenset(line.split(':')[1] for line in out.split('\n') if line.startswith('sderr')) + for instance in sd_instances: + lines = (line for line in out.split('\n') if ':' in line and line.split(':')[1] == instance) + for line in lines: + text, value = line.split('\t') + stat = text.split(':')[3] + + if stat == 'Size': + d[disk_stats.get(stat)] = bytes_to_human(float(value)) + else: + d[disk_stats.get(stat)] = value.rstrip() + + diskname = 'sd' + instance + device_facts['devices'][diskname] = d + d = {} + + return device_facts + + def get_uptime_facts(self): + uptime_facts = {} + # On Solaris, unix:0:system_misc:snaptime is created shortly after machine boots up + # and displays tiem in seconds. This is much easier than using uptime as we would + # need to have a parsing procedure for translating from human-readable to machine-readable + # format. + # Example output: + # unix:0:system_misc:snaptime 1175.410463590 + rc, out, err = self.module.run_command('/usr/bin/kstat -p unix:0:system_misc:snaptime') + + if rc != 0: + return + + uptime_facts['uptime_seconds'] = int(float(out.split('\t')[1])) + + return uptime_facts + + +class SunOSHardwareCollector(HardwareCollector): + _fact_class = SunOSHardware + _platform = 'SunOS' diff --git a/lib/ansible/module_utils/facts/namespace.py b/lib/ansible/module_utils/facts/namespace.py new file mode 100644 index 00000000000..f74992d5efc --- /dev/null +++ b/lib/ansible/module_utils/facts/namespace.py @@ -0,0 +1,39 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +class FactNamespace: + def __init__(self, namespace_name): + self.namespace_name = namespace_name + + def transform(self, name): + '''Take a text name, and transforms it as needed (add a namespace prefix, etc)''' + return name + + def _underscore(self, name): + return name.replace('-', '_') + + +class PrefixFactNamespace(FactNamespace): + def __init__(self, namespace_name, prefix=None): + super(PrefixFactNamespace, self).__init__(namespace_name) + self.prefix = prefix + + def transform(self, name): + new_name = self._underscore(name) + return '%s%s' % (self.prefix, new_name) diff --git a/lib/ansible/module_utils/facts/network/__init__.py b/lib/ansible/module_utils/facts/network/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/facts/network/aix.py b/lib/ansible/module_utils/facts/network/aix.py new file mode 100644 index 00000000000..0ea53d3ca35 --- /dev/null +++ b/lib/ansible/module_utils/facts/network/aix.py @@ -0,0 +1,144 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from ansible.module_utils.facts.network.base import NetworkCollector +from ansible.module_utils.facts.network.generic_bsd import GenericBsdIfconfigNetwork + + +class AIXNetwork(GenericBsdIfconfigNetwork): + """ + This is the AIX Network Class. + It uses the GenericBsdIfconfigNetwork unchanged. + """ + platform = 'AIX' + + def get_default_interfaces(self, route_path): + netstat_path = self.module.get_bin_path('netstat') + + rc, out, err = self.module.run_command([netstat_path, '-nr']) + + interface = dict(v4={}, v6={}) + + lines = out.splitlines() + for line in lines: + words = line.split() + if len(words) > 1 and words[0] == 'default': + if '.' in words[1]: + interface['v4']['gateway'] = words[1] + interface['v4']['interface'] = words[5] + elif ':' in words[1]: + interface['v6']['gateway'] = words[1] + interface['v6']['interface'] = words[5] + + return interface['v4'], interface['v6'] + + # AIX 'ifconfig -a' does not have three words in the interface line + def get_interfaces_info(self, ifconfig_path, ifconfig_options='-a'): + interfaces = {} + current_if = {} + ips = dict( + all_ipv4_addresses=[], + all_ipv6_addresses=[], + ) + + uname_rc = None + uname_out = None + uname_err = None + uname_path = self.module.get_bin_path('uname') + if uname_path: + uname_rc, uname_out, uname_err = self.module.run_command([uname_path, '-W']) + + rc, out, err = self.module.run_command([ifconfig_path, ifconfig_options]) + + for line in out.splitlines(): + + if line: + words = line.split() + + # only this condition differs from GenericBsdIfconfigNetwork + if re.match('^\w*\d*:', line): + current_if = self.parse_interface_line(words) + interfaces[current_if['device']] = current_if + elif words[0].startswith('options='): + self.parse_options_line(words, current_if, ips) + elif words[0] == 'nd6': + self.parse_nd6_line(words, current_if, ips) + elif words[0] == 'ether': + self.parse_ether_line(words, current_if, ips) + elif words[0] == 'media:': + self.parse_media_line(words, current_if, ips) + elif words[0] == 'status:': + self.parse_status_line(words, current_if, ips) + elif words[0] == 'lladdr': + self.parse_lladdr_line(words, current_if, ips) + elif words[0] == 'inet': + self.parse_inet_line(words, current_if, ips) + elif words[0] == 'inet6': + self.parse_inet6_line(words, current_if, ips) + else: + self.parse_unknown_line(words, current_if, ips) + + # don't bother with wpars it does not work + # zero means not in wpar + if not uname_rc and uname_out.split()[0] == '0': + + if current_if['macaddress'] == 'unknown' and re.match('^en', current_if['device']): + entstat_path = self.module.get_bin_path('entstat') + if entstat_path: + rc, out, err = self.module.run_command([entstat_path, current_if['device']]) + if rc != 0: + break + for line in out.splitlines(): + if not line: + pass + buff = re.match('^Hardware Address: (.*)', line) + if buff: + current_if['macaddress'] = buff.group(1) + + buff = re.match('^Device Type:', line) + if buff and re.match('.*Ethernet', line): + current_if['type'] = 'ether' + + # device must have mtu attribute in ODM + if 'mtu' not in current_if: + lsattr_path = self.module.get_bin_path('lsattr') + if lsattr_path: + rc, out, err = self.module.run_command([lsattr_path, '-El', current_if['device']]) + if rc != 0: + break + for line in out.splitlines(): + if line: + words = line.split() + if words[0] == 'mtu': + current_if['mtu'] = words[1] + return interfaces, ips + + # AIX 'ifconfig -a' does not inform about MTU, so remove current_if['mtu'] here + def parse_interface_line(self, words): + device = words[0][0:-1] + current_if = {'device': device, 'ipv4': [], 'ipv6': [], 'type': 'unknown'} + current_if['flags'] = self.get_options(words[1]) + current_if['macaddress'] = 'unknown' # will be overwritten later + return current_if + + +class AIXNetworkCollector(NetworkCollector): + _fact_class = AIXNetwork + _platform = 'AIX' diff --git a/lib/ansible/module_utils/facts/network/base.py b/lib/ansible/module_utils/facts/network/base.py new file mode 100644 index 00000000000..d74897dd5ee --- /dev/null +++ b/lib/ansible/module_utils/facts/network/base.py @@ -0,0 +1,70 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils.facts.collector import BaseFactCollector + + +class Network: + """ + This is a generic Network subclass of Facts. This should be further + subclassed to implement per platform. If you subclass this, + you must define: + - interfaces (a list of interface names) + - interface_ dictionary of ipv4, ipv6, and mac address information. + + All subclasses MUST define platform. + """ + platform = 'Generic' + + # FIXME: remove load_on_init when we can + def __init__(self, module, load_on_init=False): + self.module = module + + # TODO: more or less abstract/NotImplemented + def populate(self, collected_facts=None): + return {} + + +class NetworkCollector(BaseFactCollector): + # MAYBE: we could try to build this based on the arch specific implemementation of Network() or its kin + name = 'network' + _fact_class = Network + _fact_ids = set(['interfaces', + 'default_ipv4', + 'default_ipv6', + 'all_ipv4_addresses', + 'all_ipv6_addresses']) + + IPV6_SCOPE = {'0': 'global', + '10': 'host', + '20': 'link', + '40': 'admin', + '50': 'site', + '80': 'organization'} + + def collect(self, module=None, collected_facts=None): + collected_facts = collected_facts or {} + if not module: + return {} + + # Network munges cached_facts by side effect, so give it a copy + facts_obj = self._fact_class(module) + + facts_dict = facts_obj.populate(collected_facts=collected_facts) + + return facts_dict diff --git a/lib/ansible/module_utils/facts/network/darwin.py b/lib/ansible/module_utils/facts/network/darwin.py new file mode 100644 index 00000000000..4e1d4a9e3e6 --- /dev/null +++ b/lib/ansible/module_utils/facts/network/darwin.py @@ -0,0 +1,49 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils.facts.network.base import NetworkCollector +from ansible.module_utils.facts.network.generic_bsd import GenericBsdIfconfigNetwork + + +class DarwinNetwork(GenericBsdIfconfigNetwork): + """ + This is the Mac OS X/Darwin Network Class. + It uses the GenericBsdIfconfigNetwork unchanged + """ + platform = 'Darwin' + + # media line is different to the default FreeBSD one + def parse_media_line(self, words, current_if, ips): + # not sure if this is useful - we also drop information + current_if['media'] = 'Unknown' # Mac does not give us this + current_if['media_select'] = words[1] + if len(words) > 2: + # MacOSX sets the media to '' for bridge interface + # and parsing splits this into two words; this if/else helps + if words[1] == '': + current_if['media_select'] = 'Unknown' + current_if['media_type'] = 'unknown type' + else: + current_if['media_type'] = words[2][1:-1] + if len(words) > 3: + current_if['media_options'] = self.get_options(words[3]) + + +class DarwinNetworkCollector(NetworkCollector): + _fact_class = DarwinNetwork + _platform = 'Darwin' diff --git a/lib/ansible/module_utils/facts/network/dragonfly.py b/lib/ansible/module_utils/facts/network/dragonfly.py new file mode 100644 index 00000000000..e43bbb28ec9 --- /dev/null +++ b/lib/ansible/module_utils/facts/network/dragonfly.py @@ -0,0 +1,33 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils.facts.network.base import NetworkCollector +from ansible.module_utils.facts.network.generic_bsd import GenericBsdIfconfigNetwork + + +class DragonFlyNetwork(GenericBsdIfconfigNetwork): + """ + This is the DragonFly Network Class. + It uses the GenericBsdIfconfigNetwork unchanged. + """ + platform = 'DragonFly' + + +class DragonFlyNetworkCollector(NetworkCollector): + _fact_class = DragonFlyNetwork + _platform = 'DragonFly' diff --git a/lib/ansible/module_utils/facts/network/freebsd.py b/lib/ansible/module_utils/facts/network/freebsd.py new file mode 100644 index 00000000000..36f6eec7c43 --- /dev/null +++ b/lib/ansible/module_utils/facts/network/freebsd.py @@ -0,0 +1,33 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils.facts.network.base import NetworkCollector +from ansible.module_utils.facts.network.generic_bsd import GenericBsdIfconfigNetwork + + +class FreeBSDNetwork(GenericBsdIfconfigNetwork): + """ + This is the FreeBSD Network Class. + It uses the GenericBsdIfconfigNetwork unchanged. + """ + platform = 'FreeBSD' + + +class FreeBSDNetworkCollector(NetworkCollector): + _fact_class = FreeBSDNetwork + _platform = 'FreeBSD' diff --git a/lib/ansible/module_utils/facts/network/generic_bsd.py b/lib/ansible/module_utils/facts/network/generic_bsd.py new file mode 100644 index 00000000000..212c842f43b --- /dev/null +++ b/lib/ansible/module_utils/facts/network/generic_bsd.py @@ -0,0 +1,266 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re +import socket +import struct + +from ansible.module_utils.facts.network.base import Network + + +class GenericBsdIfconfigNetwork(Network): + """ + This is a generic BSD subclass of Network using the ifconfig command. + It defines + - interfaces (a list of interface names) + - interface_ dictionary of ipv4, ipv6, and mac address information. + - all_ipv4_addresses and all_ipv6_addresses: lists of all configured addresses. + """ + platform = 'Generic_BSD_Ifconfig' + + def populate(self, collected_facts=None): + network_facts = {} + ifconfig_path = self.module.get_bin_path('ifconfig') + + if ifconfig_path is None: + return network_facts + + route_path = self.module.get_bin_path('route') + + if route_path is None: + return network_facts + + default_ipv4, default_ipv6 = self.get_default_interfaces(route_path) + interfaces, ips = self.get_interfaces_info(ifconfig_path) + interfaces = self.detect_type_media(interfaces) + + self.merge_default_interface(default_ipv4, interfaces, 'ipv4') + self.merge_default_interface(default_ipv6, interfaces, 'ipv6') + network_facts['interfaces'] = interfaces.keys() + + for iface in interfaces: + network_facts[iface] = interfaces[iface] + + network_facts['default_ipv4'] = default_ipv4 + network_facts['default_ipv6'] = default_ipv6 + network_facts['all_ipv4_addresses'] = ips['all_ipv4_addresses'] + network_facts['all_ipv6_addresses'] = ips['all_ipv6_addresses'] + + return network_facts + + def detect_type_media(self, interfaces): + for iface in interfaces: + if 'media' in interfaces[iface]: + if 'ether' in interfaces[iface]['media'].lower(): + interfaces[iface]['type'] = 'ether' + return interfaces + + def get_default_interfaces(self, route_path): + + # Use the commands: + # route -n get 8.8.8.8 -> Google public DNS + # route -n get -inet6 2404:6800:400a:800::1012 -> ipv6.google.com + # to find out the default outgoing interface, address, and gateway + + command = dict(v4=[route_path, '-n', 'get', '8.8.8.8'], + v6=[route_path, '-n', 'get', '-inet6', '2404:6800:400a:800::1012']) + + interface = dict(v4={}, v6={}) + + for v in 'v4', 'v6': + + if v == 'v6' and not socket.has_ipv6: + continue + rc, out, err = self.module.run_command(command[v]) + if not out: + # v6 routing may result in + # RTNETLINK answers: Invalid argument + continue + for line in out.splitlines(): + words = line.split() + # Collect output from route command + if len(words) > 1: + if words[0] == 'interface:': + interface[v]['interface'] = words[1] + if words[0] == 'gateway:': + interface[v]['gateway'] = words[1] + + return interface['v4'], interface['v6'] + + def get_interfaces_info(self, ifconfig_path, ifconfig_options='-a'): + interfaces = {} + current_if = {} + ips = dict( + all_ipv4_addresses=[], + all_ipv6_addresses=[], + ) + # FreeBSD, DragonflyBSD, NetBSD, OpenBSD and OS X all implicitly add '-a' + # when running the command 'ifconfig'. + # Solaris must explicitly run the command 'ifconfig -a'. + rc, out, err = self.module.run_command([ifconfig_path, ifconfig_options]) + + for line in out.splitlines(): + + if line: + words = line.split() + + if words[0] == 'pass': + continue + elif re.match('^\S', line) and len(words) > 3: + current_if = self.parse_interface_line(words) + interfaces[current_if['device']] = current_if + elif words[0].startswith('options='): + self.parse_options_line(words, current_if, ips) + elif words[0] == 'nd6': + self.parse_nd6_line(words, current_if, ips) + elif words[0] == 'ether': + self.parse_ether_line(words, current_if, ips) + elif words[0] == 'media:': + self.parse_media_line(words, current_if, ips) + elif words[0] == 'status:': + self.parse_status_line(words, current_if, ips) + elif words[0] == 'lladdr': + self.parse_lladdr_line(words, current_if, ips) + elif words[0] == 'inet': + self.parse_inet_line(words, current_if, ips) + elif words[0] == 'inet6': + self.parse_inet6_line(words, current_if, ips) + elif words[0] == 'tunnel': + self.parse_tunnel_line(words, current_if, ips) + else: + self.parse_unknown_line(words, current_if, ips) + + return interfaces, ips + + def parse_interface_line(self, words): + device = words[0][0:-1] + current_if = {'device': device, 'ipv4': [], 'ipv6': [], 'type': 'unknown'} + current_if['flags'] = self.get_options(words[1]) + if 'LOOPBACK' in current_if['flags']: + current_if['type'] = 'loopback' + current_if['macaddress'] = 'unknown' # will be overwritten later + + if len(words) >= 5: # Newer FreeBSD versions + current_if['metric'] = words[3] + current_if['mtu'] = words[5] + else: + current_if['mtu'] = words[3] + + return current_if + + def parse_options_line(self, words, current_if, ips): + # Mac has options like this... + current_if['options'] = self.get_options(words[0]) + + def parse_nd6_line(self, words, current_if, ips): + # FreeBSD has options like this... + current_if['options'] = self.get_options(words[1]) + + def parse_ether_line(self, words, current_if, ips): + current_if['macaddress'] = words[1] + current_if['type'] = 'ether' + + def parse_media_line(self, words, current_if, ips): + # not sure if this is useful - we also drop information + current_if['media'] = words[1] + if len(words) > 2: + current_if['media_select'] = words[2] + if len(words) > 3: + current_if['media_type'] = words[3][1:] + if len(words) > 4: + current_if['media_options'] = self.get_options(words[4]) + + def parse_status_line(self, words, current_if, ips): + current_if['status'] = words[1] + + def parse_lladdr_line(self, words, current_if, ips): + current_if['lladdr'] = words[1] + + def parse_inet_line(self, words, current_if, ips): + # netbsd show aliases like this + # lo0: flags=8049 mtu 33184 + # inet 127.0.0.1 netmask 0xff000000 + # inet alias 127.1.1.1 netmask 0xff000000 + if words[1] == 'alias': + del words[1] + address = {'address': words[1]} + # deal with hex netmask + if re.match('([0-9a-f]){8}', words[3]) and len(words[3]) == 8: + words[3] = '0x' + words[3] + if words[3].startswith('0x'): + address['netmask'] = socket.inet_ntoa(struct.pack('!L', int(words[3], base=16))) + else: + # otherwise assume this is a dotted quad + address['netmask'] = words[3] + # calculate the network + address_bin = struct.unpack('!L', socket.inet_aton(address['address']))[0] + netmask_bin = struct.unpack('!L', socket.inet_aton(address['netmask']))[0] + address['network'] = socket.inet_ntoa(struct.pack('!L', address_bin & netmask_bin)) + # broadcast may be given or we need to calculate + if len(words) > 5: + address['broadcast'] = words[5] + else: + address['broadcast'] = socket.inet_ntoa(struct.pack('!L', address_bin | (~netmask_bin & 0xffffffff))) + # add to our list of addresses + if not words[1].startswith('127.'): + ips['all_ipv4_addresses'].append(address['address']) + current_if['ipv4'].append(address) + + def parse_inet6_line(self, words, current_if, ips): + address = {'address': words[1]} + if (len(words) >= 4) and (words[2] == 'prefixlen'): + address['prefix'] = words[3] + if (len(words) >= 6) and (words[4] == 'scopeid'): + address['scope'] = words[5] + localhost6 = ['::1', '::1/128', 'fe80::1%lo0'] + if address['address'] not in localhost6: + ips['all_ipv6_addresses'].append(address['address']) + current_if['ipv6'].append(address) + + def parse_tunnel_line(self, words, current_if, ips): + current_if['type'] = 'tunnel' + + def parse_unknown_line(self, words, current_if, ips): + # we are going to ignore unknown lines here - this may be + # a bad idea - but you can override it in your subclass + pass + + # TODO: these are module scope static function candidates + # (most of the class is really...) + def get_options(self, option_string): + start = option_string.find('<') + 1 + end = option_string.rfind('>') + if (start > 0) and (end > 0) and (end > start + 1): + option_csv = option_string[start:end] + return option_csv.split(',') + else: + return [] + + def merge_default_interface(self, defaults, interfaces, ip_type): + if 'interface' not in defaults: + return + if not defaults['interface'] in interfaces: + return + ifinfo = interfaces[defaults['interface']] + # copy all the interface values across except addresses + for item in ifinfo: + if item != 'ipv4' and item != 'ipv6': + defaults[item] = ifinfo[item] + if len(ifinfo[ip_type]) > 0: + for item in ifinfo[ip_type][0]: + defaults[item] = ifinfo[ip_type][0][item] diff --git a/lib/ansible/module_utils/facts/network/hpux.py b/lib/ansible/module_utils/facts/network/hpux.py new file mode 100644 index 00000000000..6e87ee92774 --- /dev/null +++ b/lib/ansible/module_utils/facts/network/hpux.py @@ -0,0 +1,82 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils.facts.network.base import Network, NetworkCollector + + +class HPUXNetwork(Network): + """ + HP-UX-specifig subclass of Network. Defines networking facts: + - default_interface + - interfaces (a list of interface names) + - interface_ dictionary of ipv4 address information. + """ + platform = 'HP-UX' + + def populate(self, collected_facts=None): + network_facts = {} + netstat_path = self.module.get_bin_path('netstat') + + if netstat_path is None: + return network_facts + + default_interfaces_facts = self.get_default_interfaces() + network_facts.update(default_interfaces_facts) + + interfaces = self.get_interfaces_info() + network_facts['interfaces'] = interfaces.keys() + for iface in interfaces: + network_facts[iface] = interfaces[iface] + + return network_facts + + def get_default_interfaces(self): + default_interfaces = {} + rc, out, err = self.module.run_command("/usr/bin/netstat -nr") + lines = out.splitlines() + for line in lines: + words = line.split() + if len(words) > 1: + if words[0] == 'default': + default_interfaces['default_interface'] = words[4] + default_interfaces['default_gateway'] = words[1] + + return default_interfaces + + def get_interfaces_info(self): + interfaces = {} + rc, out, err = self.module.run_command("/usr/bin/netstat -ni") + lines = out.splitlines() + for line in lines: + words = line.split() + for i in range(len(words) - 1): + if words[i][:3] == 'lan': + device = words[i] + interfaces[device] = {'device': device} + address = words[i + 3] + interfaces[device]['ipv4'] = {'address': address} + network = words[i + 2] + interfaces[device]['ipv4'] = {'network': network, + 'interface': device, + 'address': address} + return interfaces + + +class HPUXNetworkCollector(NetworkCollector): + _fact_class = HPUXNetwork + _platform = 'HP-UX' diff --git a/lib/ansible/module_utils/facts/network/hurd.py b/lib/ansible/module_utils/facts/network/hurd.py new file mode 100644 index 00000000000..f1d3a8707cf --- /dev/null +++ b/lib/ansible/module_utils/facts/network/hurd.py @@ -0,0 +1,86 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os + +from ansible.module_utils.facts.network.base import Network, NetworkCollector + + +class HurdPfinetNetwork(Network): + """ + This is a GNU Hurd specific subclass of Network. It use fsysopts to + get the ip address and support only pfinet. + """ + platform = 'GNU' + _socket_dir = '/servers/socket/' + + def populate(self, collected_facts=None): + network_facts = {} + + fsysopts_path = self.module.get_bin_path('fsysopts') + if fsysopts_path is None: + return network_facts + + socket_path = None + + for l in ('inet', 'inet6'): + link = os.path.join(self._socket_dir, l) + if os.path.exists(link): + socket_path = link + break + + # FIXME: extract to method + # FIXME: exit early on falsey socket_path and un-indent whole block + + if socket_path: + rc, out, err = self.module.run_command([fsysopts_path, '-L', socket_path]) + # FIXME: build up a interfaces datastructure, then assign into network_facts + network_facts['interfaces'] = [] + for i in out.split(): + if '=' in i and i.startswith('--'): + k, v = i.split('=', 1) + # remove '--' + k = k[2:] + if k == 'interface': + # remove /dev/ from /dev/eth0 + v = v[5:] + network_facts['interfaces'].append(v) + network_facts[v] = { + 'active': True, + 'device': v, + 'ipv4': {}, + 'ipv6': [], + } + current_if = v + elif k == 'address': + network_facts[current_if]['ipv4']['address'] = v + elif k == 'netmask': + network_facts[current_if]['ipv4']['netmask'] = v + elif k == 'address6': + address, prefix = v.split('/') + network_facts[current_if]['ipv6'].append({ + 'address': address, + 'prefix': prefix, + }) + + return network_facts + + +class HurdNetworkCollector(NetworkCollector): + _platform = 'GNU' + _fact_class = HurdPfinetNetwork diff --git a/lib/ansible/module_utils/facts/network/linux.py b/lib/ansible/module_utils/facts/network/linux.py new file mode 100644 index 00000000000..deb69883f6b --- /dev/null +++ b/lib/ansible/module_utils/facts/network/linux.py @@ -0,0 +1,311 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import glob +import os +import re +import socket +import struct + +from ansible.module_utils.facts.network.base import Network, NetworkCollector + +from ansible.module_utils.facts.utils import get_file_content + + +class LinuxNetwork(Network): + """ + This is a Linux-specific subclass of Network. It defines + - interfaces (a list of interface names) + - interface_ dictionary of ipv4, ipv6, and mac address information. + - all_ipv4_addresses and all_ipv6_addresses: lists of all configured addresses. + - ipv4_address and ipv6_address: the first non-local address for each family. + """ + platform = 'Linux' + INTERFACE_TYPE = { + '1': 'ether', + '32': 'infiniband', + '512': 'ppp', + '772': 'loopback', + '65534': 'tunnel', + } + + def populate(self, collected_facts=None): + network_facts = {} + ip_path = self.module.get_bin_path('ip') + if ip_path is None: + return network_facts + default_ipv4, default_ipv6 = self.get_default_interfaces(ip_path, + collected_facts=collected_facts) + interfaces, ips = self.get_interfaces_info(ip_path, default_ipv4, default_ipv6) + network_facts['interfaces'] = interfaces.keys() + for iface in interfaces: + network_facts[iface] = interfaces[iface] + network_facts['default_ipv4'] = default_ipv4 + network_facts['default_ipv6'] = default_ipv6 + network_facts['all_ipv4_addresses'] = ips['all_ipv4_addresses'] + network_facts['all_ipv6_addresses'] = ips['all_ipv6_addresses'] + return network_facts + + def get_default_interfaces(self, ip_path, collected_facts=None): + collected_facts = collected_facts or {} + # Use the commands: + # ip -4 route get 8.8.8.8 -> Google public DNS + # ip -6 route get 2404:6800:400a:800::1012 -> ipv6.google.com + # to find out the default outgoing interface, address, and gateway + command = dict( + v4=[ip_path, '-4', 'route', 'get', '8.8.8.8'], + v6=[ip_path, '-6', 'route', 'get', '2404:6800:400a:800::1012'] + ) + interface = dict(v4={}, v6={}) + + for v in 'v4', 'v6': + if (v == 'v6' and collected_facts.get('ansible_os_family') == 'RedHat' and + collected_facts.get('ansible_distribution_version', '').startswith('4.')): + continue + if v == 'v6' and not socket.has_ipv6: + continue + rc, out, err = self.module.run_command(command[v], errors='surrogate_then_replace') + if not out: + # v6 routing may result in + # RTNETLINK answers: Invalid argument + continue + words = out.splitlines()[0].split() + # A valid output starts with the queried address on the first line + if len(words) > 0 and words[0] == command[v][-1]: + for i in range(len(words) - 1): + if words[i] == 'dev': + interface[v]['interface'] = words[i + 1] + elif words[i] == 'src': + interface[v]['address'] = words[i + 1] + elif words[i] == 'via' and words[i + 1] != command[v][-1]: + interface[v]['gateway'] = words[i + 1] + return interface['v4'], interface['v6'] + + def get_interfaces_info(self, ip_path, default_ipv4, default_ipv6): + interfaces = {} + ips = dict( + all_ipv4_addresses=[], + all_ipv6_addresses=[], + ) + + # FIXME: maybe split into smaller methods? + # FIXME: this is pretty much a constructor + + for path in glob.glob('/sys/class/net/*'): + if not os.path.isdir(path): + continue + device = os.path.basename(path) + interfaces[device] = {'device': device} + if os.path.exists(os.path.join(path, 'address')): + macaddress = get_file_content(os.path.join(path, 'address'), default='') + if macaddress and macaddress != '00:00:00:00:00:00': + interfaces[device]['macaddress'] = macaddress + if os.path.exists(os.path.join(path, 'mtu')): + interfaces[device]['mtu'] = int(get_file_content(os.path.join(path, 'mtu'))) + if os.path.exists(os.path.join(path, 'operstate')): + interfaces[device]['active'] = get_file_content(os.path.join(path, 'operstate')) != 'down' + if os.path.exists(os.path.join(path, 'device', 'driver', 'module')): + interfaces[device]['module'] = os.path.basename(os.path.realpath(os.path.join(path, 'device', 'driver', 'module'))) + if os.path.exists(os.path.join(path, 'type')): + _type = get_file_content(os.path.join(path, 'type')) + interfaces[device]['type'] = self.INTERFACE_TYPE.get(_type, 'unknown') + if os.path.exists(os.path.join(path, 'bridge')): + interfaces[device]['type'] = 'bridge' + interfaces[device]['interfaces'] = [os.path.basename(b) for b in glob.glob(os.path.join(path, 'brif', '*'))] + if os.path.exists(os.path.join(path, 'bridge', 'bridge_id')): + interfaces[device]['id'] = get_file_content(os.path.join(path, 'bridge', 'bridge_id'), default='') + if os.path.exists(os.path.join(path, 'bridge', 'stp_state')): + interfaces[device]['stp'] = get_file_content(os.path.join(path, 'bridge', 'stp_state')) == '1' + if os.path.exists(os.path.join(path, 'bonding')): + interfaces[device]['type'] = 'bonding' + interfaces[device]['slaves'] = get_file_content(os.path.join(path, 'bonding', 'slaves'), default='').split() + interfaces[device]['mode'] = get_file_content(os.path.join(path, 'bonding', 'mode'), default='').split()[0] + interfaces[device]['miimon'] = get_file_content(os.path.join(path, 'bonding', 'miimon'), default='').split()[0] + interfaces[device]['lacp_rate'] = get_file_content(os.path.join(path, 'bonding', 'lacp_rate'), default='').split()[0] + primary = get_file_content(os.path.join(path, 'bonding', 'primary')) + if primary: + interfaces[device]['primary'] = primary + path = os.path.join(path, 'bonding', 'all_slaves_active') + if os.path.exists(path): + interfaces[device]['all_slaves_active'] = get_file_content(path) == '1' + if os.path.exists(os.path.join(path, 'bonding_slave')): + interfaces[device]['perm_macaddress'] = get_file_content(os.path.join(path, 'bonding_slave', 'perm_hwaddr'), default='') + if os.path.exists(os.path.join(path, 'device')): + interfaces[device]['pciid'] = os.path.basename(os.readlink(os.path.join(path, 'device'))) + if os.path.exists(os.path.join(path, 'speed')): + speed = get_file_content(os.path.join(path, 'speed')) + if speed is not None: + interfaces[device]['speed'] = int(speed) + + # Check whether an interface is in promiscuous mode + if os.path.exists(os.path.join(path, 'flags')): + promisc_mode = False + # The second byte indicates whether the interface is in promiscuous mode. + # 1 = promisc + # 0 = no promisc + data = int(get_file_content(os.path.join(path, 'flags')), 16) + promisc_mode = (data & 0x0100 > 0) + interfaces[device]['promisc'] = promisc_mode + + # TODO: determine if this needs to be in a nested scope/closure + def parse_ip_output(output, secondary=False): + for line in output.splitlines(): + if not line: + continue + words = line.split() + broadcast = '' + if words[0] == 'inet': + if '/' in words[1]: + address, netmask_length = words[1].split('/') + if len(words) > 3: + broadcast = words[3] + else: + # pointopoint interfaces do not have a prefix + address = words[1] + netmask_length = "32" + address_bin = struct.unpack('!L', socket.inet_aton(address))[0] + netmask_bin = (1 << 32) - (1 << 32 >> int(netmask_length)) + netmask = socket.inet_ntoa(struct.pack('!L', netmask_bin)) + network = socket.inet_ntoa(struct.pack('!L', address_bin & netmask_bin)) + iface = words[-1] + # NOTE: device is ref to outside scope + # NOTE: interfaces is also ref to outside scope + if iface != device: + interfaces[iface] = {} + if not secondary and "ipv4" not in interfaces[iface]: + interfaces[iface]['ipv4'] = {'address': address, + 'broadcast': broadcast, + 'netmask': netmask, + 'network': network} + else: + if "ipv4_secondaries" not in interfaces[iface]: + interfaces[iface]["ipv4_secondaries"] = [] + interfaces[iface]["ipv4_secondaries"].append({ + 'address': address, + 'broadcast': broadcast, + 'netmask': netmask, + 'network': network, + }) + + # add this secondary IP to the main device + if secondary: + if "ipv4_secondaries" not in interfaces[device]: + interfaces[device]["ipv4_secondaries"] = [] + interfaces[device]["ipv4_secondaries"].append({ + 'address': address, + 'broadcast': broadcast, + 'netmask': netmask, + 'network': network, + }) + + # NOTE: default_ipv4 is ref to outside scope + # If this is the default address, update default_ipv4 + if 'address' in default_ipv4 and default_ipv4['address'] == address: + default_ipv4['broadcast'] = broadcast + default_ipv4['netmask'] = netmask + default_ipv4['network'] = network + # NOTE: macadress is ref from outside scope + default_ipv4['macaddress'] = macaddress + default_ipv4['mtu'] = interfaces[device]['mtu'] + default_ipv4['type'] = interfaces[device].get("type", "unknown") + default_ipv4['alias'] = words[-1] + if not address.startswith('127.'): + ips['all_ipv4_addresses'].append(address) + elif words[0] == 'inet6': + if 'peer' == words[2]: + address = words[1] + _, prefix = words[3].split('/') + scope = words[5] + else: + address, prefix = words[1].split('/') + scope = words[3] + if 'ipv6' not in interfaces[device]: + interfaces[device]['ipv6'] = [] + interfaces[device]['ipv6'].append({ + 'address': address, + 'prefix': prefix, + 'scope': scope + }) + # If this is the default address, update default_ipv6 + if 'address' in default_ipv6 and default_ipv6['address'] == address: + default_ipv6['prefix'] = prefix + default_ipv6['scope'] = scope + default_ipv6['macaddress'] = macaddress + default_ipv6['mtu'] = interfaces[device]['mtu'] + default_ipv6['type'] = interfaces[device].get("type", "unknown") + if not address == '::1': + ips['all_ipv6_addresses'].append(address) + + ip_path = self.module.get_bin_path("ip") + + args = [ip_path, 'addr', 'show', 'primary', device] + rc, primary_data, stderr = self.module.run_command(args, errors='surrogate_then_replace') + + args = [ip_path, 'addr', 'show', 'secondary', device] + rc, secondary_data, stderr = self.module.run_command(args, errors='surrogate_then_replace') + + parse_ip_output(primary_data) + parse_ip_output(secondary_data, secondary=True) + + interfaces[device].update(self.get_ethtool_data(device)) + + # replace : by _ in interface name since they are hard to use in template + new_interfaces = {} + # i is a dict key (string) not an index int + for i in interfaces: + if ':' in i: + new_interfaces[i.replace(':', '_')] = interfaces[i] + else: + new_interfaces[i] = interfaces[i] + return new_interfaces, ips + + def get_ethtool_data(self, device): + + data = {} + ethtool_path = self.module.get_bin_path("ethtool") + # FIXME: exit early on falsey ethtool_path and un-indent + if ethtool_path: + args = [ethtool_path, '-k', device] + rc, stdout, stderr = self.module.run_command(args, errors='surrogate_then_replace') + # FIXME: exit early on falsey if we can + if rc == 0: + features = {} + for line in stdout.strip().splitlines(): + if not line or line.endswith(":"): + continue + key, value = line.split(": ") + if not value: + continue + features[key.strip().replace('-', '_')] = value.strip() + data['features'] = features + + args = [ethtool_path, '-T', device] + rc, stdout, stderr = self.module.run_command(args, errors='surrogate_then_replace') + if rc == 0: + data['timestamping'] = [m.lower() for m in re.findall('SOF_TIMESTAMPING_(\w+)', stdout)] + data['hw_timestamp_filters'] = [m.lower() for m in re.findall('HWTSTAMP_FILTER_(\w+)', stdout)] + m = re.search('PTP Hardware Clock: (\d+)', stdout) + if m: + data['phc_index'] = int(m.groups()[0]) + + return data + + +class LinuxNetworkCollector(NetworkCollector): + _platform = 'Linux' + _fact_class = LinuxNetwork diff --git a/lib/ansible/module_utils/facts/network/netbsd.py b/lib/ansible/module_utils/facts/network/netbsd.py new file mode 100644 index 00000000000..de8ceff60c3 --- /dev/null +++ b/lib/ansible/module_utils/facts/network/netbsd.py @@ -0,0 +1,48 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils.facts.network.base import NetworkCollector +from ansible.module_utils.facts.network.generic_bsd import GenericBsdIfconfigNetwork + + +class NetBSDNetwork(GenericBsdIfconfigNetwork): + """ + This is the NetBSD Network Class. + It uses the GenericBsdIfconfigNetwork + """ + platform = 'NetBSD' + + def parse_media_line(self, words, current_if, ips): + # example of line: + # $ ifconfig + # ne0: flags=8863 mtu 1500 + # ec_capabilities=1 + # ec_enabled=0 + # address: 00:20:91:45:00:78 + # media: Ethernet 10baseT full-duplex + # inet 192.168.156.29 netmask 0xffffff00 broadcast 192.168.156.255 + current_if['media'] = words[1] + if len(words) > 2: + current_if['media_type'] = words[2] + if len(words) > 3: + current_if['media_options'] = words[3].split(',') + + +class NetBSDNetworkCollector(NetworkCollector): + _fact_class = NetBSDNetwork + _platform = 'NetBSD' diff --git a/lib/ansible/module_utils/facts/network/openbsd.py b/lib/ansible/module_utils/facts/network/openbsd.py new file mode 100644 index 00000000000..9e11d82f372 --- /dev/null +++ b/lib/ansible/module_utils/facts/network/openbsd.py @@ -0,0 +1,42 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils.facts.network.base import NetworkCollector +from ansible.module_utils.facts.network.generic_bsd import GenericBsdIfconfigNetwork + + +class OpenBSDNetwork(GenericBsdIfconfigNetwork): + """ + This is the OpenBSD Network Class. + It uses the GenericBsdIfconfigNetwork. + """ + platform = 'OpenBSD' + + # OpenBSD 'ifconfig -a' does not have information about aliases + def get_interfaces_info(self, ifconfig_path, ifconfig_options='-aA'): + return super(OpenBSDNetwork, self).get_interfaces_info(ifconfig_path, ifconfig_options) + + # Return macaddress instead of lladdr + def parse_lladdr_line(self, words, current_if, ips): + current_if['macaddress'] = words[1] + current_if['type'] = 'ether' + + +class OpenBSDNetworkCollector(NetworkCollector): + _fact_class = OpenBSDNetwork + _platform = 'OpenBSD' diff --git a/lib/ansible/module_utils/facts/network/sunos.py b/lib/ansible/module_utils/facts/network/sunos.py new file mode 100644 index 00000000000..dec5888b81c --- /dev/null +++ b/lib/ansible/module_utils/facts/network/sunos.py @@ -0,0 +1,116 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from ansible.module_utils.facts.network.base import NetworkCollector +from ansible.module_utils.facts.network.generic_bsd import GenericBsdIfconfigNetwork + + +class SunOSNetwork(GenericBsdIfconfigNetwork): + """ + This is the SunOS Network Class. + It uses the GenericBsdIfconfigNetwork. + + Solaris can have different FLAGS and MTU for IPv4 and IPv6 on the same interface + so these facts have been moved inside the 'ipv4' and 'ipv6' lists. + """ + platform = 'SunOS' + + # Solaris 'ifconfig -a' will print interfaces twice, once for IPv4 and again for IPv6. + # MTU and FLAGS also may differ between IPv4 and IPv6 on the same interface. + # 'parse_interface_line()' checks for previously seen interfaces before defining + # 'current_if' so that IPv6 facts don't clobber IPv4 facts (or vice versa). + def get_interfaces_info(self, ifconfig_path): + interfaces = {} + current_if = {} + ips = dict( + all_ipv4_addresses=[], + all_ipv6_addresses=[], + ) + rc, out, err = self.module.run_command([ifconfig_path, '-a']) + + for line in out.splitlines(): + + if line: + words = line.split() + + if re.match('^\S', line) and len(words) > 3: + current_if = self.parse_interface_line(words, current_if, interfaces) + interfaces[current_if['device']] = current_if + elif words[0].startswith('options='): + self.parse_options_line(words, current_if, ips) + elif words[0] == 'nd6': + self.parse_nd6_line(words, current_if, ips) + elif words[0] == 'ether': + self.parse_ether_line(words, current_if, ips) + elif words[0] == 'media:': + self.parse_media_line(words, current_if, ips) + elif words[0] == 'status:': + self.parse_status_line(words, current_if, ips) + elif words[0] == 'lladdr': + self.parse_lladdr_line(words, current_if, ips) + elif words[0] == 'inet': + self.parse_inet_line(words, current_if, ips) + elif words[0] == 'inet6': + self.parse_inet6_line(words, current_if, ips) + else: + self.parse_unknown_line(words, current_if, ips) + + # 'parse_interface_line' and 'parse_inet*_line' leave two dicts in the + # ipv4/ipv6 lists which is ugly and hard to read. + # This quick hack merges the dictionaries. Purely cosmetic. + for iface in interfaces: + for v in 'ipv4', 'ipv6': + combined_facts = {} + for facts in interfaces[iface][v]: + combined_facts.update(facts) + if len(combined_facts.keys()) > 0: + interfaces[iface][v] = [combined_facts] + + return interfaces, ips + + def parse_interface_line(self, words, current_if, interfaces): + device = words[0][0:-1] + if device not in interfaces: + current_if = {'device': device, 'ipv4': [], 'ipv6': [], 'type': 'unknown'} + else: + current_if = interfaces[device] + flags = self.get_options(words[1]) + v = 'ipv4' + if 'IPv6' in flags: + v = 'ipv6' + if 'LOOPBACK' in flags: + current_if['type'] = 'loopback' + current_if[v].append({'flags': flags, 'mtu': words[3]}) + current_if['macaddress'] = 'unknown' # will be overwritten later + return current_if + + # Solaris displays single digit octets in MAC addresses e.g. 0:1:2:d:e:f + # Add leading zero to each octet where needed. + def parse_ether_line(self, words, current_if, ips): + macaddress = '' + for octet in words[1].split(':'): + octet = ('0' + octet)[-2:None] + macaddress += (octet + ':') + current_if['macaddress'] = macaddress[0:-1] + + +class SunOSNetworkCollector(NetworkCollector): + _fact_class = SunOSNetwork + _platform = 'SunOS' diff --git a/lib/ansible/module_utils/facts/other/__init__.py b/lib/ansible/module_utils/facts/other/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/facts/other/facter.py b/lib/ansible/module_utils/facts/other/facter.py new file mode 100644 index 00000000000..899fcc419b8 --- /dev/null +++ b/lib/ansible/module_utils/facts/other/facter.py @@ -0,0 +1,85 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + +from ansible.module_utils.facts.namespace import PrefixFactNamespace + +from ansible.module_utils.facts.collector import BaseFactCollector + + +class FacterFactCollector(BaseFactCollector): + name = 'facter' + _fact_ids = set(['facter']) + + def __init__(self, collectors=None, namespace=None): + namespace = PrefixFactNamespace(namespace_name='facter', + prefix='facter_') + super(FacterFactCollector, self).__init__(collectors=collectors, + namespace=namespace) + + def find_facter(self, module): + facter_path = module.get_bin_path('facter', opt_dirs=['/opt/puppetlabs/bin']) + cfacter_path = module.get_bin_path('cfacter', opt_dirs=['/opt/puppetlabs/bin']) + + # Prefer to use cfacter if available + if cfacter_path is not None: + facter_path = cfacter_path + + return facter_path + + def run_facter(self, module, facter_path): + # if facter is installed, and we can use --json because + # ruby-json is ALSO installed, include facter data in the JSON + rc, out, err = module.run_command(facter_path + " --puppet --json") + return rc, out, err + + def get_facter_output(self, module): + facter_path = self.find_facter(module) + if not facter_path: + return None + + rc, out, err = self.run_facter(module, facter_path) + + if rc != 0: + return None + + return out + + def collect(self, module=None, collected_facts=None): + # Note that this mirrors previous facter behavior, where there isnt + # a 'ansible_facter' key in the main fact dict, but instead, 'facter_whatever' + # items are added to the main dict. + facter_dict = {} + + if not module: + return facter_dict + + facter_output = self.get_facter_output(module) + + # TODO: if we fail, should we add a empty facter key or nothing? + if facter_output is None: + return facter_dict + + try: + facter_dict = json.loads(facter_output) + except Exception: + # FIXME: maybe raise a FactCollectorError with some info attrs? + pass + + return facter_dict diff --git a/lib/ansible/module_utils/facts/other/ohai.py b/lib/ansible/module_utils/facts/other/ohai.py new file mode 100644 index 00000000000..df292376863 --- /dev/null +++ b/lib/ansible/module_utils/facts/other/ohai.py @@ -0,0 +1,72 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + +from ansible.module_utils.facts.namespace import PrefixFactNamespace + +from ansible.module_utils.facts.collector import BaseFactCollector + + +class OhaiFactCollector(BaseFactCollector): + '''This is a subclass of Facts for including information gathered from Ohai.''' + name = 'ohai' + _fact_ids = set() + + def __init__(self, collectors=None, namespace=None): + namespace = PrefixFactNamespace(namespace_name='ohai', + prefix='ohai_') + super(OhaiFactCollector, self).__init__(collectors=collectors, + namespace=namespace) + + def find_ohai(self, module): + ohai_path = module.get_bin_path('ohai') + return ohai_path + + def run_ohai(self, module, ohai_path,): + rc, out, err = module.run_command(ohai_path) + return rc, out, err + + def get_ohai_output(self, module): + ohai_path = self.find_ohai(module) + if not ohai_path: + return None + + rc, out, err = self.run_ohai(module, ohai_path) + if rc != 0: + return None + + return out + + def collect(self, module=None, collected_facts=None): + ohai_facts = {} + if not module: + return ohai_facts + + ohai_output = self.get_ohai_output(module) + + if ohai_output is None: + return ohai_facts + + try: + ohai_facts = json.loads(ohai_output) + except Exception: + # FIXME: useful error, logging, something... + pass + + return ohai_facts diff --git a/lib/ansible/module_utils/facts/sysctl.py b/lib/ansible/module_utils/facts/sysctl.py new file mode 100644 index 00000000000..2446dab2f7d --- /dev/null +++ b/lib/ansible/module_utils/facts/sysctl.py @@ -0,0 +1,35 @@ +# This file is part of Ansible +# +# 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 . + +import re + + +def get_sysctl(module, prefixes): + sysctl_cmd = module.get_bin_path('sysctl') + cmd = [sysctl_cmd] + cmd.extend(prefixes) + + rc, out, err = module.run_command(cmd) + if rc != 0: + return dict() + + sysctl = dict() + for line in out.splitlines(): + if not line: + continue + (key, value) = re.split('\s?=\s?|: ', line, maxsplit=1) + sysctl[key] = value.strip() + + return sysctl diff --git a/lib/ansible/module_utils/facts/system/__init__.py b/lib/ansible/module_utils/facts/system/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/facts/system/apparmor.py b/lib/ansible/module_utils/facts/system/apparmor.py new file mode 100644 index 00000000000..53c3ed1856f --- /dev/null +++ b/lib/ansible/module_utils/facts/system/apparmor.py @@ -0,0 +1,39 @@ +# Collect facts related to apparmor +# +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os + +from ansible.module_utils.facts.collector import BaseFactCollector + + +class ApparmorFactCollector(BaseFactCollector): + name = 'apparmor' + _fact_ids = set() + + def collect(self, module=None, collected_facts=None): + facts_dict = {} + apparmor_facts = {} + if os.path.exists('/sys/kernel/security/apparmor'): + apparmor_facts['status'] = 'enabled' + else: + apparmor_facts['status'] = 'disabled' + + facts_dict['apparmor'] = apparmor_facts + return facts_dict diff --git a/lib/ansible/module_utils/facts/system/caps.py b/lib/ansible/module_utils/facts/system/caps.py new file mode 100644 index 00000000000..057eeda48e9 --- /dev/null +++ b/lib/ansible/module_utils/facts/system/caps.py @@ -0,0 +1,55 @@ +# Collect facts related to systems 'capabilities' via capsh +# +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +from ansible.module_utils.facts.collector import BaseFactCollector + + +class SystemCapabilitiesFactCollector(BaseFactCollector): + name = 'caps' + _fact_ids = set(['system_capabilities', + 'system_capabilities_enforced']) + + def collect(self, module=None, collected_facts=None): + facts_dict = {} + if not module: + return facts_dict + + capsh_path = module.get_bin_path('capsh') + # NOTE: early exit 'if not crash_path' and unindent rest of method -akl + if capsh_path: + # NOTE: -> get_caps_data()/parse_caps_data() for easier mocking -akl + rc, out, err = module.run_command([capsh_path, "--print"], errors='surrogate_then_replace') + enforced_caps = [] + enforced = 'NA' + for line in out.splitlines(): + if len(line) < 1: + continue + if line.startswith('Current:'): + if line.split(':')[1].strip() == '=ep': + enforced = 'False' + else: + enforced = 'True' + enforced_caps = [i.strip() for i in line.split('=')[1].split(',')] + + facts_dict['system_capabilities_enforced'] = enforced + facts_dict['system_capabilities'] = enforced_caps + + return facts_dict diff --git a/lib/ansible/module_utils/facts/system/cmdline.py b/lib/ansible/module_utils/facts/system/cmdline.py new file mode 100644 index 00000000000..612132a0787 --- /dev/null +++ b/lib/ansible/module_utils/facts/system/cmdline.py @@ -0,0 +1,50 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import shlex + +from ansible.module_utils.facts.utils import get_file_content + +from ansible.module_utils.facts.collector import BaseFactCollector + + +class CmdLineFactCollector(BaseFactCollector): + name = 'cmdline' + _fact_ids = set() + + def collect(self, module=None, collected_facts=None): + cmdline_facts = {} + + data = get_file_content('/proc/cmdline') + + if not data: + return cmdline_facts + + cmdline_facts['cmdline'] = {} + + try: + for piece in shlex.split(data): + item = piece.split('=', 1) + if len(item) == 1: + cmdline_facts['cmdline'][item[0]] = True + else: + cmdline_facts['cmdline'][item[0]] = item[1] + except ValueError: + pass + + return cmdline_facts diff --git a/lib/ansible/module_utils/facts/system/date_time.py b/lib/ansible/module_utils/facts/system/date_time.py new file mode 100644 index 00000000000..f4e59705ef8 --- /dev/null +++ b/lib/ansible/module_utils/facts/system/date_time.py @@ -0,0 +1,59 @@ +# Data and time related facts collection for ansible. +# +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import datetime +import time + +from ansible.module_utils.facts.collector import BaseFactCollector + + +class DateTimeFactCollector(BaseFactCollector): + name = 'date_time' + _fact_ids = set() + + def collect(self, module=None, collected_facts=None): + facts_dict = {} + date_time_facts = {} + + now = datetime.datetime.now() + date_time_facts['year'] = now.strftime('%Y') + date_time_facts['month'] = now.strftime('%m') + date_time_facts['weekday'] = now.strftime('%A') + date_time_facts['weekday_number'] = now.strftime('%w') + date_time_facts['weeknumber'] = now.strftime('%W') + date_time_facts['day'] = now.strftime('%d') + date_time_facts['hour'] = now.strftime('%H') + date_time_facts['minute'] = now.strftime('%M') + date_time_facts['second'] = now.strftime('%S') + date_time_facts['epoch'] = now.strftime('%s') + if date_time_facts['epoch'] == '' or date_time_facts['epoch'][0] == '%': + # NOTE: in this case, the epoch wont match the rest of the date_time facts? ie, it's a few milliseconds later..? -akl + date_time_facts['epoch'] = str(int(time.time())) + date_time_facts['date'] = now.strftime('%Y-%m-%d') + date_time_facts['time'] = now.strftime('%H:%M:%S') + date_time_facts['iso8601_micro'] = now.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ") + date_time_facts['iso8601'] = now.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ") + date_time_facts['iso8601_basic'] = now.strftime("%Y%m%dT%H%M%S%f") + date_time_facts['iso8601_basic_short'] = now.strftime("%Y%m%dT%H%M%S") + date_time_facts['tz'] = time.strftime("%Z") + date_time_facts['tz_offset'] = time.strftime("%z") + + facts_dict['date_time'] = date_time_facts + return facts_dict diff --git a/lib/ansible/module_utils/facts/system/distribution.py b/lib/ansible/module_utils/facts/system/distribution.py new file mode 100644 index 00000000000..ad41d4f7d98 --- /dev/null +++ b/lib/ansible/module_utils/facts/system/distribution.py @@ -0,0 +1,579 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import platform +import re + +from ansible.module_utils.facts.utils import get_file_content + +from ansible.module_utils.facts.collector import BaseFactCollector + + +def get_uname_version(module): + rc, out, err = module.run_command(['uname', '-v']) + if rc == 0: + return out + return None + + +def _file_exists(path, allow_empty=False): + # not finding the file, exit early + if not os.path.exists(path): + return False + + # if just the path needs to exists (ie, it can be empty) we are done + if allow_empty: + return True + + # file exists but is empty and we dont allow_empty + if os.path.getsize(path) == 0: + return False + + # file exists with some content + return True + + +class DistributionFiles: + '''has-a various distro file parsers (os-release, etc) and logic for finding the right one.''' + # every distribution name mentioned here, must have one of + # - allowempty == True + # - be listed in SEARCH_STRING + # - have a function get_distribution_DISTNAME implemented + OSDIST_LIST = ( + {'path': '/etc/oracle-release', 'name': 'OracleLinux'}, + {'path': '/etc/slackware-version', 'name': 'Slackware'}, + {'path': '/etc/redhat-release', 'name': 'RedHat'}, + {'path': '/etc/vmware-release', 'name': 'VMwareESX', 'allowempty': True}, + {'path': '/etc/openwrt_release', 'name': 'OpenWrt'}, + {'path': '/etc/system-release', 'name': 'Amazon'}, + {'path': '/etc/alpine-release', 'name': 'Alpine'}, + {'path': '/etc/arch-release', 'name': 'Archlinux', 'allowempty': True}, + {'path': '/etc/os-release', 'name': 'SuSE'}, + {'path': '/etc/SuSE-release', 'name': 'SuSE'}, + {'path': '/etc/gentoo-release', 'name': 'Gentoo'}, + {'path': '/etc/os-release', 'name': 'Debian'}, + {'path': '/etc/lsb-release', 'name': 'Mandriva'}, + {'path': '/etc/altlinux-release', 'name': 'Altlinux'}, + {'path': '/etc/sourcemage-release', 'name': 'SMGL'}, + {'path': '/etc/os-release', 'name': 'NA'}, + {'path': '/etc/coreos/update.conf', 'name': 'Coreos'}, + {'path': '/usr/lib/os-release', 'name': 'ClearLinux'}, + ) + + SEARCH_STRING = { + 'OracleLinux': 'Oracle Linux', + 'RedHat': 'Red Hat', + 'Altlinux': 'ALT Linux', + 'ClearLinux': 'Clear Linux Software for Intel Architecture', + 'SMGL': 'Source Mage GNU/Linux', + } + + def __init__(self, module): + self.module = module + + def _get_file_content(self, path): + return get_file_content(path) + + def _get_dist_file_content(self, path, allow_empty=False): + # cant find that dist file or it is incorrectly empty + if not _file_exists(path, allow_empty=allow_empty): + return False, None + + data = self._get_file_content(path) + return True, data + + def _parse_dist_file(self, name, dist_file_content, path, collected_facts): + dist_file_dict = {} + if name in self.SEARCH_STRING: + # look for the distribution string in the data and replace according to RELEASE_NAME_MAP + # only the distribution name is set, the version is assumed to be correct from platform.dist() + if self.SEARCH_STRING[name] in dist_file_content: + # this sets distribution=RedHat if 'Red Hat' shows up in data + # self.facts['distribution'] = name + dist_file_dict['distribution'] = name + else: + # this sets distribution to what's in the data, e.g. CentOS, Scientific, ... + # self.facts['distribution'] = dist_file_content.split()[0] + dist_file_dict['distribution'] = dist_file_content.split()[0] + + return True, dist_file_dict + + # call a dedicated function for parsing the file content + # TODO: replace with a map or a class + try: + # FIXME: most of these dont actually look at the dist file contents, but random other stuff + distfunc_name = 'parse_distribution_file_' + name + # print('distfunc_name: %s' % distfunc_name) + + distfunc = getattr(self, distfunc_name) + # print('distfunc: %s' % distfunc) + + parsed, dist_file_dict = distfunc(name, dist_file_content, path, collected_facts) + return parsed, dist_file_dict + except AttributeError as exc: + print('exc: %s' % exc) + # this should never happen, but if it does fail quitely and not with a traceback + return False, dist_file_dict + + return True, dist_file_dict + # to debug multiple matching release files, one can use: + # self.facts['distribution_debug'].append({path + ' ' + name: + # (parsed, + # self.facts['distribution'], + # self.facts['distribution_version'], + # self.facts['distribution_release'], + # )}) + + def _guess_distribution(self): + # try to find out which linux distribution this is + dist = platform.dist() + distribution_guess = {} + distribution_guess['distribution'] = dist[0].capitalize() or 'NA' + distribution_guess['distribution_version'] = dist[1] or 'NA' + distribution_guess['distribution_major_version'] = dist[1].split('.')[0] or 'NA' + distribution_guess['distribution_release'] = dist[2] or 'NA' + return distribution_guess + + def process_dist_files(self): + # Try to handle the exceptions now ... + # self.facts['distribution_debug'] = [] + dist_file_facts = {} + + dist_guess = self._guess_distribution() + dist_file_facts.update(dist_guess) + + for ddict in self.OSDIST_LIST: + name = ddict['name'] + path = ddict['path'] + allow_empty = ddict.get('allowempty', False) + + has_dist_file, dist_file_content = self._get_dist_file_content(path, allow_empty=allow_empty) + + if not has_dist_file: + # keep looking + continue + + # first valid os dist file we find we count + # FIXME: coreos and a few other bits expect this + # self.facts['distribution'] = name + dist_file_facts['distribution'] = name + dist_file_facts['distribution_file_variety'] = name + dist_file_facts['distribution_file_path'] = path + + parsed_dist_file, parsed_dist_file_facts = self._parse_dist_file(name, dist_file_content, path, dist_file_facts) + + dist_file_facts['distribution_file_parsed'] = parsed_dist_file + + # finally found the right os dist file and were able to parse it + if parsed_dist_file: + dist_file_facts.update(parsed_dist_file_facts) + break + + return dist_file_facts +# distribution_facts.update(dist_file_facts) +# return distribution_facts + + # TODO: FIXME: split distro file parsing into its own module or class + def parse_distribution_file_Slackware(self, name, data, path, collected_facts): + slackware_facts = {} + if 'Slackware' not in data: + return False, slackware_facts # TODO: remove + slackware_facts['distribution'] = name + version = re.findall('\w+[.]\w+', data) + if version: + slackware_facts['distribution_version'] = version[0] + return True, slackware_facts + + def parse_distribution_file_Amazon(self, name, data, path, collected_facts): + amazon_facts = {} + if 'Amazon' not in data: + # return False # TODO: remove # huh? + return False, amazon_facts # TODO: remove + amazon_facts['distribution'] = 'Amazon' + amazon_facts['distribution_version'] = data.split()[-1] + return True, amazon_facts + + def parse_distribution_file_OpenWrt(self, name, data, path, collected_facts): + openwrt_facts = {} + if 'OpenWrt' not in data: + return False, openwrt_facts # TODO: remove + openwrt_facts['distribution'] = name + version = re.search('DISTRIB_RELEASE="(.*)"', data) + if version: + openwrt_facts['distribution_version'] = version.groups()[0] + release = re.search('DISTRIB_CODENAME="(.*)"', data) + if release: + openwrt_facts['distribution_release'] = release.groups()[0] + return True, openwrt_facts + + def parse_distribution_file_Alpine(self, name, data, path, collected_facts): + alpine_facts = {} + alpine_facts['distribution'] = 'Alpine' + alpine_facts['distribution_version'] = data + return True, alpine_facts + + def parse_distribution_file_SuSE(self, name, data, path, collected_facts): + suse_facts = {} + if 'suse' not in data.lower(): + return False, suse_facts # TODO: remove if tested without this + if path == '/etc/os-release': + for line in data.splitlines(): + distribution = re.search("^NAME=(.*)", line) + if distribution: + suse_facts['distribution'] = distribution.group(1).strip('"') + # example pattern are 13.04 13.0 13 + distribution_version = re.search('^VERSION_ID="?([0-9]+\.?[0-9]*)"?', line) + if distribution_version: + suse_facts['distribution_version'] = distribution_version.group(1) + if 'open' in data.lower(): + release = re.search('^VERSION_ID="?[0-9]+\.?([0-9]*)"?', line) + if release: + suse_facts['distribution_release'] = release.groups()[0] + elif 'enterprise' in data.lower() and 'VERSION_ID' in line: + # SLES doesn't got funny release names + release = re.search('^VERSION_ID="?[0-9]+\.?([0-9]*)"?', line) + if release.group(1): + release = release.group(1) + else: + release = "0" # no minor number, so it is the first release + suse_facts['distribution_release'] = release + elif path == '/etc/SuSE-release': + if 'open' in data.lower(): + data = data.splitlines() + distdata = get_file_content(path).splitlines()[0] + suse_facts['distribution'] = distdata.split()[0] + for line in data: + release = re.search('CODENAME *= *([^\n]+)', line) + if release: + suse_facts['distribution_release'] = release.groups()[0].strip() + elif 'enterprise' in data.lower(): + lines = data.splitlines() + distribution = lines[0].split()[0] + if "Server" in data: + suse_facts['distribution'] = "SLES" + elif "Desktop" in data: + suse_facts['distribution'] = "SLED" + for line in lines: + release = re.search('PATCHLEVEL = ([0-9]+)', line) # SLES doesn't got funny release names + if release: + suse_facts['distribution_release'] = release.group(1) + suse_facts['distribution_version'] = collected_facts['distribution_version'] + '.' + release.group(1) + + return True, suse_facts + + def parse_distribution_file_Debian(self, name, data, path, collected_facts): + debian_facts = {} + if 'Debian' in data or 'Raspbian' in data: + debian_facts['distribution'] = 'Debian' + release = re.search("PRETTY_NAME=[^(]+ \(?([^)]+?)\)", data) + if release: + debian_facts['distribution_release'] = release.groups()[0] + + # Last resort: try to find release from tzdata as either lsb is missing or this is very old debian + if collected_facts['distribution_release'] == 'NA' and 'Debian' in data: + dpkg_cmd = self.module.get_bin_path('dpkg') + if dpkg_cmd: + cmd = "%s --status tzdata|grep Provides|cut -f2 -d'-'" % dpkg_cmd + rc, out, err = self.module.run_command(cmd) + if rc == 0: + debian_facts['distribution_release'] = out.strip() + elif 'Ubuntu' in data: + debian_facts['distribution'] = 'Ubuntu' + # nothing else to do, Ubuntu gets correct info from python functions + else: + return False, debian_facts + + return True, debian_facts + + def parse_distribution_file_Mandriva(self, name, data, path, collected_facts): + mandriva_facts = {} + if 'Mandriva' in data: + mandriva_facts['distribution'] = 'Mandriva' + version = re.search('DISTRIB_RELEASE="(.*)"', data) + if version: + mandriva_facts['distribution_version'] = version.groups()[0] + release = re.search('DISTRIB_CODENAME="(.*)"', data) + if release: + mandriva_facts['distribution_release'] = release.groups()[0] + mandriva_facts['distribution'] = name + else: + return False, mandriva_facts + + return True, mandriva_facts + + def parse_distribution_file_NA(self, name, data, path, collected_facts): + na_facts = {} + for line in data.splitlines(): + distribution = re.search("^NAME=(.*)", line) + if distribution and collected_facts['distribution'] == 'NA': + na_facts['distribution'] = distribution.group(1).strip('"') + version = re.search("^VERSION=(.*)", line) + if version and collected_facts['distribution_version'] == 'NA': + na_facts['distribution_version'] = version.group(1).strip('"') + return True, na_facts + + def parse_distribution_file_Coreos(self, name, data, path, collected_facts): + coreos_facts = {} + # FIXME: pass in ro copy of facts for this kind of thing + dist = platform.dist() + distro = dist[0] + + if distro.lower() == 'coreos': + if not data: + # include fix from #15230, #15228 + # TODO: verify this is ok for above bugs + return False, coreos_facts + release = re.search("^GROUP=(.*)", data) + if release: + coreos_facts['distribution_release'] = release.group(1).strip('"') + else: + return False, coreos_facts # TODO: remove if tested without this + + return True, coreos_facts + + +class Distribution(object): + """ + This subclass of Facts fills the distribution, distribution_version and distribution_release variables + + To do so it checks the existence and content of typical files in /etc containing distribution information + + This is unit tested. Please extend the tests to cover all distributions if you have them available. + """ + + # every distribution name mentioned here, must have one of + # - allowempty == True + # - be listed in SEARCH_STRING + # - have a function get_distribution_DISTNAME implemented + OSDIST_LIST = ( + {'path': '/etc/oracle-release', 'name': 'OracleLinux'}, + {'path': '/etc/slackware-version', 'name': 'Slackware'}, + {'path': '/etc/redhat-release', 'name': 'RedHat'}, + {'path': '/etc/vmware-release', 'name': 'VMwareESX', 'allowempty': True}, + {'path': '/etc/openwrt_release', 'name': 'OpenWrt'}, + {'path': '/etc/system-release', 'name': 'Amazon'}, + {'path': '/etc/alpine-release', 'name': 'Alpine'}, + {'path': '/etc/arch-release', 'name': 'Archlinux', 'allowempty': True}, + {'path': '/etc/os-release', 'name': 'SuSE'}, + {'path': '/etc/SuSE-release', 'name': 'SuSE'}, + {'path': '/etc/gentoo-release', 'name': 'Gentoo'}, + {'path': '/etc/os-release', 'name': 'Debian'}, + {'path': '/etc/lsb-release', 'name': 'Mandriva'}, + {'path': '/etc/altlinux-release', 'name': 'Altlinux'}, + {'path': '/etc/sourcemage-release', 'name': 'SMGL'}, + {'path': '/etc/os-release', 'name': 'NA'}, + {'path': '/etc/coreos/update.conf', 'name': 'Coreos'}, + {'path': '/usr/lib/os-release', 'name': 'ClearLinux'}, + ) + + SEARCH_STRING = { + 'OracleLinux': 'Oracle Linux', + 'RedHat': 'Red Hat', + 'Altlinux': 'ALT Linux', + 'ClearLinux': 'Clear Linux Software for Intel Architecture', + 'SMGL': 'Source Mage GNU/Linux', + } + + OS_FAMILY_MAP = {'RedHat': ['RedHat', 'Fedora', 'CentOS', 'Scientific', 'SLC', + 'Ascendos', 'CloudLinux', 'PSBM', 'OracleLinux', 'OVS', + 'OEL', 'Amazon', 'Virtuozzo', 'XenServer'], + 'Debian': ['Debian', 'Ubuntu', 'Raspbian', 'Neon', 'KDE neon'], + 'Suse': ['SuSE', 'SLES', 'SLED', 'openSUSE', 'openSUSE Tumbleweed', + 'SLES_SAP', 'SUSE_LINUX', 'openSUSE Leap'], + 'Archlinux': ['Archlinux', 'Manjaro'], + 'Mandrake': ['Mandrake', 'Mandriva'], + 'Solaris': ['Solaris', 'Nexenta', 'OmniOS', 'OpenIndiana', 'SmartOS'], + 'Slackware': ['Slackware'], + 'Altlinux': ['Altlinux'], + 'SGML': ['SGML'], + 'Gentoo': ['Gentoo', 'Funtoo'], + 'Alpine': ['Alpine'], + 'AIX': ['AIX'], + 'HP-UX': ['HPUX'], + 'Darwin': ['MacOSX'], + 'FreeBSD': ['FreeBSD']} + + OS_FAMILY = {} + for family, names in OS_FAMILY_MAP.items(): + for name in names: + OS_FAMILY[name] = family + + def __init__(self, module): + self.module = module + + def get_distribution_facts(self): + distribution_facts = {} + + # The platform module provides information about the running + # system/distribution. Use this as a baseline and fix buggy systems + # afterwards + system = platform.system() + distribution_facts['distribution'] = system + distribution_facts['distribution_release'] = platform.release() + distribution_facts['distribution_version'] = platform.version() + + systems_implemented = ('AIX', 'HP-UX', 'Darwin', 'FreeBSD', 'OpenBSD', 'SunOS', 'DragonFly', 'NetBSD') + + if system in systems_implemented: + cleanedname = system.replace('-', '') + distfunc = getattr(self, 'get_distribution_' + cleanedname) + dist_func_facts = distfunc() + distribution_facts.update(dist_func_facts) + elif system == 'Linux': + + distribution_files = DistributionFiles(module=self.module) + + # linux_distribution_facts = LinuxDistribution(module).get_distribution_facts() + dist_file_facts = distribution_files.process_dist_files() + + distribution_facts.update(dist_file_facts) + + distro = distribution_facts['distribution'] + + # look for a os family alias for the 'distribution', if there isnt one, use 'distribution' + distribution_facts['os_family'] = self.OS_FAMILY.get(distro, None) or distro + + return distribution_facts + + def get_distribution_AIX(self): + aix_facts = {} + rc, out, err = self.module.run_command("/usr/bin/oslevel") + data = out.split('.') + aix_facts['distribution_version'] = data[0] + aix_facts['distribution_release'] = data[1] + return aix_facts + + def get_distribution_HPUX(self): + hpux_facts = {} + rc, out, err = self.module.run_command("/usr/sbin/swlist |egrep 'HPUX.*OE.*[AB].[0-9]+\.[0-9]+'", use_unsafe_shell=True) + data = re.search('HPUX.*OE.*([AB].[0-9]+\.[0-9]+)\.([0-9]+).*', out) + if data: + hpux_facts['distribution_version'] = data.groups()[0] + hpux_facts['distribution_release'] = data.groups()[1] + return hpux_facts + + def get_distribution_Darwin(self): + darwin_facts = {} + darwin_facts['distribution'] = 'MacOSX' + rc, out, err = self.module.run_command("/usr/bin/sw_vers -productVersion") + data = out.split()[-1] + darwin_facts['distribution_version'] = data + return darwin_facts + + def get_distribution_FreeBSD(self): + freebsd_facts = {} + freebsd_facts['distribution_release'] = platform.release() + data = re.search('(\d+)\.(\d+)-RELEASE.*', freebsd_facts['distribution_release']) + if data: + freebsd_facts['distribution_major_version'] = data.group(1) + freebsd_facts['distribution_version'] = '%s.%s' % (data.group(1), data.group(2)) + return freebsd_facts + + def get_distribution_OpenBSD(self): + openbsd_facts = {} + openbsd_facts['distribution_version'] = platform.release() + rc, out, err = self.module.run_command("/sbin/sysctl -n kern.version") + match = re.match('OpenBSD\s[0-9]+.[0-9]+-(\S+)\s.*', out) + if match: + openbsd_facts['distribution_release'] = match.groups()[0] + else: + openbsd_facts['distribution_release'] = 'release' + return openbsd_facts + + def get_distribution_DragonFly(self): + return {} + + def get_distribution_NetBSD(self): + netbsd_facts = {} + # FIXME: poking at self.facts, should eventually make these each a collector + platform_release = platform.release() + netbsd_facts['distribution_major_version'] = platform_release.split('.')[0] + return netbsd_facts + + def get_distribution_SMGL(self): + smgl_facts = {} + smgl_facts['distribution'] = 'Source Mage GNU/Linux' + return smgl_facts + + def get_distribution_SunOS(self): + sunos_facts = {} + + # print('platform.release: %s' % distribution_release) + data = get_file_content('/etc/release').splitlines()[0] + + # print('get_file_content: data=%s' % data) + + if 'Solaris' in data: + ora_prefix = '' + if 'Oracle Solaris' in data: + data = data.replace('Oracle ', '') + ora_prefix = 'Oracle ' + sunos_facts['distribution'] = data.split()[0] + sunos_facts['distribution_version'] = data.split()[1] + sunos_facts['distribution_release'] = ora_prefix + data + return sunos_facts + + uname_v = get_uname_version(self.module) + distribution_version = None + + # print('uname_v: %s' % uname_v) + + if 'SmartOS' in data: + sunos_facts['distribution'] = 'SmartOS' + if _file_exists('/etc/product'): + product_data = dict([l.split(': ', 1) for l in get_file_content('/etc/product').splitlines() if ': ' in l]) + if 'Image' in product_data: + distribution_version = product_data.get('Image').split()[-1] + elif 'OpenIndiana' in data: + sunos_facts['distribution'] = 'OpenIndiana' + elif 'OmniOS' in data: + sunos_facts['distribution'] = 'OmniOS' + distribution_version = data.split()[-1] + elif uname_v is not None and 'NexentaOS_' in uname_v: + sunos_facts['distribution'] = 'Nexenta' + distribution_version = data.split()[-1].lstrip('v') + + # print('sunos_facts: %s' % sunos_facts) + if sunos_facts.get('distribution', '') in ('SmartOS', 'OpenIndiana', 'OmniOS', 'Nexenta'): + sunos_facts['distribution_release'] = data.strip() + if distribution_version is not None: + sunos_facts['distribution_version'] = distribution_version + elif uname_v is not None: + sunos_facts['distribution_version'] = uname_v.splitlines()[0].strip() + return sunos_facts + + return sunos_facts + + +class DistributionFactCollector(BaseFactCollector): + name = 'distribution' + _fact_ids = set(['distribution_version', + 'distribution_release', + 'distribution_major_version']) + + def collect(self, module=None, collected_facts=None): + collected_facts = collected_facts or {} + facts_dict = {} + if not module: + return facts_dict + + distribution = Distribution(module=module) + distro_facts = distribution.get_distribution_facts() + + return distro_facts diff --git a/lib/ansible/module_utils/facts/system/dns.py b/lib/ansible/module_utils/facts/system/dns.py new file mode 100644 index 00000000000..bd385e9de9d --- /dev/null +++ b/lib/ansible/module_utils/facts/system/dns.py @@ -0,0 +1,67 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +from ansible.module_utils.facts.utils import get_file_content + +from ansible.module_utils.facts.collector import BaseFactCollector + + +class DnsFactCollector(BaseFactCollector): + name = 'dns' + _fact_ids = set() + + def collect(self, module=None, collected_facts=None): + dns_facts = {} + + # TODO: flatten + dns_facts['dns'] = {} + + for line in get_file_content('/etc/resolv.conf', '').splitlines(): + if line.startswith('#') or line.startswith(';') or line.strip() == '': + continue + tokens = line.split() + if len(tokens) == 0: + continue + if tokens[0] == 'nameserver': + if 'nameservers' not in dns_facts['dns']: + dns_facts['dns']['nameservers'] = [] + for nameserver in tokens[1:]: + dns_facts['dns']['nameservers'].append(nameserver) + elif tokens[0] == 'domain': + if len(tokens) > 1: + dns_facts['dns']['domain'] = tokens[1] + elif tokens[0] == 'search': + dns_facts['dns']['search'] = [] + for suffix in tokens[1:]: + dns_facts['dns']['search'].append(suffix) + elif tokens[0] == 'sortlist': + dns_facts['dns']['sortlist'] = [] + for address in tokens[1:]: + dns_facts['dns']['sortlist'].append(address) + elif tokens[0] == 'options': + dns_facts['dns']['options'] = {} + if len(tokens) > 1: + for option in tokens[1:]: + option_tokens = option.split(':', 1) + if len(option_tokens) == 0: + continue + val = len(option_tokens) == 2 and option_tokens[1] or True + dns_facts['dns']['options'][option_tokens[0]] = val + + return dns_facts diff --git a/lib/ansible/module_utils/facts/system/env.py b/lib/ansible/module_utils/facts/system/env.py new file mode 100644 index 00000000000..279aad61535 --- /dev/null +++ b/lib/ansible/module_utils/facts/system/env.py @@ -0,0 +1,37 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os + +from ansible.module_utils.six import iteritems + +from ansible.module_utils.facts.collector import BaseFactCollector + + +class EnvFactCollector(BaseFactCollector): + name = 'env' + _fact_ids = set() + + def collect(self, module=None, collected_facts=None): + env_facts = {} + env_facts['env'] = {} + + for k, v in iteritems(os.environ): + env_facts['env'][k] = v + + return env_facts diff --git a/lib/ansible/module_utils/facts/system/fips.py b/lib/ansible/module_utils/facts/system/fips.py new file mode 100644 index 00000000000..20ada639f20 --- /dev/null +++ b/lib/ansible/module_utils/facts/system/fips.py @@ -0,0 +1,37 @@ +# Determine if a system is in 'fips' mode +# +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils.facts.utils import get_file_content + +from ansible.module_utils.facts.collector import BaseFactCollector + + +class FipsFactCollector(BaseFactCollector): + name = 'fips' + _fact_ids = set() + + def collect(self, module=None, collected_facts=None): + # NOTE: this is populated even if it is not set + fips_facts = {} + fips_facts['fips'] = False + data = get_file_content('/proc/sys/crypto/fips_enabled') + if data and data == '1': + fips_facts['fips'] = True + return fips_facts diff --git a/lib/ansible/module_utils/facts/system/local.py b/lib/ansible/module_utils/facts/system/local.py new file mode 100644 index 00000000000..20ba901cdbb --- /dev/null +++ b/lib/ansible/module_utils/facts/system/local.py @@ -0,0 +1,90 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import glob +import json +import os +import stat + +from ansible.module_utils.six.moves import configparser +from ansible.module_utils.six.moves import StringIO + +from ansible.module_utils.facts.utils import get_file_content + +from ansible.module_utils.facts.collector import BaseFactCollector + + +class LocalFactCollector(BaseFactCollector): + name = 'local' + _fact_ids = set() + + def collect(self, module=None, collected_facts=None): + local_facts = {} + local_facts['local'] = {} + + if not module: + return local_facts + + fact_path = module.params.get('fact_path', None) + + if not fact_path or not os.path.exists(fact_path): + return local_facts + + local = {} + for fn in sorted(glob.glob(fact_path + '/*.fact')): + # where it will sit under local facts + fact_base = os.path.basename(fn).replace('.fact', '') + if stat.S_IXUSR & os.stat(fn)[stat.ST_MODE]: + # run it + # try to read it as json first + # if that fails read it with ConfigParser + # if that fails, skip it + try: + rc, out, err = module.run_command(fn) + except UnicodeError: + fact = 'error loading fact - output of running %s was not utf-8' % fn + local[fact_base] = fact + local_facts['local'] = local + return local_facts + else: + out = get_file_content(fn, default='') + + # load raw json + fact = 'loading %s' % fact_base + try: + fact = json.loads(out) + except ValueError: + # load raw ini + cp = configparser.ConfigParser() + try: + cp.readfp(StringIO(out)) + except configparser.Error: + fact = "error loading fact - please check content" + else: + fact = {} + for sect in cp.sections(): + if sect not in fact: + fact[sect] = {} + for opt in cp.options(sect): + val = cp.get(sect, opt) + fact[sect][opt] = val + + local[fact_base] = fact + + local_facts['local'] = local + return local_facts diff --git a/lib/ansible/module_utils/facts/system/lsb.py b/lib/ansible/module_utils/facts/system/lsb.py new file mode 100644 index 00000000000..c6ee553a80d --- /dev/null +++ b/lib/ansible/module_utils/facts/system/lsb.py @@ -0,0 +1,101 @@ +# Collect facts related to LSB (Linux Standard Base) +# +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os + +from ansible.module_utils.facts.utils import get_file_lines +from ansible.module_utils.facts.collector import BaseFactCollector + + +class LSBFactCollector(BaseFactCollector): + name = 'lsb' + _fact_ids = set() + + def _lsb_release_bin(self, lsb_path, module): + lsb_facts = {} + + if not lsb_path: + return lsb_facts + + rc, out, err = module.run_command([lsb_path, "-a"], errors='surrogate_then_replace') + if rc != 0: + return lsb_facts + + for line in out.splitlines(): + if len(line) < 1 or ':' not in line: + continue + value = line.split(':', 1)[1].strip() + + if 'LSB Version:' in line: + lsb_facts['release'] = value + elif 'Distributor ID:' in line: + lsb_facts['id'] = value + elif 'Description:' in line: + lsb_facts['description'] = value + elif 'Release:' in line: + lsb_facts['release'] = value + elif 'Codename:' in line: + lsb_facts['codename'] = value + + return lsb_facts + + def _lsb_release_file(self, etc_lsb_release_location): + lsb_facts = {} + + if not os.path.exists(etc_lsb_release_location): + return lsb_facts + + for line in get_file_lines(etc_lsb_release_location): + value = line.split('=', 1)[1].strip() + + if 'DISTRIB_ID' in line: + lsb_facts['id'] = value + elif 'DISTRIB_RELEASE' in line: + lsb_facts['release'] = value + elif 'DISTRIB_DESCRIPTION' in line: + lsb_facts['description'] = value + elif 'DISTRIB_CODENAME' in line: + lsb_facts['codename'] = value + + return lsb_facts + + def collect(self, module=None, collected_facts=None): + facts_dict = {} + lsb_facts = {} + + if not module: + return facts_dict + + lsb_path = module.get_bin_path('lsb_release') + + # try the 'lsb_release' script first + if lsb_path: + lsb_facts = self._lsb_release_bin(lsb_path, + module=module) + + # no lsb_release, try looking in /etc/lsb-release + if not lsb_facts: + lsb_facts = self._lsb_release_file('/etc/lsb-release') + + if lsb_facts and 'release' in lsb_facts: + lsb_facts['major_release'] = lsb_facts['release'].split('.')[0] + + facts_dict['lsb'] = lsb_facts + return facts_dict diff --git a/lib/ansible/module_utils/facts/system/pkg_mgr.py b/lib/ansible/module_utils/facts/system/pkg_mgr.py new file mode 100644 index 00000000000..578a78deccf --- /dev/null +++ b/lib/ansible/module_utils/facts/system/pkg_mgr.py @@ -0,0 +1,73 @@ +# Collect facts related to the system package manager +# +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os + +from ansible.module_utils.facts.collector import BaseFactCollector + +# A list of dicts. If there is a platform with more than one +# package manager, put the preferred one last. If there is an +# ansible module, use that as the value for the 'name' key. +PKG_MGRS = [{'path': '/usr/bin/yum', 'name': 'yum'}, + {'path': '/usr/bin/dnf', 'name': 'dnf'}, + {'path': '/usr/bin/apt-get', 'name': 'apt'}, + {'path': '/usr/bin/zypper', 'name': 'zypper'}, + {'path': '/usr/sbin/urpmi', 'name': 'urpmi'}, + {'path': '/usr/bin/pacman', 'name': 'pacman'}, + {'path': '/bin/opkg', 'name': 'opkg'}, + {'path': '/usr/pkg/bin/pkgin', 'name': 'pkgin'}, + {'path': '/opt/local/bin/pkgin', 'name': 'pkgin'}, + {'path': '/opt/tools/bin/pkgin', 'name': 'pkgin'}, + {'path': '/opt/local/bin/port', 'name': 'macports'}, + {'path': '/usr/local/bin/brew', 'name': 'homebrew'}, + {'path': '/sbin/apk', 'name': 'apk'}, + {'path': '/usr/sbin/pkg', 'name': 'pkgng'}, + {'path': '/usr/sbin/swlist', 'name': 'HP-UX'}, + {'path': '/usr/bin/emerge', 'name': 'portage'}, + {'path': '/usr/sbin/pkgadd', 'name': 'svr4pkg'}, + {'path': '/usr/bin/pkg', 'name': 'pkg5'}, + {'path': '/usr/bin/xbps-install', 'name': 'xbps'}, + {'path': '/usr/local/sbin/pkg', 'name': 'pkgng'}, + {'path': '/usr/bin/swupd', 'name': 'swupd'}, + {'path': '/usr/sbin/sorcery', 'name': 'sorcery'}, + ] + + +# the fact ends up being 'pkg_mgr' so stick with that naming/spelling +class PkgMgrFactCollector(BaseFactCollector): + name = 'pkg_mgr' + _fact_ids = set() + + def collect(self, module=None, collected_facts=None): + facts_dict = {} + collected_facts = collected_facts or {} + + pkg_mgr_name = None + if collected_facts.get('system') == 'OpenBSD': + facts_dict['pkg_mgr'] = 'openbsd_pkg' + return facts_dict + + pkg_mgr_name = 'unknown' + for pkg in PKG_MGRS: + if os.path.exists(pkg['path']): + pkg_mgr_name = pkg['name'] + + facts_dict['pkg_mgr'] = pkg_mgr_name + return facts_dict diff --git a/lib/ansible/module_utils/facts/system/platform.py b/lib/ansible/module_utils/facts/system/platform.py new file mode 100644 index 00000000000..74e8e67c71d --- /dev/null +++ b/lib/ansible/module_utils/facts/system/platform.py @@ -0,0 +1,94 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re +import socket +import platform + +from ansible.module_utils.facts.utils import get_file_content + +from ansible.module_utils.facts.collector import BaseFactCollector + +# i86pc is a Solaris and derivatives-ism +SOLARIS_I86_RE_PATTERN = r'i([3456]86|86pc)' +solaris_i86_re = re.compile(SOLARIS_I86_RE_PATTERN) + + +class PlatformFactCollector(BaseFactCollector): + name = 'platform' + _fact_ids = set(['system', + 'kernel', + 'machine', + 'python_version', + 'machine_id']) + + def collect(self, module=None, collected_facts=None): + platform_facts = {} + # platform.system() can be Linux, Darwin, Java, or Windows + platform_facts['system'] = platform.system() + platform_facts['kernel'] = platform.release() + platform_facts['machine'] = platform.machine() + + platform_facts['python_version'] = platform.python_version() + + platform_facts['fqdn'] = socket.getfqdn() + platform_facts['hostname'] = platform.node().split('.')[0] + platform_facts['nodename'] = platform.node() + + platform_facts['domain'] = '.'.join(platform_facts['fqdn'].split('.')[1:]) + + arch_bits = platform.architecture()[0] + + platform_facts['userspace_bits'] = arch_bits.replace('bit', '') + if platform_facts['machine'] == 'x86_64': + platform_facts['architecture'] = platform_facts['machine'] + if platform_facts['userspace_bits'] == '64': + platform_facts['userspace_architecture'] = 'x86_64' + elif platform_facts['userspace_bits'] == '32': + platform_facts['userspace_architecture'] = 'i386' + elif solaris_i86_re.search(platform_facts['machine']): + platform_facts['architecture'] = 'i386' + if platform_facts['userspace_bits'] == '64': + platform_facts['userspace_architecture'] = 'x86_64' + elif platform_facts['userspace_bits'] == '32': + platform_facts['userspace_architecture'] = 'i386' + else: + platform_facts['architecture'] = platform_facts['machine'] + + if platform_facts['system'] == 'AIX': + # Attempt to use getconf to figure out architecture + # fall back to bootinfo if needed + getconf_bin = module.get_bin_path('getconf') + if getconf_bin: + rc, out, err = module.run_command([getconf_bin, 'MACHINE_ARCHITECTURE']) + data = out.splitlines() + platform_facts['architecture'] = data[0] + else: + bootinfo_bin = module.get_bin_path('bootinfo') + rc, out, err = module.run_command([bootinfo_bin, '-p']) + data = out.splitlines() + platform_facts['architecture'] = data[0] + elif platform_facts['system'] == 'OpenBSD': + platform_facts['architecture'] = platform.uname()[5] + + machine_id = get_file_content("/var/lib/dbus/machine-id") or get_file_content("/etc/machine-id") + if machine_id: + machine_id = machine_id.splitlines()[0] + platform_facts["machine_id"] = machine_id + + return platform_facts diff --git a/lib/ansible/module_utils/facts/system/python.py b/lib/ansible/module_utils/facts/system/python.py new file mode 100644 index 00000000000..172a0913ae2 --- /dev/null +++ b/lib/ansible/module_utils/facts/system/python.py @@ -0,0 +1,60 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys + +from ansible.module_utils.facts.collector import BaseFactCollector + +try: + # Check if we have SSLContext support + from ssl import create_default_context, SSLContext + del create_default_context + del SSLContext + HAS_SSLCONTEXT = True +except ImportError: + HAS_SSLCONTEXT = False + + +class PythonFactCollector(BaseFactCollector): + name = 'python' + _fact_ids = set() + + def collect(self, module=None, collected_facts=None): + python_facts = {} + python_facts['python'] = { + 'version': { + 'major': sys.version_info[0], + 'minor': sys.version_info[1], + 'micro': sys.version_info[2], + 'releaselevel': sys.version_info[3], + 'serial': sys.version_info[4] + }, + 'version_info': list(sys.version_info), + 'executable': sys.executable, + 'has_sslcontext': HAS_SSLCONTEXT + } + + try: + python_facts['python']['type'] = sys.subversion[0] + except AttributeError: + try: + python_facts['python']['type'] = sys.implementation.name + except AttributeError: + python_facts['python']['type'] = None + + return python_facts diff --git a/lib/ansible/module_utils/facts/system/selinux.py b/lib/ansible/module_utils/facts/system/selinux.py new file mode 100644 index 00000000000..e9b166f1ce8 --- /dev/null +++ b/lib/ansible/module_utils/facts/system/selinux.py @@ -0,0 +1,86 @@ +# Collect facts related to selinux +# +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils.facts.collector import BaseFactCollector + +try: + import selinux + HAVE_SELINUX = True +except ImportError: + HAVE_SELINUX = False + +SELINUX_MODE_DICT = {1: 'enforcing', + 0: 'permissive', + -1: 'disabled'} + + +class SelinuxFactCollector(BaseFactCollector): + name = 'selinux' + _fact_ids = set() + + def collect(self, module=None, collected_facts=None): + facts_dict = {} + selinux_facts = {} + + # This is weird. The value of the facts 'selinux' key can be False or a dict + if not HAVE_SELINUX: + facts_dict['selinux'] = False + facts_dict['selinux_python_present'] = False + return facts_dict + + facts_dict['selinux_python_present'] = True + + if not selinux.is_selinux_enabled(): + selinux_facts['status'] = 'disabled' + # NOTE: this could just return in the above clause and the rest of this is up an indent -akl + else: + selinux_facts['status'] = 'enabled' + + try: + selinux_facts['policyvers'] = selinux.security_policyvers() + except (AttributeError, OSError): + selinux_facts['policyvers'] = 'unknown' + + try: + (rc, configmode) = selinux.selinux_getenforcemode() + if rc == 0: + selinux_facts['config_mode'] = SELINUX_MODE_DICT.get(configmode, 'unknown') + else: + selinux_facts['config_mode'] = 'unknown' + except (AttributeError, OSError): + selinux_facts['config_mode'] = 'unknown' + + try: + mode = selinux.security_getenforce() + selinux_facts['mode'] = SELINUX_MODE_DICT.get(mode, 'unknown') + except (AttributeError, OSError): + selinux_facts['mode'] = 'unknown' + + try: + (rc, policytype) = selinux.selinux_getpolicytype() + if rc == 0: + selinux_facts['type'] = policytype + else: + selinux_facts['type'] = 'unknown' + except (AttributeError, OSError): + selinux_facts['type'] = 'unknown' + + facts_dict['selinux'] = selinux_facts + return facts_dict diff --git a/lib/ansible/module_utils/facts/system/service_mgr.py b/lib/ansible/module_utils/facts/system/service_mgr.py new file mode 100644 index 00000000000..07a6bc89897 --- /dev/null +++ b/lib/ansible/module_utils/facts/system/service_mgr.py @@ -0,0 +1,138 @@ +# Collect facts related to system service manager and init. +# +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import platform +import re + +from ansible.module_utils._text import to_native + +from ansible.module_utils.facts.utils import get_file_content +from ansible.module_utils.facts.collector import BaseFactCollector + +# The distutils module is not shipped with SUNWPython on Solaris. +# It's in the SUNWPython-devel package which also contains development files +# that don't belong on production boxes. Since our Solaris code doesn't +# depend on LooseVersion, do not import it on Solaris. +if platform.system() != 'SunOS': + from distutils.version import LooseVersion + + +class ServiceMgrFactCollector(BaseFactCollector): + name = 'service_mgr' + _fact_ids = set() + + def is_systemd_managed(self, module): + # tools must be installed + if module.get_bin_path('systemctl'): + + # this should show if systemd is the boot init system, if checking init faild to mark as systemd + # these mirror systemd's own sd_boot test http://www.freedesktop.org/software/systemd/man/sd_booted.html + for canary in ["/run/systemd/system/", "/dev/.run/systemd/", "/dev/.systemd/"]: + if os.path.exists(canary): + return True + return False + + def collect(self, module=None, collected_facts=None): + facts_dict = {} + + if not module: + return facts_dict + + collected_facts = collected_facts or {} + service_mgr_name = None + + # TODO: detect more custom init setups like bootscripts, dmd, s6, Epoch, etc + # also other OSs other than linux might need to check across several possible candidates + + # Mapping of proc_1 values to more useful names + proc_1_map = { + 'procd': 'openwrt_init', + 'runit-init': 'runit', + 'svscan': 'svc', + 'openrc-init': 'openrc', + } + + # try various forms of querying pid 1 + proc_1 = get_file_content('/proc/1/comm') + if proc_1 is None: + # FIXME: return code isnt checked + # FIXME: if stdout is empty string, odd things + # FIXME: other code seems to think we could get proc_1 == None past this point + rc, proc_1, err = module.run_command("ps -p 1 -o comm|tail -n 1", use_unsafe_shell=True) + # If the output of the command starts with what looks like a PID, then the 'ps' command + # probably didn't work the way we wanted, probably because it's busybox + if re.match(r' *[0-9]+ ', proc_1): + proc_1 = None + + # The ps command above may return "COMMAND" if the user cannot read /proc, e.g. with grsecurity + if proc_1 == "COMMAND\n": + proc_1 = None + + # FIXME: empty string proc_1 staus empty string + if proc_1 is not None: + proc_1 = os.path.basename(proc_1) + proc_1 = to_native(proc_1) + proc_1 = proc_1.strip() + + if proc_1 is not None and (proc_1 == 'init' or proc_1.endswith('sh')): + # many systems return init, so this cannot be trusted, if it ends in 'sh' it probalby is a shell in a container + proc_1 = None + + # if not init/None it should be an identifiable or custom init, so we are done! + if proc_1 is not None: + # Lookup proc_1 value in map and use proc_1 value itself if no match + # FIXME: empty string still falls through + service_mgr_name = proc_1_map.get(proc_1, proc_1) + + # FIXME: replace with a system->service_mgr_name map? + # start with the easy ones + elif collected_facts.get('distribution', None) == 'MacOSX': + # FIXME: find way to query executable, version matching is not ideal + if LooseVersion(platform.mac_ver()[0]) >= LooseVersion('10.4'): + service_mgr_name = 'launchd' + else: + service_mgr_name = 'systemstarter' + elif 'BSD' in collected_facts.get('system', '') or collected_facts.get('system') in ['Bitrig', 'DragonFly']: + # FIXME: we might want to break out to individual BSDs or 'rc' + service_mgr_name = 'bsdinit' + elif collected_facts.get('system') == 'AIX': + service_mgr_name = 'src' + elif collected_facts.get('system') == 'SunOS': + service_mgr_name = 'smf' + elif collected_facts.get('distribution') == 'OpenWrt': + service_mgr_name = 'openwrt_init' + elif collected_facts.get('system') == 'Linux': + # FIXME: mv is_systemd_managed + if self.is_systemd_managed(module=module): + service_mgr_name = 'systemd' + elif module.get_bin_path('initctl') and os.path.exists("/etc/init/"): + service_mgr_name = 'upstart' + elif os.path.exists('/sbin/openrc'): + service_mgr_name = 'openrc' + elif os.path.exists('/etc/init.d/'): + service_mgr_name = 'sysvinit' + + if not service_mgr_name: + # if we cannot detect, fallback to generic 'service' + service_mgr_name = 'service' + + facts_dict['service_mgr'] = service_mgr_name + return facts_dict diff --git a/lib/ansible/module_utils/facts/system/ssh_pub_keys.py b/lib/ansible/module_utils/facts/system/ssh_pub_keys.py new file mode 100644 index 00000000000..79d0dcad5eb --- /dev/null +++ b/lib/ansible/module_utils/facts/system/ssh_pub_keys.py @@ -0,0 +1,52 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils.facts.utils import get_file_content + +from ansible.module_utils.facts.collector import BaseFactCollector + + +class SshPubKeyFactCollector(BaseFactCollector): + name = 'ssh_pub_keys' + _fact_ids = set(['ssh_host_pub_keys', + 'ssh_host_key_dsa_public', + 'ssh_host_key_rsa_public', + 'ssh_host_key_ecdsa_public', + 'ssh_host_key_ed25519_public']) + + def collect(self, module=None, collected_facts=None): + ssh_pub_key_facts = {} + keytypes = ('dsa', 'rsa', 'ecdsa', 'ed25519') + + # list of directories to check for ssh keys + # used in the order listed here, the first one with keys is used + keydirs = ['/etc/ssh', '/etc/openssh', '/etc'] + + for keydir in keydirs: + for type_ in keytypes: + factname = 'ssh_host_key_%s_public' % type_ + if factname in ssh_pub_key_facts: + # a previous keydir was already successful, stop looking + # for keys + return ssh_pub_key_facts + key_filename = '%s/ssh_host_%s_key.pub' % (keydir, type_) + keydata = get_file_content(key_filename) + if keydata is not None: + ssh_pub_key_facts[factname] = keydata.split()[1] + + return ssh_pub_key_facts diff --git a/lib/ansible/module_utils/facts/system/user.py b/lib/ansible/module_utils/facts/system/user.py new file mode 100644 index 00000000000..745b5db3f6f --- /dev/null +++ b/lib/ansible/module_utils/facts/system/user.py @@ -0,0 +1,50 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import getpass +import os +import pwd + +from ansible.module_utils.facts.collector import BaseFactCollector + + +class UserFactCollector(BaseFactCollector): + name = 'user' + _fact_ids = set(['user_id', 'user_uid', 'user_gid', + 'user_gecos', 'user_dir', 'user_shell', + 'real_user_id', 'effective_user_id', + 'effective_group_ids']) + + def collect(self, module=None, collected_facts=None): + user_facts = {} + + user_facts['user_id'] = getpass.getuser() + + pwent = pwd.getpwnam(getpass.getuser()) + + user_facts['user_uid'] = pwent.pw_uid + user_facts['user_gid'] = pwent.pw_gid + user_facts['user_gecos'] = pwent.pw_gecos + user_facts['user_dir'] = pwent.pw_dir + user_facts['user_shell'] = pwent.pw_shell + user_facts['real_user_id'] = os.getuid() + user_facts['effective_user_id'] = os.geteuid() + user_facts['real_group_id'] = os.getgid() + user_facts['effective_group_id'] = os.getgid() + + return user_facts diff --git a/lib/ansible/module_utils/facts/timeout.py b/lib/ansible/module_utils/facts/timeout.py new file mode 100644 index 00000000000..2927b31c822 --- /dev/null +++ b/lib/ansible/module_utils/facts/timeout.py @@ -0,0 +1,66 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import signal + +# timeout function to make sure some fact gathering +# steps do not exceed a time limit + +GATHER_TIMEOUT = None +DEFAULT_GATHER_TIMEOUT = 10 + + +class TimeoutError(Exception): + pass + + +def timeout(seconds=None, error_message="Timer expired"): + + def decorator(func): + def _handle_timeout(signum, frame): + msg = 'Timer expired after %s seconds' % globals().get('GATHER_TIMEOUT') + raise TimeoutError(msg) + + def wrapper(*args, **kwargs): + local_seconds = seconds + if local_seconds is None: + local_seconds = globals().get('GATHER_TIMEOUT') or DEFAULT_GATHER_TIMEOUT + signal.signal(signal.SIGALRM, _handle_timeout) + signal.alarm(local_seconds) + + try: + result = func(*args, **kwargs) + finally: + signal.alarm(0) + return result + + return wrapper + + # If we were called as @timeout, then the first parameter will be the + # function we are to wrap instead of the number of seconds. Detect this + # and correct it by setting seconds to our default value and return the + # inner decorator function manually wrapped around the function + if callable(seconds): + func = seconds + seconds = None + return decorator(func) + + # If we were called as @timeout([...]) then python itself will take + # care of wrapping the inner decorator around the function + + return decorator diff --git a/lib/ansible/module_utils/facts/utils.py b/lib/ansible/module_utils/facts/utils.py new file mode 100644 index 00000000000..7b2f6157d03 --- /dev/null +++ b/lib/ansible/module_utils/facts/utils.py @@ -0,0 +1,59 @@ +# This file is part of Ansible +# +# 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 . + +import os + + +def get_file_content(path, default=None, strip=True): + data = default + if os.path.exists(path) and os.access(path, os.R_OK): + try: + try: + datafile = open(path) + data = datafile.read() + if strip: + data = data.strip() + if len(data) == 0: + data = default + finally: + datafile.close() + except: + # ignore errors as some jails/containers might have readable permissions but not allow reads to proc + # done in 2 blocks for 2.4 compat + pass + return data + + +def get_file_lines(path): + '''get list of lines from file''' + data = get_file_content(path) + if data: + ret = data.splitlines() + else: + ret = [] + return ret + + +def get_mount_size(mountpoint): + size_total = None + size_available = None + try: + statvfs_result = os.statvfs(mountpoint) + size_total = statvfs_result.f_frsize * statvfs_result.f_blocks + size_available = statvfs_result.f_frsize * (statvfs_result.f_bavail) + except OSError: + pass + + return size_total, size_available diff --git a/lib/ansible/module_utils/facts/virtual/__init__.py b/lib/ansible/module_utils/facts/virtual/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/ansible/module_utils/facts/virtual/base.py b/lib/ansible/module_utils/facts/virtual/base.py new file mode 100644 index 00000000000..02da049e34c --- /dev/null +++ b/lib/ansible/module_utils/facts/virtual/base.py @@ -0,0 +1,70 @@ +# base classes for virtualization facts +# -*- coding: utf-8 -*- +# +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils.facts.collector import BaseFactCollector + + +class Virtual: + """ + This is a generic Virtual subclass of Facts. This should be further + subclassed to implement per platform. If you subclass this, + you should define: + - virtualization_type + - virtualization_role + - container (e.g. solaris zones, freebsd jails, linux containers) + + All subclasses MUST define platform. + """ + platform = 'Generic' + + # FIXME: remove load_on_init if we can + def __init__(self, module, load_on_init=False): + self.module = module + + # FIXME: just here for existing tests cases till they are updated + def populate(self, collected_facts=None): + virtual_facts = self.get_virtual_facts() + + return virtual_facts + + def get_virtual_facts(self): + virtual_facts = {'virtualization_type': '', + 'virtualization_role': ''} + return virtual_facts + + +class VirtualCollector(BaseFactCollector): + name = 'virtual' + _fact_class = Virtual + _fact_ids = set(['virtualization_type', + 'virtualization_role']) + + def collect(self, module=None, collected_facts=None): + collected_facts = collected_facts or {} + if not module: + return {} + + # Network munges cached_facts by side effect, so give it a copy + facts_obj = self._fact_class(module) + + facts_dict = facts_obj.populate(collected_facts=collected_facts) + + return facts_dict diff --git a/lib/ansible/module_utils/facts/virtual/dragonfly.py b/lib/ansible/module_utils/facts/virtual/dragonfly.py new file mode 100644 index 00000000000..b176f8bf53c --- /dev/null +++ b/lib/ansible/module_utils/facts/virtual/dragonfly.py @@ -0,0 +1,25 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils.facts.virtual.freebsd import FreeBSDVirtual, VirtualCollector + + +class DragonFlyVirtualCollector(VirtualCollector): + # Note the _fact_class impl is actually the FreeBSDVirtual impl + _fact_class = FreeBSDVirtual + _platform = 'DragonFly' diff --git a/lib/ansible/module_utils/facts/virtual/freebsd.py b/lib/ansible/module_utils/facts/virtual/freebsd.py new file mode 100644 index 00000000000..132fe8d93d8 --- /dev/null +++ b/lib/ansible/module_utils/facts/virtual/freebsd.py @@ -0,0 +1,47 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os + +from ansible.module_utils.facts.virtual.base import Virtual, VirtualCollector + + +class FreeBSDVirtual(Virtual): + """ + This is a FreeBSD-specific subclass of Virtual. It defines + - virtualization_type + - virtualization_role + """ + platform = 'FreeBSD' + + def get_virtual_facts(self): + virtual_facts = {} + # Set empty values as default + virtual_facts['virtualization_type'] = '' + virtual_facts['virtualization_role'] = '' + + if os.path.exists('/dev/xen/xenstore'): + virtual_facts['virtualization_type'] = 'xen' + virtual_facts['virtualization_role'] = 'guest' + + return virtual_facts + + +class FreeBSDVirtualCollector(VirtualCollector): + _fact_class = FreeBSDVirtual + _platform = 'FreeBSD' diff --git a/lib/ansible/module_utils/facts/virtual/hpux.py b/lib/ansible/module_utils/facts/virtual/hpux.py new file mode 100644 index 00000000000..94ea6a1a4f2 --- /dev/null +++ b/lib/ansible/module_utils/facts/virtual/hpux.py @@ -0,0 +1,62 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import re + +from ansible.module_utils.facts.virtual.base import Virtual, VirtualCollector + + +class HPUXVirtual(Virtual): + """ + This is a HP-UX specific subclass of Virtual. It defines + - virtualization_type + - virtualization_role + """ + platform = 'HP-UX' + + def get_virtual_facts(self): + virtual_facts = {} + if os.path.exists('/usr/sbin/vecheck'): + rc, out, err = self.module.run_command("/usr/sbin/vecheck") + if rc == 0: + virtual_facts['virtualization_type'] = 'guest' + virtual_facts['virtualization_role'] = 'HP vPar' + if os.path.exists('/opt/hpvm/bin/hpvminfo'): + rc, out, err = self.module.run_command("/opt/hpvm/bin/hpvminfo") + if rc == 0 and re.match('.*Running.*HPVM vPar.*', out): + virtual_facts['virtualization_type'] = 'guest' + virtual_facts['virtualization_role'] = 'HPVM vPar' + elif rc == 0 and re.match('.*Running.*HPVM guest.*', out): + virtual_facts['virtualization_type'] = 'guest' + virtual_facts['virtualization_role'] = 'HPVM IVM' + elif rc == 0 and re.match('.*Running.*HPVM host.*', out): + virtual_facts['virtualization_type'] = 'host' + virtual_facts['virtualization_role'] = 'HPVM' + if os.path.exists('/usr/sbin/parstatus'): + rc, out, err = self.module.run_command("/usr/sbin/parstatus") + if rc == 0: + virtual_facts['virtualization_type'] = 'guest' + virtual_facts['virtualization_role'] = 'HP nPar' + + return virtual_facts + + +class HPUXVirtualCollector(VirtualCollector): + _fact_class = HPUXVirtual + _platform = 'HP-UX' diff --git a/lib/ansible/module_utils/facts/virtual/linux.py b/lib/ansible/module_utils/facts/virtual/linux.py new file mode 100644 index 00000000000..adb8ae4de23 --- /dev/null +++ b/lib/ansible/module_utils/facts/virtual/linux.py @@ -0,0 +1,228 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import glob +import os +import re + +from ansible.module_utils.facts.virtual.base import Virtual, VirtualCollector +from ansible.module_utils.facts.utils import get_file_content, get_file_lines + + +class LinuxVirtual(Virtual): + """ + This is a Linux-specific subclass of Virtual. It defines + - virtualization_type + - virtualization_role + """ + platform = 'Linux' + + # For more information, check: http://people.redhat.com/~rjones/virt-what/ + def get_virtual_facts(self): + virtual_facts = {} + # lxc/docker + if os.path.exists('/proc/1/cgroup'): + for line in get_file_lines('/proc/1/cgroup'): + if re.search(r'/docker(/|-[0-9a-f]+\.scope)', line): + virtual_facts['virtualization_type'] = 'docker' + virtual_facts['virtualization_role'] = 'guest' + return virtual_facts + if re.search('/lxc/', line) or re.search('/machine.slice/machine-lxc', line): + virtual_facts['virtualization_type'] = 'lxc' + virtual_facts['virtualization_role'] = 'guest' + return virtual_facts + + # lxc does not always appear in cgroups anymore but sets 'container=lxc' environment var, requires root privs + if os.path.exists('/proc/1/environ'): + for line in get_file_lines('/proc/1/environ'): + if re.search('container=lxc', line): + virtual_facts['virtualization_type'] = 'lxc' + virtual_facts['virtualization_role'] = 'guest' + return virtual_facts + + if os.path.exists('/proc/vz'): + virtual_facts['virtualization_type'] = 'openvz' + if os.path.exists('/proc/bc'): + virtual_facts['virtualization_role'] = 'host' + else: + virtual_facts['virtualization_role'] = 'guest' + return virtual_facts + + systemd_container = get_file_content('/run/systemd/container') + if systemd_container: + virtual_facts['virtualization_type'] = systemd_container + virtual_facts['virtualization_role'] = 'guest' + return virtual_facts + + if os.path.exists("/proc/xen"): + virtual_facts['virtualization_type'] = 'xen' + virtual_facts['virtualization_role'] = 'guest' + try: + for line in get_file_lines('/proc/xen/capabilities'): + if "control_d" in line: + virtual_facts['virtualization_role'] = 'host' + except IOError: + pass + return virtual_facts + + product_name = get_file_content('/sys/devices/virtual/dmi/id/product_name') + + if product_name in ['KVM', 'Bochs']: + virtual_facts['virtualization_type'] = 'kvm' + virtual_facts['virtualization_role'] = 'guest' + return virtual_facts + + if product_name == 'RHEV Hypervisor': + virtual_facts['virtualization_type'] = 'RHEV' + virtual_facts['virtualization_role'] = 'guest' + return virtual_facts + + if product_name == 'VMware Virtual Platform': + virtual_facts['virtualization_type'] = 'VMware' + virtual_facts['virtualization_role'] = 'guest' + return virtual_facts + + if product_name == 'OpenStack Nova': + virtual_facts['virtualization_type'] = 'openstack' + virtual_facts['virtualization_role'] = 'guest' + return virtual_facts + + bios_vendor = get_file_content('/sys/devices/virtual/dmi/id/bios_vendor') + + if bios_vendor == 'Xen': + virtual_facts['virtualization_type'] = 'xen' + virtual_facts['virtualization_role'] = 'guest' + return virtual_facts + + if bios_vendor == 'innotek GmbH': + virtual_facts['virtualization_type'] = 'virtualbox' + virtual_facts['virtualization_role'] = 'guest' + return virtual_facts + + sys_vendor = get_file_content('/sys/devices/virtual/dmi/id/sys_vendor') + + # FIXME: This does also match hyperv + if sys_vendor == 'Microsoft Corporation': + virtual_facts['virtualization_type'] = 'VirtualPC' + virtual_facts['virtualization_role'] = 'guest' + return virtual_facts + + if sys_vendor == 'Parallels Software International Inc.': + virtual_facts['virtualization_type'] = 'parallels' + virtual_facts['virtualization_role'] = 'guest' + return virtual_facts + + if sys_vendor == 'QEMU': + virtual_facts['virtualization_type'] = 'kvm' + virtual_facts['virtualization_role'] = 'guest' + return virtual_facts + + if sys_vendor == 'oVirt': + virtual_facts['virtualization_type'] = 'kvm' + virtual_facts['virtualization_role'] = 'guest' + return virtual_facts + + if sys_vendor == 'OpenStack Foundation': + virtual_facts['virtualization_type'] = 'openstack' + virtual_facts['virtualization_role'] = 'guest' + return virtual_facts + + if os.path.exists('/proc/self/status'): + for line in get_file_lines('/proc/self/status'): + if re.match('^VxID: \d+', line): + virtual_facts['virtualization_type'] = 'linux_vserver' + if re.match('^VxID: 0', line): + virtual_facts['virtualization_role'] = 'host' + else: + virtual_facts['virtualization_role'] = 'guest' + return virtual_facts + + if os.path.exists('/proc/cpuinfo'): + for line in get_file_lines('/proc/cpuinfo'): + if re.match('^model name.*QEMU Virtual CPU', line): + virtual_facts['virtualization_type'] = 'kvm' + elif re.match('^vendor_id.*User Mode Linux', line): + virtual_facts['virtualization_type'] = 'uml' + elif re.match('^model name.*UML', line): + virtual_facts['virtualization_type'] = 'uml' + elif re.match('^vendor_id.*PowerVM Lx86', line): + virtual_facts['virtualization_type'] = 'powervm_lx86' + elif re.match('^vendor_id.*IBM/S390', line): + virtual_facts['virtualization_type'] = 'PR/SM' + lscpu = self.module.get_bin_path('lscpu') + if lscpu: + rc, out, err = self.module.run_command(["lscpu"]) + if rc == 0: + for line in out.splitlines(): + data = line.split(":", 1) + key = data[0].strip() + if key == 'Hypervisor': + virtual_facts['virtualization_type'] = data[1].strip() + else: + virtual_facts['virtualization_type'] = 'ibm_systemz' + else: + continue + if virtual_facts['virtualization_type'] == 'PR/SM': + virtual_facts['virtualization_role'] = 'LPAR' + else: + virtual_facts['virtualization_role'] = 'guest' + return virtual_facts + + # Beware that we can have both kvm and virtualbox running on a single system + if os.path.exists("/proc/modules") and os.access('/proc/modules', os.R_OK): + modules = [] + for line in get_file_lines("/proc/modules"): + data = line.split(" ", 1) + modules.append(data[0]) + + if 'kvm' in modules: + + if os.path.isdir('/rhev/'): + + # Check whether this is a RHEV hypervisor (is vdsm running ?) + for f in glob.glob('/proc/[0-9]*/comm'): + try: + if open(f).read().rstrip() == 'vdsm': + virtual_facts['virtualization_type'] = 'RHEV' + break + except: + pass + else: + virtual_facts['virtualization_type'] = 'kvm' + + else: + virtual_facts['virtualization_type'] = 'kvm' + virtual_facts['virtualization_role'] = 'host' + return virtual_facts + + if 'vboxdrv' in modules: + virtual_facts['virtualization_type'] = 'virtualbox' + virtual_facts['virtualization_role'] = 'host' + return virtual_facts + + # If none of the above matches, return 'NA' for virtualization_type + # and virtualization_role. This allows for proper grouping. + virtual_facts['virtualization_type'] = 'NA' + virtual_facts['virtualization_role'] = 'NA' + + return virtual_facts + + +class LinuxVirtualCollector(VirtualCollector): + _fact_class = LinuxVirtual + _platform = 'Linux' diff --git a/lib/ansible/module_utils/facts/virtual/netbsd.py b/lib/ansible/module_utils/facts/virtual/netbsd.py new file mode 100644 index 00000000000..514ef8596ea --- /dev/null +++ b/lib/ansible/module_utils/facts/virtual/netbsd.py @@ -0,0 +1,50 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os + +from ansible.module_utils.facts.virtual.base import Virtual, VirtualCollector +from ansible.module_utils.facts.virtual.sysctl import VirtualSysctlDetectionMixin + + +class NetBSDVirtual(Virtual, VirtualSysctlDetectionMixin): + platform = 'NetBSD' + + def get_virtual_facts(self): + virtual_facts = {} + # Set empty values as default + virtual_facts['virtualization_type'] = '' + virtual_facts['virtualization_role'] = '' + + virtual_product_facts = self.detect_virt_product('machdep.dmi.system-product') + virtual_facts.update(virtual_product_facts) + + if virtual_facts['virtualization_type'] == '': + virtual_vendor_facts = self.detect_virt_vendor('machdep.dmi.system-vendor') + virtual_facts.update(virtual_vendor_facts) + + if os.path.exists('/dev/xencons'): + virtual_facts['virtualization_type'] = 'xen' + virtual_facts['virtualization_role'] = 'guest' + + return virtual_facts + + +class NetBSDVirtualCollector(VirtualCollector): + _fact_class = NetBSDVirtual + _platform = 'NetBSD' diff --git a/lib/ansible/module_utils/facts/virtual/openbsd.py b/lib/ansible/module_utils/facts/virtual/openbsd.py new file mode 100644 index 00000000000..42daa3375ef --- /dev/null +++ b/lib/ansible/module_utils/facts/virtual/openbsd.py @@ -0,0 +1,64 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from ansible.module_utils.facts.virtual.base import Virtual, VirtualCollector +from ansible.module_utils.facts.virtual.sysctl import VirtualSysctlDetectionMixin + +from ansible.module_utils.facts.utils import get_file_content + + +class OpenBSDVirtual(Virtual, VirtualSysctlDetectionMixin): + """ + This is a OpenBSD-specific subclass of Virtual. It defines + - virtualization_type + - virtualization_role + """ + platform = 'OpenBSD' + DMESG_BOOT = '/var/run/dmesg.boot' + + def get_virtual_facts(self): + virtual_facts = {} + + # Set empty values as default + virtual_facts['virtualization_type'] = '' + virtual_facts['virtualization_role'] = '' + + virtual_product_facts = self.detect_virt_product('hw.product') + virtual_facts.update(virtual_product_facts) + + if virtual_facts['virtualization_type'] == '': + virtual_vendor_facts = self.detect_virt_vendor('hw.vendor') + virtual_facts.update(virtual_vendor_facts) + + # Check the dmesg if vmm(4) attached, indicating the host is + # capable of virtualization. + dmesg_boot = get_file_content(OpenBSDVirtual.DMESG_BOOT) + for line in dmesg_boot.splitlines(): + match = re.match('^vmm0 at mainbus0: (SVM/RVI|VMX/EPT)$', line) + if match: + virtual_facts['virtualization_type'] = 'vmm' + virtual_facts['virtualization_role'] = 'host' + + return virtual_facts + + +class OpenBSDVirtualCollector(VirtualCollector): + _fact_class = OpenBSDVirtual + _platform = 'OpenBSD' diff --git a/lib/ansible/module_utils/facts/virtual/sunos.py b/lib/ansible/module_utils/facts/virtual/sunos.py new file mode 100644 index 00000000000..06ce661a025 --- /dev/null +++ b/lib/ansible/module_utils/facts/virtual/sunos.py @@ -0,0 +1,120 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os + +from ansible.module_utils.facts.virtual.base import Virtual, VirtualCollector + + +class SunOSVirtual(Virtual): + """ + This is a SunOS-specific subclass of Virtual. It defines + - virtualization_type + - virtualization_role + - container + """ + platform = 'SunOS' + + def get_virtual_facts(self): + virtual_facts = {} + # Check if it's a zone + + zonename = self.module.get_bin_path('zonename') + if zonename: + rc, out, err = self.module.run_command(zonename) + if rc == 0 and out.rstrip() != "global": + virtual_facts['container'] = 'zone' + # Check if it's a branded zone (i.e. Solaris 8/9 zone) + if os.path.isdir('/.SUNWnative'): + virtual_facts['container'] = 'zone' + # If it's a zone check if we can detect if our global zone is itself virtualized. + # Relies on the "guest tools" (e.g. vmware tools) to be installed + + if 'container' in virtual_facts and virtual_facts['container'] == 'zone': + modinfo = self.module.get_bin_path('modinfo') + if modinfo: + rc, out, err = self.module.run_command(modinfo) + if rc == 0: + for line in out.splitlines(): + if 'VMware' in line: + virtual_facts['virtualization_type'] = 'vmware' + virtual_facts['virtualization_role'] = 'guest' + if 'VirtualBox' in line: + virtual_facts['virtualization_type'] = 'virtualbox' + virtual_facts['virtualization_role'] = 'guest' + + if os.path.exists('/proc/vz'): + virtual_facts['virtualization_type'] = 'virtuozzo' + virtual_facts['virtualization_role'] = 'guest' + + # Detect domaining on Sparc hardware + virtinfo = self.module.get_bin_path('virtinfo') + if virtinfo: + # The output of virtinfo is different whether we are on a machine with logical + # domains ('LDoms') on a T-series or domains ('Domains') on a M-series. Try LDoms first. + rc, out, err = self.module.run_command("/usr/sbin/virtinfo -p") + # The output contains multiple lines with different keys like this: + # DOMAINROLE|impl=LDoms|control=false|io=false|service=false|root=false + # The output may also be not formatted and the returncode is set to 0 regardless of the error condition: + # virtinfo can only be run from the global zone + if rc == 0: + try: + for line in out.splitlines(): + fields = line.split('|') + if fields[0] == 'DOMAINROLE' and fields[1] == 'impl=LDoms': + virtual_facts['virtualization_type'] = 'ldom' + virtual_facts['virtualization_role'] = 'guest' + hostfeatures = [] + for field in fields[2:]: + arg = field.split('=') + if arg[1] == 'true': + hostfeatures.append(arg[0]) + if len(hostfeatures) > 0: + virtual_facts['virtualization_role'] = 'host (' + ','.join(hostfeatures) + ')' + except ValueError: + pass + + else: + smbios = self.module.get_bin_path('smbios') + if not smbios: + return + rc, out, err = self.module.run_command(smbios) + if rc == 0: + for line in out.splitlines(): + if 'VMware' in line: + virtual_facts['virtualization_type'] = 'vmware' + virtual_facts['virtualization_role'] = 'guest' + elif 'Parallels' in line: + virtual_facts['virtualization_type'] = 'parallels' + virtual_facts['virtualization_role'] = 'guest' + elif 'VirtualBox' in line: + virtual_facts['virtualization_type'] = 'virtualbox' + virtual_facts['virtualization_role'] = 'guest' + elif 'HVM domU' in line: + virtual_facts['virtualization_type'] = 'xen' + virtual_facts['virtualization_role'] = 'guest' + elif 'KVM' in line: + virtual_facts['virtualization_type'] = 'kvm' + virtual_facts['virtualization_role'] = 'guest' + + return virtual_facts + + +class SunOSVirtualCollector(VirtualCollector): + _fact_class = SunOSVirtual + _platform = 'SunOS' diff --git a/lib/ansible/module_utils/facts/virtual/sysctl.py b/lib/ansible/module_utils/facts/virtual/sysctl.py new file mode 100644 index 00000000000..eb7dbffbff6 --- /dev/null +++ b/lib/ansible/module_utils/facts/virtual/sysctl.py @@ -0,0 +1,68 @@ +# This file is part of Ansible +# +# 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 . + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + + +class VirtualSysctlDetectionMixin(object): + def detect_sysctl(self): + self.sysctl_path = self.module.get_bin_path('sysctl') + + def detect_virt_product(self, key): + virtual_product_facts = {} + self.detect_sysctl() + # FIXME: exit early on falsey self.sysctl_path and unindent + if self.sysctl_path: + rc, out, err = self.module.run_command("%s -n %s" % (self.sysctl_path, key)) + if rc == 0: + if re.match('(KVM|Bochs|SmartDC).*', out): + virtual_product_facts['virtualization_type'] = 'kvm' + virtual_product_facts['virtualization_role'] = 'guest' + elif re.match('.*VMware.*', out): + virtual_product_facts['virtualization_type'] = 'VMware' + virtual_product_facts['virtualization_role'] = 'guest' + elif out.rstrip() == 'VirtualBox': + virtual_product_facts['virtualization_type'] = 'virtualbox' + virtual_product_facts['virtualization_role'] = 'guest' + elif out.rstrip() == 'HVM domU': + virtual_product_facts['virtualization_type'] = 'xen' + virtual_product_facts['virtualization_role'] = 'guest' + elif out.rstrip() == 'Parallels': + virtual_product_facts['virtualization_type'] = 'parallels' + virtual_product_facts['virtualization_role'] = 'guest' + elif out.rstrip() == 'RHEV Hypervisor': + virtual_product_facts['virtualization_type'] = 'RHEV' + virtual_product_facts['virtualization_role'] = 'guest' + + return virtual_product_facts + + def detect_virt_vendor(self, key): + virtual_vendor_facts = {} + self.detect_sysctl() + # FIXME: exit early on falsey self.sysctl_path and unindent + if self.sysctl_path: + rc, out, err = self.module.run_command("%s -n %s" % (self.sysctl_path, key)) + if rc == 0: + if out.rstrip() == 'QEMU': + virtual_vendor_facts['virtualization_type'] = 'kvm' + virtual_vendor_facts['virtualization_role'] = 'guest' + if out.rstrip() == 'OpenBSD': + virtual_vendor_facts['virtualization_type'] = 'vmm' + virtual_vendor_facts['virtualization_role'] = 'guest' + + return virtual_vendor_facts diff --git a/lib/ansible/modules/system/setup.py b/lib/ansible/modules/system/setup.py index 94651234981..3b2455024c5 100644 --- a/lib/ansible/modules/system/setup.py +++ b/lib/ansible/modules/system/setup.py @@ -116,24 +116,152 @@ EXAMPLES = """ # Display facts from Windows hosts with custom facts stored in C(C:\\custom_facts). # ansible windows -m setup -a "fact_path='c:\\custom_facts'" """ +import fnmatch +import sys + +# import module snippets +from ansible.module_utils.basic import AnsibleModule + +from ansible.module_utils.facts import collector +from ansible.module_utils.facts.namespace import PrefixFactNamespace + +from ansible.module_utils.facts import default_collectors + + +# This is the main entry point for setup.py facts.py. +# FIXME: This is coupled to AnsibleModule (it assumes module.params has keys 'gather_subset', +# 'gather_timeout', 'filter' instead of passing those are args or oblique ds +# module is passed in and self.module.misc_AnsibleModule_methods +# are used, so hard to decouple. + +class AnsibleFactCollector(collector.BaseFactCollector): + '''A FactCollector that returns results under 'ansible_facts' top level key. + + Has a 'from_gather_subset() constructor that populates collectors based on a + gather_subset specifier.''' + + def __init__(self, collectors=None, namespace=None, filter_spec=None): + + super(AnsibleFactCollector, self).__init__(collectors=collectors, + namespace=namespace) + + self.filter_spec = filter_spec + + def _filter(self, facts_dict, filter_spec): + # assume a filter_spec='' is equilv to filter_spec='*' + if not filter_spec or filter_spec == '*': + return facts_dict + + return [(x, y) for x, y in facts_dict.items() if fnmatch.fnmatch(x, filter_spec)] + + def collect(self, module=None, collected_facts=None): + collected_facts = collected_facts or {} + + facts_dict = {} + facts_dict['ansible_facts'] = {} + + for collector_obj in self.collectors: + info_dict = {} + + # shallow copy of the accumulated collected facts to pass to each collector + # for reference. + collected_facts.update(facts_dict['ansible_facts'].copy()) + + try: + + # Note: this collects with namespaces, so collected_facts also includes namespaces + info_dict = collector_obj.collect_with_namespace(module=module, + collected_facts=collected_facts) + except Exception as e: + sys.stderr.write(repr(e)) + sys.stderr.write('\n') + + # filtered_info_dict = self._filter(info_dict, self.filter_spec) + # NOTE: If we want complicated fact dict merging, this is where it would hook in + facts_dict['ansible_facts'].update(self._filter(info_dict, self.filter_spec)) + + # TODO: this may be best place to apply fact 'filters' as well. They + # are currently ignored -akl + return facts_dict + + +class CollectorMetaDataCollector(collector.BaseFactCollector): + '''Collector that provides a facts with the gather_subset metadata.''' + + name = 'gather_subset' + _fact_ids = set([]) + + def __init__(self, collectors=None, namespace=None, gather_subset=None, module_setup=None): + super(CollectorMetaDataCollector, self).__init__(collectors, namespace) + self.gather_subset = gather_subset + self.module_setup = module_setup + + def collect(self, module=None, collected_facts=None): + meta_facts = {'gather_subset': self.gather_subset} + if self.module_setup: + meta_facts['module_setup'] = self.module_setup + return meta_facts def main(): module = AnsibleModule( - argument_spec = dict( + argument_spec=dict( gather_subset=dict(default=["all"], required=False, type='list'), gather_timeout=dict(default=10, required=False, type='int'), filter=dict(default="*", required=False), fact_path=dict(default='/etc/ansible/facts.d', required=False, type='path'), ), - supports_check_mode = True, + supports_check_mode=True, ) - data = get_all_facts(module) - module.exit_json(**data) -# import module snippets -from ansible.module_utils.basic import * -from ansible.module_utils.facts import * + gather_subset = module.params['gather_subset'] + gather_timeout = module.params['gather_timeout'] + filter_spec = module.params['filter'] + + # TODO: this mimics existing behavior where gather_subset=["!all"] actually means + # to collect nothing except for the below list + # TODO: decide what '!all' means, I lean towards making it mean none, but likely needs + # some tweaking on how gather_subset operations are performed + minimal_gather_subset = frozenset(['apparmor', 'caps', 'cmdline', 'date_time', + 'distribution', 'dns', 'env', 'fips', 'local', 'lsb', + 'pkg_mgr', 'platform', 'python', 'selinux', + 'service_mgr', 'ssh_pub_keys', 'user']) + + all_collector_classes = default_collectors.collectors + + collector_classes = \ + collector.collector_classes_from_gather_subset( + all_collector_classes=all_collector_classes, + minimal_gather_subset=minimal_gather_subset, + gather_subset=gather_subset, + gather_timeout=gather_timeout) + + # print('collector_classes: %s' % pprint.pformat(collector_classes)) + + namespace = PrefixFactNamespace(namespace_name='ansible', + prefix='ansible_') + + collectors = [] + for collector_class in collector_classes: + collector_obj = collector_class(namespace=namespace) + collectors.append(collector_obj) + + # Add a collector that knows what gather_subset we used so it it can provide a fact + collector_meta_data_collector = \ + CollectorMetaDataCollector(gather_subset=gather_subset, + module_setup=True) + collectors.append(collector_meta_data_collector) + + # print('collectors: %s' % pprint.pformat(collectors)) + + fact_collector = \ + AnsibleFactCollector(collectors=collectors, + filter_spec=filter_spec) + + facts_dict = fact_collector.collect(module=module) + + module.exit_json(**facts_dict) + if __name__ == '__main__': main() diff --git a/test/integration/inventory b/test/integration/inventory index 1e2a7bd7497..4929016b743 100644 --- a/test/integration/inventory +++ b/test/integration/inventory @@ -5,7 +5,7 @@ testhost2 ansible_ssh_host=127.0.0.1 ansible_connection=local testhost3 ansible_ssh_host=127.0.0.3 testhost4 ansible_ssh_host=127.0.0.4 # For testing fact gathering -facthost[0:9] ansible_host=1270.0.0.1 ansible_connection=local +facthost[0:20] ansible_host=1270.0.0.1 ansible_connection=local [binary_modules] testhost_binary_modules ansible_host=127.0.0.1 ansible_connection=local diff --git a/test/integration/targets/facts_d/tasks/main.yml b/test/integration/targets/facts_d/tasks/main.yml index facd19ff5ca..ca23544fbe0 100644 --- a/test/integration/targets/facts_d/tasks/main.yml +++ b/test/integration/targets/facts_d/tasks/main.yml @@ -33,6 +33,8 @@ that: - "'ansible_facts' in setup_result" - "'ansible_local' in setup_result.ansible_facts" + - "'ansible_env' not in setup_result.ansible_facts" + - "'ansible_user_id' not in setup_result.ansible_facts" - "'preferences' in setup_result.ansible_facts['ansible_local']" - "'general' in setup_result.ansible_facts['ansible_local']['preferences']" - "'bar' in setup_result.ansible_facts['ansible_local']['preferences']['general']" diff --git a/test/integration/targets/gathering_facts/test_gathering_facts.yml b/test/integration/targets/gathering_facts/test_gathering_facts.yml index 6f15f7afca3..fab438a4ec8 100644 --- a/test/integration/targets/gathering_facts/test_gathering_facts.yml +++ b/test/integration/targets/gathering_facts/test_gathering_facts.yml @@ -1,5 +1,21 @@ --- +- hosts: facthost7 + tags: [ 'fact_negation' ] + connection: local + gather_subset: "!hardware" + gather_facts: no + tasks: + - name: setup with not hardware + setup: + gather_subset: + - "!hardware" + register: not_hardware_facts + + - name: debug setup with not hardware + debug: + var: not_hardware_facts + - hosts: facthost0 tags: [ 'fact_min' ] connection: local @@ -16,6 +32,75 @@ - 'ansible_mounts|default("UNDEF_NET") != "UNDEF_HW"' - 'ansible_virtualization_role|default("UNDEF_VIRT") != "UNDEF_VIRT"' +- hosts: facthost19 + tags: [ 'fact_min' ] + connection: local + gather_facts: no + tasks: + - setup: + filter: "*env*" + register: facts_results + + - name: Test that retrieving all facts filtered to env works + assert: + that: + - 'ansible_interfaces|default("UNDEF_NET") == "UNDEF_NET"' + - 'ansible_mounts|default("UNDEF_MOUNT") == "UNDEF_MOUNT"' + - 'ansible_virtualization_role|default("UNDEF_VIRT") == "UNDEF_VIRT"' + - 'ansible_env|default("UNDEF_ENV") != "UNDEF_ENV"' + +- hosts: facthost13 + tags: [ 'fact_min' ] + connection: local + gather_facts: no + tasks: + - setup: + filter: "ansible_user_id" + register: facts_results + + - name: Test that retrieving all facts filtered to specific fact ansible_user_id works + assert: + that: + - 'ansible_user_id|default("UNDEF_USER") != "UNDEF_USER"' + - 'ansible_interfaces|default("UNDEF_NET") == "UNDEF_NET"' + - 'ansible_mounts|default("UNDEF_MOUNT") == "UNDEF_MOUNT"' + - 'ansible_virtualization_role|default("UNDEF_VIRT") == "UNDEF_VIRT"' + - 'ansible_env|default("UNDEF_ENV") == "UNDEF_ENV"' + +- hosts: facthost11 + tags: [ 'fact_min' ] + connection: local + gather_facts: no + tasks: + - setup: + filter: "*" + register: facts + + - name: Test that retrieving all facts filtered to splat + assert: + that: + - 'ansible_user_id|default("UNDEF_MIN") != "UNDEF_MIN"' + - 'ansible_interfaces|default("UNDEF_NET") != "UNDEF_NET"' + - 'ansible_mounts|default("UNDEF_NET") != "UNDEF_HW"' + - 'ansible_virtualization_role|default("UNDEF_VIRT") != "UNDEF_VIRT"' + +- hosts: facthost12 + tags: [ 'fact_min' ] + connection: local + gather_facts: no + tasks: + - setup: + filter: "" + register: facts + + - name: Test that retrieving all facts filtered to empty filter_spec works + assert: + that: + - 'ansible_user_id|default("UNDEF_MIN") != "UNDEF_MIN"' + - 'ansible_interfaces|default("UNDEF_NET") != "UNDEF_NET"' + - 'ansible_mounts|default("UNDEF_NET") != "UNDEF_HW"' + - 'ansible_virtualization_role|default("UNDEF_VIRT") != "UNDEF_VIRT"' + - hosts: facthost1 tags: [ 'fact_min' ] connection: local @@ -102,6 +187,7 @@ - 'ansible_mounts|default("UNDEF_HW") == "UNDEF_HW"' - 'ansible_virtualization_role|default("UNDEF_VIRT") != "UNDEF_VIRT"' + - hosts: facthost7 tags: [ 'fact_negation' ] connection: local diff --git a/test/sanity/pep8/legacy-files.txt b/test/sanity/pep8/legacy-files.txt index 018ba9f2eb0..7912b0adfe6 100644 --- a/test/sanity/pep8/legacy-files.txt +++ b/test/sanity/pep8/legacy-files.txt @@ -25,7 +25,6 @@ lib/ansible/module_utils/database.py lib/ansible/module_utils/docker_common.py lib/ansible/module_utils/ec2.py lib/ansible/module_utils/f5_utils.py -lib/ansible/module_utils/facts.py lib/ansible/module_utils/fortios.py lib/ansible/module_utils/gcdns.py lib/ansible/module_utils/gce.py @@ -624,7 +623,6 @@ lib/ansible/modules/system/sefcontext.py lib/ansible/modules/system/selinux.py lib/ansible/modules/system/seport.py lib/ansible/modules/system/service.py -lib/ansible/modules/system/setup.py lib/ansible/modules/system/solaris_zone.py lib/ansible/modules/system/svc.py lib/ansible/modules/system/sysctl.py diff --git a/test/units/module_utils/facts/base.py b/test/units/module_utils/facts/base.py new file mode 100644 index 00000000000..8b32c5352b9 --- /dev/null +++ b/test/units/module_utils/facts/base.py @@ -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 . +# + +# 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 diff --git a/test/units/module_utils/fixtures/findmount_output.txt b/test/units/module_utils/facts/fixtures/findmount_output.txt similarity index 100% rename from test/units/module_utils/fixtures/findmount_output.txt rename to test/units/module_utils/facts/fixtures/findmount_output.txt diff --git a/test/units/module_utils/facts/other/__init__.py b/test/units/module_utils/facts/other/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/units/module_utils/facts/other/test_facter.py b/test/units/module_utils/facts/other/test_facter.py new file mode 100644 index 00000000000..eac6abf712d --- /dev/null +++ b/test/units/module_utils/facts/other/test_facter.py @@ -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 . +# + +# 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, {}) diff --git a/test/units/module_utils/facts/other/test_ohai.py b/test/units/module_utils/facts/other/test_ohai.py new file mode 100644 index 00000000000..f5567656005 --- /dev/null +++ b/test/units/module_utils/facts/other/test_ohai.py @@ -0,0 +1,6768 @@ +# unit tests for ansible ohai 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 . +# + +# 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.ohai import OhaiFactCollector + +ohai_json_output = r''' +{ + "kernel": { + "name": "Linux", + "release": "4.9.14-200.fc25.x86_64", + "version": "#1 SMP Mon Mar 13 19:26:40 UTC 2017", + "machine": "x86_64", + "processor": "x86_64", + "os": "GNU/Linux", + "modules": { + "binfmt_misc": { + "size": "20480", + "refcount": "1" + }, + "veth": { + "size": "16384", + "refcount": "0" + }, + "xfs": { + "size": "1200128", + "refcount": "1" + }, + "xt_addrtype": { + "size": "16384", + "refcount": "2" + }, + "br_netfilter": { + "size": "24576", + "refcount": "0" + }, + "dm_thin_pool": { + "size": "65536", + "refcount": "2" + }, + "dm_persistent_data": { + "size": "69632", + "refcount": "1" + }, + "dm_bio_prison": { + "size": "16384", + "refcount": "1" + }, + "libcrc32c": { + "size": "16384", + "refcount": "2" + }, + "rfcomm": { + "size": "77824", + "refcount": "14", + "version": "1.11" + }, + "fuse": { + "size": "102400", + "refcount": "3" + }, + "ccm": { + "size": "20480", + "refcount": "2" + }, + "xt_CHECKSUM": { + "size": "16384", + "refcount": "2" + }, + "iptable_mangle": { + "size": "16384", + "refcount": "1" + }, + "ipt_MASQUERADE": { + "size": "16384", + "refcount": "7" + }, + "nf_nat_masquerade_ipv4": { + "size": "16384", + "refcount": "1" + }, + "iptable_nat": { + "size": "16384", + "refcount": "1" + }, + "nf_nat_ipv4": { + "size": "16384", + "refcount": "1" + }, + "nf_nat": { + "size": "28672", + "refcount": "2" + }, + "nf_conntrack_ipv4": { + "size": "16384", + "refcount": "4" + }, + "nf_defrag_ipv4": { + "size": "16384", + "refcount": "1" + }, + "xt_conntrack": { + "size": "16384", + "refcount": "3" + }, + "nf_conntrack": { + "size": "106496", + "refcount": "5" + }, + "ip6t_REJECT": { + "size": "16384", + "refcount": "2" + }, + "nf_reject_ipv6": { + "size": "16384", + "refcount": "1" + }, + "tun": { + "size": "28672", + "refcount": "4" + }, + "bridge": { + "size": "135168", + "refcount": "1", + "version": "2.3" + }, + "stp": { + "size": "16384", + "refcount": "1" + }, + "llc": { + "size": "16384", + "refcount": "2" + }, + "ebtable_filter": { + "size": "16384", + "refcount": "0" + }, + "ebtables": { + "size": "36864", + "refcount": "1" + }, + "ip6table_filter": { + "size": "16384", + "refcount": "1" + }, + "ip6_tables": { + "size": "28672", + "refcount": "1" + }, + "cmac": { + "size": "16384", + "refcount": "3" + }, + "uhid": { + "size": "20480", + "refcount": "2" + }, + "bnep": { + "size": "20480", + "refcount": "2", + "version": "1.3" + }, + "btrfs": { + "size": "1056768", + "refcount": "1" + }, + "xor": { + "size": "24576", + "refcount": "1" + }, + "raid6_pq": { + "size": "106496", + "refcount": "1" + }, + "loop": { + "size": "28672", + "refcount": "6" + }, + "arc4": { + "size": "16384", + "refcount": "2" + }, + "snd_hda_codec_hdmi": { + "size": "45056", + "refcount": "1" + }, + "intel_rapl": { + "size": "20480", + "refcount": "0" + }, + "x86_pkg_temp_thermal": { + "size": "16384", + "refcount": "0" + }, + "intel_powerclamp": { + "size": "16384", + "refcount": "0" + }, + "coretemp": { + "size": "16384", + "refcount": "0" + }, + "kvm_intel": { + "size": "192512", + "refcount": "0" + }, + "kvm": { + "size": "585728", + "refcount": "1" + }, + "irqbypass": { + "size": "16384", + "refcount": "1" + }, + "crct10dif_pclmul": { + "size": "16384", + "refcount": "0" + }, + "crc32_pclmul": { + "size": "16384", + "refcount": "0" + }, + "iTCO_wdt": { + "size": "16384", + "refcount": "0", + "version": "1.11" + }, + "ghash_clmulni_intel": { + "size": "16384", + "refcount": "0" + }, + "mei_wdt": { + "size": "16384", + "refcount": "0" + }, + "iTCO_vendor_support": { + "size": "16384", + "refcount": "1", + "version": "1.04" + }, + "iwlmvm": { + "size": "364544", + "refcount": "0" + }, + "intel_cstate": { + "size": "16384", + "refcount": "0" + }, + "uvcvideo": { + "size": "90112", + "refcount": "0", + "version": "1.1.1" + }, + "videobuf2_vmalloc": { + "size": "16384", + "refcount": "1" + }, + "intel_uncore": { + "size": "118784", + "refcount": "0" + }, + "videobuf2_memops": { + "size": "16384", + "refcount": "1" + }, + "videobuf2_v4l2": { + "size": "24576", + "refcount": "1" + }, + "videobuf2_core": { + "size": "40960", + "refcount": "2" + }, + "intel_rapl_perf": { + "size": "16384", + "refcount": "0" + }, + "mac80211": { + "size": "749568", + "refcount": "1" + }, + "videodev": { + "size": "172032", + "refcount": "3" + }, + "snd_usb_audio": { + "size": "180224", + "refcount": "3" + }, + "e1000e": { + "size": "249856", + "refcount": "0", + "version": "3.2.6-k" + } + } + }, + "os": "linux", + "os_version": "4.9.14-200.fc25.x86_64", + "lsb": { + "id": "Fedora", + "description": "Fedora release 25 (Twenty Five)", + "release": "25", + "codename": "TwentyFive" + }, + "platform": "fedora", + "platform_version": "25", + "platform_family": "fedora", + "packages": { + "ansible": { + "epoch": "0", + "version": "2.2.1.0", + "release": "1.fc25", + "installdate": "1486050042", + "arch": "noarch" + }, + "python3": { + "epoch": "0", + "version": "3.5.3", + "release": "3.fc25", + "installdate": "1490025957", + "arch": "x86_64" + }, + "kernel": { + "epoch": "0", + "version": "4.9.6", + "release": "200.fc25", + "installdate": "1486047522", + "arch": "x86_64" + }, + "glibc": { + "epoch": "0", + "version": "2.24", + "release": "4.fc25", + "installdate": "1483402427", + "arch": "x86_64" + } + }, + "chef_packages": { + ohai": { + "version": "13.0.0", + "ohai_root": "/home/some_user/.gem/ruby/gems/ohai-13.0.0/lib/ohai" + } + }, + "dmi": { + "dmidecode_version": "3.0" + }, + "uptime_seconds": 2509008, + "uptime": "29 days 00 hours 56 minutes 48 seconds", + "idletime_seconds": 19455087, + "idletime": "225 days 04 hours 11 minutes 27 seconds", + "memory": { + "swap": { + "cached": "262436kB", + "total": "8069116kB", + "free": "5154396kB" + }, + "hugepages": { + "total": "0", + "free": "0", + "reserved": "0", + "surplus": "0" + }, + "total": "16110540kB", + "free": "3825844kB", + "buffers": "377240kB", + "cached": "3710084kB", + "active": "8104320kB", + "inactive": "3192920kB", + "dirty": "812kB", + "writeback": "0kB", + "anon_pages": "7124992kB", + "mapped": "580700kB", + "slab": "622848kB", + "slab_reclaimable": "307300kB", + "slab_unreclaim": "315548kB", + "page_tables": "157572kB", + "nfs_unstable": "0kB", + "bounce": "0kB", + "commit_limit": "16124384kB", + "committed_as": "31345068kB", + "vmalloc_total": "34359738367kB", + "vmalloc_used": "0kB", + "vmalloc_chunk": "0kB", + "hugepage_size": "2048kB" + }, + "filesystem": { + "by_device": { + "devtmpfs": { + "kb_size": "8044124", + "kb_used": "0", + "kb_available": "8044124", + "percent_used": "0%", + "total_inodes": "2011031", + "inodes_used": "629", + "inodes_available": "2010402", + "inodes_percent_used": "1%", + "fs_type": "devtmpfs", + "mount_options": [ + "rw", + "nosuid", + "seclabel", + "size=8044124k", + "nr_inodes=2011031", + "mode=755" + ], + "mounts": [ + "/dev" + ] + }, + "tmpfs": { + "kb_size": "1611052", + "kb_used": "72", + "kb_available": "1610980", + "percent_used": "1%", + "total_inodes": "2013817", + "inodes_used": "36", + "inodes_available": "2013781", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "relatime", + "seclabel", + "size=1611052k", + "mode=700", + "uid=1000", + "gid=1000" + ], + "mounts": [ + "/dev/shm", + "/run", + "/sys/fs/cgroup", + "/tmp", + "/run/user/0", + "/run/user/1000" + ] + }, + "/dev/mapper/fedora_host--186-root": { + "kb_size": "51475068", + "kb_used": "42551284", + "kb_available": "6285960", + "percent_used": "88%", + "total_inodes": "3276800", + "inodes_used": "532908", + "inodes_available": "2743892", + "inodes_percent_used": "17%", + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "12312331-3449-4a6c-8179-a1feb2bca6ce", + "mounts": [ + "/", + "/var/lib/docker/devicemapper" + ] + }, + "/dev/sda1": { + "kb_size": "487652", + "kb_used": "126628", + "kb_available": "331328", + "percent_used": "28%", + "total_inodes": "128016", + "inodes_used": "405", + "inodes_available": "127611", + "inodes_percent_used": "1%", + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "12312311-ef40-4691-a3b6-438c3f9bc1c0", + "mounts": [ + "/boot" + ] + }, + "/dev/mapper/fedora_host--186-home": { + "kb_size": "185948124", + "kb_used": "105904724", + "kb_available": "70574680", + "percent_used": "61%", + "total_inodes": "11821056", + "inodes_used": "1266687", + "inodes_available": "10554369", + "inodes_percent_used": "11%", + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "2d3e4853-fa69-4ccf-8a6a-77b05ab0a42d", + "mounts": [ + "/home" + ] + }, + "/dev/loop0": { + "kb_size": "512000", + "kb_used": "16672", + "kb_available": "429056", + "percent_used": "4%", + "fs_type": "btrfs", + "uuid": "0f031512-ab15-497d-9abd-3a512b4a9390", + "mounts": [ + "/var/lib/machines" + ] + }, + "sysfs": { + "fs_type": "sysfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "seclabel" + ], + "mounts": [ + "/sys" + ] + }, + "proc": { + "fs_type": "proc", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime" + ], + "mounts": [ + "/proc" + ] + }, + "securityfs": { + "fs_type": "securityfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime" + ], + "mounts": [ + "/sys/kernel/security" + ] + }, + "devpts": { + "fs_type": "devpts", + "mount_options": [ + "rw", + "nosuid", + "noexec", + "relatime", + "seclabel", + "gid=5", + "mode=620", + "ptmxmode=000" + ], + "mounts": [ + "/dev/pts" + ] + }, + "cgroup": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "net_cls", + "net_prio" + ], + "mounts": [ + "/sys/fs/cgroup/systemd", + "/sys/fs/cgroup/devices", + "/sys/fs/cgroup/cpuset", + "/sys/fs/cgroup/perf_event", + "/sys/fs/cgroup/hugetlb", + "/sys/fs/cgroup/cpu,cpuacct", + "/sys/fs/cgroup/blkio", + "/sys/fs/cgroup/freezer", + "/sys/fs/cgroup/memory", + "/sys/fs/cgroup/pids", + "/sys/fs/cgroup/net_cls,net_prio" + ] + }, + "pstore": { + "fs_type": "pstore", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "seclabel" + ], + "mounts": [ + "/sys/fs/pstore" + ] + }, + "configfs": { + "fs_type": "configfs", + "mount_options": [ + "rw", + "relatime" + ], + "mounts": [ + "/sys/kernel/config" + ] + }, + "selinuxfs": { + "fs_type": "selinuxfs", + "mount_options": [ + "rw", + "relatime" + ], + "mounts": [ + "/sys/fs/selinux" + ] + }, + "debugfs": { + "fs_type": "debugfs", + "mount_options": [ + "rw", + "relatime", + "seclabel" + ], + "mounts": [ + "/sys/kernel/debug" + ] + }, + "hugetlbfs": { + "fs_type": "hugetlbfs", + "mount_options": [ + "rw", + "relatime", + "seclabel" + ], + "mounts": [ + "/dev/hugepages" + ] + }, + "mqueue": { + "fs_type": "mqueue", + "mount_options": [ + "rw", + "relatime", + "seclabel" + ], + "mounts": [ + "/dev/mqueue" + ] + }, + "systemd-1": { + "fs_type": "autofs", + "mount_options": [ + "rw", + "relatime", + "fd=40", + "pgrp=1", + "timeout=0", + "minproto=5", + "maxproto=5", + "direct", + "pipe_ino=17610" + ], + "mounts": [ + "/proc/sys/fs/binfmt_misc" + ] + }, + "/var/lib/machines.raw": { + "fs_type": "btrfs", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "space_cache", + "subvolid=5", + "subvol=/" + ], + "mounts": [ + "/var/lib/machines" + ] + }, + "fusectl": { + "fs_type": "fusectl", + "mount_options": [ + "rw", + "relatime" + ], + "mounts": [ + "/sys/fs/fuse/connections" + ] + }, + "gvfsd-fuse": { + "fs_type": "fuse.gvfsd-fuse", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "relatime", + "user_id=1000", + "group_id=1000" + ], + "mounts": [ + "/run/user/1000/gvfs" + ] + }, + "binfmt_misc": { + "fs_type": "binfmt_misc", + "mount_options": [ + "rw", + "relatime" + ], + "mounts": [ + "/proc/sys/fs/binfmt_misc" + ] + }, + "/dev/mapper/docker-253:1-1180487-0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8": { + "fs_type": "xfs", + "mount_options": [ + "rw", + "relatime", + "context=\"system_u:object_r:container_file_t:s0:c523", + "c681\"", + "nouuid", + "attr2", + "inode64", + "logbsize=64k", + "sunit=128", + "swidth=128", + "noquota" + ], + "uuid": "00e2aa25-20d8-4ad7-b3a5-c501f2f4c123", + "mounts": [ + "/var/lib/docker/devicemapper/mnt/0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8" + ] + }, + "shm": { + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "context=\"system_u:object_r:container_file_t:s0:c523", + "c681\"", + "size=65536k" + ], + "mounts": [ + "/var/lib/docker/containers/426e513ed508a451e3f70440eed040761f81529e4bc4240e7522d331f3f3bc12/shm" + ] + }, + "nsfs": { + "fs_type": "nsfs", + "mount_options": [ + "rw" + ], + "mounts": [ + "/run/docker/netns/1ce89fd79f3d" + ] + }, + "tracefs": { + "fs_type": "tracefs", + "mount_options": [ + "rw", + "relatime" + ], + "mounts": [ + "/sys/kernel/debug/tracing" + ] + }, + "/dev/loop1": { + "fs_type": "xfs", + "uuid": "00e2aa25-20d8-4ad7-b3a5-c501f2f4c123", + "mounts": [ + + ] + }, + "/dev/mapper/docker-253:1-1180487-pool": { + "mounts": [ + + ] + }, + "/dev/sr0": { + "mounts": [ + + ] + }, + "/dev/loop2": { + "mounts": [ + + ] + }, + "/dev/sda": { + "mounts": [ + + ] + }, + "/dev/sda2": { + "fs_type": "LVM2_member", + "uuid": "66Ojcd-ULtu-1cZa-Tywo-mx0d-RF4O-ysA9jK", + "mounts": [ + + ] + }, + "/dev/mapper/fedora_host--186-swap": { + "fs_type": "swap", + "uuid": "eae6059d-2fbe-4d1c-920d-a80bbeb1ac6d", + "mounts": [ + + ] + } + }, + "by_mountpoint": { + "/dev": { + "kb_size": "8044124", + "kb_used": "0", + "kb_available": "8044124", + "percent_used": "0%", + "total_inodes": "2011031", + "inodes_used": "629", + "inodes_available": "2010402", + "inodes_percent_used": "1%", + "fs_type": "devtmpfs", + "mount_options": [ + "rw", + "nosuid", + "seclabel", + "size=8044124k", + "nr_inodes=2011031", + "mode=755" + ], + "devices": [ + "devtmpfs" + ] + }, + "/dev/shm": { + "kb_size": "8055268", + "kb_used": "96036", + "kb_available": "7959232", + "percent_used": "2%", + "total_inodes": "2013817", + "inodes_used": "217", + "inodes_available": "2013600", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "seclabel" + ], + "devices": [ + "tmpfs" + ] + }, + "/run": { + "kb_size": "8055268", + "kb_used": "2280", + "kb_available": "8052988", + "percent_used": "1%", + "total_inodes": "2013817", + "inodes_used": "1070", + "inodes_available": "2012747", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "seclabel", + "mode=755" + ], + "devices": [ + "tmpfs" + ] + }, + "/sys/fs/cgroup": { + "kb_size": "8055268", + "kb_used": "0", + "kb_available": "8055268", + "percent_used": "0%", + "total_inodes": "2013817", + "inodes_used": "16", + "inodes_available": "2013801", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "ro", + "nosuid", + "nodev", + "noexec", + "seclabel", + "mode=755" + ], + "devices": [ + "tmpfs" + ] + }, + "/": { + "kb_size": "51475068", + "kb_used": "42551284", + "kb_available": "6285960", + "percent_used": "88%", + "total_inodes": "3276800", + "inodes_used": "532908", + "inodes_available": "2743892", + "inodes_percent_used": "17%", + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "d34cf5e3-3449-4a6c-8179-a1feb2bca6ce", + "devices": [ + "/dev/mapper/fedora_host--186-root" + ] + }, + "/tmp": { + "kb_size": "8055268", + "kb_used": "848396", + "kb_available": "7206872", + "percent_used": "11%", + "total_inodes": "2013817", + "inodes_used": "1353", + "inodes_available": "2012464", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "seclabel" + ], + "devices": [ + "tmpfs" + ] + }, + "/boot": { + "kb_size": "487652", + "kb_used": "126628", + "kb_available": "331328", + "percent_used": "28%", + "total_inodes": "128016", + "inodes_used": "405", + "inodes_available": "127611", + "inodes_percent_used": "1%", + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "32caaec3-ef40-4691-a3b6-438c3f9bc1c0", + "devices": [ + "/dev/sda1" + ] + }, + "/home": { + "kb_size": "185948124", + "kb_used": "105904724", + "kb_available": "70574680", + "percent_used": "61%", + "total_inodes": "11821056", + "inodes_used": "1266687", + "inodes_available": "10554369", + "inodes_percent_used": "11%", + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "2d3e4853-fa69-4ccf-8a6a-77b05ab0a42d", + "devices": [ + "/dev/mapper/fedora_host--186-home" + ] + }, + "/var/lib/machines": { + "kb_size": "512000", + "kb_used": "16672", + "kb_available": "429056", + "percent_used": "4%", + "fs_type": "btrfs", + "uuid": "0f031512-ab15-497d-9abd-3a512b4a9390", + "devices": [ + "/dev/loop0", + "/var/lib/machines.raw" + ], + "mount_options": [ + "rw", + "relatime", + "seclabel", + "space_cache", + "subvolid=5", + "subvol=/" + ] + }, + "/run/user/0": { + "kb_size": "1611052", + "kb_used": "0", + "kb_available": "1611052", + "percent_used": "0%", + "total_inodes": "2013817", + "inodes_used": "7", + "inodes_available": "2013810", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "relatime", + "seclabel", + "size=1611052k", + "mode=700" + ], + "devices": [ + "tmpfs" + ] + }, + "/run/user/1000": { + "kb_size": "1611052", + "kb_used": "72", + "kb_available": "1610980", + "percent_used": "1%", + "total_inodes": "2013817", + "inodes_used": "36", + "inodes_available": "2013781", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "relatime", + "seclabel", + "size=1611052k", + "mode=700", + "uid=1000", + "gid=1000" + ], + "devices": [ + "tmpfs" + ] + }, + "/sys": { + "fs_type": "sysfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "seclabel" + ], + "devices": [ + "sysfs" + ] + }, + "/proc": { + "fs_type": "proc", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime" + ], + "devices": [ + "proc" + ] + }, + "/sys/kernel/security": { + "fs_type": "securityfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime" + ], + "devices": [ + "securityfs" + ] + }, + "/dev/pts": { + "fs_type": "devpts", + "mount_options": [ + "rw", + "nosuid", + "noexec", + "relatime", + "seclabel", + "gid=5", + "mode=620", + "ptmxmode=000" + ], + "devices": [ + "devpts" + ] + }, + "/sys/fs/cgroup/systemd": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "xattr", + "release_agent=/usr/lib/systemd/systemd-cgroups-agent", + "name=systemd" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/fs/pstore": { + "fs_type": "pstore", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "seclabel" + ], + "devices": [ + "pstore" + ] + }, + "/sys/fs/cgroup/devices": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "devices" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/fs/cgroup/cpuset": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "cpuset" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/fs/cgroup/perf_event": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "perf_event" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/fs/cgroup/hugetlb": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "hugetlb" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/fs/cgroup/cpu,cpuacct": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "cpu", + "cpuacct" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/fs/cgroup/blkio": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "blkio" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/fs/cgroup/freezer": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "freezer" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/fs/cgroup/memory": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "memory" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/fs/cgroup/pids": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "pids" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/fs/cgroup/net_cls,net_prio": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "net_cls", + "net_prio" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/kernel/config": { + "fs_type": "configfs", + "mount_options": [ + "rw", + "relatime" + ], + "devices": [ + "configfs" + ] + }, + "/sys/fs/selinux": { + "fs_type": "selinuxfs", + "mount_options": [ + "rw", + "relatime" + ], + "devices": [ + "selinuxfs" + ] + }, + "/sys/kernel/debug": { + "fs_type": "debugfs", + "mount_options": [ + "rw", + "relatime", + "seclabel" + ], + "devices": [ + "debugfs" + ] + }, + "/dev/hugepages": { + "fs_type": "hugetlbfs", + "mount_options": [ + "rw", + "relatime", + "seclabel" + ], + "devices": [ + "hugetlbfs" + ] + }, + "/dev/mqueue": { + "fs_type": "mqueue", + "mount_options": [ + "rw", + "relatime", + "seclabel" + ], + "devices": [ + "mqueue" + ] + }, + "/proc/sys/fs/binfmt_misc": { + "fs_type": "binfmt_misc", + "mount_options": [ + "rw", + "relatime" + ], + "devices": [ + "systemd-1", + "binfmt_misc" + ] + }, + "/sys/fs/fuse/connections": { + "fs_type": "fusectl", + "mount_options": [ + "rw", + "relatime" + ], + "devices": [ + "fusectl" + ] + }, + "/run/user/1000/gvfs": { + "fs_type": "fuse.gvfsd-fuse", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "relatime", + "user_id=1000", + "group_id=1000" + ], + "devices": [ + "gvfsd-fuse" + ] + }, + "/var/lib/docker/devicemapper": { + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "d34cf5e3-3449-4a6c-8179-a1feb2bca6ce", + "devices": [ + "/dev/mapper/fedora_host--186-root" + ] + }, + "/var/lib/docker/devicemapper/mnt/0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8": { + "fs_type": "xfs", + "mount_options": [ + "rw", + "relatime", + "context=\"system_u:object_r:container_file_t:s0:c523", + "c681\"", + "nouuid", + "attr2", + "inode64", + "logbsize=64k", + "sunit=128", + "swidth=128", + "noquota" + ], + "uuid": "00e2aa25-20d8-4ad7-b3a5-c501f2f4c123", + "devices": [ + "/dev/mapper/docker-253:1-1180487-0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8" + ] + }, + "/var/lib/docker/containers/426e513ed508a451e3f70440eed040761f81529e4bc4240e7522d331f3f3bc12/shm": { + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "context=\"system_u:object_r:container_file_t:s0:c523", + "c681\"", + "size=65536k" + ], + "devices": [ + "shm" + ] + }, + "/run/docker/netns/1ce89fd79f3d": { + "fs_type": "nsfs", + "mount_options": [ + "rw" + ], + "devices": [ + "nsfs" + ] + }, + "/sys/kernel/debug/tracing": { + "fs_type": "tracefs", + "mount_options": [ + "rw", + "relatime" + ], + "devices": [ + "tracefs" + ] + } + }, + "by_pair": { + "devtmpfs,/dev": { + "device": "devtmpfs", + "kb_size": "8044124", + "kb_used": "0", + "kb_available": "8044124", + "percent_used": "0%", + "mount": "/dev", + "total_inodes": "2011031", + "inodes_used": "629", + "inodes_available": "2010402", + "inodes_percent_used": "1%", + "fs_type": "devtmpfs", + "mount_options": [ + "rw", + "nosuid", + "seclabel", + "size=8044124k", + "nr_inodes=2011031", + "mode=755" + ] + }, + "tmpfs,/dev/shm": { + "device": "tmpfs", + "kb_size": "8055268", + "kb_used": "96036", + "kb_available": "7959232", + "percent_used": "2%", + "mount": "/dev/shm", + "total_inodes": "2013817", + "inodes_used": "217", + "inodes_available": "2013600", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "seclabel" + ] + }, + "tmpfs,/run": { + "device": "tmpfs", + "kb_size": "8055268", + "kb_used": "2280", + "kb_available": "8052988", + "percent_used": "1%", + "mount": "/run", + "total_inodes": "2013817", + "inodes_used": "1070", + "inodes_available": "2012747", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "seclabel", + "mode=755" + ] + }, + "tmpfs,/sys/fs/cgroup": { + "device": "tmpfs", + "kb_size": "8055268", + "kb_used": "0", + "kb_available": "8055268", + "percent_used": "0%", + "mount": "/sys/fs/cgroup", + "total_inodes": "2013817", + "inodes_used": "16", + "inodes_available": "2013801", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "ro", + "nosuid", + "nodev", + "noexec", + "seclabel", + "mode=755" + ] + }, + "/dev/mapper/fedora_host--186-root,/": { + "device": "/dev/mapper/fedora_host--186-root", + "kb_size": "51475068", + "kb_used": "42551284", + "kb_available": "6285960", + "percent_used": "88%", + "mount": "/", + "total_inodes": "3276800", + "inodes_used": "532908", + "inodes_available": "2743892", + "inodes_percent_used": "17%", + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "d34cf5e3-3449-4a6c-8179-a1feb2bca6ce" + }, + "tmpfs,/tmp": { + "device": "tmpfs", + "kb_size": "8055268", + "kb_used": "848396", + "kb_available": "7206872", + "percent_used": "11%", + "mount": "/tmp", + "total_inodes": "2013817", + "inodes_used": "1353", + "inodes_available": "2012464", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "seclabel" + ] + }, + "/dev/sda1,/boot": { + "device": "/dev/sda1", + "kb_size": "487652", + "kb_used": "126628", + "kb_available": "331328", + "percent_used": "28%", + "mount": "/boot", + "total_inodes": "128016", + "inodes_used": "405", + "inodes_available": "127611", + "inodes_percent_used": "1%", + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "32caaec3-ef40-4691-a3b6-438c3f9bc1c0" + }, + "/dev/mapper/fedora_host--186-home,/home": { + "device": "/dev/mapper/fedora_host--186-home", + "kb_size": "185948124", + "kb_used": "105904724", + "kb_available": "70574680", + "percent_used": "61%", + "mount": "/home", + "total_inodes": "11821056", + "inodes_used": "1266687", + "inodes_available": "10554369", + "inodes_percent_used": "11%", + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "2d3e4853-fa69-4ccf-8a6a-77b05ab0a42d" + }, + "/dev/loop0,/var/lib/machines": { + "device": "/dev/loop0", + "kb_size": "512000", + "kb_used": "16672", + "kb_available": "429056", + "percent_used": "4%", + "mount": "/var/lib/machines", + "fs_type": "btrfs", + "uuid": "0f031512-ab15-497d-9abd-3a512b4a9390" + }, + "tmpfs,/run/user/0": { + "device": "tmpfs", + "kb_size": "1611052", + "kb_used": "0", + "kb_available": "1611052", + "percent_used": "0%", + "mount": "/run/user/0", + "total_inodes": "2013817", + "inodes_used": "7", + "inodes_available": "2013810", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "relatime", + "seclabel", + "size=1611052k", + "mode=700" + ] + }, + "tmpfs,/run/user/1000": { + "device": "tmpfs", + "kb_size": "1611052", + "kb_used": "72", + "kb_available": "1610980", + "percent_used": "1%", + "mount": "/run/user/1000", + "total_inodes": "2013817", + "inodes_used": "36", + "inodes_available": "2013781", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "relatime", + "seclabel", + "size=1611052k", + "mode=700", + "uid=1000", + "gid=1000" + ] + }, + "sysfs,/sys": { + "device": "sysfs", + "mount": "/sys", + "fs_type": "sysfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "seclabel" + ] + }, + "proc,/proc": { + "device": "proc", + "mount": "/proc", + "fs_type": "proc", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime" + ] + }, + "securityfs,/sys/kernel/security": { + "device": "securityfs", + "mount": "/sys/kernel/security", + "fs_type": "securityfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime" + ] + }, + "devpts,/dev/pts": { + "device": "devpts", + "mount": "/dev/pts", + "fs_type": "devpts", + "mount_options": [ + "rw", + "nosuid", + "noexec", + "relatime", + "seclabel", + "gid=5", + "mode=620", + "ptmxmode=000" + ] + }, + "cgroup,/sys/fs/cgroup/systemd": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/systemd", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "xattr", + "release_agent=/usr/lib/systemd/systemd-cgroups-agent", + "name=systemd" + ] + }, + "pstore,/sys/fs/pstore": { + "device": "pstore", + "mount": "/sys/fs/pstore", + "fs_type": "pstore", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "seclabel" + ] + }, + "cgroup,/sys/fs/cgroup/devices": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/devices", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "devices" + ] + }, + "cgroup,/sys/fs/cgroup/cpuset": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/cpuset", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "cpuset" + ] + }, + "cgroup,/sys/fs/cgroup/perf_event": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/perf_event", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "perf_event" + ] + }, + "cgroup,/sys/fs/cgroup/hugetlb": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/hugetlb", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "hugetlb" + ] + }, + "cgroup,/sys/fs/cgroup/cpu,cpuacct": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/cpu,cpuacct", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "cpu", + "cpuacct" + ] + }, + "cgroup,/sys/fs/cgroup/blkio": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/blkio", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "blkio" + ] + }, + "cgroup,/sys/fs/cgroup/freezer": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/freezer", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "freezer" + ] + }, + "cgroup,/sys/fs/cgroup/memory": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/memory", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "memory" + ] + }, + "cgroup,/sys/fs/cgroup/pids": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/pids", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "pids" + ] + }, + "cgroup,/sys/fs/cgroup/net_cls,net_prio": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/net_cls,net_prio", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "net_cls", + "net_prio" + ] + }, + "configfs,/sys/kernel/config": { + "device": "configfs", + "mount": "/sys/kernel/config", + "fs_type": "configfs", + "mount_options": [ + "rw", + "relatime" + ] + }, + "selinuxfs,/sys/fs/selinux": { + "device": "selinuxfs", + "mount": "/sys/fs/selinux", + "fs_type": "selinuxfs", + "mount_options": [ + "rw", + "relatime" + ] + }, + "debugfs,/sys/kernel/debug": { + "device": "debugfs", + "mount": "/sys/kernel/debug", + "fs_type": "debugfs", + "mount_options": [ + "rw", + "relatime", + "seclabel" + ] + }, + "hugetlbfs,/dev/hugepages": { + "device": "hugetlbfs", + "mount": "/dev/hugepages", + "fs_type": "hugetlbfs", + "mount_options": [ + "rw", + "relatime", + "seclabel" + ] + }, + "mqueue,/dev/mqueue": { + "device": "mqueue", + "mount": "/dev/mqueue", + "fs_type": "mqueue", + "mount_options": [ + "rw", + "relatime", + "seclabel" + ] + }, + "systemd-1,/proc/sys/fs/binfmt_misc": { + "device": "systemd-1", + "mount": "/proc/sys/fs/binfmt_misc", + "fs_type": "autofs", + "mount_options": [ + "rw", + "relatime", + "fd=40", + "pgrp=1", + "timeout=0", + "minproto=5", + "maxproto=5", + "direct", + "pipe_ino=17610" + ] + }, + "/var/lib/machines.raw,/var/lib/machines": { + "device": "/var/lib/machines.raw", + "mount": "/var/lib/machines", + "fs_type": "btrfs", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "space_cache", + "subvolid=5", + "subvol=/" + ] + }, + "fusectl,/sys/fs/fuse/connections": { + "device": "fusectl", + "mount": "/sys/fs/fuse/connections", + "fs_type": "fusectl", + "mount_options": [ + "rw", + "relatime" + ] + }, + "gvfsd-fuse,/run/user/1000/gvfs": { + "device": "gvfsd-fuse", + "mount": "/run/user/1000/gvfs", + "fs_type": "fuse.gvfsd-fuse", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "relatime", + "user_id=1000", + "group_id=1000" + ] + }, + "/dev/mapper/fedora_host--186-root,/var/lib/docker/devicemapper": { + "device": "/dev/mapper/fedora_host--186-root", + "mount": "/var/lib/docker/devicemapper", + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "d34cf5e3-3449-4a6c-8179-a1feb2bca6ce" + }, + "binfmt_misc,/proc/sys/fs/binfmt_misc": { + "device": "binfmt_misc", + "mount": "/proc/sys/fs/binfmt_misc", + "fs_type": "binfmt_misc", + "mount_options": [ + "rw", + "relatime" + ] + }, + "/dev/mapper/docker-253:1-1180487-0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8,/var/lib/docker/devicemapper/mnt/0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8": { + "device": "/dev/mapper/docker-253:1-1180487-0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8", + "mount": "/var/lib/docker/devicemapper/mnt/0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8", + "fs_type": "xfs", + "mount_options": [ + "rw", + "relatime", + "context=\"system_u:object_r:container_file_t:s0:c523", + "c681\"", + "nouuid", + "attr2", + "inode64", + "logbsize=64k", + "sunit=128", + "swidth=128", + "noquota" + ], + "uuid": "00e2aa25-20d8-4ad7-b3a5-c501f2f4c123" + }, + "shm,/var/lib/docker/containers/426e513ed508a451e3f70440eed040761f81529e4bc4240e7522d331f3f3bc12/shm": { + "device": "shm", + "mount": "/var/lib/docker/containers/426e513ed508a451e3f70440eed040761f81529e4bc4240e7522d331f3f3bc12/shm", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "context=\"system_u:object_r:container_file_t:s0:c523", + "c681\"", + "size=65536k" + ] + }, + "nsfs,/run/docker/netns/1ce89fd79f3d": { + "device": "nsfs", + "mount": "/run/docker/netns/1ce89fd79f3d", + "fs_type": "nsfs", + "mount_options": [ + "rw" + ] + }, + "tracefs,/sys/kernel/debug/tracing": { + "device": "tracefs", + "mount": "/sys/kernel/debug/tracing", + "fs_type": "tracefs", + "mount_options": [ + "rw", + "relatime" + ] + }, + "/dev/loop1,": { + "device": "/dev/loop1", + "fs_type": "xfs", + "uuid": "00e2aa25-20d8-4ad7-b3a5-c501f2f4c123" + }, + "/dev/mapper/docker-253:1-1180487-pool,": { + "device": "/dev/mapper/docker-253:1-1180487-pool" + }, + "/dev/sr0,": { + "device": "/dev/sr0" + }, + "/dev/loop2,": { + "device": "/dev/loop2" + }, + "/dev/sda,": { + "device": "/dev/sda" + }, + "/dev/sda2,": { + "device": "/dev/sda2", + "fs_type": "LVM2_member", + "uuid": "66Ojcd-ULtu-1cZa-Tywo-mx0d-RF4O-ysA9jK" + }, + "/dev/mapper/fedora_host--186-swap,": { + "device": "/dev/mapper/fedora_host--186-swap", + "fs_type": "swap", + "uuid": "eae6059d-2fbe-4d1c-920d-a80bbeb1ac6d" + } + } + }, + "filesystem2": { + "by_device": { + "devtmpfs": { + "kb_size": "8044124", + "kb_used": "0", + "kb_available": "8044124", + "percent_used": "0%", + "total_inodes": "2011031", + "inodes_used": "629", + "inodes_available": "2010402", + "inodes_percent_used": "1%", + "fs_type": "devtmpfs", + "mount_options": [ + "rw", + "nosuid", + "seclabel", + "size=8044124k", + "nr_inodes=2011031", + "mode=755" + ], + "mounts": [ + "/dev" + ] + }, + "tmpfs": { + "kb_size": "1611052", + "kb_used": "72", + "kb_available": "1610980", + "percent_used": "1%", + "total_inodes": "2013817", + "inodes_used": "36", + "inodes_available": "2013781", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "relatime", + "seclabel", + "size=1611052k", + "mode=700", + "uid=1000", + "gid=1000" + ], + "mounts": [ + "/dev/shm", + "/run", + "/sys/fs/cgroup", + "/tmp", + "/run/user/0", + "/run/user/1000" + ] + }, + "/dev/mapper/fedora_host--186-root": { + "kb_size": "51475068", + "kb_used": "42551284", + "kb_available": "6285960", + "percent_used": "88%", + "total_inodes": "3276800", + "inodes_used": "532908", + "inodes_available": "2743892", + "inodes_percent_used": "17%", + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "d34cf5e3-3449-4a6c-8179-a1feb2bca6ce", + "mounts": [ + "/", + "/var/lib/docker/devicemapper" + ] + }, + "/dev/sda1": { + "kb_size": "487652", + "kb_used": "126628", + "kb_available": "331328", + "percent_used": "28%", + "total_inodes": "128016", + "inodes_used": "405", + "inodes_available": "127611", + "inodes_percent_used": "1%", + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "32caaec3-ef40-4691-a3b6-438c3f9bc1c0", + "mounts": [ + "/boot" + ] + }, + "/dev/mapper/fedora_host--186-home": { + "kb_size": "185948124", + "kb_used": "105904724", + "kb_available": "70574680", + "percent_used": "61%", + "total_inodes": "11821056", + "inodes_used": "1266687", + "inodes_available": "10554369", + "inodes_percent_used": "11%", + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "2d3e4853-fa69-4ccf-8a6a-77b05ab0a42d", + "mounts": [ + "/home" + ] + }, + "/dev/loop0": { + "kb_size": "512000", + "kb_used": "16672", + "kb_available": "429056", + "percent_used": "4%", + "fs_type": "btrfs", + "uuid": "0f031512-ab15-497d-9abd-3a512b4a9390", + "mounts": [ + "/var/lib/machines" + ] + }, + "sysfs": { + "fs_type": "sysfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "seclabel" + ], + "mounts": [ + "/sys" + ] + }, + "proc": { + "fs_type": "proc", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime" + ], + "mounts": [ + "/proc" + ] + }, + "securityfs": { + "fs_type": "securityfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime" + ], + "mounts": [ + "/sys/kernel/security" + ] + }, + "devpts": { + "fs_type": "devpts", + "mount_options": [ + "rw", + "nosuid", + "noexec", + "relatime", + "seclabel", + "gid=5", + "mode=620", + "ptmxmode=000" + ], + "mounts": [ + "/dev/pts" + ] + }, + "cgroup": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "net_cls", + "net_prio" + ], + "mounts": [ + "/sys/fs/cgroup/systemd", + "/sys/fs/cgroup/devices", + "/sys/fs/cgroup/cpuset", + "/sys/fs/cgroup/perf_event", + "/sys/fs/cgroup/hugetlb", + "/sys/fs/cgroup/cpu,cpuacct", + "/sys/fs/cgroup/blkio", + "/sys/fs/cgroup/freezer", + "/sys/fs/cgroup/memory", + "/sys/fs/cgroup/pids", + "/sys/fs/cgroup/net_cls,net_prio" + ] + }, + "pstore": { + "fs_type": "pstore", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "seclabel" + ], + "mounts": [ + "/sys/fs/pstore" + ] + }, + "configfs": { + "fs_type": "configfs", + "mount_options": [ + "rw", + "relatime" + ], + "mounts": [ + "/sys/kernel/config" + ] + }, + "selinuxfs": { + "fs_type": "selinuxfs", + "mount_options": [ + "rw", + "relatime" + ], + "mounts": [ + "/sys/fs/selinux" + ] + }, + "debugfs": { + "fs_type": "debugfs", + "mount_options": [ + "rw", + "relatime", + "seclabel" + ], + "mounts": [ + "/sys/kernel/debug" + ] + }, + "hugetlbfs": { + "fs_type": "hugetlbfs", + "mount_options": [ + "rw", + "relatime", + "seclabel" + ], + "mounts": [ + "/dev/hugepages" + ] + }, + "mqueue": { + "fs_type": "mqueue", + "mount_options": [ + "rw", + "relatime", + "seclabel" + ], + "mounts": [ + "/dev/mqueue" + ] + }, + "systemd-1": { + "fs_type": "autofs", + "mount_options": [ + "rw", + "relatime", + "fd=40", + "pgrp=1", + "timeout=0", + "minproto=5", + "maxproto=5", + "direct", + "pipe_ino=17610" + ], + "mounts": [ + "/proc/sys/fs/binfmt_misc" + ] + }, + "/var/lib/machines.raw": { + "fs_type": "btrfs", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "space_cache", + "subvolid=5", + "subvol=/" + ], + "mounts": [ + "/var/lib/machines" + ] + }, + "fusectl": { + "fs_type": "fusectl", + "mount_options": [ + "rw", + "relatime" + ], + "mounts": [ + "/sys/fs/fuse/connections" + ] + }, + "gvfsd-fuse": { + "fs_type": "fuse.gvfsd-fuse", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "relatime", + "user_id=1000", + "group_id=1000" + ], + "mounts": [ + "/run/user/1000/gvfs" + ] + }, + "binfmt_misc": { + "fs_type": "binfmt_misc", + "mount_options": [ + "rw", + "relatime" + ], + "mounts": [ + "/proc/sys/fs/binfmt_misc" + ] + }, + "/dev/mapper/docker-253:1-1180487-0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8": { + "fs_type": "xfs", + "mount_options": [ + "rw", + "relatime", + "context=\"system_u:object_r:container_file_t:s0:c523", + "c681\"", + "nouuid", + "attr2", + "inode64", + "logbsize=64k", + "sunit=128", + "swidth=128", + "noquota" + ], + "uuid": "00e2aa25-20d8-4ad7-b3a5-c501f2f4c123", + "mounts": [ + "/var/lib/docker/devicemapper/mnt/0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8" + ] + }, + "shm": { + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "context=\"system_u:object_r:container_file_t:s0:c523", + "c681\"", + "size=65536k" + ], + "mounts": [ + "/var/lib/docker/containers/426e513ed508a451e3f70440eed040761f81529e4bc4240e7522d331f3f3bc12/shm" + ] + }, + "nsfs": { + "fs_type": "nsfs", + "mount_options": [ + "rw" + ], + "mounts": [ + "/run/docker/netns/1ce89fd79f3d" + ] + }, + "tracefs": { + "fs_type": "tracefs", + "mount_options": [ + "rw", + "relatime" + ], + "mounts": [ + "/sys/kernel/debug/tracing" + ] + }, + "/dev/loop1": { + "fs_type": "xfs", + "uuid": "00e2aa25-20d8-4ad7-b3a5-c501f2f4c123", + "mounts": [ + + ] + }, + "/dev/mapper/docker-253:1-1180487-pool": { + "mounts": [ + + ] + }, + "/dev/sr0": { + "mounts": [ + + ] + }, + "/dev/loop2": { + "mounts": [ + + ] + }, + "/dev/sda": { + "mounts": [ + + ] + }, + "/dev/sda2": { + "fs_type": "LVM2_member", + "uuid": "66Ojcd-ULtu-1cZa-Tywo-mx0d-RF4O-ysA9jK", + "mounts": [ + + ] + }, + "/dev/mapper/fedora_host--186-swap": { + "fs_type": "swap", + "uuid": "eae6059d-2fbe-4d1c-920d-a80bbeb1ac6d", + "mounts": [ + + ] + } + }, + "by_mountpoint": { + "/dev": { + "kb_size": "8044124", + "kb_used": "0", + "kb_available": "8044124", + "percent_used": "0%", + "total_inodes": "2011031", + "inodes_used": "629", + "inodes_available": "2010402", + "inodes_percent_used": "1%", + "fs_type": "devtmpfs", + "mount_options": [ + "rw", + "nosuid", + "seclabel", + "size=8044124k", + "nr_inodes=2011031", + "mode=755" + ], + "devices": [ + "devtmpfs" + ] + }, + "/dev/shm": { + "kb_size": "8055268", + "kb_used": "96036", + "kb_available": "7959232", + "percent_used": "2%", + "total_inodes": "2013817", + "inodes_used": "217", + "inodes_available": "2013600", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "seclabel" + ], + "devices": [ + "tmpfs" + ] + }, + "/run": { + "kb_size": "8055268", + "kb_used": "2280", + "kb_available": "8052988", + "percent_used": "1%", + "total_inodes": "2013817", + "inodes_used": "1070", + "inodes_available": "2012747", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "seclabel", + "mode=755" + ], + "devices": [ + "tmpfs" + ] + }, + "/sys/fs/cgroup": { + "kb_size": "8055268", + "kb_used": "0", + "kb_available": "8055268", + "percent_used": "0%", + "total_inodes": "2013817", + "inodes_used": "16", + "inodes_available": "2013801", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "ro", + "nosuid", + "nodev", + "noexec", + "seclabel", + "mode=755" + ], + "devices": [ + "tmpfs" + ] + }, + "/": { + "kb_size": "51475068", + "kb_used": "42551284", + "kb_available": "6285960", + "percent_used": "88%", + "total_inodes": "3276800", + "inodes_used": "532908", + "inodes_available": "2743892", + "inodes_percent_used": "17%", + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "d34cf5e3-3449-4a6c-8179-a1feb2bca6ce", + "devices": [ + "/dev/mapper/fedora_host--186-root" + ] + }, + "/tmp": { + "kb_size": "8055268", + "kb_used": "848396", + "kb_available": "7206872", + "percent_used": "11%", + "total_inodes": "2013817", + "inodes_used": "1353", + "inodes_available": "2012464", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "seclabel" + ], + "devices": [ + "tmpfs" + ] + }, + "/boot": { + "kb_size": "487652", + "kb_used": "126628", + "kb_available": "331328", + "percent_used": "28%", + "total_inodes": "128016", + "inodes_used": "405", + "inodes_available": "127611", + "inodes_percent_used": "1%", + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "32caaec3-ef40-4691-a3b6-438c3f9bc1c0", + "devices": [ + "/dev/sda1" + ] + }, + "/home": { + "kb_size": "185948124", + "kb_used": "105904724", + "kb_available": "70574680", + "percent_used": "61%", + "total_inodes": "11821056", + "inodes_used": "1266687", + "inodes_available": "10554369", + "inodes_percent_used": "11%", + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "2d3e4853-fa69-4ccf-8a6a-77b05ab0a42d", + "devices": [ + "/dev/mapper/fedora_host--186-home" + ] + }, + "/var/lib/machines": { + "kb_size": "512000", + "kb_used": "16672", + "kb_available": "429056", + "percent_used": "4%", + "fs_type": "btrfs", + "uuid": "0f031512-ab15-497d-9abd-3a512b4a9390", + "devices": [ + "/dev/loop0", + "/var/lib/machines.raw" + ], + "mount_options": [ + "rw", + "relatime", + "seclabel", + "space_cache", + "subvolid=5", + "subvol=/" + ] + }, + "/run/user/0": { + "kb_size": "1611052", + "kb_used": "0", + "kb_available": "1611052", + "percent_used": "0%", + "total_inodes": "2013817", + "inodes_used": "7", + "inodes_available": "2013810", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "relatime", + "seclabel", + "size=1611052k", + "mode=700" + ], + "devices": [ + "tmpfs" + ] + }, + "/run/user/1000": { + "kb_size": "1611052", + "kb_used": "72", + "kb_available": "1610980", + "percent_used": "1%", + "total_inodes": "2013817", + "inodes_used": "36", + "inodes_available": "2013781", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "relatime", + "seclabel", + "size=1611052k", + "mode=700", + "uid=1000", + "gid=1000" + ], + "devices": [ + "tmpfs" + ] + }, + "/sys": { + "fs_type": "sysfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "seclabel" + ], + "devices": [ + "sysfs" + ] + }, + "/proc": { + "fs_type": "proc", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime" + ], + "devices": [ + "proc" + ] + }, + "/sys/kernel/security": { + "fs_type": "securityfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime" + ], + "devices": [ + "securityfs" + ] + }, + "/dev/pts": { + "fs_type": "devpts", + "mount_options": [ + "rw", + "nosuid", + "noexec", + "relatime", + "seclabel", + "gid=5", + "mode=620", + "ptmxmode=000" + ], + "devices": [ + "devpts" + ] + }, + "/sys/fs/cgroup/systemd": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "xattr", + "release_agent=/usr/lib/systemd/systemd-cgroups-agent", + "name=systemd" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/fs/pstore": { + "fs_type": "pstore", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "seclabel" + ], + "devices": [ + "pstore" + ] + }, + "/sys/fs/cgroup/devices": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "devices" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/fs/cgroup/cpuset": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "cpuset" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/fs/cgroup/perf_event": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "perf_event" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/fs/cgroup/hugetlb": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "hugetlb" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/fs/cgroup/cpu,cpuacct": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "cpu", + "cpuacct" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/fs/cgroup/blkio": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "blkio" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/fs/cgroup/freezer": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "freezer" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/fs/cgroup/memory": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "memory" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/fs/cgroup/pids": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "pids" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/fs/cgroup/net_cls,net_prio": { + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "net_cls", + "net_prio" + ], + "devices": [ + "cgroup" + ] + }, + "/sys/kernel/config": { + "fs_type": "configfs", + "mount_options": [ + "rw", + "relatime" + ], + "devices": [ + "configfs" + ] + }, + "/sys/fs/selinux": { + "fs_type": "selinuxfs", + "mount_options": [ + "rw", + "relatime" + ], + "devices": [ + "selinuxfs" + ] + }, + "/sys/kernel/debug": { + "fs_type": "debugfs", + "mount_options": [ + "rw", + "relatime", + "seclabel" + ], + "devices": [ + "debugfs" + ] + }, + "/dev/hugepages": { + "fs_type": "hugetlbfs", + "mount_options": [ + "rw", + "relatime", + "seclabel" + ], + "devices": [ + "hugetlbfs" + ] + }, + "/dev/mqueue": { + "fs_type": "mqueue", + "mount_options": [ + "rw", + "relatime", + "seclabel" + ], + "devices": [ + "mqueue" + ] + }, + "/proc/sys/fs/binfmt_misc": { + "fs_type": "binfmt_misc", + "mount_options": [ + "rw", + "relatime" + ], + "devices": [ + "systemd-1", + "binfmt_misc" + ] + }, + "/sys/fs/fuse/connections": { + "fs_type": "fusectl", + "mount_options": [ + "rw", + "relatime" + ], + "devices": [ + "fusectl" + ] + }, + "/run/user/1000/gvfs": { + "fs_type": "fuse.gvfsd-fuse", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "relatime", + "user_id=1000", + "group_id=1000" + ], + "devices": [ + "gvfsd-fuse" + ] + }, + "/var/lib/docker/devicemapper": { + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "d34cf5e3-3449-4a6c-8179-a1feb2bca6ce", + "devices": [ + "/dev/mapper/fedora_host--186-root" + ] + }, + { + "/run/docker/netns/1ce89fd79f3d": { + "fs_type": "nsfs", + "mount_options": [ + "rw" + ], + "devices": [ + "nsfs" + ] + }, + "/sys/kernel/debug/tracing": { + "fs_type": "tracefs", + "mount_options": [ + "rw", + "relatime" + ], + "devices": [ + "tracefs" + ] + } + }, + "by_pair": { + "devtmpfs,/dev": { + "device": "devtmpfs", + "kb_size": "8044124", + "kb_used": "0", + "kb_available": "8044124", + "percent_used": "0%", + "mount": "/dev", + "total_inodes": "2011031", + "inodes_used": "629", + "inodes_available": "2010402", + "inodes_percent_used": "1%", + "fs_type": "devtmpfs", + "mount_options": [ + "rw", + "nosuid", + "seclabel", + "size=8044124k", + "nr_inodes=2011031", + "mode=755" + ] + }, + "tmpfs,/dev/shm": { + "device": "tmpfs", + "kb_size": "8055268", + "kb_used": "96036", + "kb_available": "7959232", + "percent_used": "2%", + "mount": "/dev/shm", + "total_inodes": "2013817", + "inodes_used": "217", + "inodes_available": "2013600", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "seclabel" + ] + }, + "tmpfs,/run": { + "device": "tmpfs", + "kb_size": "8055268", + "kb_used": "2280", + "kb_available": "8052988", + "percent_used": "1%", + "mount": "/run", + "total_inodes": "2013817", + "inodes_used": "1070", + "inodes_available": "2012747", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "seclabel", + "mode=755" + ] + }, + "tmpfs,/sys/fs/cgroup": { + "device": "tmpfs", + "kb_size": "8055268", + "kb_used": "0", + "kb_available": "8055268", + "percent_used": "0%", + "mount": "/sys/fs/cgroup", + "total_inodes": "2013817", + "inodes_used": "16", + "inodes_available": "2013801", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "ro", + "nosuid", + "nodev", + "noexec", + "seclabel", + "mode=755" + ] + }, + "/dev/mapper/fedora_host--186-root,/": { + "device": "/dev/mapper/fedora_host--186-root", + "kb_size": "51475068", + "kb_used": "42551284", + "kb_available": "6285960", + "percent_used": "88%", + "mount": "/", + "total_inodes": "3276800", + "inodes_used": "532908", + "inodes_available": "2743892", + "inodes_percent_used": "17%", + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "d34cf5e3-3449-4a6c-8179-a1feb2bca6ce" + }, + "tmpfs,/tmp": { + "device": "tmpfs", + "kb_size": "8055268", + "kb_used": "848396", + "kb_available": "7206872", + "percent_used": "11%", + "mount": "/tmp", + "total_inodes": "2013817", + "inodes_used": "1353", + "inodes_available": "2012464", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "seclabel" + ] + }, + "/dev/sda1,/boot": { + "device": "/dev/sda1", + "kb_size": "487652", + "kb_used": "126628", + "kb_available": "331328", + "percent_used": "28%", + "mount": "/boot", + "total_inodes": "128016", + "inodes_used": "405", + "inodes_available": "127611", + "inodes_percent_used": "1%", + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "32caaec3-ef40-4691-a3b6-438c3f9bc1c0" + }, + "/dev/mapper/fedora_host--186-home,/home": { + "device": "/dev/mapper/fedora_host--186-home", + "kb_size": "185948124", + "kb_used": "105904724", + "kb_available": "70574680", + "percent_used": "61%", + "mount": "/home", + "total_inodes": "11821056", + "inodes_used": "1266687", + "inodes_available": "10554369", + "inodes_percent_used": "11%", + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "2d3e4853-fa69-4ccf-8a6a-77b05ab0a42d" + }, + "/dev/loop0,/var/lib/machines": { + "device": "/dev/loop0", + "kb_size": "512000", + "kb_used": "16672", + "kb_available": "429056", + "percent_used": "4%", + "mount": "/var/lib/machines", + "fs_type": "btrfs", + "uuid": "0f031512-ab15-497d-9abd-3a512b4a9390" + }, + "tmpfs,/run/user/0": { + "device": "tmpfs", + "kb_size": "1611052", + "kb_used": "0", + "kb_available": "1611052", + "percent_used": "0%", + "mount": "/run/user/0", + "total_inodes": "2013817", + "inodes_used": "7", + "inodes_available": "2013810", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "relatime", + "seclabel", + "size=1611052k", + "mode=700" + ] + }, + "tmpfs,/run/user/1000": { + "device": "tmpfs", + "kb_size": "1611052", + "kb_used": "72", + "kb_available": "1610980", + "percent_used": "1%", + "mount": "/run/user/1000", + "total_inodes": "2013817", + "inodes_used": "36", + "inodes_available": "2013781", + "inodes_percent_used": "1%", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "relatime", + "seclabel", + "size=1611052k", + "mode=700", + "uid=1000", + "gid=1000" + ] + }, + "sysfs,/sys": { + "device": "sysfs", + "mount": "/sys", + "fs_type": "sysfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "seclabel" + ] + }, + "proc,/proc": { + "device": "proc", + "mount": "/proc", + "fs_type": "proc", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime" + ] + }, + "securityfs,/sys/kernel/security": { + "device": "securityfs", + "mount": "/sys/kernel/security", + "fs_type": "securityfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime" + ] + }, + "devpts,/dev/pts": { + "device": "devpts", + "mount": "/dev/pts", + "fs_type": "devpts", + "mount_options": [ + "rw", + "nosuid", + "noexec", + "relatime", + "seclabel", + "gid=5", + "mode=620", + "ptmxmode=000" + ] + }, + "cgroup,/sys/fs/cgroup/systemd": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/systemd", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "xattr", + "release_agent=/usr/lib/systemd/systemd-cgroups-agent", + "name=systemd" + ] + }, + "pstore,/sys/fs/pstore": { + "device": "pstore", + "mount": "/sys/fs/pstore", + "fs_type": "pstore", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "seclabel" + ] + }, + "cgroup,/sys/fs/cgroup/devices": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/devices", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "devices" + ] + }, + "cgroup,/sys/fs/cgroup/cpuset": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/cpuset", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "cpuset" + ] + }, + "cgroup,/sys/fs/cgroup/perf_event": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/perf_event", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "perf_event" + ] + }, + "cgroup,/sys/fs/cgroup/hugetlb": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/hugetlb", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "hugetlb" + ] + }, + "cgroup,/sys/fs/cgroup/cpu,cpuacct": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/cpu,cpuacct", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "cpu", + "cpuacct" + ] + }, + "cgroup,/sys/fs/cgroup/blkio": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/blkio", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "blkio" + ] + }, + "cgroup,/sys/fs/cgroup/freezer": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/freezer", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "freezer" + ] + }, + "cgroup,/sys/fs/cgroup/memory": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/memory", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "memory" + ] + }, + "cgroup,/sys/fs/cgroup/pids": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/pids", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "pids" + ] + }, + "cgroup,/sys/fs/cgroup/net_cls,net_prio": { + "device": "cgroup", + "mount": "/sys/fs/cgroup/net_cls,net_prio", + "fs_type": "cgroup", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "net_cls", + "net_prio" + ] + }, + "configfs,/sys/kernel/config": { + "device": "configfs", + "mount": "/sys/kernel/config", + "fs_type": "configfs", + "mount_options": [ + "rw", + "relatime" + ] + }, + "selinuxfs,/sys/fs/selinux": { + "device": "selinuxfs", + "mount": "/sys/fs/selinux", + "fs_type": "selinuxfs", + "mount_options": [ + "rw", + "relatime" + ] + }, + "debugfs,/sys/kernel/debug": { + "device": "debugfs", + "mount": "/sys/kernel/debug", + "fs_type": "debugfs", + "mount_options": [ + "rw", + "relatime", + "seclabel" + ] + }, + "hugetlbfs,/dev/hugepages": { + "device": "hugetlbfs", + "mount": "/dev/hugepages", + "fs_type": "hugetlbfs", + "mount_options": [ + "rw", + "relatime", + "seclabel" + ] + }, + "mqueue,/dev/mqueue": { + "device": "mqueue", + "mount": "/dev/mqueue", + "fs_type": "mqueue", + "mount_options": [ + "rw", + "relatime", + "seclabel" + ] + }, + "systemd-1,/proc/sys/fs/binfmt_misc": { + "device": "systemd-1", + "mount": "/proc/sys/fs/binfmt_misc", + "fs_type": "autofs", + "mount_options": [ + "rw", + "relatime", + "fd=40", + "pgrp=1", + "timeout=0", + "minproto=5", + "maxproto=5", + "direct", + "pipe_ino=17610" + ] + }, + "/var/lib/machines.raw,/var/lib/machines": { + "device": "/var/lib/machines.raw", + "mount": "/var/lib/machines", + "fs_type": "btrfs", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "space_cache", + "subvolid=5", + "subvol=/" + ] + }, + "fusectl,/sys/fs/fuse/connections": { + "device": "fusectl", + "mount": "/sys/fs/fuse/connections", + "fs_type": "fusectl", + "mount_options": [ + "rw", + "relatime" + ] + }, + "gvfsd-fuse,/run/user/1000/gvfs": { + "device": "gvfsd-fuse", + "mount": "/run/user/1000/gvfs", + "fs_type": "fuse.gvfsd-fuse", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "relatime", + "user_id=1000", + "group_id=1000" + ] + }, + "/dev/mapper/fedora_host--186-root,/var/lib/docker/devicemapper": { + "device": "/dev/mapper/fedora_host--186-root", + "mount": "/var/lib/docker/devicemapper", + "fs_type": "ext4", + "mount_options": [ + "rw", + "relatime", + "seclabel", + "data=ordered" + ], + "uuid": "d34cf5e3-3449-4a6c-8179-a1feb2bca6ce" + }, + "binfmt_misc,/proc/sys/fs/binfmt_misc": { + "device": "binfmt_misc", + "mount": "/proc/sys/fs/binfmt_misc", + "fs_type": "binfmt_misc", + "mount_options": [ + "rw", + "relatime" + ] + }, + "/dev/mapper/docker-253:1-1180487-0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8,/var/lib/docker/devicemapper/mnt/0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8": { + "device": "/dev/mapper/docker-253:1-1180487-0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8", + "mount": "/var/lib/docker/devicemapper/mnt/0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8", + "fs_type": "xfs", + "mount_options": [ + "rw", + "relatime", + "context=\"system_u:object_r:container_file_t:s0:c523", + "c681\"", + "nouuid", + "attr2", + "inode64", + "logbsize=64k", + "sunit=128", + "swidth=128", + "noquota" + ], + "uuid": "00e2aa25-20d8-4ad7-b3a5-c501f2f4c123" + }, + "shm,/var/lib/docker/containers/426e513ed508a451e3f70440eed040761f81529e4bc4240e7522d331f3f3bc12/shm": { + "device": "shm", + "mount": "/var/lib/docker/containers/426e513ed508a451e3f70440eed040761f81529e4bc4240e7522d331f3f3bc12/shm", + "fs_type": "tmpfs", + "mount_options": [ + "rw", + "nosuid", + "nodev", + "noexec", + "relatime", + "context=\"system_u:object_r:container_file_t:s0:c523", + "c681\"", + "size=65536k" + ] + }, + "nsfs,/run/docker/netns/1ce89fd79f3d": { + "device": "nsfs", + "mount": "/run/docker/netns/1ce89fd79f3d", + "fs_type": "nsfs", + "mount_options": [ + "rw" + ] + }, + "tracefs,/sys/kernel/debug/tracing": { + "device": "tracefs", + "mount": "/sys/kernel/debug/tracing", + "fs_type": "tracefs", + "mount_options": [ + "rw", + "relatime" + ] + }, + "/dev/loop1,": { + "device": "/dev/loop1", + "fs_type": "xfs", + "uuid": "00e2aa25-20d8-4ad7-b3a5-c501f2f4c123" + }, + "/dev/mapper/docker-253:1-1180487-pool,": { + "device": "/dev/mapper/docker-253:1-1180487-pool" + }, + "/dev/sr0,": { + "device": "/dev/sr0" + }, + "/dev/loop2,": { + "device": "/dev/loop2" + }, + "/dev/sda,": { + "device": "/dev/sda" + }, + "/dev/sda2,": { + "device": "/dev/sda2", + "fs_type": "LVM2_member", + "uuid": "66Ojcd-ULtu-1cZa-Tywo-mx0d-RF4O-ysA9jK" + }, + "/dev/mapper/fedora_host--186-swap,": { + "device": "/dev/mapper/fedora_host--186-swap", + "fs_type": "swap", + "uuid": "eae6059d-2fbe-4d1c-920d-a80bbeb1ac6d" + } + } + }, + "virtualization": { + "systems": { + "kvm": "host" + }, + "system": "kvm", + "role": "host", + "libvirt_version": "2.2.0", + "uri": "qemu:///system", + "capabilities": { + + }, + "nodeinfo": { + "cores": 4, + "cpus": 8, + "memory": 16110540, + "mhz": 2832, + "model": "x86_64", + "nodes": 1, + "sockets": 1, + "threads": 2 + }, + "domains": { + + }, + "networks": { + "vagrant-libvirt": { + "bridge_name": "virbr1", + "uuid": "877ddb27-b39c-427e-a7bf-1aa829389eeb" + }, + "default": { + "bridge_name": "virbr0", + "uuid": "750d2567-23a8-470d-8a2b-71cd651e30d1" + } + }, + "storage": { + "virt-images": { + "autostart": true, + "uuid": "d8a189fa-f98c-462f-9ea4-204eb77a96a1", + "allocation": 106412863488, + "available": 83998015488, + "capacity": 190410878976, + "state": 2, + "volumes": { + "rhel-atomic-host-standard-2014-7-1.qcow2": { + "key": "/home/some_user/virt-images/rhel-atomic-host-standard-2014-7-1.qcow2", + "name": "rhel-atomic-host-standard-2014-7-1.qcow2", + "path": "/home/some_user/virt-images/rhel-atomic-host-standard-2014-7-1.qcow2", + "allocation": 1087115264, + "capacity": 8589934592, + "type": 0 + }, + "atomic-beta-instance-7.qcow2": { + "key": "/home/some_user/virt-images/atomic-beta-instance-7.qcow2", + "name": "atomic-beta-instance-7.qcow2", + "path": "/home/some_user/virt-images/atomic-beta-instance-7.qcow2", + "allocation": 200704, + "capacity": 8589934592, + "type": 0 + }, + "os1-atomic-meta-data": { + "key": "/home/some_user/virt-images/os1-atomic-meta-data", + "name": "os1-atomic-meta-data", + "path": "/home/some_user/virt-images/os1-atomic-meta-data", + "allocation": 4096, + "capacity": 49, + "type": 0 + }, + "atomic-user-data": { + "key": "/home/some_user/virt-images/atomic-user-data", + "name": "atomic-user-data", + "path": "/home/some_user/virt-images/atomic-user-data", + "allocation": 4096, + "capacity": 512, + "type": 0 + }, + "qemu-snap.txt": { + "key": "/home/some_user/virt-images/qemu-snap.txt", + "name": "qemu-snap.txt", + "path": "/home/some_user/virt-images/qemu-snap.txt", + "allocation": 4096, + "capacity": 111, + "type": 0 + }, + "atomic-beta-instance-5.qcow2": { + "key": "/home/some_user/virt-images/atomic-beta-instance-5.qcow2", + "name": "atomic-beta-instance-5.qcow2", + "path": "/home/some_user/virt-images/atomic-beta-instance-5.qcow2", + "allocation": 339091456, + "capacity": 8589934592, + "type": 0 + }, + "meta-data": { + "key": "/home/some_user/virt-images/meta-data", + "name": "meta-data", + "path": "/home/some_user/virt-images/meta-data", + "allocation": 4096, + "capacity": 49, + "type": 0 + }, + "atomic-beta-instance-8.qcow2": { + "key": "/home/some_user/virt-images/atomic-beta-instance-8.qcow2", + "name": "atomic-beta-instance-8.qcow2", + "path": "/home/some_user/virt-images/atomic-beta-instance-8.qcow2", + "allocation": 322576384, + "capacity": 8589934592, + "type": 0 + }, + "user-data": { + "key": "/home/some_user/virt-images/user-data", + "name": "user-data", + "path": "/home/some_user/virt-images/user-data", + "allocation": 4096, + "capacity": 512, + "type": 0 + }, + "rhel-6-2015-10-16.qcow2": { + "key": "/home/some_user/virt-images/rhel-6-2015-10-16.qcow2", + "name": "rhel-6-2015-10-16.qcow2", + "path": "/home/some_user/virt-images/rhel-6-2015-10-16.qcow2", + "allocation": 7209422848, + "capacity": 17179869184, + "type": 0 + }, + "atomic_demo_notes.txt": { + "key": "/home/some_user/virt-images/atomic_demo_notes.txt", + "name": "atomic_demo_notes.txt", + "path": "/home/some_user/virt-images/atomic_demo_notes.txt", + "allocation": 4096, + "capacity": 354, + "type": 0 + }, + "packer-windows-2012-R2-standard": { + "key": "/home/some_user/virt-images/packer-windows-2012-R2-standard", + "name": "packer-windows-2012-R2-standard", + "path": "/home/some_user/virt-images/packer-windows-2012-R2-standard", + "allocation": 16761495552, + "capacity": 64424509440, + "type": 0 + }, + "atomic3-cidata.iso": { + "key": "/home/some_user/virt-images/atomic3-cidata.iso", + "name": "atomic3-cidata.iso", + "path": "/home/some_user/virt-images/atomic3-cidata.iso", + "allocation": 376832, + "capacity": 374784, + "type": 0 + }, + ".atomic_demo_notes.txt.swp": { + "key": "/home/some_user/virt-images/.atomic_demo_notes.txt.swp", + "name": ".atomic_demo_notes.txt.swp", + "path": "/home/some_user/virt-images/.atomic_demo_notes.txt.swp", + "allocation": 12288, + "capacity": 12288, + "type": 0 + }, + "rhel7-2015-10-13.qcow2": { + "key": "/home/some_user/virt-images/rhel7-2015-10-13.qcow2", + "name": "rhel7-2015-10-13.qcow2", + "path": "/home/some_user/virt-images/rhel7-2015-10-13.qcow2", + "allocation": 4679413760, + "capacity": 12884901888, + "type": 0 + } + } + }, + "default": { + "autostart": true, + "uuid": "c8d9d160-efc0-4207-81c2-e79d6628f7e1", + "allocation": 43745488896, + "available": 8964980736, + "capacity": 52710469632, + "state": 2, + "volumes": { + "s3than-VAGRANTSLASH-trusty64_vagrant_box_image_0.0.1.img": { + "key": "/var/lib/libvirt/images/s3than-VAGRANTSLASH-trusty64_vagrant_box_image_0.0.1.img", + "name": "s3than-VAGRANTSLASH-trusty64_vagrant_box_image_0.0.1.img", + "path": "/var/lib/libvirt/images/s3than-VAGRANTSLASH-trusty64_vagrant_box_image_0.0.1.img", + "allocation": 1258622976, + "capacity": 42949672960, + "type": 0 + }, + "centos-7.0_vagrant_box_image.img": { + "key": "/var/lib/libvirt/images/centos-7.0_vagrant_box_image.img", + "name": "centos-7.0_vagrant_box_image.img", + "path": "/var/lib/libvirt/images/centos-7.0_vagrant_box_image.img", + "allocation": 1649414144, + "capacity": 42949672960, + "type": 0 + }, + "baremettle-VAGRANTSLASH-centos-5.10_vagrant_box_image_1.0.0.img": { + "key": "/var/lib/libvirt/images/baremettle-VAGRANTSLASH-centos-5.10_vagrant_box_image_1.0.0.img", + "name": "baremettle-VAGRANTSLASH-centos-5.10_vagrant_box_image_1.0.0.img", + "path": "/var/lib/libvirt/images/baremettle-VAGRANTSLASH-centos-5.10_vagrant_box_image_1.0.0.img", + "allocation": 810422272, + "capacity": 42949672960, + "type": 0 + }, + "centos-6_vagrant_box_image.img": { + "key": "/var/lib/libvirt/images/centos-6_vagrant_box_image.img", + "name": "centos-6_vagrant_box_image.img", + "path": "/var/lib/libvirt/images/centos-6_vagrant_box_image.img", + "allocation": 1423642624, + "capacity": 42949672960, + "type": 0 + }, + "centos5-ansible_default.img": { + "key": "/var/lib/libvirt/images/centos5-ansible_default.img", + "name": "centos5-ansible_default.img", + "path": "/var/lib/libvirt/images/centos5-ansible_default.img", + "allocation": 8986624, + "capacity": 42949672960, + "type": 0 + }, + "ubuntu_default.img": { + "key": "/var/lib/libvirt/images/ubuntu_default.img", + "name": "ubuntu_default.img", + "path": "/var/lib/libvirt/images/ubuntu_default.img", + "allocation": 3446833152, + "capacity": 42949672960, + "type": 0 + } + } + }, + "boot-scratch": { + "autostart": true, + "uuid": "e5ef4360-b889-4843-84fb-366e8fb30f20", + "allocation": 43745488896, + "available": 8964980736, + "capacity": 52710469632, + "state": 2, + "volumes": { + + } + } + } + }, + "network": { + "interfaces": { + "lo": { + "mtu": "65536", + "flags": [ + "LOOPBACK", + "UP", + "LOWER_UP" + ], + "encapsulation": "Loopback", + "addresses": { + "127.0.0.1": { + "family": "inet", + "prefixlen": "8", + "netmask": "255.0.0.0", + "scope": "Node", + "ip_scope": "LOOPBACK" + }, + "::1": { + "family": "inet6", + "prefixlen": "128", + "scope": "Node", + "tags": [ + + ], + "ip_scope": "LINK LOCAL LOOPBACK" + } + }, + "state": "unknown" + }, + "em1": { + "type": "em", + "number": "1", + "mtu": "1500", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP" + ], + "encapsulation": "Ethernet", + "addresses": { + "3C:97:0E:E9:28:8E": { + "family": "lladdr" + } + }, + "state": "down", + "link_speed": 0, + "duplex": "Unknown! (255)", + "port": "Twisted Pair", + "transceiver": "internal", + "auto_negotiation": "on", + "mdi_x": "Unknown (auto)", + "ring_params": { + "max_rx": 4096, + "max_rx_mini": 0, + "max_rx_jumbo": 0, + "max_tx": 4096, + "current_rx": 256, + "current_rx_mini": 0, + "current_rx_jumbo": 0, + "current_tx": 256 + } + }, + "wlp4s0": { + "type": "wlp4s", + "number": "0", + "mtu": "1500", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "encapsulation": "Ethernet", + "addresses": { + "5C:51:4F:E6:A8:E3": { + "family": "lladdr" + }, + "192.168.1.19": { + "family": "inet", + "prefixlen": "24", + "netmask": "255.255.255.0", + "broadcast": "192.168.1.255", + "scope": "Global", + "ip_scope": "RFC1918 PRIVATE" + }, + "fe80::5e51:4fff:fee6:a8e3": { + "family": "inet6", + "prefixlen": "64", + "scope": "Link", + "tags": [ + + ], + "ip_scope": "LINK LOCAL UNICAST" + } + }, + "state": "up", + "arp": { + "192.168.1.33": "00:11:d9:39:3e:e0", + "192.168.1.20": "ac:3a:7a:a7:49:e8", + "192.168.1.17": "00:09:b0:d0:64:19", + "192.168.1.22": "ac:bc:32:82:30:bb", + "192.168.1.15": "00:11:32:2e:10:d5", + "192.168.1.1": "84:1b:5e:03:50:b2", + "192.168.1.34": "00:11:d9:5f:e8:e6", + "192.168.1.16": "dc:a5:f4:ac:22:3a", + "192.168.1.21": "74:c2:46:73:28:d8", + "192.168.1.27": "00:17:88:09:3c:bb", + "192.168.1.24": "08:62:66:90:a2:b8" + }, + "routes": [ + { + "destination": "default", + "family": "inet", + "via": "192.168.1.1", + "metric": "600", + "proto": "static" + }, + { + "destination": "66.187.232.64", + "family": "inet", + "via": "192.168.1.1", + "metric": "600", + "proto": "static" + }, + { + "destination": "192.168.1.0/24", + "family": "inet", + "scope": "link", + "metric": "600", + "proto": "kernel", + "src": "192.168.1.19" + }, + { + "destination": "192.168.1.1", + "family": "inet", + "scope": "link", + "metric": "600", + "proto": "static" + }, + { + "destination": "fe80::/64", + "family": "inet6", + "metric": "256", + "proto": "kernel" + } + ], + "ring_params": { + "max_rx": 0, + "max_rx_mini": 0, + "max_rx_jumbo": 0, + "max_tx": 0, + "current_rx": 0, + "current_rx_mini": 0, + "current_rx_jumbo": 0, + "current_tx": 0 + } + }, + "virbr1": { + "type": "virbr", + "number": "1", + "mtu": "1500", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP" + ], + "encapsulation": "Ethernet", + "addresses": { + "52:54:00:B4:68:A9": { + "family": "lladdr" + }, + "192.168.121.1": { + "family": "inet", + "prefixlen": "24", + "netmask": "255.255.255.0", + "broadcast": "192.168.121.255", + "scope": "Global", + "ip_scope": "RFC1918 PRIVATE" + } + }, + "state": "1", + "routes": [ + { + "destination": "192.168.121.0/24", + "family": "inet", + "scope": "link", + "proto": "kernel", + "src": "192.168.121.1" + } + ], + "ring_params": { + + } + }, + "virbr1-nic": { + "type": "virbr", + "number": "1-nic", + "mtu": "1500", + "flags": [ + "BROADCAST", + "MULTICAST" + ], + "encapsulation": "Ethernet", + "addresses": { + "52:54:00:B4:68:A9": { + "family": "lladdr" + } + }, + "state": "disabled", + "link_speed": 10, + "duplex": "Full", + "port": "Twisted Pair", + "transceiver": "internal", + "auto_negotiation": "off", + "mdi_x": "Unknown", + "ring_params": { + + } + }, + "virbr0": { + "type": "virbr", + "number": "0", + "mtu": "1500", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP" + ], + "encapsulation": "Ethernet", + "addresses": { + "52:54:00:CE:82:5E": { + "family": "lladdr" + }, + "192.168.137.1": { + "family": "inet", + "prefixlen": "24", + "netmask": "255.255.255.0", + "broadcast": "192.168.137.255", + "scope": "Global", + "ip_scope": "RFC1918 PRIVATE" + } + }, + "state": "1", + "routes": [ + { + "destination": "192.168.137.0/24", + "family": "inet", + "scope": "link", + "proto": "kernel", + "src": "192.168.137.1" + } + ], + "ring_params": { + + } + }, + "virbr0-nic": { + "type": "virbr", + "number": "0-nic", + "mtu": "1500", + "flags": [ + "BROADCAST", + "MULTICAST" + ], + "encapsulation": "Ethernet", + "addresses": { + "52:54:00:CE:82:5E": { + "family": "lladdr" + } + }, + "state": "disabled", + "link_speed": 10, + "duplex": "Full", + "port": "Twisted Pair", + "transceiver": "internal", + "auto_negotiation": "off", + "mdi_x": "Unknown", + "ring_params": { + + } + }, + "docker0": { + "type": "docker", + "number": "0", + "mtu": "1500", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "encapsulation": "Ethernet", + "addresses": { + "02:42:EA:15:D8:84": { + "family": "lladdr" + }, + "172.17.0.1": { + "family": "inet", + "prefixlen": "16", + "netmask": "255.255.0.0", + "scope": "Global", + "ip_scope": "RFC1918 PRIVATE" + }, + "fe80::42:eaff:fe15:d884": { + "family": "inet6", + "prefixlen": "64", + "scope": "Link", + "tags": [ + + ], + "ip_scope": "LINK LOCAL UNICAST" + } + }, + "state": "0", + "arp": { + "172.17.0.2": "02:42:ac:11:00:02", + "172.17.0.4": "02:42:ac:11:00:04", + "172.17.0.3": "02:42:ac:11:00:03" + }, + "routes": [ + { + "destination": "172.17.0.0/16", + "family": "inet", + "scope": "link", + "proto": "kernel", + "src": "172.17.0.1" + }, + { + "destination": "fe80::/64", + "family": "inet6", + "metric": "256", + "proto": "kernel" + } + ], + "ring_params": { + + } + }, + "vethf20ff12": { + "type": "vethf20ff1", + "number": "2", + "mtu": "1500", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "encapsulation": "Ethernet", + "addresses": { + "AE:6E:2B:1E:A1:31": { + "family": "lladdr" + }, + "fe80::ac6e:2bff:fe1e:a131": { + "family": "inet6", + "prefixlen": "64", + "scope": "Link", + "tags": [ + + ], + "ip_scope": "LINK LOCAL UNICAST" + } + }, + "state": "forwarding", + "routes": [ + { + "destination": "fe80::/64", + "family": "inet6", + "metric": "256", + "proto": "kernel" + } + ], + "link_speed": 10000, + "duplex": "Full", + "port": "Twisted Pair", + "transceiver": "internal", + "auto_negotiation": "off", + "mdi_x": "Unknown", + "ring_params": { + + } + }, + "tun0": { + "type": "tun", + "number": "0", + "mtu": "1360", + "flags": [ + "MULTICAST", + "NOARP", + "UP", + "LOWER_UP" + ], + "addresses": { + "10.10.120.68": { + "family": "inet", + "prefixlen": "21", + "netmask": "255.255.248.0", + "broadcast": "10.10.127.255", + "scope": "Global", + "ip_scope": "RFC1918 PRIVATE" + }, + "fe80::365e:885c:31ca:7670": { + "family": "inet6", + "prefixlen": "64", + "scope": "Link", + "tags": [ + "flags", + "800" + ], + "ip_scope": "LINK LOCAL UNICAST" + } + }, + "state": "unknown", + "routes": [ + { + "destination": "10.0.0.0/8", + "family": "inet", + "via": "10.10.120.1", + "metric": "50", + "proto": "static" + }, + { + "destination": "10.10.120.0/21", + "family": "inet", + "scope": "link", + "metric": "50", + "proto": "kernel", + "src": "10.10.120.68" + }, + { + "destination": "fe80::/64", + "family": "inet6", + "metric": "256", + "proto": "kernel" + } + ] + } + }, + "default_interface": "wlp4s0", + "default_gateway": "192.168.1.1" + }, + "counters": { + "network": { + "interfaces": { + "lo": { + "tx": { + "queuelen": "1", + "bytes": "202568405", + "packets": "1845473", + "errors": "0", + "drop": "0", + "carrier": "0", + "collisions": "0" + }, + "rx": { + "bytes": "202568405", + "packets": "1845473", + "errors": "0", + "drop": "0", + "overrun": "0" + } + }, + "em1": { + "tx": { + "queuelen": "1000", + "bytes": "673898037", + "packets": "1631282", + "errors": "0", + "drop": "0", + "carrier": "0", + "collisions": "0" + }, + "rx": { + "bytes": "1536186718", + "packets": "1994394", + "errors": "0", + "drop": "0", + "overrun": "0" + } + }, + "wlp4s0": { + "tx": { + "queuelen": "1000", + "bytes": "3927670539", + "packets": "15146886", + "errors": "0", + "drop": "0", + "carrier": "0", + "collisions": "0" + }, + "rx": { + "bytes": "12367173401", + "packets": "23981258", + "errors": "0", + "drop": "0", + "overrun": "0" + } + }, + "virbr1": { + "tx": { + "queuelen": "1000", + "bytes": "0", + "packets": "0", + "errors": "0", + "drop": "0", + "carrier": "0", + "collisions": "0" + }, + "rx": { + "bytes": "0", + "packets": "0", + "errors": "0", + "drop": "0", + "overrun": "0" + } + }, + "virbr1-nic": { + "tx": { + "queuelen": "1000", + "bytes": "0", + "packets": "0", + "errors": "0", + "drop": "0", + "carrier": "0", + "collisions": "0" + }, + "rx": { + "bytes": "0", + "packets": "0", + "errors": "0", + "drop": "0", + "overrun": "0" + } + }, + "virbr0": { + "tx": { + "queuelen": "1000", + "bytes": "0", + "packets": "0", + "errors": "0", + "drop": "0", + "carrier": "0", + "collisions": "0" + }, + "rx": { + "bytes": "0", + "packets": "0", + "errors": "0", + "drop": "0", + "overrun": "0" + } + }, + "virbr0-nic": { + "tx": { + "queuelen": "1000", + "bytes": "0", + "packets": "0", + "errors": "0", + "drop": "0", + "carrier": "0", + "collisions": "0" + }, + "rx": { + "bytes": "0", + "packets": "0", + "errors": "0", + "drop": "0", + "overrun": "0" + } + }, + "docker0": { + "rx": { + "bytes": "2471313", + "packets": "36915", + "errors": "0", + "drop": "0", + "overrun": "0" + }, + "tx": { + "bytes": "413371670", + "packets": "127713", + "errors": "0", + "drop": "0", + "carrier": "0", + "collisions": "0" + } + }, + "vethf20ff12": { + "rx": { + "bytes": "34391", + "packets": "450", + "errors": "0", + "drop": "0", + "overrun": "0" + }, + "tx": { + "bytes": "17919115", + "packets": "108069", + "errors": "0", + "drop": "0", + "carrier": "0", + "collisions": "0" + } + }, + "tun0": { + "tx": { + "queuelen": "100", + "bytes": "22343462", + "packets": "253442", + "errors": "0", + "drop": "0", + "carrier": "0", + "collisions": "0" + }, + "rx": { + "bytes": "115160002", + "packets": "197529", + "errors": "0", + "drop": "0", + "overrun": "0" + } + } + } + } + }, + "ipaddress": "192.168.1.19", + "macaddress": "5C:51:4F:E6:A8:E3", + "ip6address": "fe80::42:eaff:fe15:d884", + "cpu": { + "0": { + "vendor_id": "GenuineIntel", + "family": "6", + "model": "60", + "model_name": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz", + "stepping": "3", + "mhz": "3238.714", + "cache_size": "6144 KB", + "physical_id": "0", + "core_id": "0", + "cores": "4", + "flags": [ + "fpu", + "vme", + "de", + "pse", + "tsc", + "msr", + "pae", + "mce", + "cx8", + "apic", + "sep", + "mtrr", + "pge", + "mca", + "cmov", + "pat", + "pse36", + "clflush", + "dts", + "acpi", + "mmx", + "fxsr", + "sse", + "sse2", + "ss", + "ht", + "tm", + "pbe", + "syscall", + "nx", + "pdpe1gb", + "rdtscp", + "lm", + "constant_tsc", + "arch_perfmon", + "pebs", + "bts", + "rep_good", + "nopl", + "xtopology", + "nonstop_tsc", + "aperfmperf", + "eagerfpu", + "pni", + "pclmulqdq", + "dtes64", + "monitor", + "ds_cpl", + "vmx", + "smx", + "est", + "tm2", + "ssse3", + "sdbg", + "fma", + "cx16", + "xtpr", + "pdcm", + "pcid", + "sse4_1", + "sse4_2", + "x2apic", + "movbe", + "popcnt", + "tsc_deadline_timer", + "aes", + "xsave", + "avx", + "f16c", + "rdrand", + "lahf_lm", + "abm", + "epb", + "tpr_shadow", + "vnmi", + "flexpriority", + "ept", + "vpid", + "fsgsbase", + "tsc_adjust", + "bmi1", + "avx2", + "smep", + "bmi2", + "erms", + "invpcid", + "xsaveopt", + "dtherm", + "ida", + "arat", + "pln", + "pts" + ] + }, + "1": { + "vendor_id": "GenuineIntel", + "family": "6", + "model": "60", + "model_name": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz", + "stepping": "3", + "mhz": "3137.200", + "cache_size": "6144 KB", + "physical_id": "0", + "core_id": "0", + "cores": "4", + "flags": [ + "fpu", + "vme", + "de", + "pse", + "tsc", + "msr", + "pae", + "mce", + "cx8", + "apic", + "sep", + "mtrr", + "pge", + "mca", + "cmov", + "pat", + "pse36", + "clflush", + "dts", + "acpi", + "mmx", + "fxsr", + "sse", + "sse2", + "ss", + "ht", + "tm", + "pbe", + "syscall", + "nx", + "pdpe1gb", + "rdtscp", + "lm", + "constant_tsc", + "arch_perfmon", + "pebs", + "bts", + "rep_good", + "nopl", + "xtopology", + "nonstop_tsc", + "aperfmperf", + "eagerfpu", + "pni", + "pclmulqdq", + "dtes64", + "monitor", + "ds_cpl", + "vmx", + "smx", + "est", + "tm2", + "ssse3", + "sdbg", + "fma", + "cx16", + "xtpr", + "pdcm", + "pcid", + "sse4_1", + "sse4_2", + "x2apic", + "movbe", + "popcnt", + "tsc_deadline_timer", + "aes", + "xsave", + "avx", + "f16c", + "rdrand", + "lahf_lm", + "abm", + "epb", + "tpr_shadow", + "vnmi", + "flexpriority", + "ept", + "vpid", + "fsgsbase", + "tsc_adjust", + "bmi1", + "avx2", + "smep", + "bmi2", + "erms", + "invpcid", + "xsaveopt", + "dtherm", + "ida", + "arat", + "pln", + "pts" + ] + }, + "2": { + "vendor_id": "GenuineIntel", + "family": "6", + "model": "60", + "model_name": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz", + "stepping": "3", + "mhz": "3077.050", + "cache_size": "6144 KB", + "physical_id": "0", + "core_id": "1", + "cores": "4", + "flags": [ + "fpu", + "vme", + "de", + "pse", + "tsc", + "msr", + "pae", + "mce", + "cx8", + "apic", + "sep", + "mtrr", + "pge", + "mca", + "cmov", + "pat", + "pse36", + "clflush", + "dts", + "acpi", + "mmx", + "fxsr", + "sse", + "sse2", + "ss", + "ht", + "tm", + "pbe", + "syscall", + "nx", + "pdpe1gb", + "rdtscp", + "lm", + "constant_tsc", + "arch_perfmon", + "pebs", + "bts", + "rep_good", + "nopl", + "xtopology", + "nonstop_tsc", + "aperfmperf", + "eagerfpu", + "pni", + "pclmulqdq", + "dtes64", + "monitor", + "ds_cpl", + "vmx", + "smx", + "est", + "tm2", + "ssse3", + "sdbg", + "fma", + "cx16", + "xtpr", + "pdcm", + "pcid", + "sse4_1", + "sse4_2", + "x2apic", + "movbe", + "popcnt", + "tsc_deadline_timer", + "aes", + "xsave", + "avx", + "f16c", + "rdrand", + "lahf_lm", + "abm", + "epb", + "tpr_shadow", + "vnmi", + "flexpriority", + "ept", + "vpid", + "fsgsbase", + "tsc_adjust", + "bmi1", + "avx2", + "smep", + "bmi2", + "erms", + "invpcid", + "xsaveopt", + "dtherm", + "ida", + "arat", + "pln", + "pts" + ] + }, + "3": { + "vendor_id": "GenuineIntel", + "family": "6", + "model": "60", + "model_name": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz", + "stepping": "3", + "mhz": "2759.655", + "cache_size": "6144 KB", + "physical_id": "0", + "core_id": "1", + "cores": "4", + "flags": [ + "fpu", + "vme", + "de", + "pse", + "tsc", + "msr", + "pae", + "mce", + "cx8", + "apic", + "sep", + "mtrr", + "pge", + "mca", + "cmov", + "pat", + "pse36", + "clflush", + "dts", + "acpi", + "mmx", + "fxsr", + "sse", + "sse2", + "ss", + "ht", + "tm", + "pbe", + "syscall", + "nx", + "pdpe1gb", + "rdtscp", + "lm", + "constant_tsc", + "arch_perfmon", + "pebs", + "bts", + "rep_good", + "nopl", + "xtopology", + "nonstop_tsc", + "aperfmperf", + "eagerfpu", + "pni", + "pclmulqdq", + "dtes64", + "monitor", + "ds_cpl", + "vmx", + "smx", + "est", + "tm2", + "ssse3", + "sdbg", + "fma", + "cx16", + "xtpr", + "pdcm", + "pcid", + "sse4_1", + "sse4_2", + "x2apic", + "movbe", + "popcnt", + "tsc_deadline_timer", + "aes", + "xsave", + "avx", + "f16c", + "rdrand", + "lahf_lm", + "abm", + "epb", + "tpr_shadow", + "vnmi", + "flexpriority", + "ept", + "vpid", + "fsgsbase", + "tsc_adjust", + "bmi1", + "avx2", + "smep", + "bmi2", + "erms", + "invpcid", + "xsaveopt", + "dtherm", + "ida", + "arat", + "pln", + "pts" + ] + }, + "4": { + "vendor_id": "GenuineIntel", + "family": "6", + "model": "60", + "model_name": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz", + "stepping": "3", + "mhz": "3419.000", + "cache_size": "6144 KB", + "physical_id": "0", + "core_id": "2", + "cores": "4", + "flags": [ + "fpu", + "vme", + "de", + "pse", + "tsc", + "msr", + "pae", + "mce", + "cx8", + "apic", + "sep", + "mtrr", + "pge", + "mca", + "cmov", + "pat", + "pse36", + "clflush", + "dts", + "acpi", + "mmx", + "fxsr", + "sse", + "sse2", + "ss", + "ht", + "tm", + "pbe", + "syscall", + "nx", + "pdpe1gb", + "rdtscp", + "lm", + "constant_tsc", + "arch_perfmon", + "pebs", + "bts", + "rep_good", + "nopl", + "xtopology", + "nonstop_tsc", + "aperfmperf", + "eagerfpu", + "pni", + "pclmulqdq", + "dtes64", + "monitor", + "ds_cpl", + "vmx", + "smx", + "est", + "tm2", + "ssse3", + "sdbg", + "fma", + "cx16", + "xtpr", + "pdcm", + "pcid", + "sse4_1", + "sse4_2", + "x2apic", + "movbe", + "popcnt", + "tsc_deadline_timer", + "aes", + "xsave", + "avx", + "f16c", + "rdrand", + "lahf_lm", + "abm", + "epb", + "tpr_shadow", + "vnmi", + "flexpriority", + "ept", + "vpid", + "fsgsbase", + "tsc_adjust", + "bmi1", + "avx2", + "smep", + "bmi2", + "erms", + "invpcid", + "xsaveopt", + "dtherm", + "ida", + "arat", + "pln", + "pts" + ] + }, + "5": { + "vendor_id": "GenuineIntel", + "family": "6", + "model": "60", + "model_name": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz", + "stepping": "3", + "mhz": "2752.569", + "cache_size": "6144 KB", + "physical_id": "0", + "core_id": "2", + "cores": "4", + "flags": [ + "fpu", + "vme", + "de", + "pse", + "tsc", + "msr", + "pae", + "mce", + "cx8", + "apic", + "sep", + "mtrr", + "pge", + "mca", + "cmov", + "pat", + "pse36", + "clflush", + "dts", + "acpi", + "mmx", + "fxsr", + "sse", + "sse2", + "ss", + "ht", + "tm", + "pbe", + "syscall", + "nx", + "pdpe1gb", + "rdtscp", + "lm", + "constant_tsc", + "arch_perfmon", + "pebs", + "bts", + "rep_good", + "nopl", + "xtopology", + "nonstop_tsc", + "aperfmperf", + "eagerfpu", + "pni", + "pclmulqdq", + "dtes64", + "monitor", + "ds_cpl", + "vmx", + "smx", + "est", + "tm2", + "ssse3", + "sdbg", + "fma", + "cx16", + "xtpr", + "pdcm", + "pcid", + "sse4_1", + "sse4_2", + "x2apic", + "movbe", + "popcnt", + "tsc_deadline_timer", + "aes", + "xsave", + "avx", + "f16c", + "rdrand", + "lahf_lm", + "abm", + "epb", + "tpr_shadow", + "vnmi", + "flexpriority", + "ept", + "vpid", + "fsgsbase", + "tsc_adjust", + "bmi1", + "avx2", + "smep", + "bmi2", + "erms", + "invpcid", + "xsaveopt", + "dtherm", + "ida", + "arat", + "pln", + "pts" + ] + }, + "6": { + "vendor_id": "GenuineIntel", + "family": "6", + "model": "60", + "model_name": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz", + "stepping": "3", + "mhz": "2953.619", + "cache_size": "6144 KB", + "physical_id": "0", + "core_id": "3", + "cores": "4", + "flags": [ + "fpu", + "vme", + "de", + "pse", + "tsc", + "msr", + "pae", + "mce", + "cx8", + "apic", + "sep", + "mtrr", + "pge", + "mca", + "cmov", + "pat", + "pse36", + "clflush", + "dts", + "acpi", + "mmx", + "fxsr", + "sse", + "sse2", + "ss", + "ht", + "tm", + "pbe", + "syscall", + "nx", + "pdpe1gb", + "rdtscp", + "lm", + "constant_tsc", + "arch_perfmon", + "pebs", + "bts", + "rep_good", + "nopl", + "xtopology", + "nonstop_tsc", + "aperfmperf", + "eagerfpu", + "pni", + "pclmulqdq", + "dtes64", + "monitor", + "ds_cpl", + "vmx", + "smx", + "est", + "tm2", + "ssse3", + "sdbg", + "fma", + "cx16", + "xtpr", + "pdcm", + "pcid", + "sse4_1", + "sse4_2", + "x2apic", + "movbe", + "popcnt", + "tsc_deadline_timer", + "aes", + "xsave", + "avx", + "f16c", + "rdrand", + "lahf_lm", + "abm", + "epb", + "tpr_shadow", + "vnmi", + "flexpriority", + "ept", + "vpid", + "fsgsbase", + "tsc_adjust", + "bmi1", + "avx2", + "smep", + "bmi2", + "erms", + "invpcid", + "xsaveopt", + "dtherm", + "ida", + "arat", + "pln", + "pts" + ] + }, + "7": { + "vendor_id": "GenuineIntel", + "family": "6", + "model": "60", + "model_name": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz", + "stepping": "3", + "mhz": "2927.087", + "cache_size": "6144 KB", + "physical_id": "0", + "core_id": "3", + "cores": "4", + "flags": [ + "fpu", + "vme", + "de", + "pse", + "tsc", + "msr", + "pae", + "mce", + "cx8", + "apic", + "sep", + "mtrr", + "pge", + "mca", + "cmov", + "pat", + "pse36", + "clflush", + "dts", + "acpi", + "mmx", + "fxsr", + "sse", + "sse2", + "ss", + "ht", + "tm", + "pbe", + "syscall", + "nx", + "pdpe1gb", + "rdtscp", + "lm", + "constant_tsc", + "arch_perfmon", + "pebs", + "bts", + "rep_good", + "nopl", + "xtopology", + "nonstop_tsc", + "aperfmperf", + "eagerfpu", + "pni", + "pclmulqdq", + "dtes64", + "monitor", + "ds_cpl", + "vmx", + "smx", + "est", + "tm2", + "ssse3", + "sdbg", + "fma", + "cx16", + "xtpr", + "pdcm", + "pcid", + "sse4_1", + "sse4_2", + "x2apic", + "movbe", + "popcnt", + "tsc_deadline_timer", + "aes", + "xsave", + "avx", + "f16c", + "rdrand", + "lahf_lm", + "abm", + "epb", + "tpr_shadow", + "vnmi", + "flexpriority", + "ept", + "vpid", + "fsgsbase", + "tsc_adjust", + "bmi1", + "avx2", + "smep", + "bmi2", + "erms", + "invpcid", + "xsaveopt", + "dtherm", + "ida", + "arat", + "pln", + "pts" + ] + }, + "total": 8, + "real": 1, + "cores": 4 + }, + "etc": { + "passwd": { + "root": { + "dir": "/root", + "gid": 0, + "uid": 0, + "shell": "/bin/bash", + "gecos": "root" + }, + "bin": { + "dir": "/bin", + "gid": 1, + "uid": 1, + "shell": "/sbin/nologin", + "gecos": "bin" + }, + "daemon": { + "dir": "/sbin", + "gid": 2, + "uid": 2, + "shell": "/sbin/nologin", + "gecos": "daemon" + }, + "adm": { + "dir": "/var/adm", + "gid": 4, + "uid": 3, + "shell": "/sbin/nologin", + "gecos": "adm" + }, + "lp": { + "dir": "/var/spool/lpd", + "gid": 7, + "uid": 4, + "shell": "/sbin/nologin", + "gecos": "lp" + }, + "sync": { + "dir": "/sbin", + "gid": 0, + "uid": 5, + "shell": "/bin/sync", + "gecos": "sync" + }, + "shutdown": { + "dir": "/sbin", + "gid": 0, + "uid": 6, + "shell": "/sbin/shutdown", + "gecos": "shutdown" + }, + "halt": { + "dir": "/sbin", + "gid": 0, + "uid": 7, + "shell": "/sbin/halt", + "gecos": "halt" + }, + "mail": { + "dir": "/var/spool/mail", + "gid": 12, + "uid": 8, + "shell": "/sbin/nologin", + "gecos": "mail" + }, + "operator": { + "dir": "/root", + "gid": 0, + "uid": 11, + "shell": "/sbin/nologin", + "gecos": "operator" + }, + "games": { + "dir": "/usr/games", + "gid": 100, + "uid": 12, + "shell": "/sbin/nologin", + "gecos": "games" + }, + "ftp": { + "dir": "/var/ftp", + "gid": 50, + "uid": 14, + "shell": "/sbin/nologin", + "gecos": "FTP User" + }, + "nobody": { + "dir": "/", + "gid": 99, + "uid": 99, + "shell": "/sbin/nologin", + "gecos": "Nobody" + }, + "avahi-autoipd": { + "dir": "/var/lib/avahi-autoipd", + "gid": 170, + "uid": 170, + "shell": "/sbin/nologin", + "gecos": "Avahi IPv4LL Stack" + }, + "dbus": { + "dir": "/", + "gid": 81, + "uid": 81, + "shell": "/sbin/nologin", + "gecos": "System message bus" + }, + "polkitd": { + "dir": "/", + "gid": 999, + "uid": 999, + "shell": "/sbin/nologin", + "gecos": "User for polkitd" + }, + "abrt": { + "dir": "/etc/abrt", + "gid": 173, + "uid": 173, + "shell": "/sbin/nologin", + "gecos": "" + }, + "usbmuxd": { + "dir": "/", + "gid": 113, + "uid": 113, + "shell": "/sbin/nologin", + "gecos": "usbmuxd user" + }, + "colord": { + "dir": "/var/lib/colord", + "gid": 998, + "uid": 998, + "shell": "/sbin/nologin", + "gecos": "User for colord" + }, + "geoclue": { + "dir": "/var/lib/geoclue", + "gid": 997, + "uid": 997, + "shell": "/sbin/nologin", + "gecos": "User for geoclue" + }, + "rpc": { + "dir": "/var/lib/rpcbind", + "gid": 32, + "uid": 32, + "shell": "/sbin/nologin", + "gecos": "Rpcbind Daemon" + }, + "rpcuser": { + "dir": "/var/lib/nfs", + "gid": 29, + "uid": 29, + "shell": "/sbin/nologin", + "gecos": "RPC Service User" + }, + "nfsnobody": { + "dir": "/var/lib/nfs", + "gid": 65534, + "uid": 65534, + "shell": "/sbin/nologin", + "gecos": "Anonymous NFS User" + }, + "qemu": { + "dir": "/", + "gid": 107, + "uid": 107, + "shell": "/sbin/nologin", + "gecos": "qemu user" + }, + "rtkit": { + "dir": "/proc", + "gid": 172, + "uid": 172, + "shell": "/sbin/nologin", + "gecos": "RealtimeKit" + }, + "radvd": { + "dir": "/", + "gid": 75, + "uid": 75, + "shell": "/sbin/nologin", + "gecos": "radvd user" + }, + "tss": { + "dir": "/dev/null", + "gid": 59, + "uid": 59, + "shell": "/sbin/nologin", + "gecos": "Account used by the trousers package to sandbox the tcsd daemon" + }, + "unbound": { + "dir": "/etc/unbound", + "gid": 995, + "uid": 996, + "shell": "/sbin/nologin", + "gecos": "Unbound DNS resolver" + }, + "openvpn": { + "dir": "/etc/openvpn", + "gid": 994, + "uid": 995, + "shell": "/sbin/nologin", + "gecos": "OpenVPN" + }, + "saslauth": { + "dir": "/run/saslauthd", + "gid": 76, + "uid": 994, + "shell": "/sbin/nologin", + "gecos": "\"Saslauthd user\"" + }, + "avahi": { + "dir": "/var/run/avahi-daemon", + "gid": 70, + "uid": 70, + "shell": "/sbin/nologin", + "gecos": "Avahi mDNS/DNS-SD Stack" + }, + "pulse": { + "dir": "/var/run/pulse", + "gid": 992, + "uid": 993, + "shell": "/sbin/nologin", + "gecos": "PulseAudio System Daemon" + }, + "gdm": { + "dir": "/var/lib/gdm", + "gid": 42, + "uid": 42, + "shell": "/sbin/nologin", + "gecos": "" + }, + "gnome-initial-setup": { + "dir": "/run/gnome-initial-setup/", + "gid": 990, + "uid": 992, + "shell": "/sbin/nologin", + "gecos": "" + }, + "nm-openconnect": { + "dir": "/", + "gid": 989, + "uid": 991, + "shell": "/sbin/nologin", + "gecos": "NetworkManager user for OpenConnect" + }, + "sshd": { + "dir": "/var/empty/sshd", + "gid": 74, + "uid": 74, + "shell": "/sbin/nologin", + "gecos": "Privilege-separated SSH" + }, + "chrony": { + "dir": "/var/lib/chrony", + "gid": 988, + "uid": 990, + "shell": "/sbin/nologin", + "gecos": "" + }, + "tcpdump": { + "dir": "/", + "gid": 72, + "uid": 72, + "shell": "/sbin/nologin", + "gecos": "" + }, + "some_user": { + "dir": "/home/some_user", + "gid": 1000, + "uid": 1000, + "shell": "/bin/bash", + "gecos": "some_user" + }, + "systemd-journal-gateway": { + "dir": "/var/log/journal", + "gid": 191, + "uid": 191, + "shell": "/sbin/nologin", + "gecos": "Journal Gateway" + }, + "postgres": { + "dir": "/var/lib/pgsql", + "gid": 26, + "uid": 26, + "shell": "/bin/bash", + "gecos": "PostgreSQL Server" + }, + "dockerroot": { + "dir": "/var/lib/docker", + "gid": 977, + "uid": 984, + "shell": "/sbin/nologin", + "gecos": "Docker User" + }, + "apache": { + "dir": "/usr/share/httpd", + "gid": 48, + "uid": 48, + "shell": "/sbin/nologin", + "gecos": "Apache" + }, + "systemd-network": { + "dir": "/", + "gid": 974, + "uid": 982, + "shell": "/sbin/nologin", + "gecos": "systemd Network Management" + }, + "systemd-resolve": { + "dir": "/", + "gid": 973, + "uid": 981, + "shell": "/sbin/nologin", + "gecos": "systemd Resolver" + }, + "systemd-bus-proxy": { + "dir": "/", + "gid": 972, + "uid": 980, + "shell": "/sbin/nologin", + "gecos": "systemd Bus Proxy" + }, + "systemd-journal-remote": { + "dir": "//var/log/journal/remote", + "gid": 970, + "uid": 979, + "shell": "/sbin/nologin", + "gecos": "Journal Remote" + }, + "systemd-journal-upload": { + "dir": "//var/log/journal/upload", + "gid": 969, + "uid": 978, + "shell": "/sbin/nologin", + "gecos": "Journal Upload" + }, + "setroubleshoot": { + "dir": "/var/lib/setroubleshoot", + "gid": 967, + "uid": 977, + "shell": "/sbin/nologin", + "gecos": "" + }, + "oprofile": { + "dir": "/var/lib/oprofile", + "gid": 16, + "uid": 16, + "shell": "/sbin/nologin", + "gecos": "Special user account to be used by OProfile" + } + }, + "group": { + "root": { + "gid": 0, + "members": [ + + ] + }, + "bin": { + "gid": 1, + "members": [ + + ] + }, + "daemon": { + "gid": 2, + "members": [ + + ] + }, + "sys": { + "gid": 3, + "members": [ + + ] + }, + "adm": { + "gid": 4, + "members": [ + "logcheck" + ] + }, + "tty": { + "gid": 5, + "members": [ + + ] + }, + "disk": { + "gid": 6, + "members": [ + + ] + }, + "lp": { + "gid": 7, + "members": [ + + ] + }, + "mem": { + "gid": 8, + "members": [ + + ] + }, + "kmem": { + "gid": 9, + "members": [ + + ] + }, + "wheel": { + "gid": 10, + "members": [ + + ] + }, + "cdrom": { + "gid": 11, + "members": [ + + ] + }, + "mail": { + "gid": 12, + "members": [ + + ] + }, + "man": { + "gid": 15, + "members": [ + + ] + }, + "dialout": { + "gid": 18, + "members": [ + "lirc" + ] + }, + "floppy": { + "gid": 19, + "members": [ + + ] + }, + "games": { + "gid": 20, + "members": [ + + ] + }, + "tape": { + "gid": 30, + "members": [ + + ] + }, + "video": { + "gid": 39, + "members": [ + + ] + }, + "ftp": { + "gid": 50, + "members": [ + + ] + }, + "lock": { + "gid": 54, + "members": [ + "lirc" + ] + }, + "audio": { + "gid": 63, + "members": [ + + ] + }, + "nobody": { + "gid": 99, + "members": [ + + ] + }, + "users": { + "gid": 100, + "members": [ + + ] + }, + "utmp": { + "gid": 22, + "members": [ + + ] + }, + "utempter": { + "gid": 35, + "members": [ + + ] + }, + "avahi-autoipd": { + "gid": 170, + "members": [ + + ] + }, + "systemd-journal": { + "gid": 190, + "members": [ + + ] + }, + "dbus": { + "gid": 81, + "members": [ + + ] + }, + "polkitd": { + "gid": 999, + "members": [ + + ] + }, + "abrt": { + "gid": 173, + "members": [ + + ] + }, + "dip": { + "gid": 40, + "members": [ + + ] + }, + "usbmuxd": { + "gid": 113, + "members": [ + + ] + }, + "colord": { + "gid": 998, + "members": [ + + ] + }, + "geoclue": { + "gid": 997, + "members": [ + + ] + }, + "ssh_keys": { + "gid": 996, + "members": [ + + ] + }, + "rpc": { + "gid": 32, + "members": [ + + ] + }, + "rpcuser": { + "gid": 29, + "members": [ + + ] + }, + "nfsnobody": { + "gid": 65534, + "members": [ + + ] + }, + "kvm": { + "gid": 36, + "members": [ + "qemu" + ] + }, + "qemu": { + "gid": 107, + "members": [ + + ] + }, + "rtkit": { + "gid": 172, + "members": [ + + ] + }, + "radvd": { + "gid": 75, + "members": [ + + ] + }, + "tss": { + "gid": 59, + "members": [ + + ] + }, + "unbound": { + "gid": 995, + "members": [ + + ] + }, + "openvpn": { + "gid": 994, + "members": [ + + ] + }, + "saslauth": { + "gid": 76, + "members": [ + + ] + }, + "avahi": { + "gid": 70, + "members": [ + + ] + }, + "brlapi": { + "gid": 993, + "members": [ + + ] + }, + "pulse": { + "gid": 992, + "members": [ + + ] + }, + "pulse-access": { + "gid": 991, + "members": [ + + ] + }, + "gdm": { + "gid": 42, + "members": [ + + ] + }, + "gnome-initial-setup": { + "gid": 990, + "members": [ + + ] + }, + "nm-openconnect": { + "gid": 989, + "members": [ + + ] + }, + "sshd": { + "gid": 74, + "members": [ + + ] + }, + "slocate": { + "gid": 21, + "members": [ + + ] + }, + "chrony": { + "gid": 988, + "members": [ + + ] + }, + "tcpdump": { + "gid": 72, + "members": [ + + ] + }, + "some_user": { + "gid": 1000, + "members": [ + "some_user" + ] + }, + "docker": { + "gid": 986, + "members": [ + "some_user" + ] + } + }, + "c": { + "gcc": { + "target": "x86_64-redhat-linux", + "configured_with": "../configure --enable-bootstrap --enable-languages=c,c++,objc,obj-c++,fortran,ada,go,lto --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-plugin --enable-initfini-array --disable-libgcj --with-isl --enable-libmpx --enable-gnu-indirect-function --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux", + "thread_model": "posix", + "description": "gcc version 6.3.1 20161221 (Red Hat 6.3.1-1) (GCC) ", + "version": "6.3.1" + }, + "glibc": { + "version": "2.24", + "description": "GNU C Library (GNU libc) stable release version 2.24, by Roland McGrath et al." + } + }, + "lua": { + "version": "5.3.4" + }, + "ruby": { + "platform": "x86_64-linux", + "version": "2.3.3", + "release_date": "2016-11-21", + "target": "x86_64-redhat-linux-gnu", + "target_cpu": "x86_64", + "target_vendor": "redhat", + "target_os": "linux", + "host": "x86_64-redhat-linux-gnu", + "host_cpu": "x86_64", + "host_os": "linux-gnu", + "host_vendor": "redhat", + "bin_dir": "/usr/bin", + "ruby_bin": "/usr/bin/ruby", + "gems_dir": "/home/some_user/.gem/ruby", + "gem_bin": "/usr/bin/gem" + } + }, + "command": { + "ps": "ps -ef" + }, + "root_group": "root", + "fips": { + "kernel": { + "enabled": false + } + }, + "hostname": "myhostname", + "machinename": "myhostname", + "fqdn": "myhostname", + "domain": null, + "machine_id": "1234567abcede123456123456123456a", + "privateaddress": "192.168.1.100", + "keys": { + "ssh": { + + } + }, + "time": { + "timezone": "EDT" + }, + "sessions": { + "by_session": { + "1918": { + "session": "1918", + "uid": "1000", + "user": "some_user", + "seat": null + }, + "5": { + "session": "5", + "uid": "1000", + "user": "some_user", + "seat": "seat0" + }, + "3": { + "session": "3", + "uid": "0", + "user": "root", + "seat": "seat0" + } + }, + "by_user": { + "some_user": [ + { + "session": "1918", + "uid": "1000", + "user": "some_user", + "seat": null + }, + { + "session": "5", + "uid": "1000", + "user": "some_user", + "seat": "seat0" + } + ], + "root": [ + { + "session": "3", + "uid": "0", + "user": "root", + "seat": "seat0" + } + ] + } + }, + "hostnamectl": { + "static_hostname": "myhostname", + "icon_name": "computer-laptop", + "chassis": "laptop", + "machine_id": "24dc16bd7694404c825b517ab46d9d6b", + "machine_id": "12345123451234512345123451242323", + "boot_id": "3d5d5512341234123412341234123423", + "operating_system": "Fedora 25 (Workstation Edition)", + "cpe_os_name": "cpe", + "kernel": "Linux 4.9.14-200.fc25.x86_64", + "architecture": "x86-64" + }, + "block_device": { + "dm-1": { + "size": "104857600", + "removable": "0", + "rotational": "0", + "physical_block_size": "512", + "logical_block_size": "512" + }, + "loop1": { + "size": "209715200", + "removable": "0", + "rotational": "1", + "physical_block_size": "512", + "logical_block_size": "512" + }, + "sr0": { + "size": "2097151", + "removable": "1", + "model": "DVD-RAM UJ8E2", + "rev": "SB01", + "state": "running", + "timeout": "30", + "vendor": "MATSHITA", + "queue_depth": "1", + "rotational": "1", + "physical_block_size": "512", + "logical_block_size": "512" + }, + "dm-2": { + "size": "378093568", + "removable": "0", + "rotational": "0", + "physical_block_size": "512", + "logical_block_size": "512" + }, + "loop2": { + "size": "4194304", + "removable": "0", + "rotational": "1", + "physical_block_size": "512", + "logical_block_size": "512" + }, + "dm-0": { + "size": "16138240", + "removable": "0", + "rotational": "0", + "physical_block_size": "512", + "logical_block_size": "512" + }, + "loop0": { + "size": "1024000", + "removable": "0", + "rotational": "1", + "physical_block_size": "512", + "logical_block_size": "512" + }, + "sda": { + "size": "500118192", + "removable": "0", + "model": "SAMSUNG MZ7TD256", + "rev": "2L5Q", + "state": "running", + "timeout": "30", + "vendor": "ATA", + "queue_depth": "31", + "rotational": "0", + "physical_block_size": "512", + "logical_block_size": "512" + }, + "dm-5": { + "size": "20971520", + "removable": "0", + "rotational": "1", + "physical_block_size": "512", + "logical_block_size": "512" + }, + "dm-3": { + "size": "209715200", + "removable": "0", + "rotational": "1", + "physical_block_size": "512", + "logical_block_size": "512" + } + }, + "sysconf": { + "LINK_MAX": 65000, + "_POSIX_LINK_MAX": 65000, + "MAX_CANON": 255, + "_POSIX_MAX_CANON": 255, + "MAX_INPUT": 255, + "_POSIX_MAX_INPUT": 255, + "NAME_MAX": 255, + "_POSIX_NAME_MAX": 255, + "PATH_MAX": 4096, + "_POSIX_PATH_MAX": 4096, + "PIPE_BUF": 4096, + "_POSIX_PIPE_BUF": 4096, + "SOCK_MAXBUF": null, + "_POSIX_ASYNC_IO": null, + "_POSIX_CHOWN_RESTRICTED": 1, + "_POSIX_NO_TRUNC": 1, + "_POSIX_PRIO_IO": null, + "_POSIX_SYNC_IO": null, + "_POSIX_VDISABLE": 0, + "ARG_MAX": 2097152, + "ATEXIT_MAX": 2147483647, + "CHAR_BIT": 8, + "CHAR_MAX": 127, + "CHAR_MIN": -128, + "CHILD_MAX": 62844, + "CLK_TCK": 100, + "INT_MAX": 2147483647, + "INT_MIN": -2147483648, + "IOV_MAX": 1024, + "LOGNAME_MAX": 256, + "LONG_BIT": 64, + "MB_LEN_MAX": 16, + "NGROUPS_MAX": 65536, + "NL_ARGMAX": 4096, + "NL_LANGMAX": 2048, + "NL_MSGMAX": 2147483647, + "NL_NMAX": 2147483647, + "NL_SETMAX": 2147483647, + "NL_TEXTMAX": 2147483647, + "NSS_BUFLEN_GROUP": 1024, + "NSS_BUFLEN_PASSWD": 1024, + "NZERO": 20, + "OPEN_MAX": 1024, + "PAGESIZE": 4096, + "PAGE_SIZE": 4096, + "PASS_MAX": 8192, + "PTHREAD_DESTRUCTOR_ITERATIONS": 4, + "PTHREAD_KEYS_MAX": 1024, + "PTHREAD_STACK_MIN": 16384, + "PTHREAD_THREADS_MAX": null, + "SCHAR_MAX": 127, + "SCHAR_MIN": -128, + "SHRT_MAX": 32767, + "SHRT_MIN": -32768, + "SSIZE_MAX": 32767, + "TTY_NAME_MAX": 32, + "TZNAME_MAX": 6, + "UCHAR_MAX": 255, + "UINT_MAX": 4294967295, + "UIO_MAXIOV": 1024, + "ULONG_MAX": 18446744073709551615, + "USHRT_MAX": 65535, + "WORD_BIT": 32, + "_AVPHYS_PAGES": 955772, + "_NPROCESSORS_CONF": 8, + "_NPROCESSORS_ONLN": 8, + "_PHYS_PAGES": 4027635, + "_POSIX_ARG_MAX": 2097152, + "_POSIX_ASYNCHRONOUS_IO": 200809, + "_POSIX_CHILD_MAX": 62844, + "_POSIX_FSYNC": 200809, + "_POSIX_JOB_CONTROL": 1, + "_POSIX_MAPPED_FILES": 200809, + "_POSIX_MEMLOCK": 200809, + "_POSIX_MEMLOCK_RANGE": 200809, + "_POSIX_MEMORY_PROTECTION": 200809, + "_POSIX_MESSAGE_PASSING": 200809, + "_POSIX_NGROUPS_MAX": 65536, + "_POSIX_OPEN_MAX": 1024, + "_POSIX_PII": null, + "_POSIX_PII_INTERNET": null, + "_POSIX_PII_INTERNET_DGRAM": null, + "_POSIX_PII_INTERNET_STREAM": null, + "_POSIX_PII_OSI": null, + "_POSIX_PII_OSI_CLTS": null, + "_POSIX_PII_OSI_COTS": null, + "_POSIX_PII_OSI_M": null, + "_POSIX_PII_SOCKET": null, + "_POSIX_PII_XTI": null, + "_POSIX_POLL": null, + "_POSIX_PRIORITIZED_IO": 200809, + "_POSIX_PRIORITY_SCHEDULING": 200809, + "_POSIX_REALTIME_SIGNALS": 200809, + "_POSIX_SAVED_IDS": 1, + "_POSIX_SELECT": null, + "_POSIX_SEMAPHORES": 200809, + "_POSIX_SHARED_MEMORY_OBJECTS": 200809, + "_POSIX_SSIZE_MAX": 32767, + "_POSIX_STREAM_MAX": 16, + "_POSIX_SYNCHRONIZED_IO": 200809, + "_POSIX_THREADS": 200809, + "_POSIX_THREAD_ATTR_STACKADDR": 200809, + "_POSIX_THREAD_ATTR_STACKSIZE": 200809, + "_POSIX_THREAD_PRIORITY_SCHEDULING": 200809, + "_POSIX_THREAD_PRIO_INHERIT": 200809, + "_POSIX_THREAD_PRIO_PROTECT": 200809, + "_POSIX_THREAD_ROBUST_PRIO_INHERIT": null, + "_POSIX_THREAD_ROBUST_PRIO_PROTECT": null, + "_POSIX_THREAD_PROCESS_SHARED": 200809, + "_POSIX_THREAD_SAFE_FUNCTIONS": 200809, + "_POSIX_TIMERS": 200809, + "TIMER_MAX": null, + "_POSIX_TZNAME_MAX": 6, + "_POSIX_VERSION": 200809, + "_T_IOV_MAX": null, + "_XOPEN_CRYPT": 1, + "_XOPEN_ENH_I18N": 1, + "_XOPEN_LEGACY": 1, + "_XOPEN_REALTIME": 1, + "_XOPEN_REALTIME_THREADS": 1, + "_XOPEN_SHM": 1, + "_XOPEN_UNIX": 1, + "_XOPEN_VERSION": 700, + "_XOPEN_XCU_VERSION": 4, + "_XOPEN_XPG2": 1, + "_XOPEN_XPG3": 1, + "_XOPEN_XPG4": 1, + "BC_BASE_MAX": 99, + "BC_DIM_MAX": 2048, + "BC_SCALE_MAX": 99, + "BC_STRING_MAX": 1000, + "CHARCLASS_NAME_MAX": 2048, + "COLL_WEIGHTS_MAX": 255, + "EQUIV_CLASS_MAX": null, + "EXPR_NEST_MAX": 32, + "LINE_MAX": 2048, + "POSIX2_BC_BASE_MAX": 99, + "POSIX2_BC_DIM_MAX": 2048, + "POSIX2_BC_SCALE_MAX": 99, + "POSIX2_BC_STRING_MAX": 1000, + "POSIX2_CHAR_TERM": 200809, + "POSIX2_COLL_WEIGHTS_MAX": 255, + "POSIX2_C_BIND": 200809, + "POSIX2_C_DEV": 200809, + "POSIX2_C_VERSION": 200809, + "POSIX2_EXPR_NEST_MAX": 32, + "POSIX2_FORT_DEV": null, + "POSIX2_FORT_RUN": null, + "_POSIX2_LINE_MAX": 2048, + "POSIX2_LINE_MAX": 2048, + "POSIX2_LOCALEDEF": 200809, + "POSIX2_RE_DUP_MAX": 32767, + "POSIX2_SW_DEV": 200809, + "POSIX2_UPE": null, + "POSIX2_VERSION": 200809, + "RE_DUP_MAX": 32767, + "PATH": "/usr/bin", + "CS_PATH": "/usr/bin", + "LFS_CFLAGS": null, + "LFS_LDFLAGS": null, + "LFS_LIBS": null, + "LFS_LINTFLAGS": null, + "LFS64_CFLAGS": "-D_LARGEFILE64_SOURCE", + "LFS64_LDFLAGS": null, + "LFS64_LIBS": null, + "LFS64_LINTFLAGS": "-D_LARGEFILE64_SOURCE", + "_XBS5_WIDTH_RESTRICTED_ENVS": "XBS5_LP64_OFF64", + "XBS5_WIDTH_RESTRICTED_ENVS": "XBS5_LP64_OFF64", + "_XBS5_ILP32_OFF32": null, + "XBS5_ILP32_OFF32_CFLAGS": null, + "XBS5_ILP32_OFF32_LDFLAGS": null, + "XBS5_ILP32_OFF32_LIBS": null, + "XBS5_ILP32_OFF32_LINTFLAGS": null, + "_XBS5_ILP32_OFFBIG": null, + "XBS5_ILP32_OFFBIG_CFLAGS": null, + "XBS5_ILP32_OFFBIG_LDFLAGS": null, + "XBS5_ILP32_OFFBIG_LIBS": null, + "XBS5_ILP32_OFFBIG_LINTFLAGS": null, + "_XBS5_LP64_OFF64": 1, + "XBS5_LP64_OFF64_CFLAGS": "-m64", + "XBS5_LP64_OFF64_LDFLAGS": "-m64", + "XBS5_LP64_OFF64_LIBS": null, + "XBS5_LP64_OFF64_LINTFLAGS": null, + "_XBS5_LPBIG_OFFBIG": null, + "XBS5_LPBIG_OFFBIG_CFLAGS": null, + "XBS5_LPBIG_OFFBIG_LDFLAGS": null, + "XBS5_LPBIG_OFFBIG_LIBS": null, + "XBS5_LPBIG_OFFBIG_LINTFLAGS": null, + "_POSIX_V6_ILP32_OFF32": null, + "POSIX_V6_ILP32_OFF32_CFLAGS": null, + "POSIX_V6_ILP32_OFF32_LDFLAGS": null, + "POSIX_V6_ILP32_OFF32_LIBS": null, + "POSIX_V6_ILP32_OFF32_LINTFLAGS": null, + "_POSIX_V6_WIDTH_RESTRICTED_ENVS": "POSIX_V6_LP64_OFF64", + "POSIX_V6_WIDTH_RESTRICTED_ENVS": "POSIX_V6_LP64_OFF64", + "_POSIX_V6_ILP32_OFFBIG": null, + "POSIX_V6_ILP32_OFFBIG_CFLAGS": null, + "POSIX_V6_ILP32_OFFBIG_LDFLAGS": null, + "POSIX_V6_ILP32_OFFBIG_LIBS": null, + "POSIX_V6_ILP32_OFFBIG_LINTFLAGS": null, + "_POSIX_V6_LP64_OFF64": 1, + "POSIX_V6_LP64_OFF64_CFLAGS": "-m64", + "POSIX_V6_LP64_OFF64_LDFLAGS": "-m64", + "POSIX_V6_LP64_OFF64_LIBS": null, + "POSIX_V6_LP64_OFF64_LINTFLAGS": null, + "_POSIX_V6_LPBIG_OFFBIG": null, + "POSIX_V6_LPBIG_OFFBIG_CFLAGS": null, + "POSIX_V6_LPBIG_OFFBIG_LDFLAGS": null, + "POSIX_V6_LPBIG_OFFBIG_LIBS": null, + "POSIX_V6_LPBIG_OFFBIG_LINTFLAGS": null, + "_POSIX_V7_ILP32_OFF32": null, + "POSIX_V7_ILP32_OFF32_CFLAGS": null, + "POSIX_V7_ILP32_OFF32_LDFLAGS": null, + "POSIX_V7_ILP32_OFF32_LIBS": null, + "POSIX_V7_ILP32_OFF32_LINTFLAGS": null, + "_POSIX_V7_WIDTH_RESTRICTED_ENVS": "POSIX_V7_LP64_OFF64", + "POSIX_V7_WIDTH_RESTRICTED_ENVS": "POSIX_V7_LP64_OFF64", + "_POSIX_V7_ILP32_OFFBIG": null, + "POSIX_V7_ILP32_OFFBIG_CFLAGS": null, + "POSIX_V7_ILP32_OFFBIG_LDFLAGS": null, + "POSIX_V7_ILP32_OFFBIG_LIBS": null, + "POSIX_V7_ILP32_OFFBIG_LINTFLAGS": null, + "_POSIX_V7_LP64_OFF64": 1, + "POSIX_V7_LP64_OFF64_CFLAGS": "-m64", + "POSIX_V7_LP64_OFF64_LDFLAGS": "-m64", + "POSIX_V7_LP64_OFF64_LIBS": null, + "POSIX_V7_LP64_OFF64_LINTFLAGS": null, + "_POSIX_V7_LPBIG_OFFBIG": null, + "POSIX_V7_LPBIG_OFFBIG_CFLAGS": null, + "POSIX_V7_LPBIG_OFFBIG_LDFLAGS": null, + "POSIX_V7_LPBIG_OFFBIG_LIBS": null, + "POSIX_V7_LPBIG_OFFBIG_LINTFLAGS": null, + "_POSIX_ADVISORY_INFO": 200809, + "_POSIX_BARRIERS": 200809, + "_POSIX_BASE": null, + "_POSIX_C_LANG_SUPPORT": null, + "_POSIX_C_LANG_SUPPORT_R": null, + "_POSIX_CLOCK_SELECTION": 200809, + "_POSIX_CPUTIME": 200809, + "_POSIX_THREAD_CPUTIME": 200809, + "_POSIX_DEVICE_SPECIFIC": null, + "_POSIX_DEVICE_SPECIFIC_R": null, + "_POSIX_FD_MGMT": null, + "_POSIX_FIFO": null, + "_POSIX_PIPE": null, + "_POSIX_FILE_ATTRIBUTES": null, + "_POSIX_FILE_LOCKING": null, + "_POSIX_FILE_SYSTEM": null, + "_POSIX_MONOTONIC_CLOCK": 200809, + "_POSIX_MULTI_PROCESS": null, + "_POSIX_SINGLE_PROCESS": null, + "_POSIX_NETWORKING": null, + "_POSIX_READER_WRITER_LOCKS": 200809, + "_POSIX_SPIN_LOCKS": 200809, + "_POSIX_REGEXP": 1, + "_REGEX_VERSION": null, + "_POSIX_SHELL": 1, + "_POSIX_SIGNALS": null, + "_POSIX_SPAWN": 200809, + "_POSIX_SPORADIC_SERVER": null, + "_POSIX_THREAD_SPORADIC_SERVER": null, + "_POSIX_SYSTEM_DATABASE": null, + "_POSIX_SYSTEM_DATABASE_R": null, + "_POSIX_TIMEOUTS": 200809, + "_POSIX_TYPED_MEMORY_OBJECTS": null, + "_POSIX_USER_GROUPS": null, + "_POSIX_USER_GROUPS_R": null, + "POSIX2_PBS": null, + "POSIX2_PBS_ACCOUNTING": null, + "POSIX2_PBS_LOCATE": null, + "POSIX2_PBS_TRACK": null, + "POSIX2_PBS_MESSAGE": null, + "SYMLOOP_MAX": null, + "STREAM_MAX": 16, + "AIO_LISTIO_MAX": null, + "AIO_MAX": null, + "AIO_PRIO_DELTA_MAX": 20, + "DELAYTIMER_MAX": 2147483647, + "HOST_NAME_MAX": 64, + "LOGIN_NAME_MAX": 256, + "MQ_OPEN_MAX": null, + "MQ_PRIO_MAX": 32768, + "_POSIX_DEVICE_IO": null, + "_POSIX_TRACE": null, + "_POSIX_TRACE_EVENT_FILTER": null, + "_POSIX_TRACE_INHERIT": null, + "_POSIX_TRACE_LOG": null, + "RTSIG_MAX": 32, + "SEM_NSEMS_MAX": null, + "SEM_VALUE_MAX": 2147483647, + "SIGQUEUE_MAX": 62844, + "FILESIZEBITS": 64, + "POSIX_ALLOC_SIZE_MIN": 4096, + "POSIX_REC_INCR_XFER_SIZE": null, + "POSIX_REC_MAX_XFER_SIZE": null, + "POSIX_REC_MIN_XFER_SIZE": 4096, + "POSIX_REC_XFER_ALIGN": 4096, + "SYMLINK_MAX": null, + "GNU_LIBC_VERSION": "glibc 2.24", + "GNU_LIBPTHREAD_VERSION": "NPTL 2.24", + "POSIX2_SYMLINKS": 1, + "LEVEL1_ICACHE_SIZE": 32768, + "LEVEL1_ICACHE_ASSOC": 8, + "LEVEL1_ICACHE_LINESIZE": 64, + "LEVEL1_DCACHE_SIZE": 32768, + "LEVEL1_DCACHE_ASSOC": 8, + "LEVEL1_DCACHE_LINESIZE": 64, + "LEVEL2_CACHE_SIZE": 262144, + "LEVEL2_CACHE_ASSOC": 8, + "LEVEL2_CACHE_LINESIZE": 64, + "LEVEL3_CACHE_SIZE": 6291456, + "LEVEL3_CACHE_ASSOC": 12, + "LEVEL3_CACHE_LINESIZE": 64, + "LEVEL4_CACHE_SIZE": 0, + "LEVEL4_CACHE_ASSOC": 0, + "LEVEL4_CACHE_LINESIZE": 0, + "IPV6": 200809, + "RAW_SOCKETS": 200809, + "_POSIX_IPV6": 200809, + "_POSIX_RAW_SOCKETS": 200809 + }, + "init_package": "systemd", + "shells": [ + "/bin/sh", + "/bin/bash", + "/sbin/nologin", + "/usr/bin/sh", + "/usr/bin/bash", + "/usr/sbin/nologin", + "/usr/bin/zsh", + "/bin/zsh" + ], + "ohai_time": 1492535225.41052, + "cloud_v2": null, + "cloud": null +} +''' # noqa + + +class TestOhaiCollector(BaseFactsTest): + __test__ = True + gather_subset = ['!all', 'ohai'] + valid_subsets = ['ohai'] + fact_namespace = 'ansible_ohai' + collector_class = OhaiFactCollector + + 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/ohai') + mock_module.run_command = Mock(return_value=(0, ohai_json_output, '')) + return mock_module + + @patch('ansible.module_utils.facts.other.ohai.OhaiFactCollector.get_ohai_output') + def test_bogus_json(self, mock_get_ohai_output): + module = self._mock_module() + + # bogus json + mock_get_ohai_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.ohai.OhaiFactCollector.run_ohai') + def test_ohai_non_zero_return_code(self, mock_run_ohai): + module = self._mock_module() + + # bogus json + mock_run_ohai.return_value = (1, '{}', '') + + fact_collector = self.collector_class() + facts_dict = fact_collector.collect(module=module) + + self.assertIsInstance(facts_dict, dict) + + # This assumes no 'ohai' entry at all is correct + self.assertNotIn('ohai', facts_dict) + self.assertEqual(facts_dict, {}) diff --git a/test/units/module_utils/facts/system/__init__.py b/test/units/module_utils/facts/system/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/units/module_utils/facts/system/test_lsb.py b/test/units/module_utils/facts/system/test_lsb.py new file mode 100644 index 00000000000..6bc53470370 --- /dev/null +++ b/test/units/module_utils/facts/system/test_lsb.py @@ -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 . +# + +# 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') diff --git a/test/units/module_utils/facts/test_collector.py b/test/units/module_utils/facts/test_collector.py new file mode 100644 index 00000000000..76c291a9363 --- /dev/null +++ b/test/units/module_utils/facts/test_collector.py @@ -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 . +# + +# 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'])) diff --git a/test/units/module_utils/facts/test_collectors.py b/test/units/module_utils/facts/test_collectors.py new file mode 100644 index 00000000000..9920052364c --- /dev/null +++ b/test/units/module_utils/facts/test_collectors.py @@ -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 . +# + +# 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 diff --git a/test/units/module_utils/test_facts.py b/test/units/module_utils/facts/test_facts.py similarity index 71% rename from test/units/module_utils/test_facts.py rename to test/units/module_utils/facts/test_facts.py index 8ee30275e39..7ee36ae2100 100644 --- a/test/units/module_utils/test_facts.py +++ b/test/units/module_utils/facts/test_facts.py @@ -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 . +# # 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') diff --git a/test/units/module_utils/facts/test_timeout.py b/test/units/module_utils/facts/test_timeout.py index 9c452c4397e..bd18655f29d 100644 --- a/test/units/module_utils/facts/test_timeout.py +++ b/test/units/module_utils/facts/test_timeout.py @@ -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)' diff --git a/test/units/module_utils/test_distribution_version.py b/test/units/module_utils/test_distribution_version.py index 135d890adbc..148f1a9747e 100644 --- a/test/units/module_utils/test_distribution_version.py +++ b/test/units/module_utils/test_distribution_version.py @@ -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) diff --git a/test/units/modules/system/test_setup.py b/test/units/modules/system/test_setup.py new file mode 100644 index 00000000000..51159ad8f32 --- /dev/null +++ b/test/units/modules/system/test_setup.py @@ -0,0 +1,308 @@ +# unit tests for ansible/module_utils/facts/__init__.py +# -*- 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 . +# + +# Make coding more python3-ish +from __future__ import (absolute_import, division) +__metaclass__ = type + +# for testing +from ansible.compat.tests import unittest +from ansible.compat.tests.mock import Mock + +from ansible.module_utils.facts import collector + +from ansible.module_utils.facts.other.facter import FacterFactCollector +from ansible.module_utils.facts.other.ohai import OhaiFactCollector + +from ansible.module_utils.facts.system.apparmor import ApparmorFactCollector +from ansible.module_utils.facts.system.caps import SystemCapabilitiesFactCollector +from ansible.module_utils.facts.system.date_time import DateTimeFactCollector +from ansible.module_utils.facts.system.env import EnvFactCollector +from ansible.module_utils.facts.system.distribution import DistributionFactCollector +from ansible.module_utils.facts.system.dns import DnsFactCollector +from ansible.module_utils.facts.system.fips import FipsFactCollector +from ansible.module_utils.facts.system.local import LocalFactCollector +from ansible.module_utils.facts.system.lsb import LSBFactCollector +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.user import UserFactCollector + +# from ansible.module_utils.facts.hardware.base import HardwareCollector +from ansible.module_utils.facts.network.base import NetworkCollector +from ansible.module_utils.facts.virtual.base import VirtualCollector + +# module under test +from ansible.modules.system import setup + + +ALL_COLLECTOR_CLASSES = \ + [PlatformFactCollector, + DistributionFactCollector, + SelinuxFactCollector, + ApparmorFactCollector, + SystemCapabilitiesFactCollector, + FipsFactCollector, + PkgMgrFactCollector, + ServiceMgrFactCollector, + LSBFactCollector, + DateTimeFactCollector, + UserFactCollector, + LocalFactCollector, + EnvFactCollector, + DnsFactCollector, + PythonFactCollector, + # FIXME: re-enable when hardware doesnt Hardware() doesnt munge self.facts + # HardwareCollector + NetworkCollector, + VirtualCollector, + OhaiFactCollector, + FacterFactCollector] + + +def mock_module(gather_subset=None): + if gather_subset is None: + gather_subset = ['all', '!facter', '!ohai'] + mock_module = Mock() + mock_module.params = {'gather_subset': gather_subset, + 'gather_timeout': 5, + 'filter': '*'} + mock_module.get_bin_path = Mock(return_value=None) + return mock_module + + +def _collectors(module, + all_collector_classes=None, + minimal_gather_subset=None): + gather_subset = module.params.get('gather_subset') + if all_collector_classes is None: + all_collector_classes = ALL_COLLECTOR_CLASSES + if minimal_gather_subset is None: + minimal_gather_subset = frozenset([]) + + collector_classes = \ + collector.collector_classes_from_gather_subset(all_collector_classes=all_collector_classes, + minimal_gather_subset=minimal_gather_subset, + gather_subset=gather_subset) + + collectors = [] + for collector_class in collector_classes: + collector_obj = collector_class() + collectors.append(collector_obj) + + # Add a collector that knows what gather_subset we used so it it can provide a fact + collector_meta_data_collector = \ + setup.CollectorMetaDataCollector(gather_subset=gather_subset, + module_setup=True) + collectors.append(collector_meta_data_collector) + + return collectors + + +# FIXME: this is brute force, but hopefully enough to get some refactoring to make facts testable +class TestInPlace(unittest.TestCase): + def _mock_module(self, gather_subset=None): + return mock_module(gather_subset=gather_subset) + + def _collectors(self, module, + all_collector_classes=None, + minimal_gather_subset=None): + return _collectors(module=module, + all_collector_classes=all_collector_classes, + minimal_gather_subset=minimal_gather_subset) + + def test(self): + gather_subset = ['all'] + mock_module = self._mock_module(gather_subset=gather_subset) + all_collector_classes = [EnvFactCollector] + collectors = self._collectors(mock_module, + all_collector_classes=all_collector_classes) + + fact_collector = \ + setup.AnsibleFactCollector(collectors=collectors) + + res = fact_collector.collect(module=mock_module) + self.assertIsInstance(res, dict) + self.assertIn('ansible_facts', res) + self.assertIsInstance(res['ansible_facts'], dict) + self.assertIn('env', res['ansible_facts']) + self.assertIn('gather_subset', res['ansible_facts']) + self.assertEqual(res['ansible_facts']['gather_subset'], ['all']) + + def test1(self): + gather_subset = ['all'] + mock_module = self._mock_module(gather_subset=gather_subset) + collectors = self._collectors(mock_module) + + fact_collector = \ + setup.AnsibleFactCollector(collectors=collectors) + + res = fact_collector.collect(module=mock_module) + self.assertIsInstance(res, dict) + self.assertIn('ansible_facts', res) + # just assert it's not almost empty + # with run_command and get_file_content mock, many facts are empty, like network + self.assertGreater(len(res['ansible_facts']), 20) + + def test_empty_all_collector_classes(self): + mock_module = self._mock_module() + all_collector_classes = [] + + collectors = self._collectors(mock_module, + all_collector_classes=all_collector_classes) + + fact_collector = \ + setup.AnsibleFactCollector(collectors=collectors) + + res = fact_collector.collect() + self.assertIsInstance(res, dict) + self.assertIn('ansible_facts', res) + # just assert it's not almost empty + self.assertLess(len(res['ansible_facts']), 3) + +# def test_facts_class(self): +# mock_module = self._mock_module() +# Facts(mock_module) + +# def test_facts_class_load_on_init_false(self): +# mock_module = self._mock_module() +# Facts(mock_module, load_on_init=False) +# # FIXME: assert something + + +class TestCollectedFacts(unittest.TestCase): + gather_subset = ['all', '!facter', '!ohai'] + min_fact_count = 30 + max_fact_count = 1000 + + # TODO: add ansible_cmdline, ansible_*_pubkey* back when TempFactCollector goes away + expected_facts = ['date_time', + 'user_id', 'distribution', + 'gather_subset', 'module_setup', + 'env'] + not_expected_facts = ['facter', 'ohai'] + + def _mock_module(self, gather_subset=None): + return mock_module(gather_subset=self.gather_subset) + + def setUp(self): + mock_module = self._mock_module() + collectors = self._collectors(mock_module) + + fact_collector = \ + setup.AnsibleFactCollector(collectors=collectors) + self.facts = fact_collector.collect(module=mock_module) + + def _collectors(self, module, + all_collector_classes=None, + minimal_gather_subset=None): + return _collectors(module=module, + all_collector_classes=all_collector_classes, + minimal_gather_subset=minimal_gather_subset) + + def test_basics(self): + self._assert_basics(self.facts) + + def test_expected_facts(self): + self._assert_expected_facts(self.facts) + + def test_not_expected_facts(self): + self._assert_not_expected_facts(self.facts) + + def _assert_basics(self, facts): + self.assertIsInstance(facts, dict) + self.assertIn('ansible_facts', facts) + # just assert it's not almost empty + self.assertGreaterEqual(len(facts['ansible_facts']), self.min_fact_count) + # and that is not huge number of keys + self.assertLess(len(facts['ansible_facts']), self.max_fact_count) + + # everything starts with ansible_ namespace + def _assert_ansible_namespace(self, facts): + subfacts = facts['ansible_facts'] + + # FIXME: kluge for non-namespace fact + subfacts.pop('module_setup', None) + subfacts.pop('gather_subset', None) + + for fact_key in subfacts: + self.assertTrue(fact_key.startswith('ansible_'), + 'The fact name "%s" does not startwith "ansible_"' % fact_key) + + def _assert_expected_facts(self, facts): + subfacts = facts['ansible_facts'] + + import pprint + pprint.pprint(subfacts) + subfacts_keys = sorted(subfacts.keys()) + for expected_fact in self.expected_facts: + self.assertIn(expected_fact, subfacts_keys) + # self.assertIsInstance(subfacts['ansible_env'], dict) + + # self.assertIsInstance(subfacts['ansible_env'], dict) + + # self._assert_ssh_facts(subfacts) + + def _assert_not_expected_facts(self, facts): + subfacts = facts['ansible_facts'] + + subfacts_keys = sorted(subfacts.keys()) + for not_expected_fact in self.not_expected_facts: + self.assertNotIn(not_expected_fact, subfacts_keys) + + def _assert_ssh_facts(self, subfacts): + self.assertIn('ssh_host_key_rsa_public', subfacts.keys()) + + +class ExceptionThrowingCollector(collector.BaseFactCollector): + def collect(self, module=None, collected_facts=None): + raise Exception('A collector failed') + + +class TestExceptionCollectedFacts(TestCollectedFacts): + + def _collectors(self, module, + all_collector_classes=None, + minimal_gather_subset=None): + collectors = _collectors(module=module, + all_collector_classes=all_collector_classes, + minimal_gather_subset=minimal_gather_subset) + + c = [ExceptionThrowingCollector()] + collectors + return c + + +class TestOnlyExceptionCollector(TestCollectedFacts): + expected_facts = [] + min_fact_count = 0 + + def _collectors(self, module, + all_collector_classes=None, + minimal_gather_subset=None): + return [ExceptionThrowingCollector()] + + +class TestMinimalCollectedFacts(TestCollectedFacts): + gather_subset = ['!all'] + min_fact_count = 1 + max_fact_count = 10 + expected_facts = ['gather_subset', + 'module_setup'] + not_expected_facts = ['lsb']