2012-02-24 08:25:09 +00:00
|
|
|
#!/usr/bin/python
|
2012-08-03 01:29:10 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
2012-02-24 08:25:09 +00:00
|
|
|
|
2012-02-29 00:08:09 +00:00
|
|
|
# (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/>.
|
|
|
|
|
2012-04-17 23:59:23 +00:00
|
|
|
import array
|
|
|
|
import fcntl
|
|
|
|
import glob
|
|
|
|
import platform
|
|
|
|
import re
|
|
|
|
import socket
|
|
|
|
import struct
|
2012-02-24 08:25:09 +00:00
|
|
|
|
2012-05-30 15:42:05 +00:00
|
|
|
try:
|
|
|
|
import selinux
|
|
|
|
HAVE_SELINUX=True
|
|
|
|
except ImportError:
|
|
|
|
HAVE_SELINUX=False
|
|
|
|
|
2012-02-24 08:25:09 +00:00
|
|
|
try:
|
|
|
|
import json
|
|
|
|
except ImportError:
|
|
|
|
import simplejson as json
|
|
|
|
|
2012-07-18 21:46:28 +00:00
|
|
|
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 (eg. 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.
|
|
|
|
"""
|
|
|
|
|
|
|
|
_I386RE = re.compile(r'i[3456]86')
|
|
|
|
# For the most part, we assume that platform.dist() will tell the truth.
|
|
|
|
# This is the fallback to handle unknowns or exceptions
|
|
|
|
OSDIST_DICT = { '/etc/redhat-release': 'RedHat',
|
|
|
|
'/etc/vmware-release': 'VMwareESX' }
|
|
|
|
SELINUX_MODE_DICT = { 1: 'enforcing', 0: 'permissive', -1: 'disabled' }
|
|
|
|
|
2012-07-18 23:22:43 +00:00
|
|
|
def __init__(self):
|
|
|
|
self.facts = {}
|
2012-07-18 21:46:28 +00:00
|
|
|
self.get_platform_facts()
|
|
|
|
self.get_distribution_facts()
|
|
|
|
self.get_public_ssh_host_keys()
|
|
|
|
self.get_selinux_facts()
|
|
|
|
|
2012-07-18 23:22:43 +00:00
|
|
|
def populate(self):
|
|
|
|
return self.facts
|
|
|
|
|
2012-07-18 21:46:28 +00:00
|
|
|
# Platform
|
|
|
|
# patform.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'] = self.facts['fqdn'].split('.')[0]
|
|
|
|
if self.facts['machine'] == 'x86_64':
|
|
|
|
self.facts['architecture'] = self.facts['machine']
|
|
|
|
elif Facts._I386RE.search(self.facts['machine']):
|
|
|
|
self.facts['architecture'] = 'i386'
|
|
|
|
else:
|
|
|
|
self.facts['archtecture'] = self.facts['machine']
|
|
|
|
if self.facts['system'] == 'Linux':
|
|
|
|
self.get_distribution_facts()
|
|
|
|
|
|
|
|
# platform.dist() is deprecated in 2.6
|
|
|
|
# in 2.6 and newer, you should use platform.linux_distribution()
|
|
|
|
def get_distribution_facts(self):
|
|
|
|
dist = platform.dist()
|
|
|
|
self.facts['distribution'] = dist[0].capitalize() or 'NA'
|
|
|
|
self.facts['distribution_version'] = dist[1] or 'NA'
|
|
|
|
self.facts['distribution_release'] = dist[2] or 'NA'
|
|
|
|
# Try to handle the exceptions now ...
|
|
|
|
for (path, name) in Facts.OSDIST_DICT.items():
|
|
|
|
if os.path.exists(path):
|
|
|
|
if self.facts['distribution'] == 'Fedora':
|
|
|
|
pass
|
|
|
|
elif name == 'RedHat':
|
|
|
|
data = get_file_content(path)
|
|
|
|
if 'Red Hat' in data:
|
|
|
|
self.facts['distribution'] = name
|
|
|
|
else:
|
|
|
|
self.facts['distribution'] = data.split()[0]
|
2012-04-17 23:59:23 +00:00
|
|
|
else:
|
2012-07-18 21:46:28 +00:00
|
|
|
self.facts['distribution'] = name
|
|
|
|
|
|
|
|
def get_public_ssh_host_keys(self):
|
|
|
|
dsa = get_file_content('/etc/ssh/ssh_host_dsa_key.pub')
|
|
|
|
rsa = get_file_content('/etc/ssh/ssh_host_rsa_key.pub')
|
|
|
|
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]
|
|
|
|
|
|
|
|
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'
|
2012-08-08 05:57:44 +00:00
|
|
|
try:
|
|
|
|
self.facts['selinux']['policyvers'] = selinux.security_policyvers()
|
2012-08-08 15:56:12 +00:00
|
|
|
except OSError, e:
|
2012-08-08 05:57:44 +00:00
|
|
|
self.facts['selinux']['policyvers'] = 'unknown'
|
|
|
|
try:
|
|
|
|
(rc, configmode) = selinux.selinux_getenforcemode()
|
2012-08-11 16:38:05 +00:00
|
|
|
if rc == 0:
|
|
|
|
self.facts['selinux']['config_mode'] = Facts.SELINUX_MODE_DICT.get(configmode, 'unknown')
|
2012-08-08 05:57:44 +00:00
|
|
|
else:
|
|
|
|
self.facts['selinux']['config_mode'] = 'unknown'
|
|
|
|
except OSError, e:
|
|
|
|
self.facts['selinux']['config_mode'] = 'unknown'
|
|
|
|
try:
|
|
|
|
mode = selinux.security_getenforce()
|
2012-08-11 16:38:05 +00:00
|
|
|
self.facts['selinux']['mode'] = Facts.SELINUX_MODE_DICT.get(mode, 'unknown')
|
2012-08-08 05:57:44 +00:00
|
|
|
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'
|
2012-07-18 21:46:28 +00:00
|
|
|
|
|
|
|
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'
|
|
|
|
|
2012-07-18 23:22:43 +00:00
|
|
|
def __new__(cls, *arguments, **keyword):
|
2012-07-18 21:46:28 +00:00
|
|
|
subclass = cls
|
|
|
|
for sc in Hardware.__subclasses__():
|
|
|
|
if sc.platform == platform.system():
|
|
|
|
subclass = sc
|
|
|
|
return super(cls, subclass).__new__(subclass, *arguments, **keyword)
|
|
|
|
|
2012-07-18 23:22:43 +00:00
|
|
|
def __init__(self):
|
|
|
|
Facts.__init__(self)
|
2012-07-18 21:46:28 +00:00
|
|
|
|
|
|
|
def populate(self):
|
2012-07-18 23:22:43 +00:00
|
|
|
return self.facts
|
2012-07-18 21:46:28 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
"""
|
2012-08-11 16:35:58 +00:00
|
|
|
|
2012-07-18 21:46:28 +00:00
|
|
|
platform = 'Linux'
|
|
|
|
MEMORY_FACTS = ['MemTotal', 'SwapTotal', 'MemFree', 'SwapFree']
|
|
|
|
# DMI bits
|
2012-08-11 16:35:58 +00:00
|
|
|
DMI_DICT = dict(
|
|
|
|
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',
|
|
|
|
bios_date = '/sys/devices/virtual/dmi/id/bios_date',
|
|
|
|
bios_version = '/sys/devices/virtual/dmi/id/bios_version'
|
|
|
|
)
|
|
|
|
# DMI SPEC -- http://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.0.pdf
|
2012-07-18 21:46:28 +00:00
|
|
|
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",
|
2012-08-02 14:22:51 +00:00
|
|
|
"CompactPCI", "AdvancedTCA", "Blade" ]
|
2012-07-18 21:46:28 +00:00
|
|
|
|
2012-07-18 23:22:43 +00:00
|
|
|
def __init__(self):
|
|
|
|
Hardware.__init__(self)
|
2012-07-18 21:46:28 +00:00
|
|
|
|
|
|
|
def populate(self):
|
|
|
|
self.get_cpu_facts()
|
|
|
|
self.get_memory_facts()
|
|
|
|
self.get_dmi_facts()
|
2012-07-18 23:22:43 +00:00
|
|
|
return self.facts
|
2012-07-18 21:46:28 +00:00
|
|
|
|
|
|
|
def get_memory_facts(self):
|
|
|
|
if not os.access("/proc/meminfo", os.R_OK):
|
|
|
|
return
|
|
|
|
for line in open("/proc/meminfo").readlines():
|
|
|
|
data = line.split(":", 1)
|
|
|
|
key = data[0]
|
|
|
|
if key in LinuxHardware.MEMORY_FACTS:
|
|
|
|
val = data[1].strip().split(' ')[0]
|
|
|
|
self.facts["%s_mb" % key.lower()] = long(val) / 1024
|
|
|
|
|
|
|
|
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 open("/proc/cpuinfo").readlines():
|
|
|
|
data = line.split(":", 1)
|
|
|
|
key = data[0].strip()
|
|
|
|
if key == 'model name':
|
|
|
|
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())
|
2012-04-17 23:59:23 +00:00
|
|
|
else:
|
2012-07-18 21:46:28 +00:00
|
|
|
self.facts['processor_count'] = i
|
|
|
|
self.facts['processor_cores'] = 'NA'
|
|
|
|
|
|
|
|
def get_dmi_facts(self):
|
|
|
|
for (key,path) in LinuxHardware.DMI_DICT.items():
|
|
|
|
data = get_file_content(path)
|
|
|
|
if data is not None:
|
|
|
|
if key == 'form_factor':
|
2012-08-02 14:06:55 +00:00
|
|
|
try:
|
|
|
|
self.facts['form_factor'] = LinuxHardware.FORM_FACTOR[int(data)]
|
|
|
|
except IndexError, e:
|
|
|
|
self.facts['form_factor'] = 'unknown (%s)' % data
|
2012-07-18 21:46:28 +00:00
|
|
|
else:
|
|
|
|
self.facts[key] = data
|
|
|
|
else:
|
|
|
|
self.facts[key] = 'NA'
|
|
|
|
|
|
|
|
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'
|
|
|
|
|
2012-07-18 23:22:43 +00:00
|
|
|
def __init__(self):
|
|
|
|
Hardware.__init__(self)
|
2012-07-18 21:46:28 +00:00
|
|
|
|
|
|
|
def populate(self):
|
|
|
|
self.get_cpu_facts()
|
|
|
|
self.get_memory_facts()
|
2012-07-18 23:22:43 +00:00
|
|
|
return self.facts
|
2012-07-18 21:46:28 +00:00
|
|
|
|
|
|
|
def get_cpu_facts(self):
|
|
|
|
cmd = subprocess.Popen("/usr/sbin/psrinfo -v", shell=True,
|
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
|
out, err = cmd.communicate()
|
|
|
|
self.facts['processor'] = []
|
|
|
|
for line in out.split('\n'):
|
|
|
|
if 'processor operates' in line:
|
|
|
|
if 'processor' not in self.facts:
|
|
|
|
self.facts['processor'] = []
|
|
|
|
self.facts['processor'].append(line.strip())
|
|
|
|
self.facts['processor_cores'] = 'NA'
|
|
|
|
self.facts['processor_count'] = len(self.facts['processor'])
|
|
|
|
|
|
|
|
def get_memory_facts(self):
|
|
|
|
cmd = subprocess.Popen("/usr/sbin/prtconf", shell=False,
|
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
|
out, err = cmd.communicate()
|
|
|
|
for line in out.split('\n'):
|
|
|
|
if 'Memory size' in line:
|
|
|
|
self.facts['memtotal_mb'] = line.split()[2]
|
|
|
|
cmd = subprocess.Popen("/usr/sbin/swap -s", shell=True,
|
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
|
out, err = cmd.communicate()
|
|
|
|
allocated = long(out.split()[1][:-1])
|
|
|
|
reserved = long(out.split()[5][:-1])
|
|
|
|
used = long(out.split()[8][:-1])
|
|
|
|
free = long(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
|
|
|
|
|
|
|
|
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
|
|
|
|
"""
|
|
|
|
platform = 'FreeBSD'
|
|
|
|
DMESG_BOOT = '/var/run/dmesg.boot'
|
|
|
|
|
2012-07-18 23:22:43 +00:00
|
|
|
def __init__(self):
|
|
|
|
Hardware.__init__(self)
|
2012-07-18 21:46:28 +00:00
|
|
|
|
|
|
|
def populate(self):
|
|
|
|
self.get_cpu_facts()
|
|
|
|
self.get_memory_facts()
|
2012-07-18 23:22:43 +00:00
|
|
|
return self.facts
|
2012-07-18 21:46:28 +00:00
|
|
|
|
|
|
|
def get_cpu_facts(self):
|
|
|
|
self.facts['processor'] = []
|
|
|
|
cmd = subprocess.Popen("/sbin/sysctl -n hw.ncpu", shell=True,
|
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
|
out, err = cmd.communicate()
|
|
|
|
self.facts['processor_count'] = out.strip()
|
|
|
|
for line in open(FreeBSDHardware.DMESG_BOOT).readlines():
|
|
|
|
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):
|
|
|
|
cmd = subprocess.Popen("/sbin/sysctl vm.stats", shell=True,
|
2012-04-17 23:59:23 +00:00
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
|
out, err = cmd.communicate()
|
|
|
|
for line in out.split('\n'):
|
|
|
|
data = line.split()
|
2012-07-18 21:46:28 +00:00
|
|
|
if 'vm.stats.vm.v_page_size' in line:
|
|
|
|
pagesize = long(data[1])
|
|
|
|
if 'vm.stats.vm.v_page_count' in line:
|
|
|
|
pagecount = long(data[1])
|
|
|
|
if 'vm.stats.vm.v_free_count' in line:
|
|
|
|
freecount = long(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%
|
2012-08-07 00:07:02 +00:00
|
|
|
#
|
2012-07-18 21:46:28 +00:00
|
|
|
cmd = subprocess.Popen("/usr/sbin/swapinfo -m", shell=True,
|
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
|
out, err = cmd.communicate()
|
|
|
|
lines = out.split('\n')
|
|
|
|
if len(lines[-1]) == 0:
|
|
|
|
lines.pop()
|
|
|
|
data = lines[-1].split()
|
|
|
|
self.facts['swaptotal_mb'] = data[1]
|
|
|
|
self.facts['swapfree_mb'] = data[3]
|
|
|
|
|
|
|
|
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_<name> dictionary of ipv4, ipv6, and mac address information.
|
|
|
|
|
|
|
|
All subclasses MUST define platform.
|
|
|
|
"""
|
|
|
|
platform = 'Generic'
|
|
|
|
|
2012-07-20 06:37:10 +00:00
|
|
|
IPV6_SCOPE = { '0' : 'global',
|
|
|
|
'10' : 'host',
|
|
|
|
'20' : 'link',
|
|
|
|
'40' : 'admin',
|
|
|
|
'50' : 'site',
|
|
|
|
'80' : 'organization' }
|
|
|
|
|
2012-07-18 23:22:43 +00:00
|
|
|
def __new__(cls, *arguments, **keyword):
|
2012-07-18 21:46:28 +00:00
|
|
|
subclass = cls
|
|
|
|
for sc in Network.__subclasses__():
|
|
|
|
if sc.platform == platform.system():
|
|
|
|
subclass = sc
|
|
|
|
return super(cls, subclass).__new__(subclass, *arguments, **keyword)
|
|
|
|
|
2012-07-18 23:22:43 +00:00
|
|
|
def __init__(self):
|
|
|
|
Facts.__init__(self)
|
2012-07-18 21:46:28 +00:00
|
|
|
|
|
|
|
def populate(self):
|
2012-07-18 23:22:43 +00:00
|
|
|
return self.facts
|
2012-07-18 21:46:28 +00:00
|
|
|
|
|
|
|
class LinuxNetwork(Network):
|
|
|
|
"""
|
|
|
|
This is a Linux-specific subclass of Network. It defines
|
|
|
|
- interfaces (a list of interface names)
|
|
|
|
- interface_<name> dictionary of ipv4, ipv6, and mac address information.
|
2012-08-11 10:39:22 +00:00
|
|
|
- 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.
|
2012-07-18 21:46:28 +00:00
|
|
|
"""
|
|
|
|
platform = 'Linux'
|
|
|
|
|
2012-07-18 23:22:43 +00:00
|
|
|
def __init__(self):
|
|
|
|
Network.__init__(self)
|
2012-07-18 21:46:28 +00:00
|
|
|
|
|
|
|
def populate(self):
|
2012-08-11 06:40:54 +00:00
|
|
|
interfaces, ips = self.parse_ip_addr()
|
|
|
|
|
|
|
|
self.facts['interfaces'] = interfaces.keys()
|
|
|
|
for iface in interfaces:
|
|
|
|
self.facts[iface] = interfaces[iface]
|
|
|
|
|
2012-08-11 10:39:22 +00:00
|
|
|
for key in ips:
|
|
|
|
self.facts[key] = ips[key]
|
2012-07-18 21:46:28 +00:00
|
|
|
|
2012-08-11 10:39:22 +00:00
|
|
|
return self.facts
|
2012-08-11 06:40:54 +00:00
|
|
|
|
|
|
|
def parse_ip_addr(self):
|
|
|
|
interfaces = {}
|
2012-08-11 16:08:55 +00:00
|
|
|
ips = dict(
|
|
|
|
all_ipv4_addresses = [],
|
|
|
|
all_ipv6_addresses = [],
|
|
|
|
ipv4_address = None,
|
|
|
|
ipv6_address = None
|
|
|
|
)
|
2012-08-11 06:40:54 +00:00
|
|
|
|
|
|
|
output = subprocess.Popen(['ip','addr'], stdout=subprocess.PIPE).communicate()[0]
|
|
|
|
for line in output.split('\n'):
|
|
|
|
if line:
|
|
|
|
words = line.split()
|
|
|
|
if not line.startswith(' '):
|
|
|
|
device = words[1][0:-1]
|
|
|
|
mtu = words[4]
|
|
|
|
elif words[0].startswith('link/'):
|
|
|
|
iface_type = words[0].split('/')[1]
|
|
|
|
if iface_type == 'void':
|
|
|
|
macaddress = 'unknown'
|
|
|
|
else:
|
|
|
|
macaddress = words[1]
|
|
|
|
elif words[0] == 'inet':
|
|
|
|
address, netmask_length = words[1].split('/')
|
|
|
|
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 an interface has multiple IPv4 addresses, make up an
|
|
|
|
# interface name for each address
|
|
|
|
if iface in interfaces:
|
|
|
|
i = 0
|
|
|
|
while '{0}:{1}'.format(iface, i) in interfaces:
|
|
|
|
i += 1
|
|
|
|
iface = '{0}:{1}'.format(iface, i)
|
|
|
|
|
|
|
|
interfaces[iface] = {}
|
|
|
|
interfaces[iface]['macaddress'] = macaddress
|
|
|
|
interfaces[iface]['mtu'] = mtu
|
|
|
|
interfaces[iface]['device'] = device
|
|
|
|
interfaces[iface]['ipv4'] = {'address': address,
|
|
|
|
'netmask': netmask,
|
|
|
|
'network': network}
|
|
|
|
|
2012-08-11 16:08:55 +00:00
|
|
|
if not address.startswith('127.'):
|
|
|
|
ips['all_ipv4_addresses'].append(address)
|
|
|
|
if not ips['ipv4_address'] or ips['ipv4_address'].startswith('127.'):
|
2012-08-11 10:39:22 +00:00
|
|
|
ips['ipv4_address'] = address
|
2012-08-11 06:40:54 +00:00
|
|
|
|
|
|
|
elif words[0] == 'inet6':
|
|
|
|
address, prefix = words[1].split('/')
|
|
|
|
scope = words[3]
|
|
|
|
|
|
|
|
iface = device
|
|
|
|
if iface not in interfaces:
|
|
|
|
interfaces[iface] = {}
|
|
|
|
interfaces[iface]['macaddress'] = macaddress
|
|
|
|
interfaces[iface]['mtu'] = mtu
|
|
|
|
interfaces[iface]['device'] = device
|
|
|
|
if 'ipv6' not in interfaces[iface]:
|
|
|
|
interfaces[iface]['ipv6'] = []
|
|
|
|
interfaces[iface]['ipv6'].append( {
|
|
|
|
'address': address,
|
|
|
|
'prefix': prefix,
|
|
|
|
'scope': scope} )
|
|
|
|
|
2012-08-11 16:08:55 +00:00
|
|
|
if not address == '::1':
|
|
|
|
ips['all_ipv6_addresses'].append(address)
|
2012-08-11 10:39:22 +00:00
|
|
|
if not ips['ipv6_address'] or ips['ipv6_address'] == '::1':
|
|
|
|
ips['ipv6_address'] = address
|
2012-08-11 06:40:54 +00:00
|
|
|
|
|
|
|
return interfaces, ips
|
2012-07-18 21:46:28 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
All subclasses MUST define platform.
|
|
|
|
"""
|
|
|
|
|
2012-07-18 23:22:43 +00:00
|
|
|
def __new__(cls, *arguments, **keyword):
|
2012-07-18 21:46:28 +00:00
|
|
|
subclass = cls
|
|
|
|
for sc in Virtual.__subclasses__():
|
|
|
|
if sc.platform == platform.system():
|
|
|
|
subclass = sc
|
|
|
|
return super(cls, subclass).__new__(subclass, *arguments, **keyword)
|
|
|
|
|
2012-07-18 23:22:43 +00:00
|
|
|
def __init__(self):
|
|
|
|
Facts.__init__(self)
|
2012-07-18 21:46:28 +00:00
|
|
|
|
|
|
|
def populate(self):
|
2012-07-18 23:22:43 +00:00
|
|
|
return self.facts
|
2012-07-18 21:46:28 +00:00
|
|
|
|
|
|
|
class LinuxVirtual(Virtual):
|
|
|
|
"""
|
|
|
|
This is a Linux-specific subclass of Virtual. It defines
|
|
|
|
- virtualization_type
|
|
|
|
- virtualization_role
|
|
|
|
"""
|
|
|
|
platform = 'Linux'
|
|
|
|
|
2012-07-18 23:22:43 +00:00
|
|
|
def __init__(self):
|
|
|
|
Virtual.__init__(self)
|
2012-07-18 21:46:28 +00:00
|
|
|
|
|
|
|
def populate(self):
|
2012-07-18 23:22:43 +00:00
|
|
|
self.get_virtual_facts()
|
|
|
|
return self.facts
|
|
|
|
|
|
|
|
def get_virtual_facts(self):
|
2012-07-18 21:46:28 +00:00
|
|
|
if os.path.exists("/proc/xen"):
|
|
|
|
self.facts['virtualization_type'] = 'xen'
|
2012-08-11 12:33:43 +00:00
|
|
|
if os.path.exists('/proc/xen/capabilities'):
|
|
|
|
self.facts['virtualization_role'] = 'host'
|
|
|
|
else:
|
|
|
|
self.facts['virtualization_role'] = 'guest'
|
|
|
|
|
|
|
|
elif os.path.exists('/proc/vz'):
|
|
|
|
self.facts['virtualization_type'] = 'openvz'
|
|
|
|
if os.path.exists('/proc/vz/version'):
|
2012-07-18 21:46:28 +00:00
|
|
|
self.facts['virtualization_role'] = 'host'
|
2012-08-11 12:33:43 +00:00
|
|
|
else:
|
|
|
|
self.facts['virtualization_role'] = 'guest'
|
|
|
|
|
|
|
|
elif get_file_content('/sys/devices/virtual/dmi/id/product_name') in ['KVM','Bochs']:
|
|
|
|
self.facts['virtualization_type'] = 'kvm'
|
|
|
|
self.facts['virtualization_role'] = 'guest'
|
|
|
|
|
|
|
|
elif get_file_content('/sys/devices/virtual/dmi/id/sys_vendor') == 'VMware, Inc.':
|
|
|
|
self.facts['virtualization_type'] = 'VMware'
|
|
|
|
self.facts['virtualization_role'] = 'guest'
|
|
|
|
|
|
|
|
elif get_file_content('/sys/devices/virtual/dmi/id/sys_vendor') == 'Microsoft Corporation':
|
|
|
|
self.facts['virtualization_type'] = 'VirtualPC'
|
|
|
|
self.facts['virtualization_role'] = 'guest'
|
|
|
|
|
|
|
|
elif os.path.exists("/proc/modules"):
|
2012-07-18 21:46:28 +00:00
|
|
|
modules = []
|
|
|
|
for line in open("/proc/modules").readlines():
|
|
|
|
data = line.split(" ", 1)
|
|
|
|
modules.append(data[0])
|
|
|
|
if 'kvm' in modules:
|
|
|
|
self.facts['virtualization_type'] = 'kvm'
|
|
|
|
self.facts['virtualization_role'] = 'host'
|
|
|
|
elif 'vboxdrv' in modules:
|
|
|
|
self.facts['virtualization_type'] = 'virtualbox'
|
|
|
|
self.facts['virtualization_role'] = 'host'
|
|
|
|
elif 'vboxguest' in modules:
|
|
|
|
self.facts['virtualization_type'] = 'virtualbox'
|
|
|
|
self.facts['virtualization_role'] = 'guest'
|
2012-04-17 23:59:23 +00:00
|
|
|
|
2012-07-18 23:41:14 +00:00
|
|
|
def get_file_content(path):
|
|
|
|
data = None
|
|
|
|
if os.path.exists(path) and os.access(path, os.R_OK):
|
|
|
|
data = open(path).read().strip()
|
|
|
|
if len(data) == 0:
|
|
|
|
data = None
|
|
|
|
return data
|
|
|
|
|
2012-04-17 23:59:23 +00:00
|
|
|
def ansible_facts():
|
|
|
|
facts = {}
|
2012-07-18 23:22:43 +00:00
|
|
|
facts.update(Facts().populate())
|
|
|
|
facts.update(Hardware().populate())
|
|
|
|
facts.update(Network().populate())
|
|
|
|
facts.update(Virtual().populate())
|
|
|
|
return facts
|
2012-04-17 23:59:23 +00:00
|
|
|
|
2012-07-09 07:52:00 +00:00
|
|
|
# ===========================================
|
2012-06-26 17:49:26 +00:00
|
|
|
|
2012-07-28 21:14:23 +00:00
|
|
|
def run_setup(module):
|
|
|
|
|
|
|
|
setup_options = {}
|
|
|
|
facts = ansible_facts()
|
2012-08-07 00:07:02 +00:00
|
|
|
|
2012-07-28 21:14:23 +00:00
|
|
|
for (k, v) in facts.items():
|
|
|
|
setup_options["ansible_%s" % k] = v
|
|
|
|
|
|
|
|
# if facter is installed, and we can use --json because
|
|
|
|
# ruby-json is ALSO installed, include facter data in the JSON
|
|
|
|
|
|
|
|
if os.path.exists("/usr/bin/facter"):
|
2012-08-11 16:35:58 +00:00
|
|
|
cmd = subprocess.Popen("/usr/bin/facter --json", shell=True,
|
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
|
out, err = cmd.communicate()
|
|
|
|
facter = True
|
|
|
|
try:
|
|
|
|
facter_ds = json.loads(out)
|
|
|
|
except:
|
|
|
|
facter = False
|
|
|
|
if facter:
|
|
|
|
for (k,v) in facter_ds.items():
|
|
|
|
setup_options["facter_%s" % k] = v
|
2012-07-28 21:14:23 +00:00
|
|
|
|
|
|
|
# ditto for ohai, but just top level string keys
|
|
|
|
# because it contains a lot of nested stuff we can't use for
|
|
|
|
# templating w/o making a nicer key for it (TODO)
|
|
|
|
|
|
|
|
if os.path.exists("/usr/bin/ohai"):
|
2012-08-11 16:35:58 +00:00
|
|
|
cmd = subprocess.Popen("/usr/bin/ohai", shell=True,
|
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
|
out, err = cmd.communicate()
|
|
|
|
ohai = True
|
|
|
|
try:
|
|
|
|
ohai_ds = json.loads(out)
|
|
|
|
except:
|
|
|
|
ohai = False
|
|
|
|
if ohai:
|
|
|
|
for (k,v) in ohai_ds.items():
|
|
|
|
if type(v) == str or type(v) == unicode:
|
|
|
|
k2 = "ohai_%s" % k
|
|
|
|
setup_options[k2] = v
|
2012-07-28 21:14:23 +00:00
|
|
|
|
|
|
|
setup_result = {}
|
|
|
|
setup_result['ansible_facts'] = setup_options
|
|
|
|
|
|
|
|
# hack to keep --verbose from showing all the setup module results
|
|
|
|
setup_result['verbose_override'] = True
|
|
|
|
|
|
|
|
return setup_result
|
|
|
|
|
|
|
|
def main():
|
|
|
|
module = AnsibleModule(
|
|
|
|
argument_spec = dict()
|
|
|
|
)
|
|
|
|
data = run_setup(module)
|
|
|
|
module.exit_json(**data)
|
|
|
|
|
|
|
|
# this is magic, see lib/ansible/module_common.py
|
|
|
|
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
|
|
|
main()
|