diff --git a/lib/ansible/executor/action_write_locks.py b/lib/ansible/executor/action_write_locks.py index 6f81b7e912..b6486cb7d4 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 4d0de4f9c7..0000000000 --- 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 0000000000..e69de29bb2 diff --git a/lib/ansible/module_utils/facts/collector.py b/lib/ansible/module_utils/facts/collector.py new file mode 100644 index 0000000000..d55a9f0254 --- /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 0000000000..7a5caedbb7 --- /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 0000000000..e69de29bb2 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 0000000000..eb228ffcf5 --- /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 0000000000..8f6e452495 --- /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 0000000000..c99e459353 --- /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 0000000000..ea24151fdb --- /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 0000000000..609a13f339 --- /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 0000000000..24daa1948c --- /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 0000000000..306e13c135 --- /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 0000000000..33851e199c --- /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 0000000000..3b58794bf8 --- /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 0000000000..04577e4dc8 --- /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 0000000000..595e100a88 --- /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 0000000000..f74992d5ef --- /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 0000000000..e69de29bb2 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 0000000000..0ea53d3ca3 --- /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 0000000000..d74897dd5e --- /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 0000000000..4e1d4a9e3e --- /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 0000000000..e43bbb28ec --- /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 0000000000..36f6eec7c4 --- /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 0000000000..212c842f43 --- /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 0000000000..6e87ee9277 --- /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 0000000000..f1d3a8707c --- /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 0000000000..deb69883f6 --- /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 0000000000..de8ceff60c --- /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 0000000000..9e11d82f37 --- /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 0000000000..dec5888b81 --- /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 0000000000..e69de29bb2 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 0000000000..899fcc419b --- /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 0000000000..df29237686 --- /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 0000000000..2446dab2f7 --- /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 0000000000..e69de29bb2 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 0000000000..53c3ed1856 --- /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 0000000000..057eeda48e --- /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 0000000000..612132a078 --- /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 0000000000..f4e59705ef --- /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 0000000000..ad41d4f7d9 --- /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 0000000000..bd385e9de9 --- /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 0000000000..279aad6153 --- /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 0000000000..20ada639f2 --- /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 0000000000..20ba901cdb --- /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 0000000000..c6ee553a80 --- /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 0000000000..578a78decc --- /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 0000000000..74e8e67c71 --- /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 0000000000..172a0913ae --- /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 0000000000..e9b166f1ce --- /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 0000000000..07a6bc8989 --- /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 0000000000..79d0dcad5e --- /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 0000000000..745b5db3f6 --- /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 0000000000..2927b31c82 --- /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 0000000000..7b2f6157d0 --- /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 0000000000..e69de29bb2 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 0000000000..02da049e34 --- /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 0000000000..b176f8bf53 --- /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 0000000000..132fe8d93d --- /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 0000000000..94ea6a1a4f --- /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 0000000000..adb8ae4de2 --- /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 0000000000..514ef8596e --- /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 0000000000..42daa3375e --- /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 0000000000..06ce661a02 --- /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 0000000000..eb7dbffbff --- /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 9465123498..3b2455024c 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 1e2a7bd749..4929016b74 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 facd19ff5c..ca23544fbe 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 6f15f7afca..fab438a4ec 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 018ba9f2eb..7912b0adfe 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 0000000000..8b32c5352b --- /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 0000000000..e69de29bb2 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 0000000000..eac6abf712 --- /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 0000000000..f556765600 --- /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 0000000000..e69de29bb2 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 0000000000..6bc5347037 --- /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 0000000000..76c291a936 --- /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 0000000000..9920052364 --- /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 8ee30275e3..7ee36ae210 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 9c452c4397..bd18655f29 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 135d890adb..148f1a9747 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 0000000000..51159ad8f3 --- /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']