Initial implementation of the service scanner module

This commit is contained in:
Matthew Jones 2015-03-19 13:21:45 -04:00
parent fc72a662b5
commit dd44c0a0f2
3 changed files with 175 additions and 3 deletions

View File

@ -12,16 +12,69 @@ from pymongo import MongoClient
logger = logging.getLogger('awx.main.commands.run_fact_cache_receiver')
from pymongo.son_manipulator import SONManipulator
class KeyTransform(SONManipulator):
"""Transforms keys going to database and restores them coming out.
This allows keys with dots in them to be used (but does break searching on
them unless the find command also uses the transform.
Example & test:
# To allow `.` (dots) in keys
import pymongo
client = pymongo.MongoClient("mongodb://localhost")
db = client['delete_me']
db.add_son_manipulator(KeyTransform(".", "_dot_"))
db['mycol'].remove()
db['mycol'].update({'_id': 1}, {'127.0.0.1': 'localhost'}, upsert=True,
manipulate=True)
print db['mycol'].find().next()
print db['mycol'].find({'127_dot_0_dot_0_dot_1': 'localhost'}).next()
Note: transformation could be easily extended to be more complex.
"""
def __init__(self, replace, replacement):
self.replace = replace
self.replacement = replacement
def transform_key(self, key):
"""Transform key for saving to database."""
return key.replace(self.replace, self.replacement)
def revert_key(self, key):
"""Restore transformed key returning from database."""
return key.replace(self.replacement, self.replace)
def transform_incoming(self, son, collection):
"""Recursively replace all keys that need transforming."""
for (key, value) in son.items():
if self.replace in key:
if isinstance(value, dict):
son[self.transform_key(key)] = self.transform_incoming(
son.pop(key), collection)
else:
son[self.transform_key(key)] = son.pop(key)
elif isinstance(value, dict): # recurse into sub-docs
son[key] = self.transform_incoming(value, collection)
return son
def transform_outgoing(self, son, collection):
return son
class FactCacheReceiver(object):
def __init__(self):
self.client = MongoClient('localhost', 27017)
def process_fact_message(self, message):
host = message['host']
host = message['host'].replace(".", "_")
facts = message['facts']
date_key = message['date_key']
host_db = self.client.host_facts
host_db.add_son_manipulator(KeyTransform(".", "_"))
host_db.add_son_manipulator(KeyTransform("$", "_"))
host_collection = host_db[host]
facts.update(dict(tower_host=host, datetime=date_key))
rec = host_collection.find({"datetime": date_key})
@ -35,7 +88,6 @@ class FactCacheReceiver(object):
def run_receiver(self):
with Socket('fact_cache', 'r') as facts:
for message in facts.listen():
print("Message received: " + str(message))
if 'host' not in message or 'facts' not in message or 'date_key' not in message:
continue
self.process_fact_message(message)

View File

@ -39,6 +39,8 @@ def main():
argument_spec = dict())
packages = []
# TODO: module_utils/basic.py in ansible contains get_distribution() and get_distribution_version()
# which can be used here and is accessible by this script instead of this basic detector.
if os.path.exists("/etc/redhat-release"):
packages = rpm_package_list()
elif os.path.exists("/etc/os-release"):

View File

@ -0,0 +1,118 @@
#!/usr/bin/env python
import re
from ansible.module_utils.basic import * # noqa
class BaseService(object):
def __init__(self, module):
self.module = module
class ServiceScanService(BaseService):
def gather_services(self):
services = {}
service_path = self.module.get_bin_path("service")
if service_path is None:
return None
initctl_path = self.module.get_bin_path("initctl")
chkconfig_path = self.module.get_bin_path("chkconfig")
# Upstart and sysvinit
if initctl_path is not None and chkconfig_path is None:
rc, stdout, stderr = self.module.run_command("%s --status-all 2>&1 | grep -E \"\\[ (\\+|\\-) \\]\"" % service_path, use_unsafe_shell=True)
for line in stdout.split("\n"):
line_data = line.split()
if len(line_data) < 4:
continue # Skipping because we expected more data
service_name = " ".join(line_data[3:])
service_state = "running" if line_data[1] == "+" else "stopped"
services[service_name] = {"name": service_name, "state": service_state, "source": "sysv"}
rc, stdout, stderr = self.module.run_command("%s list" % initctl_path)
real_stdout = stdout.replace("\r","")
for line in real_stdout.split("\n"):
line_data = line.split()
if len(line_data) < 2:
continue
service_name = line_data[0]
if line_data[1].find("/") == -1: # we expect this to look like: start/running
continue
service_goal = line_data[1].split("/")[0]
service_state = line_data[1].split("/")[1].replace(",","")
if len(line_data) > 3: # If there's a pid associated with the service it'll be on the end of this string "process 418"
if line_data[2] == 'process':
pid = line_data[3]
else:
pid = None
else:
pid = None
payload = {"name": service_name, "state": service_state, "goal": service_goal, "source": "upstart"}
services[service_name] = payload
# RH sysvinit
elif chkconfig_path is not None:
#print '%s --status-all | grep -E "is (running|stopped)"' % service_path
rc, stdout, stderr = self.module.run_command('%s --status-all | grep -E "dead|is (running|stopped)"' % service_path, use_unsafe_shell=True)
for line in stdout.split("\n"):
line_data = line.split()
if re.match(".+\(pid.+[0-9]+\).+is running", line) is not None and len(line_data) == 5:
service_name = line_data[0]
service_pid = line_data[2].replace(")","")
service_state = "running"
elif len(line_data) > 2 and line_data[1] == "dead":
service_name = line_data[0]
service_pid = None
service_state = "dead"
elif len(line_data) == 3:
service_name = line_data[0]
service_pid = None
service_state = "stopped"
else:
continue
service_data = {"name": service_name, "state": service_state, "source": "sysv"}
services[service_name] = service_data
# rc, stdout, stderr = self.module.run_command("%s --list" % chkconfig_path)
# Do something with chkconfig status
return services
class SystemctlScanService(BaseService):
def systemd_enabled(self):
# Check if init is the systemd command, using comm as cmdline could be symlink
try:
f = open('/proc/1/comm', 'r')
except IOError:
# If comm doesn't exist, old kernel, no systemd
return False
for line in f:
if 'systemd' in line:
return True
return False
def gather_services(self):
services = {}
if not self.systemd_enabled():
return None
systemctl_path = self.module.get_bin_path("systemctl", opt_dirs=["/usr/bin", "/usr/local/bin"])
if systemctl_path is None:
return None
rc, stdout, stderr = self.module.run_command("%s list-unit-files --type=service | tail -n +2 | head -n -2" % systemctl_path, use_unsafe_shell=True)
for line in stdout.split("\n"):
line_data = line.split()
if len(line_data) != 2:
continue
services[line_data[0]] = {"name": line_data[0], "state": "running" if line_data[1] == "enabled" else "stopped"}
return services
def main():
module = AnsibleModule(argument_spec = dict())
service_modules = (ServiceScanService, SystemctlScanService)
all_services = {}
for svc_module in service_modules:
svcmod = svc_module(module)
svc = svcmod.gather_services()
if svc is not None:
all_services.update(svc)
results = dict(ansible_facts=dict(services=all_services))
module.exit_json(**results)
main()