/v2/ansible/module_utils/facts.py
Python | 1098 lines | 1022 code | 29 blank | 47 comment | 66 complexity | f2a4315605397bcfcc91a6930557d052 MD5 | raw file
- # (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
- #
- # 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 <http://www.gnu.org/licenses/>.
- import os
- import stat
- import array
- import errno
- import fcntl
- import fnmatch
- import glob
- import platform
- import re
- import signal
- import socket
- import struct
- import datetime
- import getpass
- import pwd
- import ConfigParser
- import StringIO
- from string import maketrans
- try:
- import selinux
- HAVE_SELINUX=True
- except ImportError:
- HAVE_SELINUX=False
- try:
- import json
- except ImportError:
- import simplejson as json
- # --------------------------------------------------------------
- # timeout function to make sure some fact gathering
- # steps do not exceed a time limit
- class TimeoutError(Exception):
- pass
- def timeout(seconds=10, error_message="Timer expired"):
- def decorator(func):
- def _handle_timeout(signum, frame):
- raise TimeoutError(error_message)
- def wrapper(*args, **kwargs):
- signal.signal(signal.SIGALRM, _handle_timeout)
- signal.alarm(seconds)
- try:
- result = func(*args, **kwargs)
- finally:
- signal.alarm(0)
- return result
- return wrapper
- 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
- OSDIST_LIST = ( ('/etc/oracle-release', 'OracleLinux'),
- ('/etc/redhat-release', 'RedHat'),
- ('/etc/vmware-release', 'VMwareESX'),
- ('/etc/openwrt_release', 'OpenWrt'),
- ('/etc/system-release', 'OtherLinux'),
- ('/etc/alpine-release', 'Alpine'),
- ('/etc/release', 'Solaris'),
- ('/etc/arch-release', 'Archlinux'),
- ('/etc/SuSE-release', 'SuSE'),
- ('/etc/os-release', 'SuSE'),
- ('/etc/gentoo-release', 'Gentoo'),
- ('/etc/os-release', 'Debian'),
- ('/etc/os-release', 'NA'),
- ('/etc/lsb-release', 'Mandriva'))
- 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' : '/opt/local/bin/pkgin', 'name' : 'pkgin' },
- { 'path' : '/opt/local/bin/port', 'name' : 'macports' },
- { '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' : 'pkg' },
- ]
- def __init__(self, load_on_init=True):
- self.facts = {}
- if load_on_init:
- self.get_platform_facts()
- self.get_distribution_facts()
- self.get_cmdline()
- self.get_public_ssh_host_keys()
- self.get_selinux_facts()
- self.get_fips_facts()
- self.get_pkg_mgr_facts()
- self.get_lsb_facts()
- self.get_date_time_facts()
- self.get_user_facts()
- self.get_local_facts()
- self.get_env_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'] == 'Linux':
- self.get_distribution_facts()
- elif self.facts['system'] == 'AIX':
- try:
- rc, out, err = module.run_command("/usr/sbin/bootinfo -p")
- data = out.split('\n')
- self.facts['architecture'] = data[0]
- except:
- self.facts['architecture'] = 'Not Available'
- elif self.facts['system'] == 'OpenBSD':
- self.facts['architecture'] = platform.uname()[5]
- def get_local_facts(self):
- fact_path = 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
- rc, out, err = module.run_command(fn)
- else:
- out = get_file_content(fn, default='')
- # load raw json
- fact = 'loading %s' % fact_base
- try:
- fact = json.loads(out)
- except ValueError, e:
- # load raw ini
- cp = ConfigParser.ConfigParser()
- try:
- cp.readfp(StringIO.StringIO(out))
- except ConfigParser.Error, e:
- fact="error loading fact - please check content"
- else:
- fact = {}
- #print cp.sections()
- 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
- # platform.dist() is deprecated in 2.6
- # in 2.6 and newer, you should use platform.linux_distribution()
- def get_distribution_facts(self):
- # 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',
- XenServer = 'RedHat', Ubuntu = 'Debian', Debian = 'Debian', Raspbian = 'Debian', SLES = 'Suse',
- SLED = 'Suse', openSUSE = 'Suse', SuSE = 'Suse', Gentoo = 'Gentoo', Funtoo = 'Gentoo',
- Archlinux = 'Archlinux', Mandriva = 'Mandrake', Mandrake = 'Mandrake',
- Solaris = 'Solaris', Nexenta = 'Solaris', OmniOS = 'Solaris', OpenIndiana = 'Solaris',
- SmartOS = 'Solaris', AIX = 'AIX', Alpine = 'Alpine', MacOSX = 'Darwin',
- FreeBSD = 'FreeBSD', HPUX = 'HP-UX'
- )
- # TODO: Rewrite this to use the function references in a dict pattern
- # as it's much cleaner than this massive if-else
- if self.facts['system'] == 'AIX':
- self.facts['distribution'] = 'AIX'
- rc, out, err = module.run_command("/usr/bin/oslevel")
- data = out.split('.')
- self.facts['distribution_version'] = data[0]
- self.facts['distribution_release'] = data[1]
- elif self.facts['system'] == 'HP-UX':
- self.facts['distribution'] = 'HP-UX'
- rc, out, err = 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]
- elif self.facts['system'] == 'Darwin':
- self.facts['distribution'] = 'MacOSX'
- rc, out, err = module.run_command("/usr/bin/sw_vers -productVersion")
- data = out.split()[-1]
- self.facts['distribution_version'] = data
- elif self.facts['system'] == 'FreeBSD':
- self.facts['distribution'] = 'FreeBSD'
- self.facts['distribution_release'] = platform.release()
- self.facts['distribution_version'] = platform.version()
- elif self.facts['system'] == 'NetBSD':
- self.facts['distribution'] = 'NetBSD'
- self.facts['distribution_release'] = platform.release()
- self.facts['distribution_version'] = platform.version()
- elif self.facts['system'] == 'OpenBSD':
- self.facts['distribution'] = 'OpenBSD'
- self.facts['distribution_release'] = platform.release()
- rc, out, err = 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_version'] = match.groups()[0]
- else:
- self.facts['distribution_version'] = 'release'
- else:
- 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 ...
- for (path, name) in Facts.OSDIST_LIST:
- if os.path.exists(path):
- if os.path.getsize(path) > 0:
- if self.facts['distribution'] in ('Fedora', ):
- # Once we determine the value is one of these distros
- # we trust the values are always correct
- break
- elif name == 'OracleLinux':
- data = get_file_content(path)
- if 'Oracle Linux' in data:
- self.facts['distribution'] = name
- else:
- self.facts['distribution'] = data.split()[0]
- break
- elif name == 'RedHat':
- data = get_file_content(path)
- if 'Red Hat' in data:
- self.facts['distribution'] = name
- else:
- self.facts['distribution'] = data.split()[0]
- break
- elif name == 'OtherLinux':
- data = get_file_content(path)
- if 'Amazon' in data:
- self.facts['distribution'] = 'Amazon'
- self.facts['distribution_version'] = data.split()[-1]
- break
- elif name == 'OpenWrt':
- data = get_file_content(path)
- if 'OpenWrt' in data:
- 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]
- break
- elif name == 'Alpine':
- data = get_file_content(path)
- self.facts['distribution'] = name
- self.facts['distribution_version'] = data
- break
- elif name == 'Solaris':
- data = get_file_content(path).split('\n')[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
- break
- uname_rc, uname_out, uname_err = module.run_command(['uname', '-v'])
- 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').split('\n') 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_rc == 0 and 'NexentaOS_' in uname_out:
- 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_rc == 0:
- self.facts['distribution_version'] = uname_out.split('\n')[0].strip()
- break
- elif name == 'SuSE':
- data = get_file_content(path)
- if 'suse' in data.lower():
- if path == '/etc/os-release':
- for line in data.splitlines():
- distribution = re.search("^NAME=(.*)", line)
- if distribution:
- self.facts['distribution'] = distribution.group(1).strip('"')
- distribution_version = re.search('^VERSION_ID="?([0-9]+\.?[0-9]*)"?', line) # example pattern are 13.04 13.0 13
- if distribution_version:
- self.facts['distribution_version'] = distribution_version.group(1)
- if 'open' in data.lower():
- release = re.search("^PRETTY_NAME=[^(]+ \(?([^)]+?)\)", line)
- if release:
- self.facts['distribution_release'] = release.groups()[0]
- elif 'enterprise' in data.lower():
- release = re.search('^VERSION_ID="?[0-9]+\.?([0-9]*)"?', line) # SLES doesn't got funny release names
- if release:
- release = release.group(1)
- else:
- release = "0" # no minor number, so it is the first release
- self.facts['distribution_release'] = release
- break
- elif path == '/etc/SuSE-release':
- if 'open' in data.lower():
- data = data.splitlines()
- distdata = get_file_content(path).split('\n')[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)
- elif name == 'Debian':
- data = get_file_content(path)
- if 'Debian' in data or 'Raspbian' in data:
- release = re.search("PRETTY_NAME=[^(]+ \(?([^)]+?)\)", data)
- if release:
- self.facts['distribution_release'] = release.groups()[0]
- break
- elif name == 'Mandriva':
- data = get_file_content(path)
- if 'Mandriva' in data:
- 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
- break
- elif name == 'NA':
- data = get_file_content(path)
- for line in data.splitlines():
- distribution = re.search("^NAME=(.*)", line)
- if distribution:
- self.facts['distribution'] = distribution.group(1).strip('"')
- version = re.search("^VERSION=(.*)", line)
- if version:
- self.facts['distribution_version'] = version.group(1).strip('"')
- if self.facts['distribution'].lower() == 'coreos':
- data = get_file_content('/etc/coreos/update.conf')
- release = re.search("^GROUP=(.*)", data)
- if release:
- self.facts['distribution_release'] = release.group(1).strip('"')
- else:
- self.facts['distribution'] = name
- machine_id = get_file_content("/var/lib/dbus/machine-id") or get_file_content("/etc/machine-id")
- if machine_id:
- machine_id = machine_id.split('\n')[0]
- self.facts["machine_id"] = machine_id
- self.facts['os_family'] = self.facts['distribution']
- if self.facts['distribution'] in OS_FAMILY:
- self.facts['os_family'] = OS_FAMILY[self.facts['distribution']]
- 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, e:
- pass
- def get_public_ssh_host_keys(self):
- dsa_filename = '/etc/ssh/ssh_host_dsa_key.pub'
- rsa_filename = '/etc/ssh/ssh_host_rsa_key.pub'
- ecdsa_filename = '/etc/ssh/ssh_host_ecdsa_key.pub'
- if self.facts['system'] == 'Darwin':
- dsa_filename = '/etc/ssh_host_dsa_key.pub'
- rsa_filename = '/etc/ssh_host_rsa_key.pub'
- ecdsa_filename = '/etc/ssh_host_ecdsa_key.pub'
- dsa = get_file_content(dsa_filename)
- rsa = get_file_content(rsa_filename)
- ecdsa = get_file_content(ecdsa_filename)
- if dsa is None:
- dsa = 'NA'
- else:
- self.facts['ssh_host_key_dsa_public'] = dsa.split()[1]
- if rsa is None:
- rsa = 'NA'
- else:
- self.facts['ssh_host_key_rsa_public'] = rsa.split()[1]
- if ecdsa is None:
- ecdsa = 'NA'
- else:
- self.facts['ssh_host_key_ecdsa_public'] = ecdsa.split()[1]
- def get_pkg_mgr_facts(self):
- self.facts['pkg_mgr'] = 'unknown'
- for pkg in Facts.PKG_MGRS:
- if os.path.exists(pkg['path']):
- self.facts['pkg_mgr'] = pkg['name']
- if self.facts['system'] == 'OpenBSD':
- self.facts['pkg_mgr'] = 'openbsd_pkg'
- def get_lsb_facts(self):
- lsb_path = module.get_bin_path('lsb_release')
- if lsb_path:
- rc, out, err = module.run_command([lsb_path, "-a"])
- if rc == 0:
- self.facts['lsb'] = {}
- for line in out.split('\n'):
- 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
- if 'lsb' in self.facts and 'release' in self.facts['lsb']:
- self.facts['lsb']['major_release'] = self.facts['lsb']['release'].split('.')[0]
- 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
- else:
- return self.facts
- 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
- return
- self.facts['selinux'] = {}
- 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 OSError, e:
- 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 OSError, e:
- self.facts['selinux']['config_mode'] = 'unknown'
- try:
- mode = selinux.security_getenforce()
- self.facts['selinux']['mode'] = Facts.SELINUX_MODE_DICT.get(mode, 'unknown')
- except OSError, e:
- 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 OSError, e:
- self.facts['selinux']['type'] = 'unknown'
- 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']['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']['tz'] = time.strftime("%Z")
- self.facts['date_time']['tz_offset'] = time.strftime("%z")
- # 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
- def get_env_facts(self):
- self.facts['env'] = {}
- for k,v in os.environ.iteritems():
- self.facts['env'][k] = v
- 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):
- subclass = cls
- for sc in Hardware.__subclasses__():
- if sc.platform == platform.system():
- subclass = sc
- return super(cls, subclass).__new__(subclass, *arguments, **keyword)
- def __init__(self):
- Facts.__init__(self)
- def populate(self):
- return self.facts
- 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'))
- def __init__(self):
- Hardware.__init__(self)
- def populate(self):
- 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_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()] = long(val) / 1024
- if key in self.MEMORY_FACTS:
- val = data[1].strip().split(' ')[0]
- memstats[key.lower()] = long(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 == 'model name' or key == 'Processor' or key == 'vendor_id':
- 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())
- 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:
- self.facts['processor_count'] = sockets and len(sockets) or i
- self.facts['processor_cores'] = sockets.values() and sockets.values()[0] or 1
- self.facts['processor_threads_per_core'] = ((cores.values() and
- cores.values()[0] or 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, e:
- 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 = 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) = 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.split('\n') 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'
- @timeout(10)
- def get_mount_facts(self):
- self.facts['mounts'] = []
- mtab = get_file_content('/etc/mtab', '')
- for line in mtab.split('\n'):
- if line.startswith('/'):
- fields = line.rstrip('\n').split()
- if(fields[2] != 'none'):
- size_total = None
- size_available = None
- try:
- statvfs_result = os.statvfs(fields[1])
- size_total = statvfs_result.f_bsize * statvfs_result.f_blocks
- size_available = statvfs_result.f_bsize * (statvfs_result.f_bavail)
- except OSError, e:
- continue
- uuid = 'NA'
- lsblkPath = module.get_bin_path("lsblk")
- if lsblkPath:
- rc, out, err = module.run_command("%s -ln --output UUID %s" % (lsblkPath, fields[0]), use_unsafe_shell=True)
- if rc == 0:
- uuid = out.strip()
- self.facts['mounts'].append(
- {'mount': fields[1],
- 'device':fields[0],
- 'fstype': fields[2],
- 'options': fields[3],
- # statvfs data
- 'size_total': size_total,
- 'size_available': size_available,
- 'uuid': uuid,
- })
- def get_device_facts(self):
- self.facts['devices'] = {}
- lspci = module.get_bin_path('lspci')
- if lspci:
- rc, pcidata, err = module.run_command([lspci, '-D'])
- else:
- pcidata = None
- try:
- block_devs = os.listdir("/sys/block")
- except OSError:
- return
- for block in block_devs:
- virtual = 1
- sysfs_no_links = 0
- try:
- path = os.readlink(os.path.join("/sys/block/", block))
- except OSError, e:
- 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']:
- 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)
- 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/physical_block_size")
- if not part['sectorsize']:
- part['sectorsize'] = get_file_content(part_sysdir + "/queue/hw_sector_size",512)
- part['size'] = module.pretty_bytes((float(part['sectors']) * float(part['sectorsize'])))
- 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/physical_block_size")
- if not d['sectorsize']:
- d['sectorsize'] = get_file_content(sysdir + "/queue/hw_sector_size",512)
- d['size'] = 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)
- d['host'] = m.group(1)
- d['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:
- d['holders'].append(name)
- else:
- d['holders'].append(folder)
- self.facts['devices'][diskname] = d
- def get_uptime_facts(self):
- uptime_seconds_string = get_file_content('/proc/uptime').split(' ')[0]
- self.facts['uptime_seconds'] = int(float(uptime_seconds_string))
- 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 __init__(self):
- Hardware.__init__(self)
- def populate(self):
- self.get_cpu_facts()
- self.get_memory_facts()
- try:
- self.get_mount_facts()
- except TimeoutError:
- pass
- return self.facts
- def get_cpu_facts(self):
- physid = 0
- sockets = {}
- rc, out, err = module.run_command("/usr/bin/kstat cpu_info")
- self.facts['processor'] = []
- for line in out.split('\n'):
- 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 = module.run_command(["/usr/sbin/prtconf"])
- for line in out.split('\n'):
- if 'Memory size' in line:
- self.facts['memtotal_mb'] = line.split()[2]
- rc, out, err = module.run_command("/usr/sbin/swap -s")
-