From 5fdfa23476eea552f322192b1794e86bfe9d4f30 Mon Sep 17 00:00:00 2001 From: Jan Mach <jan.mach@cesnet.cz> Date: Wed, 31 May 2017 12:54:52 +0200 Subject: [PATCH] Code quality improvements in executable files in bin project subdirectory. Greatly improved code quality using pylint and pyflakes. All files now rank above 9 points with custom .pylintrc-bin file. Following exceptions were added to default pylint configuration: * line-too-long - I do not want to have calls to logging service spanning over multiple lines and distracting from code flow. I want them to be only single line, no matter how long, because it is not important. * bad-whitespace - I like to add additional whitespaces in places, where they should not be, mostly to align related items vertically, which in my opinion improves readability. * logging-format-interpolation - In Python3 I prefer string.format() to string % (args). Additionally the pattern for module name validation was altered, because of our executable file naming policy (added suppot for '-' and '.' characters in name). (Redmine issue: #3443) --- .pylintrc-bin | 425 +++++++++++++++++++++++++++++++++++++++ bin/mentat-backup.py | 116 ++++++----- bin/mentat-cleanup.py | 117 ++++++----- bin/mentat-controller.py | 229 +++++++++++---------- bin/mentat-dbmngr.py | 113 ++++++----- bin/mentat-ideagen.py | 88 ++++---- bin/mentat-inspector.py | 48 ++--- bin/mentat-netmngr.py | 118 +++++------ bin/mentat-sampler.py | 67 +++--- bin/mentat-storage.py | 60 +++--- 10 files changed, 938 insertions(+), 443 deletions(-) create mode 100644 .pylintrc-bin diff --git a/.pylintrc-bin b/.pylintrc-bin new file mode 100644 index 000000000..93125393e --- /dev/null +++ b/.pylintrc-bin @@ -0,0 +1,425 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. +jobs=1 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +disable=line-too-long,bad-whitespace,logging-format-interpolation,print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable= + + +[REPORTS] + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio).You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + + +[BASIC] + +# Naming hint for argument names +argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct argument names +argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Naming hint for attribute names +attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct attribute names +attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming hint for function names +function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct function names +function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for method names +method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct method names +method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Naming hint for module names +module-name-hint=(([a-z_][-.a-z0-9_]*)|([A-Z][-a-zA-Z0-9]+))$ + +# Regular expression matching correct module names +module-rgx=(([a-z_][-a-z0-9_]*)|([A-Z][-.a-zA-Z0-9]+))$ + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +property-classes=abc.abstractproperty + +# Naming hint for variable names +variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct variable names +variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,future.builtins + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )?<?https?://\S+>?$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma,dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of statements in function / method body +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[IMPORTS] + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/bin/mentat-backup.py b/bin/mentat-backup.py index 14e18fe12..7a739d3af 100755 --- a/bin/mentat-backup.py +++ b/bin/mentat-backup.py @@ -7,12 +7,22 @@ # Use of this source is governed by the MIT license, see LICENSE file. #------------------------------------------------------------------------------- + +""" +Script providing Mentat system backup functions and features. +""" + + +__version__ = "0.1" +__author__ = "Jan Mach <jan.mach@cesnet.cz>" +__credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>" + + import os import re import time import math import glob -import logging import pprint # @@ -39,12 +49,9 @@ COLLECTIONS = ( class MentatBackupScript(pyzenkit.zenscript.ZenScript): """ - Script providing Mentat system backup functions and features + Script providing Mentat system backup functions and features. """ - def __init__(self, **kwargs): - super().__init__(**kwargs) - def get_default_command(self): """ Return the name of a default script operation. @@ -79,7 +86,7 @@ class MentatBackupScript(pyzenkit.zenscript.ZenScript): """ # First calculate thresholds for collection backups. flf = self.config.get('regular', False) - (time_l, time_h) = self.calculate_interval_thresholds(self.config['interval'], flag_floor = flf); + (time_l, time_h) = self.calculate_interval_thresholds(self.config['interval'], flag_floor = flf) self.logger.info("Lower backup period threshold: {} ({})".format(time.strftime('%Y-%m-%d %H:%M', time.localtime(time_l)), time_l)) self.logger.info("Upper backup period threshold: {} ({})".format(time.strftime('%Y-%m-%d %H:%M', time.localtime(time_h)), time_h)) @@ -96,7 +103,7 @@ class MentatBackupScript(pyzenkit.zenscript.ZenScript): self.logger.debug("Backup file name: '{}'".format(backup_file)) # Perform actual backup. - result = self._backup_database(dir_tmp, backup_file, time_l, time_h, self.pstate) + result = self._backup_database(dir_tmp, backup_file, time_l, time_h) if not self.config['no_upload']: # Mount the remote directory to local directory. @@ -153,11 +160,11 @@ class MentatBackupScript(pyzenkit.zenscript.ZenScript): self.logger.info("Latest backup is from '{}'".format(result['backups'][0]['ts_str'])) self.logger.info("Oldest backup is from '{}'".format(result['backups'][-1]['ts_str'])) previous = None - for bi in result['backups']: - if previous: - if bi['step'] > 86400: - self.logger.info("Missing '{:,d}' backup files between '{}' and '{}'".format(math.floor((bi['step'] / 86400) - 1), previous['ts_str'], bi['ts_str'])) - previous = bi + for bkp in result['backups']: + if previous is not None: + if bkp['step'] > 86400: + self.logger.info("Missing '{:,d}' backup files between '{}' and '{}'".format(math.floor((bkp['step'] / 86400) - 1), previous['ts_str'], bkp['ts_str'])) + previous = bkp else: self.logger.warning("There are no backup files") @@ -171,7 +178,7 @@ class MentatBackupScript(pyzenkit.zenscript.ZenScript): # INTERNAL METHODS #--------------------------------------------------------------------------- - def _backup_database(self, dir_tmp, backup_file, time_l, time_h, state): + def _backup_database(self, dir_tmp, backup_file, time_l, time_h): """ Perform backup of all configured database collections. """ @@ -190,26 +197,26 @@ class MentatBackupScript(pyzenkit.zenscript.ZenScript): (coll, ts_attr, system) = rule if system: self.logger.info("Performing backup of system collection '{}'".format(coll)) - cmd = "mongodump -o {} -d '{}' -c '{}'".format(dir_tmp, 'mentat', coll) + cmd = r"mongodump -o {} -d '{}' -c '{}'".format(dir_tmp, 'mentat', coll) result['dumpcommands'].append(cmd) self.execute_command(cmd) else: self.logger.info("Performing backup of collection '{}'".format(coll)) - cmd = "mongodump -o {} -d '{}' -c '{}' -q '{{\"{}\": {{\"\$gte\": {}, \"\$lt\": {}}}}}'".format(dir_tmp, 'mentat', coll, ts_attr, time_l, time_h) + cmd = r"mongodump -o {} -d '{}' -c '{}' -q '{{\"{}\": {{\"\$gte\": {}, \"\$lt\": {}}}}}'".format(dir_tmp, 'mentat', coll, ts_attr, time_l, time_h) result['dumpcommands'].append(cmd) self.execute_command(cmd) self.logger.info("Packing the database backup into archive file '{}'".format(backup_file)) - cmd = "tar -czPf {} {}".format(backup_file, dir_tmp) + cmd = r"tar -czPf {} {}".format(backup_file, dir_tmp) self.execute_command(cmd) self.logger.info("Removing local backup directory '{}'".format(dir_tmp)) - cmd = "rm -rf {}".format(dir_tmp) + cmd = r"rm -rf {}".format(dir_tmp) self.execute_command(cmd) - si = os.stat(backup_file) - result['backup_file'] = backup_file, - result['backup_size'] = si.st_size + sti = os.stat(backup_file) + result['backup_file'] = backup_file + result['backup_size'] = sti.st_size self.logger.info("Created backup file '{}' with size {:,.2f} KB".format(result['backup_file'], (result['backup_size']/1024))) return result @@ -234,12 +241,12 @@ class MentatBackupScript(pyzenkit.zenscript.ZenScript): """ backup_files = sorted(glob.glob(backup_mask), reverse = True) self.logger.debug("Found following local backup files >>>\n{}".format(pprint.pformat(backup_files, indent=4))) - for bf in backup_files: - if bf == backup_file: + for bfl in backup_files: + if bfl == backup_file: self.logger.info("Keeping local copy of latest backup file '{}'".format(backup_file)) else: - self.logger.info("Removing local copy of older backup file '{}'".format(bf)) - os.remove(bf) + self.logger.info("Removing local copy of older backup file '{}'".format(bfl)) + os.remove(bfl) def _analyze_remote(self, mount_point, mode): """ @@ -251,33 +258,33 @@ class MentatBackupScript(pyzenkit.zenscript.ZenScript): if os.path.isdir(tgtdir): self.logger.info("Analyzing remote backup directory '{}':".format(tgtdir)) backupfiles = os.listdir(tgtdir) - ptrn = re.compile('mentatdb_' + mode + '_(\d{12})\.tar\.gz') - for f in sorted(backupfiles, reverse=True): - fn = os.path.join(tgtdir, f) - if not os.path.isfile(fn): + ptrn = re.compile('mentatdb_' + mode + r'_(\d{12})\.tar\.gz') + for bfl in sorted(backupfiles, reverse=True): + bfp = os.path.join(tgtdir, bfl) + if not os.path.isfile(bfp): continue - m = ptrn.match(f) - if not m: + match = ptrn.match(bfl) + if not match: continue - a = { - 'file': f, - 'path': fn, - 'ts_raw': m.group(1), - 'ts_str': time.strftime('%Y-%m-%d', time.strptime(m.group(1), '%Y%m%d%H%M')), - 'ts': time.mktime(time.strptime(m.group(1), '%Y%m%d%H%M')) + anl = { + 'file': bfl, + 'path': bfp, + 'ts_raw': match.group(1), + 'ts_str': time.strftime('%Y-%m-%d', time.strptime(match.group(1), '%Y%m%d%H%M')), + 'ts': time.mktime(time.strptime(match.group(1), '%Y%m%d%H%M')) } - si = os.stat(fn) - a['size'] = si.st_size + sti = os.stat(bfp) + anl['size'] = sti.st_size if not previous: - a['step'] = 0 - a['value'] = 1 + anl['step'] = 0 + anl['value'] = 1 else: - a['step'] = int(previous['ts'] - a['ts']) - a['value'] = int(previous['value'] + (a['step'] / 86400)) + anl['step'] = int(previous['ts'] - anl['ts']) + anl['value'] = int(previous['value'] + (anl['step'] / 86400)) - previous = a + previous = anl - result['backups'].append(a) + result['backups'].append(anl) self.logger.debug("Backup analysis: {}".format(pprint.pformat(result))) else: self.logger.error("Remote backup directory '{}' does not exist".format(tgtdir)) @@ -308,16 +315,27 @@ class MentatBackupScript(pyzenkit.zenscript.ZenScript): else: self.logger.info("Remote storage already unmounted from path '{}'".format(mount_point)) + +# +# Execute the script. +# if __name__ == "__main__": - """ - Execute the MentatBackupScript script. - """ - script = MentatBackupScript( + + SCRIPT = MentatBackupScript( + + description = 'mentat-netmngr.py - Abuse group network management script for Mentat system database', + + # + # Configure required script paths. + # path_cfg = '/etc/mentat', path_log = '/var/mentat/log', path_run = '/var/mentat/run', path_tmp = '/tmp', + # + # Override default configurations. + # default_config_dir = '/etc/mentat/core', ) - script.run() + SCRIPT.run() diff --git a/bin/mentat-cleanup.py b/bin/mentat-cleanup.py index ca3ed7c2f..0136e8cf9 100755 --- a/bin/mentat-cleanup.py +++ b/bin/mentat-cleanup.py @@ -7,15 +7,22 @@ # Use of this source is governed by the MIT license, see LICENSE file. #------------------------------------------------------------------------------- + +""" +Script providing Mentat system cleanup functions and features. +""" + + +__version__ = "0.1" +__author__ = "Jan Mach <jan.mach@cesnet.cz>" +__credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>" + + import os -import re import sys import time -import math -import glob import json -import logging -import pprint + # # Custom libraries @@ -24,6 +31,7 @@ import pyzenkit.zenscript import pydgets.widgets import mentat.storage + # # Global variables. # @@ -31,9 +39,11 @@ SECS_DAY = 60 * 60 * 24 # Number of seconds in a day SECS_WEEK = SECS_DAY * 7 # Number of seconds in a week SECS_YEAR = 31556926 # Number of seconds in a year (approximately) + # Current time (second precission) TS = int(time.time()) + # List of possible item TTLs TTLS = { 'y': SECS_YEAR, @@ -78,10 +88,12 @@ THRESHOLDS = { 'd': {'l': 'items older than one day', 't': (TS - TTLS['d'])}, } + class MentatCleanupScript(pyzenkit.zenscript.ZenScript): """ - Script providing Mentat system cleanup functions and features + Script providing Mentat system cleanup functions and features. """ + CONFIG_DB_PATH = 'db_path' CONFIG_SIMULATE = 'simulate' CONFIG_SOCKET_TIMEOUT = 'socket_timeout' @@ -118,8 +130,8 @@ class MentatCleanupScript(pyzenkit.zenscript.ZenScript): (self.CONFIG_COLLECTIONS, []), (self.CONFIG_CACHES, []), ) - for c in cfgs: - config[c[0]] = kwargs.get('default_' + c[0], c[1]) + for cfg in cfgs: + config[cfg[0]] = kwargs.get('default_' + cfg[0], cfg[1]) return config def _init_custom(self, config, argparser, **kwargs): @@ -213,25 +225,25 @@ class MentatCleanupScript(pyzenkit.zenscript.ZenScript): """ table_columns = [ - { 'label': 'Date' }, - { 'label': 'FS removed [#]', 'data_formating': '{:,d}', 'align': '>' }, - { 'label': 'DB removed [#]', 'data_formating': '{:,d}', 'align': '>' }, - ] + { 'label': 'Date' }, + { 'label': 'FS removed [#]', 'data_formating': '{:,d}', 'align': '>' }, + { 'label': 'DB removed [#]', 'data_formating': '{:,d}', 'align': '>' }, + ] table_data = [] - for an in evaluation[self.RLEVKEY_ANALYSES]: - clrslt = an.get('cleanup', None) + for anl in evaluation[self.RLEVKEY_ANALYSES]: + clrslt = anl.get('cleanup', None) if clrslt: table_data.append( [ - an['label'], - an['cleanup']['caches']['removed_cnt'], - an['cleanup']['collections']['removed_cnt'], + anl['label'], + anl['cleanup']['caches']['removed_cnt'], + anl['cleanup']['collections']['removed_cnt'], ] ) else: table_data.append( [ - an['label'], + anl['label'], 0, 0, ] @@ -261,14 +273,14 @@ class MentatCleanupScript(pyzenkit.zenscript.ZenScript): # Perform cleanup od selected database collections result['collections'] = [] for collection in self.c('collections'): - r = self._cleanup_collection(**collection) - result['collections'].append(r) + res = self._cleanup_collection(**collection) + result['collections'].append(res) # Perform cleanup of selected folder caches result['caches'] = [] for cache in self.c('caches'): - r = self._cleanup_cache(**cache) - result['caches'].append(r) + res = self._cleanup_cache(**cache) + result['caches'].append(res) # Measure disk usage after cleanup result['fsstats_post'] = self._fsstats(self.c('db_path')) @@ -279,11 +291,12 @@ class MentatCleanupScript(pyzenkit.zenscript.ZenScript): # INTERNAL METHODS #--------------------------------------------------------------------------- - def _fsstats(self, fs): + @staticmethod + def _fsstats(fpath): """ Calculate the filesystem statistics. """ - fsstats = os.statvfs(fs) + fsstats = os.statvfs(fpath) return dict( zip( ('f_bsize', 'f_frsize', 'f_blocks', 'f_bfree', 'f_bavail', 'f_files', 'f_ffree', 'f_favail', 'f_flag', 'f_namemax'), @@ -321,27 +334,25 @@ class MentatCleanupScript(pyzenkit.zenscript.ZenScript): result['collection'], result['threshold'], result['threshold_str'], - result['filter']) - ) + result['filter'])) - db = self.storage.database(database) - result['stats_pre'] = db.collection_stats(collection) + dbh = self.storage.database(database) + result['stats_pre'] = dbh.collection_stats(collection) if not self.c('simulate'): - res = db.collection(collection).delete_many(filter_spec) + res = dbh.collection(collection).delete_many(filter_spec) result['removed_cnt'] = res.deleted_count else: - result['removed_cnt'] = db.collection(collection).find(filter_spec).count() + result['removed_cnt'] = dbh.collection(collection).find(filter_spec).count() - db = self.storage.database(database) - result['stats_post'] = db.collection_stats(collection) + dbh = self.storage.database(database) + result['stats_post'] = dbh.collection_stats(collection) self.logger.info("Collection '{}.{}' cleanup done, removed: {:,d} | kept: {:,d}".format( result['database'], result['collection'], result['removed_cnt'], - result['stats_post']['count']) - ) + result['stats_post']['count'])) # Except: pymongo.errors.OperationFailure: command SON([('collstats', 'logs')]) on namespace mentat.$cmd failed: Collection [mentat.logs] not found. except: @@ -374,39 +385,41 @@ class MentatCleanupScript(pyzenkit.zenscript.ZenScript): } self.logger.info("Cache '{}' cleanup started with threshold '{}': '{}'".format(result['cache'], result['threshold'], result['threshold_str'])) - for f in os.listdir(cache): - fn = os.path.join(cache, f) - if os.path.isfile(fn): - st = os.stat(fn) + for fln in os.listdir(cache): + flp = os.path.join(cache, fln) + if os.path.isfile(flp): + fst = os.stat(flp) result['files_cnt'] += 1 - result['files_bytes'] += st.st_size - if st.st_mtime < threshold['t']: + result['files_bytes'] += fst.st_size + if fst.st_mtime < threshold['t']: try: if not self.c('simulate'): - os.remove(fn) + os.remove(flp) result['removed_cnt'] += 1 - result['removed_bytes'] += st.st_size + result['removed_bytes'] += fst.st_size except: - self.error("Unable to remove cache file '{}'".format(fn)) + self.error("Unable to remove cache file '{}'".format(flp)) result['error_cnt'] += 1 - result['errors'].append(fn) + result['errors'].append(flp) else: result['kept_cnt'] += 1 - result['kept_bytes'] += st.st_size + result['kept_bytes'] += fst.st_size self.logger.info("Cache '{}' cleanup done, removed: {:,d} | kept: {:,d} | errors: {:,d}".format(result['cache'], result['removed_cnt'], result['kept_cnt'], result['error_cnt'])) return result + +# +# Execute the script. +# if __name__ == "__main__": - """ - Execute the MentatCleanupScript script. - """ - script = MentatCleanupScript( + + SCRIPT = MentatCleanupScript( description = 'mentat-cleanup.py - Mentat system database and cache folder cleanup script', # - # Configure required daemon paths + # Configure required script paths. # path_bin = '/usr/local/bin', path_cfg = '/etc/mentat', @@ -415,8 +428,8 @@ if __name__ == "__main__": path_tmp = '/tmp', # - # Override default configurations + # Override default configurations. # default_config_dir = '/etc/mentat/core', ) - script.run() + SCRIPT.run() diff --git a/bin/mentat-controller.py b/bin/mentat-controller.py index 78bf501d6..545c59977 100755 --- a/bin/mentat-controller.py +++ b/bin/mentat-controller.py @@ -7,6 +7,17 @@ # Use of this source is governed by the MIT license, see LICENSE file. #------------------------------------------------------------------------------- + +""" +Script providing Mentat system control functions and features. +""" + + +__version__ = "0.1" +__author__ = "Jan Mach <jan.mach@cesnet.cz>" +__credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>" + + import os import re import time @@ -56,8 +67,9 @@ SIGNALS_TO_NAMES_DICT = dict((getattr(signal, n), n) \ class MentatControllerScript(pyzenkit.zenscript.ZenScript): """ - Script providing Mentat system control functions and features + Script providing Mentat system control functions and features. """ + CONFIG_TARGET = 'target' def _init_argparser(self, **kwargs): @@ -80,8 +92,8 @@ class MentatControllerScript(pyzenkit.zenscript.ZenScript): cfgs = ( (self.CONFIG_TARGET, None), ) - for c in cfgs: - config[c[0]] = kwargs.get('default_' + c[0], c[1]) + for cfg in cfgs: + config[cfg[0]] = kwargs.get('default_' + cfg[0], cfg[1]) return config def _get_target(self): @@ -104,19 +116,19 @@ class MentatControllerScript(pyzenkit.zenscript.ZenScript): self.modules = collections.OrderedDict() # Analyze and initialize module configurations - for c in self.c('modules'): + for mod in self.c('modules'): record = {} # Executable is mandatory attribute - if c.get('exec'): - record['exec'] = c['exec'] + if mod.get('exec'): + record['exec'] = mod['exec'] else: raise pyzenkit.baseapp.ZenAppSetupException("Module definition in configuration file is missing mandatory attribute 'exec'.") - record['name'] = c.get('name', record['exec']) - record['args'] = c.get('args', []) - record['paralel'] = c.get('paralel', False) - record['count'] = int(c.get('count', 0)) + record['name'] = mod.get('name', record['exec']) + record['args'] = mod.get('args', []) + record['paralel'] = mod.get('paralel', False) + record['count'] = int(mod.get('count', 0)) if record['paralel'] and not record['count'] > 1: raise pyzenkit.baseapp.ZenAppSetupException("Module '{}' is configured to run in paralel and the 'count' attribute is missing or below '2'.".format(record['name'])) @@ -131,13 +143,14 @@ class MentatControllerScript(pyzenkit.zenscript.ZenScript): #--------------------------------------------------------------------------- - def _analyze_pid_file(self, pid_file, pid_file_path): + @staticmethod + def _analyze_pid_file(pid_file, pid_file_path): """ Analyze given PID file. """ try: - m = re.search('^(.+?)(?:\.(\d+))?\.pid$', pid_file) - if not m: + match = re.search(r'^(.+?)(?:\.(\d+))?\.pid$', pid_file) + if not match: return None pid = None @@ -146,14 +159,13 @@ class MentatControllerScript(pyzenkit.zenscript.ZenScript): pid = int(pid) return { - "name": m.group(1), + "name": match.group(1), "file": pid_file, "path": pid_file_path, "pid": int(pid), - "paralel": True if m.group(2) else False + "paralel": True if match.group(2) else False } except: - daemon.logger.error("Unable to analyze PID file '{}'".format(pid_file_path)) return None def _analyze_pid_files(self): @@ -176,33 +188,35 @@ class MentatControllerScript(pyzenkit.zenscript.ZenScript): pid_files[record['name']][record['pid']] = record return pid_files - def _analyze_process_ps(self, pid, process, cmdline): + @staticmethod + def _analyze_process_ps(pid, process, cmdline): """ Analyze given process (detected via ps utility). """ - m = re.match('([^\s]*(?:perl|python3))\s+(?:-T\s+)?[^\s]+(mentat-[^\s]+)(?:\s+(.*))?', cmdline) - if not m: + match = re.match(r'([^\s]*(?:perl|python3))\s+(?:-T\s+)?[^\s]+(mentat-[^\s]+)(?:\s+(.*))?', cmdline) + if not match: return None - if m.group(2) in ['mentat-controller.py', 'mentat-statistician', 'mentat-reporter-ng']: + if match.group(2) in ['mentat-controller.py', 'mentat-statistician', 'mentat-reporter-ng']: return None record = { - 'name': m.group(2), - 'exec': m.group(2), - 'args': m.group(3), + 'process': process, + 'name': match.group(2), + 'exec': match.group(2), + 'args': match.group(3), 'pid': int(pid), 'paralel': False } if record['args']: # Attempt to parse custom name from command line arguments - m = re.search('--name(?: |=)([^\s]+)', record['args']) - if m: - record['name'] = m.group(1) + match = re.search(r'--name(?: |=)([^\s]+)', record['args']) + if match: + record['name'] = match.group(1) # Attempt to detect paralel mode switch from command line arguments - m = re.search('--paralel', record['args']) - if m: + match = re.search('--paralel', record['args']) + if match: record['paralel'] = True return record @@ -212,23 +226,24 @@ class MentatControllerScript(pyzenkit.zenscript.ZenScript): Analyze all running processes via ps utility. """ processes = collections.defaultdict(dict) - s = subprocess.Popen(['/bin/ps axo pid,comm,args | grep mentat | grep -v grep'], stdout = subprocess.PIPE, shell = True) - for line in s.stdout: + proc = subprocess.Popen(['/bin/ps axo pid,comm,args | grep mentat | grep -v grep'], stdout = subprocess.PIPE, shell = True) + for line in proc.stdout: line = line.rstrip() line = line.decode('utf-8') - m = re.match('\s*(\d+)\s+([^\s]+)\s+(.+)$', line) - if not m: + match = re.match(r'\s*(\d+)\s+([^\s]+)\s+(.+)$', line) + if not match: continue - record = self._analyze_process_ps(m.group(1), m.group(2), m.group(3)) + record = self._analyze_process_ps(match.group(1), match.group(2), match.group(3)) if not record: continue processes[record['name']][record['pid']] = record return processes - def _module_status(self, problems, conf_data, pidf_data, proc_data): + @staticmethod + def _module_status(problems, conf_data, pidf_data, proc_data): """ Analyze status of given module. """ @@ -324,11 +339,11 @@ class MentatControllerScript(pyzenkit.zenscript.ZenScript): # A) Analyze status of configured modules for mname, mdata in self.modules.items(): status['modules'][mname] = self._module_status( - status['problems'], - mdata, - status['pid_files'].get(mname, None), - status['processes'].get(mname, None) - ) + status['problems'], + mdata, + status['pid_files'].get(mname, None), + status['processes'].get(mname, None) + ) # B) Analyze unconfigured PID files for mname in sorted(status['pid_files'].keys()): @@ -337,11 +352,11 @@ class MentatControllerScript(pyzenkit.zenscript.ZenScript): continue for pid in sorted(status['pid_files'][mname].keys()): status['problems'].append(( - 'warning', - 'pidfile', - status['pid_files'][mname][pid]['path'], - "Unknown pid file '{}' for PID '{}'".format(status['pid_files'][mname][pid]['path'], pid)) - ) + 'warning', + 'pidfile', + status['pid_files'][mname][pid]['path'], + "Unknown pid file '{}' for PID '{}'".format(status['pid_files'][mname][pid]['path'], pid) + )) # C) Analyze unconfigured running modules for mname in sorted(status['processes'].keys()): @@ -350,11 +365,11 @@ class MentatControllerScript(pyzenkit.zenscript.ZenScript): continue for pid in sorted(status['processes'][mname].keys()): status['problems'].append(( - 'warning', - 'pidfile', - pid, - "Unknown process '{}' with PID '{}'".format(status['processes'][mname][pid]['exec'], pid)) - ) + 'warning', + 'pidfile', + pid, + "Unknown process '{}' with PID '{}'".format(status['processes'][mname][pid]['exec'], pid) + )) # D) Determine overall status of the whole system for mname, mstatus in status['modules'].items(): @@ -375,12 +390,12 @@ class MentatControllerScript(pyzenkit.zenscript.ZenScript): self.logger.info("Status of configured Mentat modules:") complist = self._get_target() for mname in complist: - s = status['modules'][mname] - self.logger.info("Module '{}': '{}'".format(mname, s[1])) + stat = status['modules'][mname] + self.logger.info("Module '{}': '{}'".format(mname, stat[1])) if status['problems']: - for p in status['problems']: - self.logger.warning(p[3]) + for prob in status['problems']: + self.logger.warning(prob[3]) self.logger.info("Overall system status: '{}'".format(status['result'][1])) self.rc = status['result'][0] @@ -443,18 +458,18 @@ class MentatControllerScript(pyzenkit.zenscript.ZenScript): self.logger.info("Starting all configured Mentat modules:") complist = self._get_target() for mname in complist: - s = status['modules'][mname] - f = status['pid_files'].get(mname, None) - p = status['processes'].get(mname, None) + stat = status['modules'][mname] + pidf = status['pid_files'].get(mname, None) + proc = status['processes'].get(mname, None) - if s[0] == STATUS_RUNNING_OK: + if stat[0] == STATUS_RUNNING_OK: self.logger.info("Module '{}': Module is already running properly, nothing to do".format(mname)) - elif s[0] == STATUS_NOT_RUNNING: + elif stat[0] == STATUS_NOT_RUNNING: self.logger.info("Module '{}': Launching module".format(mname)) - self._module_start(self.modules[mname], f, p) - elif s[0] == STATUS_RUNNING_FEWER: + self._module_start(self.modules[mname], pidf, proc) + elif stat[0] == STATUS_RUNNING_FEWER: self.logger.info("Module '{}': Module is running in fewer than required instances".format(mname)) - elif s[0] == STATUS_RUNNING_MORE: + elif stat[0] == STATUS_RUNNING_MORE: self.logger.info("Module '{}': Module is running in more than required instances".format(mname)) else: self.logger.error("Module '{}': Module is in weird state, unable to perform automatic startup".format(mname)) @@ -496,20 +511,20 @@ class MentatControllerScript(pyzenkit.zenscript.ZenScript): self.logger.info("Stopping all configured Mentat modules:") complist = self._get_target() for mname in complist: - s = status['modules'][mname] - f = status['pid_files'].get(mname, None) - p = status['processes'].get(mname, None) + stat = status['modules'][mname] + pidf = status['pid_files'].get(mname, None) + proc = status['processes'].get(mname, None) - if s[0] == STATUS_NOT_RUNNING: + if stat[0] == STATUS_NOT_RUNNING: self.logger.info("Module '{}': Module is already not running, nothing to do".format(mname)) - elif s[0] in (STATUS_RUNNING_OK, - STATUS_RUNNING_PF_MISSING, - STATUS_RUNNING_PID_MISSMATCH, - STATUS_RUNNING_INCONSISTENT, - STATUS_RUNNING_FEWER, - STATUS_RUNNING_MORE): + elif stat[0] in (STATUS_RUNNING_OK, + STATUS_RUNNING_PF_MISSING, + STATUS_RUNNING_PID_MISSMATCH, + STATUS_RUNNING_INCONSISTENT, + STATUS_RUNNING_FEWER, + STATUS_RUNNING_MORE): self.logger.info("Module '{}': Stopping module".format(mname)) - self._module_signal(self.modules[mname], f, p, signal.SIGINT) + self._module_signal(self.modules[mname], pidf, proc, signal.SIGINT) else: self.logger.error("Module '{}': Module is in weird state, unable to perform automatic shutdown".format(mname)) @@ -543,20 +558,20 @@ class MentatControllerScript(pyzenkit.zenscript.ZenScript): self.logger.info("Killing all configured Mentat modules:") complist = self._get_target() for mname in complist: - s = status['modules'][mname] - f = status['pid_files'].get(mname, None) - p = status['processes'].get(mname, None) + stat = status['modules'][mname] + pidf = status['pid_files'].get(mname, None) + proc = status['processes'].get(mname, None) - if s[0] == STATUS_NOT_RUNNING: + if stat[0] == STATUS_NOT_RUNNING: self.logger.info("Module '{}': Module is already not running, nothing to do".format(mname)) - elif s[0] in (STATUS_RUNNING_OK, - STATUS_RUNNING_PF_MISSING, - STATUS_RUNNING_PID_MISSMATCH, - STATUS_RUNNING_INCONSISTENT, - STATUS_RUNNING_FEWER, - STATUS_RUNNING_MORE): + elif stat[0] in (STATUS_RUNNING_OK, + STATUS_RUNNING_PF_MISSING, + STATUS_RUNNING_PID_MISSMATCH, + STATUS_RUNNING_INCONSISTENT, + STATUS_RUNNING_FEWER, + STATUS_RUNNING_MORE): self.logger.info("Module '{}': Killing module".format(mname)) - self._module_signal(self.modules[mname], f, p, signal.SIGKILL) + self._module_signal(self.modules[mname], pidf, proc, signal.SIGKILL) else: self.logger.error("Module '{}': Module is in weird state, unable to kill it".format(mname)) @@ -571,14 +586,14 @@ class MentatControllerScript(pyzenkit.zenscript.ZenScript): self.logger.info("Sending SIGUSR1 signal to all configured Mentat modules:") complist = self._get_target() for mname in complist: - s = status['modules'][mname] - f = status['pid_files'].get(mname, None) - p = status['processes'].get(mname, None) + stat = status['modules'][mname] + pidf = status['pid_files'].get(mname, None) + proc = status['processes'].get(mname, None) - if not re.search('\.py$', self.modules[mname]['exec']): + if not re.search(r'\.py$', self.modules[mname]['exec']): self.logger.warning("Module '{}': Module is unable to handle this signal".format(mname)) - elif s[0] == STATUS_RUNNING_OK: - self._module_signal(self.modules[mname], f, p, signal.SIGUSR1) + elif stat[0] == STATUS_RUNNING_OK: + self._module_signal(self.modules[mname], pidf, proc, signal.SIGUSR1) else: self.logger.error("Module '{}': Unable to send signal".format(mname)) @@ -593,14 +608,14 @@ class MentatControllerScript(pyzenkit.zenscript.ZenScript): self.logger.info("Sending SIGUSR2 signal to all configured Mentat modules:") complist = self._get_target() for mname in complist: - s = status['modules'][mname] - f = status['pid_files'].get(mname, None) - p = status['processes'].get(mname, None) + stat = status['modules'][mname] + pidf = status['pid_files'].get(mname, None) + proc = status['processes'].get(mname, None) - if not re.search('\.py$', self.modules[mname]['exec']): + if not re.search(r'\.py$', self.modules[mname]['exec']): self.logger.warning("Module '{}': Module is unable to handle this signal".format(mname)) - elif s[0] == STATUS_RUNNING_OK: - self._module_signal(self.modules[mname], f, p, signal.SIGUSR2) + elif stat[0] == STATUS_RUNNING_OK: + self._module_signal(self.modules[mname], pidf, proc, signal.SIGUSR2) else: self.logger.error("Module '{}': Unable to send signal".format(mname)) @@ -615,12 +630,12 @@ class MentatControllerScript(pyzenkit.zenscript.ZenScript): self.logger.info("Sending SIGHUP signal to all configured Mentat modules:") complist = self._get_target() for mname in complist: - s = status['modules'][mname] - f = status['pid_files'].get(mname, None) - p = status['processes'].get(mname, None) + stat = status['modules'][mname] + pidf = status['pid_files'].get(mname, None) + proc = status['processes'].get(mname, None) - if s[0] == STATUS_RUNNING_OK: - self._module_signal(self.modules[mname], f, p, signal.SIGHUP) + if stat[0] == STATUS_RUNNING_OK: + self._module_signal(self.modules[mname], pidf, proc, signal.SIGHUP) else: self.logger.error("Module '{}': Unable to send signal".format(mname)) @@ -632,17 +647,17 @@ class MentatControllerScript(pyzenkit.zenscript.ZenScript): # status (return rc according to the overall status) # +# +# Execute the script. +# if __name__ == "__main__": - """ - Execute the script. - """ - script = MentatControllerScript( + SCRIPT = MentatControllerScript( description = 'mentat-controller.py - Mentat system control script', # - # Configure required deamon paths + # Configure required script paths. # path_bin = '/usr/local/bin', path_cfg = '/etc/mentat', @@ -651,8 +666,8 @@ if __name__ == "__main__": path_tmp = '/tmp', # - # Override default configurations + # Override default configurations. # default_config_dir = '/etc/mentat/core', ) - script.run() + SCRIPT.run() diff --git a/bin/mentat-dbmngr.py b/bin/mentat-dbmngr.py index 45872e738..de71930f4 100755 --- a/bin/mentat-dbmngr.py +++ b/bin/mentat-dbmngr.py @@ -7,15 +7,17 @@ # Use of this source is governed by the MIT license, see LICENSE file. #------------------------------------------------------------------------------- + +""" +Script providing Mentat system database management functions and features. +""" + + import os import re import json import time -import math -import glob -import logging import pprint - import smtplib from email.mime.text import MIMEText @@ -27,6 +29,7 @@ import pyzenkit.zenscript import mentat.const import mentat.storage + class MentatDbmngrScript(pyzenkit.zenscript.ZenScript): """ Script providing Mentat system database management functions and features. @@ -49,18 +52,18 @@ class MentatDbmngrScript(pyzenkit.zenscript.ZenScript): This command will determine the current status of all databases and its collections. """ - db_config = self.c(mentat.const.CKEY_CORE_DATABASE) - db_schema = db_config[mentat.const.CKEY_CORE_DATABASE_SCHEMA] - s = mentat.storage.Storage() + db_config = self.c(mentat.const.CKEY_CORE_DATABASE) + db_schema = db_config[mentat.const.CKEY_CORE_DATABASE_SCHEMA] + db_storage = mentat.storage.Storage() - for db_n in s.database_names(): + for db_n in db_storage.database_names(): if db_n in ('admin','local'): continue self.logger.info("Inspecting database '{}'".format(db_n)) if not db_n in db_schema: self.logger.warning("Database '{}' exists, but is not configured".format(db_n)) else: - db_h = s.database(db_n) + db_h = db_storage.database(db_n) db_c = db_schema[db_n] for col_n in db_h.collection_names(): if not col_n in db_c['collections']: @@ -77,7 +80,7 @@ class MentatDbmngrScript(pyzenkit.zenscript.ZenScript): if not idx_n in index_list_current: self.logger.info("Index '{}.{}':'{}' is missing".format(db_n, col_n, idx_n)) - s.close() + db_storage.close() return self.RESULT_SUCCESS def cbk_command_init(self): @@ -86,13 +89,13 @@ class MentatDbmngrScript(pyzenkit.zenscript.ZenScript): This command will perform all necessary database initializations. """ - db_config = self.c(mentat.const.CKEY_CORE_DATABASE) - db_schema = db_config[mentat.const.CKEY_CORE_DATABASE_SCHEMA] - s = mentat.storage.Storage() + db_config = self.c(mentat.const.CKEY_CORE_DATABASE) + db_schema = db_config[mentat.const.CKEY_CORE_DATABASE_SCHEMA] + db_storage = mentat.storage.Storage() for db_n in sorted(db_schema.keys()): db_c = db_schema[db_n] - db_h = s.database(db_n) + db_h = db_storage.database(db_n) self.logger.info("Initializing database '{}'".format(db_n)) for col_n in sorted(db_c['collections'].keys()): @@ -108,7 +111,7 @@ class MentatDbmngrScript(pyzenkit.zenscript.ZenScript): else: self.logger.info("Index '{}.{}':'{}' already exists".format(db_n, col_n, idx['name'])) - s.close() + db_storage.close() return self.RESULT_SUCCESS def cbk_command_watchdog(self): @@ -117,10 +120,12 @@ class MentatDbmngrScript(pyzenkit.zenscript.ZenScript): This command will attempt to profile various collection data. """ - dbconfig = self.c(mentat.const.CKEY_CORE_DATABASE) - storage = mentat.storage.Storage() - database = storage.database('mentat') - collection = database.collection('alerts') + db_config = self.c(mentat.const.CKEY_CORE_DATABASE) + db_cfg = db_config[mentat.const.CKEY_CORE_DATABASE_CONFIG] + db_storage = mentat.storage.Storage() + + database = db_storage.database(db_cfg['db']) + collection = database.collection(db_cfg['col_alerts']) delta = 60 * 60 * 6 last_alert = None @@ -133,7 +138,7 @@ class MentatDbmngrScript(pyzenkit.zenscript.ZenScript): if storage_time: age = time.time() - storage_time if age > delta: - self._email_alert_age(age) + self._email_alert(age) return self.RESULT_SUCCESS @@ -143,10 +148,12 @@ class MentatDbmngrScript(pyzenkit.zenscript.ZenScript): This command will attempt to profile various collection data. """ - dbconfig = self.c(mentat.const.CKEY_CORE_DATABASE) - storage = mentat.storage.Storage() - database = storage.database('mentat') - collection = database.collection('alerts') + db_config = self.c(mentat.const.CKEY_CORE_DATABASE) + db_cfg = db_config[mentat.const.CKEY_CORE_DATABASE_CONFIG] + db_storage = mentat.storage.Storage() + + database = db_storage.database(db_cfg['db']) + collection = database.collection(db_cfg['col_alerts']) profile_dir = '/tmp/mentat-db-profile' if not os.path.exists(profile_dir): @@ -157,8 +164,8 @@ class MentatDbmngrScript(pyzenkit.zenscript.ZenScript): cursor = collection.find().sort("_CESNET.StorageTime", -1) tcount = cursor.count() try: - ptrn_ip = re.compile('(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?:\/\d{1,2}|(?:-|..)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?|[:a-fA-F0-9]+:[:a-fA-F0-9]*(?:\/\d{1,3}|(?:-|..)[:a-fA-F0-9]+:[:a-fA-F0-9]*)?)') - ptrn_sc = re.compile('[ /.]+') + ptrn_ip = re.compile(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?:\/\d{1,2}|(?:-|..)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?|[:a-fA-F0-9]+:[:a-fA-F0-9]*(?:\/\d{1,3}|(?:-|..)[:a-fA-F0-9]+:[:a-fA-F0-9]*)?)') + ptrn_sc = re.compile(r'[ /.]+') print("Found total of '{}' events".format(tcount)) for event in cursor: counter += 1 @@ -172,25 +179,25 @@ class MentatDbmngrScript(pyzenkit.zenscript.ZenScript): else: key += '*' if 'Node' in event: - n = event['Node'][-1] - #if 'Name' in n: - # key += '_+++_' + n.get('Name', '???') + node = event['Node'][-1] + #if 'Name' in node: + # key += '_+++_' + node.get('Name', '???') #else: # key += '_+++_*_' - if 'Type' in n: - key += '_+++_' + ':'.join(sorted(n.get('Type', ['???']))) + if 'Type' in node: + key += '_+++_' + ':'.join(sorted(node.get('Type', ['???']))) else: key += '_+++_*_' else: key += '_+++_*_' if 'Source' in event: - s = set() + vset = set() for src in event['Source']: if 'Type' in src: - for x in src.get('Type', ['???']): - s.add(x) - if len(s): - key += '_+++_' + ':'.join(s) + for vtype in src.get('Type', ['???']): + vset.add(vtype) + if len(vset): + key += '_+++_' + ':'.join(vset) else: key += '_+++_*_' else: @@ -203,12 +210,12 @@ class MentatDbmngrScript(pyzenkit.zenscript.ZenScript): else: key += '_+++_*' event_map[key] = event_map.get(key, 0) + 1 - if (event_map[key] == 1): - fn = os.path.join(profile_dir, "{}.idea".format(key)) - fh = open(fn, 'w') + if event_map[key] == 1: + fln = os.path.join(profile_dir, "{}.idea".format(key)) + flh = open(fln, 'w') msgraw = event.get('msg_raw2', event.get('msg_raw')) - json.dump(json.loads(msgraw), fh, sort_keys=True, indent=4) - fh.close() + json.dump(json.loads(msgraw), flh, sort_keys=True, indent=4) + flh.close() except Exception as exc: print("Exception: {}".format(exc)) @@ -222,26 +229,28 @@ class MentatDbmngrScript(pyzenkit.zenscript.ZenScript): #--------------------------------------------------------------------------- def _email_alert(self, age): - msg = MIMEText("Attention: Last alert in database is older than 6 hours !!!") + msg = MIMEText("Attention: Last alert in database is older than 6 hours (age: {} seconds) !!!".format(age)) msg['Subject'] = 'Mentat watchdog alert' msg['From'] = self.c('mail_from') msg['To'] = self.c('mail_to') - s = smtplib.SMTP('localhost') - s.send_message(msg) - s.quit() + smtp = smtplib.SMTP('localhost') + smtp.send_message(msg) + smtp.quit() + +# +# Execute the script. +# if __name__ == "__main__": - """ - Execute the MentatDbmngrScript script. - """ - script = MentatDbmngrScript( + + SCRIPT = MentatDbmngrScript( description = 'mentat-dbmngr.py - Mentat system database management script', # - # Configure required daemon paths + # Configure required script paths. # path_bin = '/usr/local/bin', path_cfg = '/etc/mentat', @@ -250,8 +259,8 @@ if __name__ == "__main__": path_tmp = '/tmp', # - # Override default configurations + # Override default configurations. # default_config_dir = '/etc/mentat/core', ) - script.run() + SCRIPT.run() diff --git a/bin/mentat-ideagen.py b/bin/mentat-ideagen.py index bd0f56ca2..28473085f 100755 --- a/bin/mentat-ideagen.py +++ b/bin/mentat-ideagen.py @@ -7,31 +7,22 @@ # Use of this source is governed by the MIT license, see LICENSE file. #------------------------------------------------------------------------------- +""" +Script for generating IDEA message, mainly fo testing and development purposes. +""" + import os -import re -import sys import time -import math -import glob -import json -import logging import string import random from jinja2 import Environment, PackageLoader -# Generate the path to custom 'lib' directory -lib = os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib')) -sys.path.insert(0, lib) -lib = os.path.abspath(os.path.join(os.path.dirname(__file__), '../bin')) -sys.path.insert(0, lib) - # # Custom libraries # -import pydgets.widgets import pyzenkit.zenscript -list_ips_source = [ +LIST_IPS_SOURCE = [ '192.168.0.1', '10.0.0.1', '192.168.0.2', @@ -41,10 +32,15 @@ list_ips_source = [ '192.168.0.4', '10.0.0.4', '192.168.0.5', - '10.0.0.5' + '10.0.0.5', + '195.113.144.234', + '195.113.144.194', + '195.113.144.201', + '195.113.144.230', + '78.128.211.97' ] -list_ips_target = [ +LIST_IPS_TARGET = [ '192.168.5.1', '10.10.0.1', '192.168.5.2', @@ -54,10 +50,15 @@ list_ips_target = [ '192.168.5.4', '10.10.0.4', '192.168.5.5', - '10.10.0.5' + '10.10.0.5', + '195.113.144.234', + '195.113.144.194', + '195.113.144.201', + '195.113.144.230', + '78.128.211.97' ] -list_node_sws = [ +LIST_NODE_SWS = [ 'Kippo', 'Dionaea', 'LaBrea', @@ -66,7 +67,7 @@ list_node_sws = [ 'FlowMon' ] -list_node_names = [ +LIST_NODE_NAMES = [ 'cz.cesnet.lister', 'cz.cesnet.rimmer', 'cz.cesnet.kryten' @@ -75,7 +76,7 @@ list_node_names = [ 'cz.cesnet.queeg' ] -list_categories = [ +LIST_CATEGORIES = [ 'Recon.Scanning', 'Attempt.Exploit', 'Abusive.Spam', @@ -86,8 +87,9 @@ list_categories = [ class MentatIdeagenScript(pyzenkit.zenscript.ZenScript): """ - Script providing Mentat system backup functions and features + Script for generating IDEA message, mainly fo testing and development purposes. """ + CONFIG_COUNT = 'count' CONFIG_BACKOFF = 'backoff' CONFIG_STEADY = 'steady' @@ -130,8 +132,8 @@ class MentatIdeagenScript(pyzenkit.zenscript.ZenScript): (self.CONFIG_STEADY, False), (self.CONFIG_QUEUE_DIR, '/var/tmp'), ) - for c in cfgs: - config[c[0]] = kwargs.get('default_' + c[0], c[1]) + for cfg in cfgs: + config[cfg[0]] = kwargs.get('default_' + cfg[0], cfg[1]) return config def get_default_command(self): @@ -140,7 +142,8 @@ class MentatIdeagenScript(pyzenkit.zenscript.ZenScript): """ return 'generate' - def _generate_id(self, size=6, chars=string.ascii_lowercase + string.digits): + @staticmethod + def _generate_id(size=6, chars=string.ascii_lowercase + string.digits): return '{}{}'.format('testmsg-', ''.join(random.choice(chars) for _ in range(size))) def _generate_variables(self): @@ -150,11 +153,11 @@ class MentatIdeagenScript(pyzenkit.zenscript.ZenScript): variables = {} variables['message_id'] = self._generate_id(20) variables['detect_time'] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) - variables['category'] = random.choice(list_categories) - variables['source_ip'] = random.choice(list_ips_source) - variables['target_ip'] = random.choice(list_ips_target) - variables['node_sw'] = random.choice(list_node_sws) - variables['node_name'] = random.choice(list_node_names) + variables['category'] = random.choice(LIST_CATEGORIES) + variables['source_ip'] = random.choice(LIST_IPS_SOURCE) + variables['target_ip'] = random.choice(LIST_IPS_TARGET) + variables['node_sw'] = random.choice(LIST_NODE_SWS) + variables['node_name'] = random.choice(LIST_NODE_NAMES) return variables def _save_message(self, variables, msg): @@ -178,15 +181,13 @@ class MentatIdeagenScript(pyzenkit.zenscript.ZenScript): folder caches. """ template = self.templ_env.get_template('msg.01.idea.j2') - msg_counter = 0 try: while True: - for i in range(self.c(self.CONFIG_COUNT)): - msg_counter = msg_counter + 1 + for idx in range(1, self.c(self.CONFIG_COUNT) + 1): variables = self._generate_variables() idea_message = template.render(**variables) ifn = self._save_message(variables, idea_message) - self.p("[{:0,d}] Generated new IDEA message into file '{}'".format(msg_counter,ifn)) + self.p("[{:0,d}] Generated new IDEA message into file '{}'".format(idx, ifn)) if not self.c('steady'): break @@ -197,17 +198,28 @@ class MentatIdeagenScript(pyzenkit.zenscript.ZenScript): except KeyboardInterrupt: pass + +# +# Execute the script. +# if __name__ == '__main__': - """ - Execute the MentatCleanupScript script. - """ - script = MentatIdeagenScript( + + SCRIPT = MentatIdeagenScript( + + description = 'mentat-dbmngr.py - Mentat system database management script', + + # + # Configure required script paths. + # path_bin = '/usr/local/bin', path_cfg = '/etc/mentat', path_log = '/var/mentat/log', path_run = '/var/mentat/run', path_tmp = '/tmp', + # + # Override default configurations. + # default_config_dir = '/etc/mentat/core', ) - script.run() + SCRIPT.run() diff --git a/bin/mentat-inspector.py b/bin/mentat-inspector.py index a90dc644b..d114ae46f 100755 --- a/bin/mentat-inspector.py +++ b/bin/mentat-inspector.py @@ -19,23 +19,10 @@ __author__ = "Jan Mach <jan.mach@cesnet.cz>" __credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>" -# -# Python system libraries -# -import os -import sys - - -# Generate the path to custom 'lib' directory -lib = os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib')) -sys.path.insert(0, lib) - - # # Custom libraries # -import pyzenkit.baseapp -import pyzenkit.zendaemon +import mentat.const import mentat.daemon.piper import mentat.daemon.component.parser import mentat.daemon.component.inspector @@ -44,20 +31,25 @@ import mentat.daemon.component.commiter class MentatInspectorDaemon(mentat.daemon.piper.PiperDaemon): + """ + Implementation of real-time message processing daemon capable of inspecting + `IDEA <https://idea.cesnet.cz/en/index>`__ messaes according to given + set of filtering rules and performing number of associated actions. + """ pass +# +# Execute the daemon. +# if __name__ == "__main__": - """ - Execute the daemon. - """ - daemon = MentatInspectorDaemon( + DAEMON = MentatInspectorDaemon( - description = 'mentat-inspector - IDEA message inspection daemon', + description = 'mentat-inspector.py - IDEA message inspection daemon', # - # Configure required deamon paths + # Configure required deamon paths. # path_bin = '/usr/local/bin', path_cfg = '/etc/mentat', @@ -66,23 +58,25 @@ if __name__ == "__main__": path_tmp = '/var/tmp', # - # Override default configurations + # Override default configurations. # default_config_dir = '/etc/mentat/core', default_queue_in_dir = '/var/mentat/spool/mentat-inspector.py', default_queue_out_dir = '/var/mentat/spool/__done_a__', - default_stats_interval = 20, + default_stats_interval = mentat.const.DFLT_INTERVAL_STATISTICS, # - # Schedule initial events + # Schedule initial events. # - schedule = [('start',)], + schedule = [ + (mentat.const.DFLT_EVENT_START,) + ], schedule_after = [ - (20, 'log_statistics') + (mentat.const.DFLT_INTERVAL_STATISTICS, mentat.const.DFLT_EVENT_LOG_STATISTICS) ], # - # Define required daemon components + # Define required daemon components. # components = [ mentat.daemon.component.parser.ParserDaemonComponent(), @@ -91,4 +85,4 @@ if __name__ == "__main__": mentat.daemon.component.commiter.CommiterDaemonComponent() ] ) - daemon.run() + DAEMON.run() diff --git a/bin/mentat-netmngr.py b/bin/mentat-netmngr.py index f3247cde7..8e53023fd 100755 --- a/bin/mentat-netmngr.py +++ b/bin/mentat-netmngr.py @@ -7,29 +7,25 @@ # Use of this source is governed by the MIT license, see LICENSE file. #------------------------------------------------------------------------------- + """ Script providing functions for abuse group network management for Mentat system database. Still work in progress, use with caution. """ + __version__ = "0.1" __author__ = "Jan Mach <jan.mach@cesnet.cz>" __credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>" -import os -import re -import json -import time -import math -import glob -import logging + import collections import pprint + # # Custom libraries # -import iprange import pyzenkit.jsonconf import pyzenkit.zenscript import mentat.const @@ -48,6 +44,7 @@ CONFIG_WHOIS_FILE = 'whois_file' WHOIS_KEY_TYPE = '__whois_type__' WHOIS_KEY_ABOUT_GENERIC = '__whois_about__' WHOIS_KEY_ABOUT_NEGISTRY = '__negistry_about__' + WHOIS_TYPE_GENERIC = 'whois' WHOIS_TYPE_NEGISTRY = 'negistry' @@ -88,8 +85,8 @@ class MentatNetmngrScript(pyzenkit.zenscript.ZenScript): (CONFIG_DB_ARGS, None), (CONFIG_WHOIS_FILE, None) ) - for c in cfgs: - config[c[0]] = kwargs.get('default_' + c[0], c[1]) + for cfg in cfgs: + config[cfg[0]] = kwargs.get('default_' + cfg[0], cfg[1]) return config def _configure_postprocess(self): @@ -105,13 +102,12 @@ class MentatNetmngrScript(pyzenkit.zenscript.ZenScript): # Configure undefined database settings from core settings. for cfg in ( - (CONFIG_DATABASE, db_config['db']), - (CONFIG_COLLECTION, db_config['col_groups']), - (CONFIG_DB_HOST, db_connection['host']), - (CONFIG_DB_PORT, db_connection['port']), - (CONFIG_DB_TIMEOUT, db_connection['timeout']), - (CONFIG_DB_ARGS, db_connection['args']) - ): + (CONFIG_DATABASE, db_config['db']), + (CONFIG_COLLECTION, db_config['col_groups']), + (CONFIG_DB_HOST, db_connection['host']), + (CONFIG_DB_PORT, db_connection['port']), + (CONFIG_DB_TIMEOUT, db_connection['timeout']), + (CONFIG_DB_ARGS, db_connection['args'])): if self.config[cfg[0]] is None: self.config[cfg[0]] = cfg[1] @@ -176,18 +172,19 @@ class MentatNetmngrScript(pyzenkit.zenscript.ZenScript): self.logger.info("Loaded reference whois file '{} :: {}' ({})".format(whois_file, whois_file_type, whois_file_about)) return (whois_file_type, whois_file_data) - def _process_whois_data(self, whois_file_data, whois_file_type): + @staticmethod + def _process_whois_data(whois_file_data, whois_file_type): """ Process reference whois file. """ # Load whois data into dictionary of dictionaries for easy searching and # comparisons. processed_data = collections.defaultdict(dict) - for network_key, network_data in whois_file_data.items(): - nr = mentat.whois.NetworkRecord(network_data, source = whois_file_type) - nrkey = str(nr) + for network_data in whois_file_data.values(): + nwr = mentat.whois.NetworkRecord(network_data, source = whois_file_type) + nwrkey = str(nwr) for abuse_group in network_data['resolved_abuses']: - processed_data[abuse_group][nrkey] = nr + processed_data[abuse_group][nwrkey] = nwr return processed_data #--------------------------------------------------------------------------- @@ -199,25 +196,25 @@ class MentatNetmngrScript(pyzenkit.zenscript.ZenScript): for group_name in sorted(wi_file_data.keys()): if not group_name in abuse_group_dict: self.logger.warning("'{}' Inserting missing abuse group defined in loaded '{}' whois file".format(group_name, wi_file_type)) - ag = mentat.whois.AbuseGroup({ - '_id': group_name, - 'networks': wi_file_data[group_name].values() - }) - pprint.pprint(ag) - pprint.pprint(ag['networks'][0]['_id']) - self.collection.insert_one(ag.export()) + abg = mentat.whois.AbuseGroup({ + '_id': group_name, + 'networks': wi_file_data[group_name].values() + }) + pprint.pprint(abg) + pprint.pprint(abg['networks'][0]['_id']) + self.collection.insert_one(abg.export()) def _groups_remove_extra(self, abuse_group_dict, wi_file_data, wi_file_type): """ Remove extra abuse groups from database. """ for group_name in sorted(abuse_group_dict.keys()): - ag = abuse_group_dict[group_name] - if ag['source'] == wi_file_type and not group_name in wi_file_data: + abg = abuse_group_dict[group_name] + if abg['source'] == wi_file_type and not group_name in wi_file_data: self.logger.warning("'{}' Consider deletion of extra abuse group missing in loaded '{}' whois file".format(group_name, wi_file_type)) # Do not delete yet. - #self.collection.delete_one({'_id': ag['_id']}) + #self.collection.delete_one({'_id': abg['_id']}) def _groups_update_existing(self, abuse_group_dict, wi_file_data, wi_file_type): """ @@ -226,14 +223,14 @@ class MentatNetmngrScript(pyzenkit.zenscript.ZenScript): for group_name in sorted(abuse_group_dict.keys()): if group_name in wi_file_data: self.logger.info("'{}' Updating abuse group according to loaded '{}' whois file".format(group_name, wi_file_type)) - ag = abuse_group_dict[group_name] - changelog = ag.update_networks(wi_file_data[group_name].values(), wi_file_type) + abg = abuse_group_dict[group_name] + changelog = abg.update_networks(wi_file_data[group_name].values(), wi_file_type) if changelog: - for ch in changelog: - self.logger.warning("'{}' Abuse group network change according to loaded '{}' whois file: {}".format(group_name, wi_file_type, ch)) - pprint.pprint(ag) - pprint.pprint(ag['_id']) - self.collection.update_one({'_id': ag['_id']}, {'$set': {'networks': ag.export()['networks']}}) + for chl in changelog: + self.logger.warning("'{}' Abuse group network change according to loaded '{}' whois file: {}".format(group_name, wi_file_type, chl)) + pprint.pprint(abg) + pprint.pprint(abg['_id']) + self.collection.update_one({'_id': abg['_id']}, {'$set': {'networks': abg.export()['networks']}}) #--------------------------------------------------------------------------- # OPERATION CALLBACK IMPLEMENTATIONS @@ -273,7 +270,8 @@ class MentatNetmngrScript(pyzenkit.zenscript.ZenScript): # Check 01: The abuse group should have any related networks defined, # otherwise it might be useless (with exception of '__UNKNOWN__' group). # - if not 'networks' in abuse_group or not len(abuse_group['networks']): + network_count = len(abuse_group['networks']) + if not 'networks' in abuse_group or not network_count > 0: self.logger.warning("'{}::{}' Abuse group does not have any networks defined, consider deletion".format(group_source, group_name)) continue @@ -292,9 +290,9 @@ class MentatNetmngrScript(pyzenkit.zenscript.ZenScript): # for network in abuse_group['networks']: try: - nr = mentat.whois.NetworkRecord(network) - nrkey = str(nr) - self.logger.debug("'{}::{}' Network record '{}::{}'".format(group_source, group_name, nr['source'], nrkey)) + nwr = mentat.whois.NetworkRecord(network) + nwrkey = str(nwr) + self.logger.debug("'{}::{}' Network record '{}::{}'".format(group_source, group_name, nwr['source'], nwrkey)) if wi_file_data and group_name in wi_file_data: @@ -302,23 +300,23 @@ class MentatNetmngrScript(pyzenkit.zenscript.ZenScript): # Point out network records marked as coming from given # source and not present in that particular source. # - if nr['source'] == wi_file_type and not nrkey in wi_file_data[group_name]: - self.logger.warning("'{}::{}' Network record '{}::{}' is not defined in loaded '{}' whois file, consider deletion".format(group_source, group_name, nr['source'], nrkey, wi_file_type)) - elif nr['source'] == wi_file_type: - del wi_file_data[group_name][nrkey] + if nwr['source'] == wi_file_type and not nwrkey in wi_file_data[group_name]: + self.logger.warning("'{}::{}' Network record '{}::{}' is not defined in loaded '{}' whois file, consider deletion".format(group_source, group_name, nwr['source'], nwrkey, wi_file_type)) + elif nwr['source'] == wi_file_type: + del wi_file_data[group_name][nwrkey] else: - self.logger.info("'{}::{}' Extra network record '{}::{}'".format(group_source, group_name, nr['source'], nrkey)) + self.logger.info("'{}::{}' Extra network record '{}::{}'".format(group_source, group_name, nwr['source'], nwrkey)) - except ValueError as e: - self.logger.warning("'{}::{}' Invalid network record {} - '{}'".format(group_source, group_name, pprint.pformat(network), str(e))) + except ValueError as exc: + self.logger.warning("'{}::{}' Invalid network record {} - '{}'".format(group_source, group_name, pprint.pformat(network), str(exc))) if wi_file_data: for group_name in wi_file_data.keys(): if not group_name in abuse_group_dict: self.logger.warning("'{}' Abuse group defined in loaded '{}' whois file but missing in database".format(group_name, wi_file_type)) - for nrkey in wi_file_data[group_name].keys(): - self.logger.warning("'{}' Missing network record '{}::{}'".format(group_name, wi_file_data[group_name][nrkey]['source'], nrkey)) + for nwrkey in wi_file_data[group_name].keys(): + self.logger.warning("'{}' Missing network record '{}::{}'".format(group_name, wi_file_data[group_name][nwrkey]['source'], nwrkey)) return self.RESULT_SUCCESS @@ -344,16 +342,18 @@ class MentatNetmngrScript(pyzenkit.zenscript.ZenScript): self._groups_remove_extra(abuse_group_dict, wi_file_data, wi_file_type) self._groups_update_existing(abuse_group_dict, wi_file_data, wi_file_type) + +# +# Execute the script. +# if __name__ == "__main__": - """ - Execute the MentatNetmngrScript script. - """ - script = MentatNetmngrScript( + + SCRIPT = MentatNetmngrScript( description = 'mentat-netmngr.py - Abuse group network management script for Mentat system database', # - # Configure required daemon paths + # Configure required script paths. # path_bin = '/usr/local/bin', path_cfg = '/etc/mentat', @@ -362,8 +362,8 @@ if __name__ == "__main__": path_tmp = '/tmp', # - # Override default configurations + # Override default configurations. # default_config_dir = '/etc/mentat/core', ) - script.run() + SCRIPT.run() diff --git a/bin/mentat-sampler.py b/bin/mentat-sampler.py index 829d692a5..4132c02d7 100755 --- a/bin/mentat-sampler.py +++ b/bin/mentat-sampler.py @@ -7,30 +7,31 @@ # Use of this source is governed by the MIT license, see LICENSE file. #------------------------------------------------------------------------------- -import pprint -import os -import sys +""" +Real-time message processing daemon capable of sampling `IDEA <https://idea.cesnet.cz/en/index>`__ +messages according to various policies. +""" + + +__version__ = "0.1" +__author__ = "Jan Mach <jan.mach@cesnet.cz>" +__credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>" -# Generate the path to custom 'lib' directory -lib = os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib')) -sys.path.insert(0, lib) -lib = os.path.abspath(os.path.join(os.path.dirname(__file__), '../bin')) -sys.path.insert(0, lib) # # Custom libraries # -import pyzenkit.baseapp -import pyzenkit.zendaemon import mentat.daemon.piper import mentat.daemon.component.parser import mentat.daemon.component.sampler import mentat.daemon.component.commiter + class MentatSamplerDaemon(mentat.daemon.piper.PiperDaemon): """ - Daemon for message inspections. + Implementation of real-time message processing daemon capable of sampling + `IDEA <https://idea.cesnet.cz/en/index>`__ messages according to various policies. """ CONFIG_SAMPLING_KEYS = 'sampling_keys' @@ -58,36 +59,54 @@ class MentatSamplerDaemon(mentat.daemon.piper.PiperDaemon): (self.CONFIG_SAMPLING_LIMIT, 10), (self.CONFIG_SAMPLING_POLICY, 'simple'), ) - for c in cfgs: - config[c[0]] = kwargs.pop('default_' + c[0], c[1]) + for cfg in cfgs: + config[cfg[0]] = kwargs.pop('default_' + cfg[0], cfg[1]) return config + +# +# Execute the daemon. +# if __name__ == "__main__": - """ - Execute the daemon. - """ - # Perform test run. - daemon = MentatSamplerDaemon( + + DAEMON = MentatSamplerDaemon( + + description = 'mentat-sampler.py - IDEA message sampling daemon', + + # + # Configure required deamon paths. + # path_bin = '/usr/local/bin', path_cfg = '/etc/mentat', path_log = '/var/mentat/log', path_run = '/var/mentat/run', path_tmp = '/tmp', + # + # Override default configurations. + # default_config_dir = '/etc/mentat/core', default_queue_in_dir = '/var/mentat/spool/mentat-sampler.py', - default_queue_out_dir = '/var/mentat/spool/mentat-sampler.py/tmp', - default_stats_interval = 20, + default_queue_out_dir = None, + default_stats_interval = mentat.const.DFLT_INTERVAL_STATISTICS, - description = 'mentat-sampler - Message sampling daemon', - schedule = [('start',)], + # + # Schedule initial events. + # + schedule = [ + (mentat.const.DFLT_EVENT_START,) + ], schedule_after = [ - (10, 'log_statistics') + (mentat.const.DFLT_INTERVAL_STATISTICS, mentat.const.DFLT_EVENT_LOG_STATISTICS) ], + + # + # Define required daemon components. + # components = [ mentat.daemon.component.parser.ParserDaemonComponent(), mentat.daemon.component.sampler.SamplerDaemonComponent(), mentat.daemon.component.commiter.CommiterDaemonComponent() ] ) - daemon.run() + DAEMON.run() diff --git a/bin/mentat-storage.py b/bin/mentat-storage.py index 9b43963f2..b021f41ae 100755 --- a/bin/mentat-storage.py +++ b/bin/mentat-storage.py @@ -65,24 +65,9 @@ __author__ = "Jan Mach <jan.mach@cesnet.cz>" __credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>" -# -# Python system libraries. -# -import os -import sys - - -# Generate the path to custom 'lib' directory. This is usefull when launching -# modules from development directory tree. -lib = os.path.abspath(os.path.join(os.path.dirname(__file__), '../lib')) -sys.path.insert(0, lib) - - # # Custom libraries. # -import pyzenkit.baseapp -import pyzenkit.zendaemon import mentat.const import mentat.daemon.piper import mentat.daemon.component.parser @@ -91,6 +76,11 @@ import mentat.daemon.component.commiter class MentatStorageDaemon(mentat.daemon.piper.PiperDaemon): + """ + Implementation of real-time message processing daemon capable of storing + `IDEA <https://idea.cesnet.cz/en/index>`__ messages into persistent storage. + Currently only `MongoDB <https://www.mongodb.com/>`__ NoSQL database is supported. + """ def _init_argparser(self, **kwargs): """ @@ -120,8 +110,8 @@ class MentatStorageDaemon(mentat.daemon.piper.PiperDaemon): (mentat.daemon.component.storage.CONFIG_DB_TIMEOUT, None), (mentat.daemon.component.storage.CONFIG_DB_ARGS, None) ) - for c in cfgs: - config[c[0]] = kwargs.get('default_' + c[0], c[1]) + for cfg in cfgs: + config[cfg[0]] = kwargs.get('default_' + cfg[0], cfg[1]) return config def _configure_postprocess(self): @@ -137,27 +127,27 @@ class MentatStorageDaemon(mentat.daemon.piper.PiperDaemon): # Configure undefined database settings from core settings. for cfg in ( - (mentat.daemon.component.storage.CONFIG_DATABASE, db_config['db']), - (mentat.daemon.component.storage.CONFIG_COLLECTION, db_config['col_alerts']), - (mentat.daemon.component.storage.CONFIG_DB_HOST, db_connection['host']), - (mentat.daemon.component.storage.CONFIG_DB_PORT, db_connection['port']), - (mentat.daemon.component.storage.CONFIG_DB_TIMEOUT, db_connection['timeout']), - (mentat.daemon.component.storage.CONFIG_DB_ARGS, db_connection['args']) - ): + (mentat.daemon.component.storage.CONFIG_DATABASE, db_config['db']), + (mentat.daemon.component.storage.CONFIG_COLLECTION, db_config['col_alerts']), + (mentat.daemon.component.storage.CONFIG_DB_HOST, db_connection['host']), + (mentat.daemon.component.storage.CONFIG_DB_PORT, db_connection['port']), + (mentat.daemon.component.storage.CONFIG_DB_TIMEOUT, db_connection['timeout']), + (mentat.daemon.component.storage.CONFIG_DB_ARGS, db_connection['args'])): if self.config[cfg[0]] is None: self.config[cfg[0]] = cfg[1] + +# +# Execute the daemon. +# if __name__ == "__main__": - """ - Execute the daemon. - """ - daemon = MentatStorageDaemon( + DAEMON = MentatStorageDaemon( - description = 'mentat-storage - IDEA message storing daemon', + description = 'mentat-storage.py - IDEA message storing daemon', # - # Configure required deamon paths + # Configure required deamon paths. # path_bin = '/usr/local/bin', path_cfg = '/etc/mentat', @@ -166,7 +156,7 @@ if __name__ == "__main__": path_tmp = '/var/tmp', # - # Override default configurations + # Override default configurations. # default_config_dir = '/etc/mentat/core', default_queue_in_dir = '/var/mentat/spool/mentat-storage.py', @@ -174,17 +164,17 @@ if __name__ == "__main__": default_stats_interval = mentat.const.DFLT_INTERVAL_STATISTICS, # - # Schedule initial events + # Schedule initial events. # schedule = [ (mentat.const.DFLT_EVENT_START,) ], schedule_after = [ - (mentat.const.DFLT_INTERVAL_STATISTICS, 'log_statistics') + (mentat.const.DFLT_INTERVAL_STATISTICS, mentat.const.DFLT_EVENT_LOG_STATISTICS) ], # - # Define required daemon components + # Define required daemon components. # components = [ mentat.daemon.component.parser.ParserDaemonComponent(), @@ -192,4 +182,4 @@ if __name__ == "__main__": mentat.daemon.component.commiter.CommiterDaemonComponent() ] ) - daemon.run() + DAEMON.run() -- GitLab