diff options
author | Michael DeHaan <mdehaan@redhat.com> | 2008-02-07 13:13:24 -0500 |
---|---|---|
committer | Michael DeHaan <mdehaan@redhat.com> | 2008-02-07 13:13:24 -0500 |
commit | 8f2ff4d7c902d534d68ff1a16418b7be492033bf (patch) | |
tree | 73cd958ea6f8e0728592fec759848280b8891f12 /certmaster/minion/modules | |
parent | 5b2601a56907b02efc6567354fa051ef08d97b6f (diff) | |
download | certmaster-8f2ff4d7c902d534d68ff1a16418b7be492033bf.tar.gz certmaster-8f2ff4d7c902d534d68ff1a16418b7be492033bf.tar.xz certmaster-8f2ff4d7c902d534d68ff1a16418b7be492033bf.zip |
Carving away at func some more to just get down to cert items, still lots
more to do.
Diffstat (limited to 'certmaster/minion/modules')
31 files changed, 2264 insertions, 0 deletions
diff --git a/certmaster/minion/modules/Makefile b/certmaster/minion/modules/Makefile new file mode 100755 index 0000000..f2bc6c4 --- /dev/null +++ b/certmaster/minion/modules/Makefile @@ -0,0 +1,18 @@ + + +PYFILES = $(wildcard *.py) + +PYCHECKER = /usr/bin/pychecker +PYFLAKES = /usr/bin/pyflakes + +clean:: + @rm -fv *.pyc *~ .*~ *.pyo + @find . -name .\#\* -exec rm -fv {} \; + @rm -fv *.rpm + + +pychecker:: + @$(PYCHECKER) $(PYFILES) || exit 0 + +pyflakes:: + @$(PYFLAKES) $(PYFILES) || exit 0 diff --git a/certmaster/minion/modules/__init__.py b/certmaster/minion/modules/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/certmaster/minion/modules/__init__.py diff --git a/certmaster/minion/modules/certmaster.py b/certmaster/minion/modules/certmaster.py new file mode 100644 index 0000000..9ca484f --- /dev/null +++ b/certmaster/minion/modules/certmaster.py @@ -0,0 +1,65 @@ +## -*- coding: utf-8 -*- +## +## Process lister (control TBA) +## +## Copyright 2008, Red Hat, Inc +## Michael DeHaan <mdehaan@redhat.com> +## +## This software may be freely redistributed under the terms of the GNU +## general public license. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## + +# other modules +import sub_process +import codes + +# our modules +import func_module +from func import certmaster as certmaster + +# ================================= + +class CertMasterModule(func_module.FuncModule): + + version = "0.0.1" + api_version = "0.0.1" + description = "Administers certs on an overlord." + + def get_hosts_to_sign(self, list_of_hosts): + """ + ... + """ + list_of_hosts = self.__listify(list_of_hosts) + cm = certmaster.CertMaster() + return cm.get_csrs_waiting() + + def sign_hosts(self, list_of_hosts): + """ + ... + """ + list_of_hosts = self.__listify(list_of_hosts) + cm = certmaster.CertMaster() + for x in list_of_hosts: + cm.sign_this_csr(x) + return True + + def cleanup_hosts(self, list_of_hosts): + """ + ... + """ + list_of_hosts = self.__listify(list_of_hosts) + cm = certmaster.CertMaster() + for x in list_of_hosts: + cm.remove_this_cert(x) + return True + + def __listify(self, list_of_hosts): + if type(list_of_hosts) is type([]): + return list_of_hosts + else: + return [ list_of_hosts ] + diff --git a/certmaster/minion/modules/command.py b/certmaster/minion/modules/command.py new file mode 100644 index 0000000..cc463cf --- /dev/null +++ b/certmaster/minion/modules/command.py @@ -0,0 +1,44 @@ +# Copyright 2007, Red Hat, Inc +# James Bowes <jbowes@redhat.com> +# Steve 'Ashcrow' Milner <smilner@redhat.com> +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +""" +Abitrary command execution module for func. +""" + +import func_module +import sub_process + +class Command(func_module.FuncModule): + + version = "0.0.1" + api_version = "0.0.1" + description = "Works with shell commands." + + def run(self, command): + """ + Runs a command, returning the return code, stdout, and stderr as a tuple. + NOT FOR USE WITH INTERACTIVE COMMANDS. + """ + + cmdref = sub_process.Popen(command.split(), stdout=sub_process.PIPE, + stderr=sub_process.PIPE, shell=False) + data = cmdref.communicate() + return (cmdref.returncode, data[0], data[1]) + + def exists(self, command): + """ + Checks to see if a command exists on the target system(s). + """ + import os + + if os.access(command, os.X_OK): + return True + return False diff --git a/certmaster/minion/modules/copyfile.py b/certmaster/minion/modules/copyfile.py new file mode 100644 index 0000000..150af88 --- /dev/null +++ b/certmaster/minion/modules/copyfile.py @@ -0,0 +1,109 @@ +# Copyright 2007, Red Hat, Inc +# seth vidal +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + +import sha +import os +import time +import shutil + +import func_module + + +class CopyFile(func_module.FuncModule): + + version = "0.0.1" + api_version = "0.0.2" + description = "Allows for smart copying of a file." + + def _checksum_blob(self, blob): + thissum = sha.new() + thissum.update(blob) + return thissum.hexdigest() + + def checksum(self, thing): + + CHUNK=2**16 + thissum = sha.new() + if os.path.exists(thing): + fo = open(thing, 'r', CHUNK) + chunk = fo.read + while chunk: + chunk = fo.read(CHUNK) + thissum.update(chunk) + fo.close() + del fo + else: + # assuming it's a string of some kind + thissum.update(thing) + + return thissum.hexdigest() + + + def copyfile(self, filepath, filebuf, mode=0644, uid=0, gid=0, force=None): + # -1 = problem file was not copied + # 1 = file was copied + # 0 = file was not copied b/c file is unchanged + + + # we should probably verify mode,uid,gid are valid as well + + dirpath = os.path.dirname(filepath) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + remote_sum = self._checksum_blob(filebuf.data) + local_sum = 0 + if os.path.exists(filepath): + local_sum = self.checksum(filepath) + + if remote_sum != local_sum or force is not None: + # back up the localone + if os.path.exists(filepath): + if not self._backuplocal(filepath): + return -1 + + # do the new write + try: + fo = open(filepath, 'w') + fo.write(filebuf.data) + fo.close() + del fo + except (IOError, OSError), e: + # XXX logger output here + return -1 + else: + return 0 + + # hmm, need to figure out proper exceptions -akl + try: + # we could intify the mode here if it's a string + os.chmod(filepath, mode) + os.chown(filepath, uid, gid) + except (IOError, OSError), e: + return -1 + + return 1 + + def _backuplocal(self, fn): + """ + make a date-marked backup of the specified file, + return True or False on success or failure + """ + # backups named basename-YYYY-MM-DD@HH:MM~ + ext = time.strftime("%Y-%m-%d@%H:%M~", time.localtime(time.time())) + backupdest = '%s.%s' % (fn, ext) + + try: + shutil.copy2(fn, backupdest) + except shutil.Error, e: + #XXX logger output here + return False + return True diff --git a/certmaster/minion/modules/filetracker.py b/certmaster/minion/modules/filetracker.py new file mode 100644 index 0000000..f5f9dbb --- /dev/null +++ b/certmaster/minion/modules/filetracker.py @@ -0,0 +1,192 @@ +## func +## +## filetracker +## maintains a manifest of files of which to keep track +## provides file meta-data (and optionally full data) to func-inventory +## +## (C) Vito Laurenza <vitolaurenza@gmail.com> +## + Michael DeHaan <mdehaan@redhat.com> +## +## This software may be freely redistributed under the terms of the GNU +## general public license. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## + +# func modules +import func_module + +# other modules +from stat import * +import glob +import os +import md5 + +# defaults +CONFIG_FILE='/etc/func/modules/filetracker.conf' + +class FileTracker(func_module.FuncModule): + + version = "0.0.1" + api_version = "0.0.1" + description = "Maintains a manifest of files to keep track of." + + def __load(self): + """ + Parse file and return data structure. + """ + + filehash = {} + if os.path.exists(CONFIG_FILE): + config = open(CONFIG_FILE, "r") + data = config.read() + lines = data.split("\n") + for line in lines: + tokens = line.split(None) + if len(tokens) < 2: + continue + scan_mode = tokens[0] + path = " ".join(tokens[1:]) + if str(scan_mode).lower() == "0": + scan_mode = 0 + else: + scan_mode = 1 + filehash[path] = scan_mode + return filehash + + #========================================================== + + def __save(self, filehash): + """ + Write data structure to file. + """ + + config = open(CONFIG_FILE, "w+") + for (path, scan_mode) in filehash.iteritems(): + config.write("%s %s\n" % (scan_mode, path)) + config.close() + + #========================================================== + + def track(self, file_name, full_scan=0): + """ + Adds files to keep track of. + full_scan implies tracking the full contents of the file, defaults to off + """ + + filehash = self.__load() + filehash[file_name] = full_scan + self.__save(filehash) + return 1 + + #========================================================== + + def untrack(self, file_name): + """ + Stop keeping track of a file. + This routine is tolerant of most errors since we're forgetting about the file anyway. + """ + + filehash = self.__load() + if file_name in filehash.keys(): + del filehash[file_name] + self.__save(filehash) + return 1 + + #========================================================== + + def inventory(self, flatten=1, checksum_enabled=1): + """ + Returns information on all tracked files + By default, 'flatten' is passed in as True, which makes printouts very clean in diffs + for use by func-inventory. If you are writting another software application, using flatten=False will + prevent the need to parse the returns. + """ + + # XMLRPC feeds us strings from the CLI when it shouldn't + flatten = int(flatten) + checksum_enabled = int(checksum_enabled) + + filehash = self.__load() + + # we'll either return a very flat string (for clean diffs) + # or a data structure + if flatten: + results = "" + else: + results = [] + + for (file_name, scan_type) in filehash.iteritems(): + + if not os.path.exists(file_name): + if flatten: + results = results + "%s: does not exist\n" % file_name + else: + results.append("%s: does not exist\n" % file_name) + continue + + this_result = [] + + # ----- always process metadata + filestat = os.stat(file_name) + mode = filestat[ST_MODE] + mtime = filestat[ST_MTIME] + uid = filestat[ST_UID] + gid = filestat[ST_GID] + if not os.path.isdir(file_name) and checksum_enabled: + sum_handle = open(file_name) + hash = self.__sumfile(sum_handle) + sum_handle.close() + else: + hash = "N/A" + + # ------ what we return depends on flatten + if flatten: + this_result = "%s: mode=%s mtime=%s uid=%s gid=%s md5sum=%s\n" % (file_name,mode,mtime,uid,gid,hash) + else: + this_result = [file_name,mode,mtime,uid,gid,hash] + + # ------ add on file data only if requested + if scan_type != 0 and os.path.isfile(file_name): + tracked_file = open(file_name) + data = tracked_file.read() + if flatten: + this_result = this_result + "*** DATA ***\n" + data + "\n*** END DATA ***\n\n" + else: + this_result.append(data) + tracked_file.close() + + if os.path.isdir(file_name): + if not file_name.endswith("/"): + file_name = file_name + "/" + files = glob.glob(file_name + "*") + if flatten: + this_result = this_result + "*** FILES ***\n" + "\n".join(files) + "\n*** END FILES ***\n\n" + else: + this_result.append({"files" : files}) + + if flatten: + results = results + "\n" + this_result + else: + results.append(this_result) + + + return results + + #========================================================== + + def __sumfile(self, fobj): + """ + Returns an md5 hash for an object with read() method. + credit: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/266486 + """ + + m = md5.new() + while True: + d = fobj.read(8096) + if not d: + break + m.update(d) + return m.hexdigest() diff --git a/certmaster/minion/modules/func_module.py b/certmaster/minion/modules/func_module.py new file mode 100644 index 0000000..7d476dc --- /dev/null +++ b/certmaster/minion/modules/func_module.py @@ -0,0 +1,76 @@ +## +## Copyright 2007, Red Hat, Inc +## see AUTHORS +## +## This software may be freely redistributed under the terms of the GNU +## general public license. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## + +import inspect + +from func import logger +from func.config import read_config +from func.commonconfig import FuncdConfig + + +class FuncModule(object): + + # the version is meant to + version = "0.0.0" + api_version = "0.0.0" + description = "No Description provided" + + def __init__(self): + + config_file = '/etc/func/minion.conf' + self.config = read_config(config_file, FuncdConfig) + self.__init_log() + self.__base_methods = { + # __'s so we don't clobber useful names + "module_version" : self.__module_version, + "module_api_version" : self.__module_api_version, + "module_description" : self.__module_description, + "list_methods" : self.__list_methods + } + + def __init_log(self): + log = logger.Logger() + self.logger = log.logger + + def register_rpc(self, handlers, module_name): + # add the internal methods, note that this means they + # can get clobbbered by subclass versions + for meth in self.__base_methods: + handlers["%s.%s" % (module_name, meth)] = self.__base_methods[meth] + + # register our module's handlers + for name, handler in self.__list_handlers().items(): + handlers["%s.%s" % (module_name, name)] = handler + + def __list_handlers(self): + """ Return a dict of { handler_name, method, ... }. + All methods that do not being with an underscore will be exposed. + We also make sure to not expose our register_rpc method. + """ + handlers = {} + for attr in dir(self): + if inspect.ismethod(getattr(self, attr)) and attr[0] != '_' and \ + attr != 'register_rpc': + handlers[attr] = getattr(self, attr) + return handlers + + def __list_methods(self): + return self.__list_handlers().keys() + self.__base_methods.keys() + + def __module_version(self): + return self.version + + def __module_api_version(self): + return self.api_version + + def __module_description(self): + return self.description diff --git a/certmaster/minion/modules/func_module.py.orig b/certmaster/minion/modules/func_module.py.orig new file mode 100644 index 0000000..c911b91 --- /dev/null +++ b/certmaster/minion/modules/func_module.py.orig @@ -0,0 +1,65 @@ +## +## Copyright 2007, Red Hat, Inc +## see AUTHORS +## +## This software may be freely redistributed under the terms of the GNU +## general public license. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## + +import inspect + +from func import logger +from func.config import read_config +from func.commonconfig import FuncdConfig + + +class FuncModule(object): + + # the version is meant to + version = "0.0.0" + api_version = "0.0.0" + description = "No Description provided" + + def __init__(self): + + config_file = '/etc/func/minion.conf' + self.config = read_config(config_file, FuncdConfig) + self.__init_log() + self.__base_methods = { + # __'s so we don't clobber useful names + "module_version" : self.__module_version, + "module_api_version" : self.__module_api_version, + "module_description" : self.__module_description, + "list_methods" : self.__list_methods + } + + def __init_log(self): + log = logger.Logger() + self.logger = log.logger + + def register_rpc(self, handlers, module_name): + # add the internal methods, note that this means they + # can get clobbbered by subclass versions + for meth in self.__base_methods: + handlers["%s.%s" % (module_name, meth)] = self.__base_methods[meth] + + # register all methods that don't start with an underscore + for attr in dir(self): + if inspect.ismethod(getattr(self, attr)) and attr[0] != '_': + handlers["%s.%s" % (module_name, attr)] = getattr(self, attr) + + def __list_methods(self): + return self.methods.keys() + self.__base_methods.keys() + + def __module_version(self): + return self.version + + def __module_api_version(self): + return self.api_version + + def __module_description(self): + return self.description diff --git a/certmaster/minion/modules/hardware.py b/certmaster/minion/modules/hardware.py new file mode 100644 index 0000000..46b1821 --- /dev/null +++ b/certmaster/minion/modules/hardware.py @@ -0,0 +1,130 @@ +## +## Hardware profiler plugin +## requires the "smolt" client package be installed +## but also relies on lspci for some things +## +## Copyright 2007, Red Hat, Inc +## Michael DeHaan <mdehaan@redhat.com> +## +## This software may be freely redistributed under the terms of the GNU +## general public license. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## + + +# other modules +import sys + +# our modules +import sub_process +import func_module + +# ================================= + +class HardwareModule(func_module.FuncModule): + + version = "0.0.1" + api_version = "0.0.1" + description = "Hardware profiler." + + def hal_info(self): + """ + Returns the output of lshal, but split up into seperate devices + for easier parsing. Each device is a entry in the return hash. + """ + + cmd = sub_process.Popen(["/usr/bin/lshal"],shell=False,stdout=sub_process.PIPE) + data = cmd.communicate()[0] + + data = data.split("\n") + + results = {} + current = "" + label = data[0] + for d in data: + if d == '': + results[label] = current + current = "" + label = "" + else: + if label == "": + label = d + current = current + d + + return results + + def inventory(self): + data = hw_info(with_devices=True) + # remove bogomips because it keeps changing for laptops + # and makes inventory tracking noisy + if data.has_key("bogomips"): + del data["bogomips"] + return data + + def info(self,with_devices=True): + """ + Returns a struct of hardware information. By default, this pulls down + all of the devices. If you don't care about them, set with_devices to + False. + """ + return hw_info(with_devices) + +# ================================= + +def hw_info(with_devices=True): + + # this may fail if smolt is not installed. That's ok. hal_info will + # still work. + + # hack: smolt is not installed in site-packages + sys.path.append("/usr/share/smolt/client") + import smolt + + hardware = smolt.Hardware() + host = hardware.host + + # NOTE: casting is needed because these are DBusStrings, not real strings + data = { + 'os' : str(host.os), + 'defaultRunlevel' : str(host.defaultRunlevel), + 'bogomips' : str(host.bogomips), + 'cpuVendor' : str(host.cpuVendor), + 'cpuModel' : str(host.cpuModel), + 'numCpus' : str(host.numCpus), + 'cpuSpeed' : str(host.cpuSpeed), + 'systemMemory' : str(host.systemMemory), + 'systemSwap' : str(host.systemSwap), + 'kernelVersion' : str(host.kernelVersion), + 'language' : str(host.language), + 'platform' : str(host.platform), + 'systemVendor' : str(host.systemVendor), + 'systemModel' : str(host.systemModel), + 'formfactor' : str(host.formfactor), + 'selinux_enabled' : str(host.selinux_enabled), + 'selinux_enforce' : str(host.selinux_enforce) + } + + # if no hardware info requested, just return the above bits + if not with_devices: + return data + + collection = data["devices"] = [] + + for item in hardware.deviceIter(): + + (VendorID,DeviceID,SubsysVendorID,SubsysDeviceID,Bus,Driver,Type,Description) = item + + collection.append({ + "VendorID" : str(VendorID), + "DeviceID" : str(DeviceID), + "SubsysVendorID" : str(SubsysVendorID), + "Bus" : str(Bus), + "Driver" : str(Driver), + "Type" : str(Type), + "Description" : str(Description) + }) + + return data diff --git a/certmaster/minion/modules/jobs.py b/certmaster/minion/modules/jobs.py new file mode 100644 index 0000000..69fb75f --- /dev/null +++ b/certmaster/minion/modules/jobs.py @@ -0,0 +1,36 @@ +## (Largely internal) module for access to asynchoronously dispatched +## module job ID's. The Func Client() module wraps most of this usage +## so it's not entirely relevant to folks using the CLI or Func API +## directly. +## +## Copyright 2008, Red Hat, Inc +## Michael DeHaan <mdehaan@redhat.com> +## +## This software may be freely redistributed under the terms of the GNU +## general public license. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## + +import codes +from func import jobthing +import func_module + +# ================================= + +class JobsModule(func_module.FuncModule): + + version = "0.0.1" + api_version = "0.0.1" + description = "Internal module for tracking background minion tasks." + + def job_status(self, job_id): + """ + Returns job status in the form of (status, datastruct). + Datastruct is undefined for unfinished jobs. See jobthing.py and + Wiki details on async invocation for more information. + """ + return jobthing.job_status(job_id) + diff --git a/certmaster/minion/modules/mount.py b/certmaster/minion/modules/mount.py new file mode 100644 index 0000000..0db914f --- /dev/null +++ b/certmaster/minion/modules/mount.py @@ -0,0 +1,84 @@ +## +## Mount manager +## +## Copyright 2007, Red Hat, Inc +## John Eckersberg <jeckersb@redhat.com> +## +## This software may be freely redistributed under the terms of the GNU +## general public license. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## + +import sub_process, os +import func_module + + +class MountModule(func_module.FuncModule): + + version = "0.0.1" + api_version = "0.0.1" + description = "Mounting, unmounting and getting information on mounted filesystems." + + def list(self): + cmd = sub_process.Popen(["/bin/cat", "/proc/mounts"], executable="/bin/cat", stdout=sub_process.PIPE, shell=False) + data = cmd.communicate()[0] + + mounts = [] + lines = [l for l in data.split("\n") if l] #why must you append blank crap? + + for line in lines: + curmount = {} + tokens = line.split() + curmount['device'] = tokens[0] + curmount['dir'] = tokens[1] + curmount['type'] = tokens[2] + curmount['options'] = tokens[3] + mounts.append(curmount) + + return mounts + + def mount(self, device, dir, type="auto", options=None, createdir=False): + cmdline = ["/bin/mount", "-t", type] + if options: + cmdline.append("-o") + cmdline.append(options) + cmdline.append(device) + cmdline.append(dir) + if createdir: + try: + os.makedirs(dir) + except: + return False + cmd = sub_process.Popen(cmdline, executable="/bin/mount", stdout=sub_process.PIPE, shell=False) + if cmd.wait() == 0: + return True + else: + return False + + def umount(self, dir, killall=False, force=False, lazy=False): + # succeed if its not mounted + if not os.path.ismount(dir): + return True + + if killall: + cmd = sub_process.Popen(["/sbin/fuser", "-mk", dir], executable="/sbin/fuser", stdout=sub_process.PIPE, shell=False) + cmd.wait() + + cmdline = ["/bin/umount"] + if force: + cmdline.append("-f") + if lazy: + cmdline.append("-l") + cmdline.append(dir) + + cmd = sub_process.Popen(cmdline, executable="/bin/umount", stdout=sub_process.PIPE, shell=False) + if cmd.wait() == 0: + return True + else: + return False + + def inventory(self, flatten=True): + return self.list() diff --git a/certmaster/minion/modules/nagios-check.py b/certmaster/minion/modules/nagios-check.py new file mode 100644 index 0000000..f1c0714 --- /dev/null +++ b/certmaster/minion/modules/nagios-check.py @@ -0,0 +1,34 @@ +# Copyright 2007, Red Hat, Inc +# James Bowes <jbowes@redhat.com> +# Seth Vidal modified command.py to be nagios-check.py +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +""" +Abitrary command execution module for func. +""" + +import func_module +import sub_process + +class Nagios(func_module.FuncModule): + + version = "0.0.1" + api_version = "0.0.1" + description = "Runs nagios checks." + + def run(self, check_command): + """ + Runs a nagios check returning the return code, stdout, and stderr as a tuple. + """ + nagios_path='/usr/lib/nagios/plugins' + command = '%s/%s' % (nagios_path, check_command) + + cmdref = sub_process.Popen(command.split(),stdout=sub_process.PIPE,stderr=sub_process.PIPE, shell=False) + data = cmdref.communicate() + return (cmdref.returncode, data[0], data[1]) diff --git a/certmaster/minion/modules/netapp/README b/certmaster/minion/modules/netapp/README new file mode 100644 index 0000000..5ecb205 --- /dev/null +++ b/certmaster/minion/modules/netapp/README @@ -0,0 +1,8 @@ +This module is meant to be installed on a minion which is configured +as an admin host for one or more NetApp filers. Since we can't get +our funcy awesomeness on the actual filer the admin host will have to do. + +Requirements: + +- passphraseless ssh key access from root on the netapp admin minion + to root on the target filer diff --git a/certmaster/minion/modules/netapp/TODO b/certmaster/minion/modules/netapp/TODO new file mode 100644 index 0000000..25d914c --- /dev/null +++ b/certmaster/minion/modules/netapp/TODO @@ -0,0 +1,5 @@ +Wrap every possible NetApp command :) + +I'm only going to do the ones that are important to me. If you have +some that are important to you, feel free to submit patches to +func-list@redhat.com and harness the power of open source! diff --git a/certmaster/minion/modules/netapp/__init__.py b/certmaster/minion/modules/netapp/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/certmaster/minion/modules/netapp/__init__.py diff --git a/certmaster/minion/modules/netapp/common.py b/certmaster/minion/modules/netapp/common.py new file mode 100644 index 0000000..979c95c --- /dev/null +++ b/certmaster/minion/modules/netapp/common.py @@ -0,0 +1,49 @@ +## +## NetApp Filer 'common' Module +## +## Copyright 2008, Red Hat, Inc +## John Eckersberg <jeckersb@redhat.com> +## +## This software may be freely redistributed under the terms of the GNU +## general public license. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## + +import re +import sub_process + +SSH = '/usr/bin/ssh' +SSH_USER = 'root' +SSH_OPTS = '-o forwardagent=no' +class GenericSSHError(Exception): pass +class NetappCommandError(Exception): pass + +def ssh(host, cmdargs, input=None, user=SSH_USER): + cmdline = [SSH, SSH_OPTS, "%s@%s" % (user, host)] + cmdline.extend(cmdargs) + + cmd = sub_process.Popen(cmdline, + executable=SSH, + stdin=sub_process.PIPE, + stdout=sub_process.PIPE, + stderr=sub_process.PIPE, + shell=False) + + (out, err) = cmd.communicate(input) + + if cmd.wait() != 0: + raise GenericSSHError, err + else: + return out + err + +def check_output(regex, output): + #strip newlines + output = output.replace('\n', ' ') + if re.search(regex, output): + return True + else: + raise NetappCommandError, output + diff --git a/certmaster/minion/modules/netapp/snap.py b/certmaster/minion/modules/netapp/snap.py new file mode 100644 index 0000000..8f3f209 --- /dev/null +++ b/certmaster/minion/modules/netapp/snap.py @@ -0,0 +1,49 @@ +## +## NetApp Filer 'snap' Module +## +## Copyright 2008, Red Hat, Inc +## John Eckersberg <jeckersb@redhat.com> +## +## This software may be freely redistributed under the terms of the GNU +## general public license. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## + +import re +from func.minion.modules import func_module +from func.minion.modules.netapp.common import * + +class Snap(func_module.FuncModule): + + # Update these if need be. + version = "0.0.1" + api_version = "0.0.1" + description = "Interface to the 'snap' command" + + def create(self, filer, vol, snap): + """ + TODO: Document me ... + """ + regex = """creating snapshot...""" + cmd_opts = ['snap', 'create', vol, snap] + output = ssh(filer, cmd_opts) + return check_output(regex, output) + + def delete(self, filer, vol, snap): + """ + TODO: Document me ... + """ + regex = """deleting snapshot...""" + cmd_opts = ['snap', 'delete', vol, snap] + output = ssh(filer, cmd_opts) + return check_output(regex, output) + + def list(self, filer, vol): + """ + TODO: Document me ... + """ + return True + diff --git a/certmaster/minion/modules/netapp/vol/__init__.py b/certmaster/minion/modules/netapp/vol/__init__.py new file mode 100644 index 0000000..14ce0ac --- /dev/null +++ b/certmaster/minion/modules/netapp/vol/__init__.py @@ -0,0 +1,128 @@ +## +## NetApp Filer 'Vol' Module +## +## Copyright 2008, Red Hat, Inc +## John Eckersberg <jeckersb@redhat.com> +## +## This software may be freely redistributed under the terms of the GNU +## general public license. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## + +import re +from func.minion.modules import func_module +from func.minion.modules.netapp.common import * + +class Vol(func_module.FuncModule): + + # Update these if need be. + version = "0.0.1" + api_version = "0.0.1" + description = "Interface to the 'vol' command" + + def create(self, filer, vol, aggr, size): + """ + TODO: Document me ... + """ + regex = """Creation of volume .* has completed.""" + cmd_opts = ['vol', 'create', vol, aggr, size] + output = ssh(filer, cmd_opts) + return check_output(regex, output) + + def destroy(self, filer, vol): + """ + TODO: Document me ... + """ + regex = """Volume .* destroyed.""" + cmd_opts = ['vol', 'destroy', vol, '-f'] + output = ssh(filer, cmd_opts) + return check_output(regex, output) + + def offline(self, filer, vol): + """ + TODO: Document me ... + """ + regex = """Volume .* is now offline.""" + cmd_opts = ['vol', 'offline', vol] + output = ssh(filer, cmd_opts) + return check_output(regex, output) + + def online(self, filer, vol): + """ + TODO: Document me ... + """ + regex = """Volume .* is now online.""" + cmd_opts = ['vol', 'online', vol] + output = ssh(filer, cmd_opts) + return check_output(regex, output) + + def status(self, filer, vol=None): + """ + TODO: Document me ... + """ + cmd_opts = ['vol', 'status'] + output = ssh(filer, cmd_opts) + + output = output.replace(',', ' ') + lines = output.split('\n')[1:] + + vols = [] + current_vol = {} + for line in lines: + tokens = line.split() + if len(tokens) >= 2 and tokens[1] in ('online', 'offline', 'restricted'): + if current_vol: vols.append(current_vol) + current_vol = {'name': tokens[0], + 'state': tokens[1], + 'status': [foo for foo in tokens[2:] if '=' not in foo], + 'options': [foo for foo in tokens[2:] if '=' in foo]} + else: + current_vol['status'].extend([foo for foo in tokens if '=' not in foo]) + current_vol['options'].extend([foo for foo in tokens if '=' in foo]) + vols.append(current_vol) + + if vol: + try: + return [foo for foo in vols if foo['name'] == vol][0] + except: + raise NetappCommandError, "No such volume: %s" % vol + else: + return vols + + def size(self, filer, vol, delta=None): + """ + TODO: Document me ... + """ + stat_regex = """vol size: Flexible volume .* has size .*.""" + resize_regex = """vol size: Flexible volume .* size set to .*.""" + cmd_opts = ['vol', 'size', vol] + + if delta: + cmd_opts.append(delta) + output = ssh(filer, cmd_opts) + return check_output(resize_regex, output) + else: + output = ssh(filer, cmd_opts) + check_output(stat_regex, output) + return output.split()[-1][:-1] + + def options(self, filer, args): + """ + TODO: Document me ... + """ + pass + + def rename(self, filer, args): + """ + TODO: Document me ... + """ + pass + + def restrict(self, filer, args): + """ + TODO: Document me ... + """ + pass diff --git a/certmaster/minion/modules/netapp/vol/clone.py b/certmaster/minion/modules/netapp/vol/clone.py new file mode 100644 index 0000000..715d8a8 --- /dev/null +++ b/certmaster/minion/modules/netapp/vol/clone.py @@ -0,0 +1,46 @@ +## +## NetApp Filer 'vol.clone' Module +## +## Copyright 2008, Red Hat, Inc +## John Eckersberg <jeckersb@redhat.com> +## +## This software may be freely redistributed under the terms of the GNU +## general public license. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## + +import re +from func.minion.modules import func_module +from func.minion.modules.netapp.common import * + +class Clone(func_module.FuncModule): + + # Update these if need be. + version = "0.0.1" + api_version = "0.0.1" + description = "Interface to the 'vol' command" + + def create(self, filer, vol, parent, snap): + """ + TODO: Document me ... + """ + regex = """Creation of clone volume .* has completed.""" + cmd_opts = ['vol', 'clone', 'create', vol, '-b', parent, snap] + output = ssh(filer, cmd_opts) + return check_output(regex, output) + + def split(self, filer, vol): + """ + TODO: Document me ... + """ + # only worry about 'start' now, I don't terribly care to automate the rest + regex = """Clone volume .* will be split from its parent.""" + cmd_opts = ['vol', 'clone', 'split', 'start', vol] + output = ssh(filer, cmd_opts) + return check_output(regex, output) + + + diff --git a/certmaster/minion/modules/networktest.py b/certmaster/minion/modules/networktest.py new file mode 100644 index 0000000..0e6fbb2 --- /dev/null +++ b/certmaster/minion/modules/networktest.py @@ -0,0 +1,64 @@ +# Copyright 2008, Red Hat, Inc +# Steve 'Ashcrow' Milner <smilner@redhat.com> +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + +import func_module +from codes import FuncException + +import sub_process + +class NetworkTest(func_module.FuncModule): + + version = "0.0.1" + api_version = "0.0.1" + description = "Defines various network testing tools." + + def ping(self, *args): + if '-c' not in args: + raise(FuncException("You must define a count with -c!")) + return self.__run_command('/bin/ping', self.__args_to_list(args)) + + def netstat(self, *args): + return self.__run_command('/bin/netstat', + self.__args_to_list(args)) + + def traceroute(self, *args): + return self.__run_command('/bin/traceroute', + self.__args_to_list(args)) + + def dig(self, *args): + return self.__run_command('/usr/bin/dig', + self.__args_to_list(args)) + + def isportopen(self, host, port): + # FIXME: the return api here needs some work... -akl + import socket + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + timeout = 3.0 + sock.settimeout(timeout) + try: + sock.connect((host, int(port))) + except socket.error, e: + sock.close() + return [1, ("connection to %s:%s failed" % (host, port), "%s" % e)] + except socket.timeout: + sock.close() + return [2, ("connection to %s:%s timed out after %s seconds" % (host, port, timeout))] + + sock.close() + return [0, "connection to %s:%s succeeded" % (host, port)] + + def __args_to_list(self, args): + return [arg for arg in args] + + def __run_command(self, command, opts=[]): + full_cmd = [command] + opts + cmd = sub_process.Popen(full_cmd, stdout=sub_process.PIPE) + return [line for line in cmd.communicate()[0].split('\n')] diff --git a/certmaster/minion/modules/process.py b/certmaster/minion/modules/process.py new file mode 100644 index 0000000..848e847 --- /dev/null +++ b/certmaster/minion/modules/process.py @@ -0,0 +1,216 @@ +## -*- coding: utf-8 -*- +## +## Process lister (control TBA) +## +## Copyright 2007, Red Hat, Inc +## Michael DeHaan <mdehaan@redhat.com> +## +## This software may be freely redistributed under the terms of the GNU +## general public license. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## + +# other modules +import sub_process +import codes + +# our modules +import func_module + +# ================================= + +class ProcessModule(func_module.FuncModule): + + version = "0.0.1" + api_version = "0.0.1" + description = "Process related reporting and control." + + def info(self, flags="-auxh"): + """ + Returns a struct of hardware information. By default, this pulls down + all of the devices. If you don't care about them, set with_devices to + False. + """ + + flags.replace(";", "") # prevent stupidity + + cmd = sub_process.Popen(["/bin/ps", flags], executable="/bin/ps", + stdout=sub_process.PIPE, + stderr=sub_process.PIPE, + shell=False) + + data, error = cmd.communicate() + + # We can get warnings for odd formatting. warnings != errors. + if error and error[:7] != "Warning": + raise codes.FuncException(error.split('\n')[0]) + + results = [] + for x in data.split("\n"): + tokens = x.split() + results.append(tokens) + + return results + + def mem(self): + """ + Returns a list of per-program memory usage. + + Private + Shared = RAM used Program + + [["39.4 MiB", "10.3 MiB", "49.8 MiB", "Xorg"], + ["42.2 MiB", "12.4 MiB", "54.6 MiB", "nautilus"], + ["52.3 MiB", "10.8 MiB", "63.0 MiB", "liferea-bin"] + ["171.6 MiB", "11.9 MiB", "183.5 MiB", "firefox-bin"]] + + Taken from the ps_mem.py script written by Pádraig Brady. + http://www.pixelbeat.org/scripts/ps_mem.py + """ + import os + our_pid=os.getpid() + results = [] + have_smaps=0 + have_pss=0 + + def kernel_ver(): + """ (major,minor,release) """ + kv=open("/proc/sys/kernel/osrelease").readline().split(".")[:3] + for char in "-_": + kv[2]=kv[2].split(char)[0] + return (int(kv[0]), int(kv[1]), int(kv[2])) + + kv=kernel_ver() + + def getMemStats(pid): + """ return Rss,Pss,Shared (note Private = Rss-Shared) """ + Shared_lines=[] + Pss_lines=[] + pagesize=os.sysconf("SC_PAGE_SIZE")/1024 #KiB + Rss=int(open("/proc/"+str(pid)+"/statm").readline().split()[1])*pagesize + if os.path.exists("/proc/"+str(pid)+"/smaps"): #stat + global have_smaps + have_smaps=1 + for line in open("/proc/"+str(pid)+"/smaps").readlines(): #open + #Note in smaps Shared+Private = Rss above + #The Rss in smaps includes video card mem etc. + if line.startswith("Shared"): + Shared_lines.append(line) + elif line.startswith("Pss"): + global have_pss + have_pss=1 + Pss_lines.append(line) + Shared=sum([int(line.split()[1]) for line in Shared_lines]) + Pss=sum([int(line.split()[1]) for line in Pss_lines]) + elif (2,6,1) <= kv <= (2,6,9): + Pss=0 + Shared=0 #lots of overestimation, but what can we do? + else: + Pss=0 + Shared=int(open("/proc/"+str(pid)+"/statm").readline().split()[2])*pagesize + return (Rss, Pss, Shared) + + cmds={} + shareds={} + count={} + for pid in os.listdir("/proc/"): + try: + pid = int(pid) #note Thread IDs not listed in /proc/ + if pid ==our_pid: continue + except: + continue + cmd = file("/proc/%d/status" % pid).readline()[6:-1] + try: + exe = os.path.basename(os.path.realpath("/proc/%d/exe" % pid)) + if exe.startswith(cmd): + cmd=exe #show non truncated version + #Note because we show the non truncated name + #one can have separated programs as follows: + #584.0 KiB + 1.0 MiB = 1.6 MiB mozilla-thunder (exe -> bash) + #56.0 MiB + 22.2 MiB = 78.2 MiB mozilla-thunderbird-bin + except: + #permission denied or + #kernel threads don't have exe links or + #process gone + continue + try: + rss, pss, shared = getMemStats(pid) + private = rss-shared + #Note shared is always a subset of rss (trs is not always) + except: + continue #process gone + if shareds.get(cmd): + if pss: #add shared portion of PSS together + shareds[cmd]+=pss-private + elif shareds[cmd] < shared: #just take largest shared val + shareds[cmd]=shared + else: + if pss: + shareds[cmd]=pss-private + else: + shareds[cmd]=shared + cmds[cmd]=cmds.setdefault(cmd,0)+private + if count.has_key(cmd): + count[cmd] += 1 + else: + count[cmd] = 1 + + #Add max shared mem for each program + total=0 + for cmd in cmds.keys(): + cmds[cmd]=cmds[cmd]+shareds[cmd] + total+=cmds[cmd] #valid if PSS available + + sort_list = cmds.items() + sort_list.sort(lambda x,y:cmp(x[1],y[1])) + sort_list=filter(lambda x:x[1],sort_list) #get rid of zero sized processes + + #The following matches "du -h" output + def human(num, power="Ki"): + powers=["Ki","Mi","Gi","Ti"] + while num >= 1000: #4 digits + num /= 1024.0 + power=powers[powers.index(power)+1] + return "%.1f %s" % (num,power) + + def cmd_with_count(cmd, count): + if count>1: + return "%s (%u)" % (cmd, count) + else: + return cmd + + for cmd in sort_list: + results.append([ + "%sB" % human(cmd[1]-shareds[cmd[0]]), + "%sB" % human(shareds[cmd[0]]), + "%sB" % human(cmd[1]), + "%s" % cmd_with_count(cmd[0], count[cmd[0]]) + ]) + if have_pss: + results.append(["", "", "", "%sB" % human(total)]) + + return results + + memory = mem + + def kill(self,pid,signal="TERM"): + if pid == "0": + raise codes.FuncException("Killing pid group 0 not permitted") + if signal == "": + # this is default /bin/kill behaviour, + # it claims, but enfore it anyway + signal = "-TERM" + if signal[0] != "-": + signal = "-%s" % signal + rc = sub_process.call(["/bin/kill",signal, pid], + executable="/bin/kill", shell=False) + print rc + return rc + + def pkill(self,name,level=""): + # example killall("thunderbird","-9") + rc = sub_process.call(["/usr/bin/pkill", name, level], + executable="/usr/bin/pkill", shell=False) + return rc diff --git a/certmaster/minion/modules/process.py.orig b/certmaster/minion/modules/process.py.orig new file mode 100644 index 0000000..bdd5193 --- /dev/null +++ b/certmaster/minion/modules/process.py.orig @@ -0,0 +1,221 @@ +## -*- coding: utf-8 -*- +## +## Process lister (control TBA) +## +## Copyright 2007, Red Hat, Inc +## Michael DeHaan <mdehaan@redhat.com> +## +## This software may be freely redistributed under the terms of the GNU +## general public license. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## + +# other modules +import sub_process +import codes + +# our modules +from modules import func_module + +# ================================= + +class ProcessModule(func_module.FuncModule): + def __init__(self): + self.methods = { + "info" : self.info, + "kill" : self.kill, + "pkill" : self.pkill, + "mem" : self.mem + } + func_module.FuncModule.__init__(self) + + def info(self, flags="-auxh"): + """ + Returns a struct of hardware information. By default, this pulls down + all of the devices. If you don't care about them, set with_devices to + False. + """ + + flags.replace(";", "") # prevent stupidity + + cmd = sub_process.Popen(["/bin/ps", flags], executable="/bin/ps", + stdout=sub_process.PIPE, + stderr=sub_process.PIPE, + shell=False) + + data, error = cmd.communicate() + + # We can get warnings for odd formatting. warnings != errors. + if error and error[:7] != "Warning": + raise codes.FuncException(error.split('\n')[0]) + + results = [] + for x in data.split("\n"): + tokens = x.split() + results.append(tokens) + + return results + + def mem(self): + """ + Returns a list of per-program memory usage. + + Private + Shared = RAM used Program + + [["39.4 MiB", "10.3 MiB", "49.8 MiB", "Xorg"], + ["42.2 MiB", "12.4 MiB", "54.6 MiB", "nautilus"], + ["52.3 MiB", "10.8 MiB", "63.0 MiB", "liferea-bin"] + ["171.6 MiB", "11.9 MiB", "183.5 MiB", "firefox-bin"]] + + Taken from the ps_mem.py script written by Pádraig Brady. + http://www.pixelbeat.org/scripts/ps_mem.py + """ + import os + our_pid=os.getpid() + results = [] + have_smaps=0 + have_pss=0 + + def kernel_ver(): + """ (major,minor,release) """ + kv=open("/proc/sys/kernel/osrelease").readline().split(".")[:3] + for char in "-_": + kv[2]=kv[2].split(char)[0] + return (int(kv[0]), int(kv[1]), int(kv[2])) + + kv=kernel_ver() + + def getMemStats(pid): + """ return Rss,Pss,Shared (note Private = Rss-Shared) """ + Shared_lines=[] + Pss_lines=[] + pagesize=os.sysconf("SC_PAGE_SIZE")/1024 #KiB + Rss=int(open("/proc/"+str(pid)+"/statm").readline().split()[1])*pagesize + if os.path.exists("/proc/"+str(pid)+"/smaps"): #stat + global have_smaps + have_smaps=1 + for line in open("/proc/"+str(pid)+"/smaps").readlines(): #open + #Note in smaps Shared+Private = Rss above + #The Rss in smaps includes video card mem etc. + if line.startswith("Shared"): + Shared_lines.append(line) + elif line.startswith("Pss"): + global have_pss + have_pss=1 + Pss_lines.append(line) + Shared=sum([int(line.split()[1]) for line in Shared_lines]) + Pss=sum([int(line.split()[1]) for line in Pss_lines]) + elif (2,6,1) <= kv <= (2,6,9): + Pss=0 + Shared=0 #lots of overestimation, but what can we do? + else: + Pss=0 + Shared=int(open("/proc/"+str(pid)+"/statm").readline().split()[2])*pagesize + return (Rss, Pss, Shared) + + cmds={} + shareds={} + count={} + for pid in os.listdir("/proc/"): + try: + pid = int(pid) #note Thread IDs not listed in /proc/ + if pid ==our_pid: continue + except: + continue + cmd = file("/proc/%d/status" % pid).readline()[6:-1] + try: + exe = os.path.basename(os.path.realpath("/proc/%d/exe" % pid)) + if exe.startswith(cmd): + cmd=exe #show non truncated version + #Note because we show the non truncated name + #one can have separated programs as follows: + #584.0 KiB + 1.0 MiB = 1.6 MiB mozilla-thunder (exe -> bash) + #56.0 MiB + 22.2 MiB = 78.2 MiB mozilla-thunderbird-bin + except: + #permission denied or + #kernel threads don't have exe links or + #process gone + continue + try: + rss, pss, shared = getMemStats(pid) + private = rss-shared + #Note shared is always a subset of rss (trs is not always) + except: + continue #process gone + if shareds.get(cmd): + if pss: #add shared portion of PSS together + shareds[cmd]+=pss-private + elif shareds[cmd] < shared: #just take largest shared val + shareds[cmd]=shared + else: + if pss: + shareds[cmd]=pss-private + else: + shareds[cmd]=shared + cmds[cmd]=cmds.setdefault(cmd,0)+private + if count.has_key(cmd): + count[cmd] += 1 + else: + count[cmd] = 1 + + #Add max shared mem for each program + total=0 + for cmd in cmds.keys(): + cmds[cmd]=cmds[cmd]+shareds[cmd] + total+=cmds[cmd] #valid if PSS available + + sort_list = cmds.items() + sort_list.sort(lambda x,y:cmp(x[1],y[1])) + sort_list=filter(lambda x:x[1],sort_list) #get rid of zero sized processes + + #The following matches "du -h" output + def human(num, power="Ki"): + powers=["Ki","Mi","Gi","Ti"] + while num >= 1000: #4 digits + num /= 1024.0 + power=powers[powers.index(power)+1] + return "%.1f %s" % (num,power) + + def cmd_with_count(cmd, count): + if count>1: + return "%s (%u)" % (cmd, count) + else: + return cmd + + for cmd in sort_list: + results.append([ + "%sB" % human(cmd[1]-shareds[cmd[0]]), + "%sB" % human(shareds[cmd[0]]), + "%sB" % human(cmd[1]), + "%s" % cmd_with_count(cmd[0], count[cmd[0]]) + ]) + if have_pss: + results.append(["", "", "", "%sB" % human(total)]) + + return results + + def kill(self,pid,signal="TERM"): + if pid == "0": + raise codes.FuncException("Killing pid group 0 not permitted") + if signal == "": + # this is default /bin/kill behaviour, + # it claims, but enfore it anyway + signal = "-TERM" + if signal[0] != "-": + signal = "-%s" % signal + rc = sub_process.call(["/bin/kill",signal, pid], + executable="/bin/kill", shell=False) + print rc + return rc + + def pkill(self,name,level=""): + # example killall("thunderbird","-9") + rc = sub_process.call(["/usr/bin/pkill", name, level], + executable="/usr/bin/pkill", shell=False) + return rc + +methods = ProcessModule() +register_rpc = methods.register_rpc diff --git a/certmaster/minion/modules/reboot.py b/certmaster/minion/modules/reboot.py new file mode 100644 index 0000000..c4fb275 --- /dev/null +++ b/certmaster/minion/modules/reboot.py @@ -0,0 +1,21 @@ +# Copyright 2007, Red Hat, Inc +# James Bowes <jbowes@redhat.com> +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import func_module +import sub_process + +class Reboot(func_module.FuncModule): + + version = "0.0.1" + api_version = "0.0.1" + description = "Reboots a machine." + + def reboot(self, when='now', message=''): + return sub_process.call(["/sbin/shutdown", '-r', when, message]) diff --git a/certmaster/minion/modules/rpms.py b/certmaster/minion/modules/rpms.py new file mode 100644 index 0000000..ae26cb4 --- /dev/null +++ b/certmaster/minion/modules/rpms.py @@ -0,0 +1,44 @@ +# Copyright 2007, Red Hat, Inc +# Michael DeHaan <mdehaan@redhat.com> +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import func_module +import rpm + +class RpmModule(func_module.FuncModule): + + version = "0.0.1" + api_version = "0.0.1" + description = "RPM related commands." + + def inventory(self, flatten=True): + """ + Returns information on all installed packages. + By default, 'flatten' is passed in as True, which makes printouts very + clean in diffs for use by func-inventory. If you are writting another + software application, using flatten=False will prevent the need to + parse the returns. + """ + # I have not been able to get flatten=False to work if there + # is more than 491 entries in the dict -- ashcrow + ts = rpm.TransactionSet() + mi = ts.dbMatch() + results = [] + for hdr in mi: + name = hdr['name'] + epoch = (hdr['epoch'] or 0) + version = hdr['version'] + release = hdr['release'] + arch = hdr['arch'] + if flatten: + results.append("%s %s %s %s %s" % (name, epoch, version, + release, arch)) + else: + results.append([name, epoch, version, release, arch]) + return results diff --git a/certmaster/minion/modules/service.py b/certmaster/minion/modules/service.py new file mode 100644 index 0000000..062aea5 --- /dev/null +++ b/certmaster/minion/modules/service.py @@ -0,0 +1,88 @@ +## func +## +## Copyright 2007, Red Hat, Inc +## Michael DeHaan <mdehaan@redhat.com> +## +## This software may be freely redistributed under the terms of the GNU +## general public license. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## +## + +import codes +import func_module + +import sub_process +import os + +class Service(func_module.FuncModule): + + version = "0.0.1" + api_version = "0.0.1" + description = "Allows for service control via func." + + def __command(self, service_name, command): + + filename = os.path.join("/etc/rc.d/init.d/",service_name) + if os.path.exists(filename): + return sub_process.call(["/sbin/service", service_name, command]) + else: + raise codes.FuncException("Service not installed: %s" % service_name) + + def start(self, service_name): + return self.__command(service_name, "start") + + def stop(self, service_name): + return self.__command(service_name, "stop") + + def restart(self, service_name): + return self.__command(service_name, "restart") + + def reload(self, service_name): + return self.__command(service_name, "reload") + + def status(self, service_name): + return self.__command(service_name, "status") + + def inventory(self): + return { + "running" : self.get_running(), + "enabled" : self.get_enabled() + } + + def get_enabled(self): + """ + Get the list of services that are enabled at the various runlevels. Xinetd services + only provide whether or not they are running, not specific runlevel info. + """ + + chkconfig = sub_process.Popen(["/sbin/chkconfig", "--list"], stdout=sub_process.PIPE) + data = chkconfig.communicate()[0] + results = [] + for line in data.split("\n"): + if line.find("0:") != -1: + # regular services + tokens = line.split() + results.append((tokens[0],tokens[1:])) + elif line.find(":") != -1 and not line.endswith(":"): + # xinetd.d based services + tokens = line.split() + tokens[0] = tokens[0].replace(":","") + results.append((tokens[0],tokens[1])) + return results + + def get_running(self): + """ + Get a list of which services are running, stopped, or disabled. + """ + chkconfig = sub_process.Popen(["/sbin/service", "--status-all"], stdout=sub_process.PIPE) + data = chkconfig.communicate()[0] + results = [] + for line in data.split("\n"): + if line.find(" is ") != -1: + tokens = line.split() + results.append((tokens[0], tokens[-1].replace("...",""))) + return results diff --git a/certmaster/minion/modules/smart.py b/certmaster/minion/modules/smart.py new file mode 100644 index 0000000..f410f09 --- /dev/null +++ b/certmaster/minion/modules/smart.py @@ -0,0 +1,47 @@ +## +## Grabs status from SMART to see if your hard drives are ok +## Returns in the format of (return code, [line1, line2, line3,...]) +## +## Copyright 2007, Red Hat, Inc +## Michael DeHaan <mdehaan@redhat.com> +## +## This software may be freely redistributed under the terms of the GNU +## general public license. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +## + +# other modules +import sub_process + +# our modules +import func_module + +# ================================= + +class SmartModule(func_module.FuncModule): + + version = "0.0.1" + api_version = "0.0.1" + description = "Grabs status from SMART to see if your hard drives are ok." + + def info(self,flags="-q onecheck"): + """ + Returns a struct of hardware information. By default, this pulls down + all of the devices. If you don't care about them, set with_devices to + False. + """ + + flags.replace(";","") # prevent stupidity + + cmd = sub_process.Popen("/usr/sbin/smartd %s" % flags,stdout=sub_process.PIPE,shell=True) + data = cmd.communicate()[0] + + results = [] + + for x in data.split("\n"): + results.append(x) + + return (cmd.returncode, results) diff --git a/certmaster/minion/modules/snmp.py b/certmaster/minion/modules/snmp.py new file mode 100644 index 0000000..c992db1 --- /dev/null +++ b/certmaster/minion/modules/snmp.py @@ -0,0 +1,38 @@ +# Copyright 2007, Red Hat, Inc +# James Bowes <jbowes@redhat.com> +# Seth Vidal modified command.py to be snmp.py +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +""" +Abitrary command execution module for func. +""" + +import func_module +import sub_process +base_snmp_command = '/usr/bin/snmpget -v2c -Ov -OQ' + +class Snmp(func_module.FuncModule): + + version = "0.0.1" + api_version = "0.0.1" + description = "SNMP related calls through func." + + def get(self, oid, rocommunity, hostname='localhost'): + """ + Runs an snmpget on a specific oid returns the output of the call. + """ + command = '%s -c %s %s %s' % (base_snmp_command, rocommunity, hostname, oid) + + cmdref = sub_process.Popen(command.split(),stdout=sub_process.PIPE,stderr=sub_process.PIPE, shell=False) + data = cmdref.communicate() + return (cmdref.returncode, data[0], data[1]) + + #def walk(self, oid, rocommunity): + + #def table(self, oid, rocommunity): diff --git a/certmaster/minion/modules/sysctl.py b/certmaster/minion/modules/sysctl.py new file mode 100644 index 0000000..1f11d55 --- /dev/null +++ b/certmaster/minion/modules/sysctl.py @@ -0,0 +1,31 @@ +# Copyright 2008, Red Hat, Inc +# Luke Macken <lmacken@redhat.com> +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import func_module +import sub_process + +class SysctlModule(func_module.FuncModule): + + version = "0.0.1" + description = "Configure kernel parameters at runtime" + + def __run(self, cmd): + cmd = sub_process.Popen(cmd.split(), stdout=sub_process.PIPE, + stderr=sub_process.PIPE, shell=False) + return [line for line in cmd.communicate()[0].strip().split('\n')] + + def list(self): + return self.__run("/sbin/sysctl -a") + + def get(self, name): + return self.__run("/sbin/sysctl -n %s" % name) + + def set(self, name, value): + return self.__run("/sbin/sysctl -w %s=%s" % (name, value)) diff --git a/certmaster/minion/modules/test.py b/certmaster/minion/modules/test.py new file mode 100644 index 0000000..6f7c5fa --- /dev/null +++ b/certmaster/minion/modules/test.py @@ -0,0 +1,29 @@ +import func_module +import time +import exceptions + +class Test(func_module.FuncModule): + version = "11.11.11" + api_version = "0.0.1" + description = "Just a very simple example module" + + def add(self, numb1, numb2): + return numb1 + numb2 + + def ping(self): + return 1 + + def sleep(self,t): + """ + Sleeps for t seconds, and returns time of day. + Simply a test function for trying out async and threaded voodoo. + """ + t = int(t) + time.sleep(t) + return time.time() + + def explode(self): + """ + Testing remote exception handling is useful + """ + raise exceptions.Exception("khhhhhhaaaaaan!!!!!!") diff --git a/certmaster/minion/modules/virt.py b/certmaster/minion/modules/virt.py new file mode 100644 index 0000000..04d36bd --- /dev/null +++ b/certmaster/minion/modules/virt.py @@ -0,0 +1,277 @@ +""" +Virt management features + +Copyright 2007, Red Hat, Inc +Michael DeHaan <mdehaan@redhat.com> + +This software may be freely redistributed under the terms of the GNU +general public license. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +# warning: virt management is rather complicated +# to see a simple example of func, look at the +# service control module. API docs on how +# to use this to come. + +# other modules +import os +import sub_process +import libvirt + +# our modules +import codes +import func_module + +VIRT_STATE_NAME_MAP = { + 0 : "running", + 1 : "running", + 2 : "running", + 3 : "paused", + 4 : "shutdown", + 5 : "shutdown", + 6 : "crashed" +} + +class FuncLibvirtConnection(object): + + version = "0.0.1" + api_version = "0.0.1" + description = "Virtualization items through func." + + def __init__(self): + + cmd = sub_process.Popen("uname -r", shell=True, stdout=sub_process.PIPE) + output = cmd.communicate()[0] + + if output.find("xen") != -1: + conn = libvirt.open(None) + else: + conn = libvirt.open("qemu:///system") + + if not conn: + raise codes.FuncException("hypervisor connection failure") + + self.conn = conn + + def find_vm(self, vmid): + """ + Extra bonus feature: vmid = -1 returns a list of everything + """ + conn = self.conn + + vms = [] + + # this block of code borrowed from virt-manager: + # get working domain's name + ids = conn.listDomainsID(); + for id in ids: + vm = conn.lookupByID(id) + vms.append(vm) + # get defined domain + names = conn.listDefinedDomains() + for name in names: + vm = conn.lookupByName(name) + vms.append(vm) + + if vmid == -1: + return vms + + for vm in vms: + if vm.name() == vmid: + return vm + + raise codes.FuncException("virtual machine %s not found" % vmid) + + def shutdown(self, vmid): + return self.find_vm(vmid).shutdown() + + def pause(self, vmid): + return self.suspend(self.conn,vmid) + + def unpause(self, vmid): + return self.resume(self.conn,vmid) + + def suspend(self, vmid): + return self.find_vm(vmid).suspend() + + def resume(self, vmid): + return self.find_vm(vmid).resume() + + def create(self, vmid): + return self.find_vm(vmid).create() + + def destroy(self, vmid): + return self.find_vm(vmid).destroy() + + def undefine(self, vmid): + return self.find_vm(vmid).undefine() + + def get_status2(self, vm): + state = vm.info()[0] + # print "DEBUG: state: %s" % state + return VIRT_STATE_NAME_MAP.get(state,"unknown") + + def get_status(self, vmid): + state = self.find_vm(vmid).info()[0] + return VIRT_STATE_NAME_MAP.get(state,"unknown") + + + +class Virt(func_module.FuncModule): + + def __get_conn(self): + self.conn = FuncLibvirtConnection() + return self.conn + + def state(self): + vms = self.list_vms() + state = [] + for vm in vms: + state_blurb = self.conn.get_status(vm) + state.append("%s %s" % (vm,state_blurb)) + return state + + + def info(self): + vms = self.list_vms() + info = dict() + for vm in vms: + data = self.conn.find_vm(vm).info() + # libvirt returns maxMem, memory, and cpuTime as long()'s, which + # xmlrpclib tries to convert to regular int's during serialization. + # This throws exceptions, so convert them to strings here and + # assume the other end of the xmlrpc connection can figure things + # out or doesn't care. + info[vm] = { + "state" : VIRT_STATE_NAME_MAP.get(data[0],"unknown"), + "maxMem" : str(data[1]), + "memory" : str(data[2]), + "nrVirtCpu" : data[3], + "cpuTime" : str(data[4]) + } + return info + + + def list_vms(self): + self.conn = self.__get_conn() + vms = self.conn.find_vm(-1) + results = [] + for x in vms: + try: + results.append(x.name()) + except: + pass + return results + + def install(self, server_name, target_name, system=False): + + """ + Install a new virt system by way of a named cobbler profile. + """ + + # Example: + # install("bootserver.example.org", "fc7webserver", True) + + conn = self.__get_conn() + + if conn is None: + raise codes.FuncException("no connection") + + if not os.path.exists("/usr/bin/koan"): + raise codes.FuncException("no /usr/bin/koan") + target = "profile" + if system: + target = "system" + + # TODO: FUTURE: set --virt-path in cobbler or here + koan_args = [ + "/usr/bin/koan", + "--virt", + "--virt-graphics", # enable VNC + "--%s=%s" % (target, target_name), + "--server=%s" % server_name + ] + + rc = sub_process.call(koan_args,shell=False) + if rc == 0: + return 0 + else: + raise codes.FuncException("koan returned %d" % rc) + + + def shutdown(self, vmid): + """ + Make the machine with the given vmid stop running. + Whatever that takes. + """ + self.__get_conn() + self.conn.shutdown(vmid) + return 0 + + + def pause(self, vmid): + + """ + Pause the machine with the given vmid. + """ + self.__get_conn() + self.conn.suspend(vmid) + return 0 + + + def unpause(self, vmid): + + """ + Unpause the machine with the given vmid. + """ + + self.__get_conn() + self.conn.resume(vmid) + return 0 + + + def create(self, vmid): + + """ + Start the machine via the given mac address. + """ + self.__get_conn() + self.conn.create(vmid) + return 0 + + + def destroy(self, vmid): + + """ + Pull the virtual power from the virtual domain, giving it virtually no + time to virtually shut down. + """ + self.__get_conn() + self.conn.destroy(vmid) + return 0 + + + def undefine(self, vmid): + + """ + Stop a domain, and then wipe it from the face of the earth. + by deleting the disk image and it's configuration file. + """ + + self.__get_conn() + self.conn.undefine(vmid) + return 0 + + + def get_status(self, vmid): + + """ + Return a state suitable for server consumption. Aka, codes.py values, not XM output. + """ + + self.__get_conn() + return self.conn.get_status(vmid) diff --git a/certmaster/minion/modules/yumcmd.py b/certmaster/minion/modules/yumcmd.py new file mode 100644 index 0000000..f952372 --- /dev/null +++ b/certmaster/minion/modules/yumcmd.py @@ -0,0 +1,50 @@ +# Copyright 2007, Red Hat, Inc +# James Bowes <jbowes@redhat.com> +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import func_module + +import yum + +# XXX Use internal yum callback or write a useful one. +class DummyCallback(object): + + def event(self, state, data=None): + pass + +class Yum(func_module.FuncModule): + + version = "0.0.1" + api_version = "0.0.1" + description = "Package updates through yum." + + def update(self): + # XXX support updating specific rpms + ayum = yum.YumBase() + ayum.doGenericSetup() + ayum.doRepoSetup() + try: + ayum.doLock() + ayum.update() + ayum.buildTransaction() + ayum.processTransaction( + callback=DummyCallback()) + finally: + ayum.closeRpmDB() + ayum.doUnlock() + return True + + def check_update(self, repo=None): + """Returns a list of packages due to be updated""" + ayum = yum.YumBase() + ayum.doConfigSetup() + ayum.doTsSetup() + if repo is not None: + ayum.repos.enableRepo(repo) + return map(str, ayum.doPackageLists('updates').updates) |