Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring for testing #18

Open
wants to merge 41 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
3defc90
Preparation for entry-points.
exhuma Jan 15, 2017
5070576
CRON subcommand added.
exhuma Jan 20, 2017
23ec84a
Added "crontab" to the dependencides.
exhuma Jan 20, 2017
f9573e3
Moved vendor package into munininfluxdb
exhuma Jan 20, 2017
2a39260
Removed bash script, updated docs.
exhuma Jan 20, 2017
3f18fc2
WIP: separate CLI command to dump the RRD files.
exhuma Mar 15, 2017
e3a0a22
vendor package no longer required.
exhuma Mar 15, 2017
73e920a
Upgraded to latest storable version (Py3)
exhuma Mar 15, 2017
acea451
Added egg-info to gitignore
exhuma Mar 16, 2017
85fe31a
Moved "cron" code into an external adapter & tests.
exhuma Mar 20, 2017
eb24d43
Extracted logic for parsing datafile lines.
exhuma Mar 21, 2017
584baad
Refactored datafile parsing for testing.
exhuma Mar 22, 2017
99d69c1
Added some simple tests.
exhuma Mar 22, 2017
86844d1
Added .cache to gitignore
exhuma Mar 23, 2017
1b96c71
Further refactoring of settings generation.
exhuma Mar 23, 2017
68c93c5
Added test for RRD and XML filename generation
exhuma Mar 23, 2017
bc2e102
Addded two placeholder tests.
exhuma Mar 27, 2017
aae4511
Tests for Grafana panels and queries & bugfixes
exhuma Mar 29, 2017
88cc02b
Moved "ask_password" from influxdbclient to utils.
exhuma Apr 3, 2017
6fd6253
Centralized "mock" import (py2/py3)
exhuma Apr 4, 2017
f94fe40
Making it easier to mock influxdb
exhuma Apr 5, 2017
6a059d8
Replaced no-op string statement with code-comment.
exhuma Apr 5, 2017
49d7ecf
Added a bunch of tests.
exhuma Apr 11, 2017
0ffe3d8
Add logging to unhide some tracebacks.
exhuma Apr 11, 2017
7b425a4
Add TODO markers.
exhuma Apr 11, 2017
1130b47
Add test for grafana panel "to_json" method.
exhuma Apr 11, 2017
aa734ae
Additional grafana tests
exhuma Apr 11, 2017
8667971
Add tests for GrafanaApi
exhuma Apr 12, 2017
38f7788
Add setup.py extra requirements for development.
exhuma Apr 19, 2017
018a253
Omit unit-tests from coverage.
exhuma Apr 19, 2017
816a2f2
Added tests for dashboard creation.
exhuma Apr 24, 2017
83f390a
Fill in test for "Dashboard.generate_simple"
exhuma Apr 24, 2017
9f2d225
Exclude development code from test-coverage
exhuma Apr 24, 2017
7480077
Add test for munin.cleanup.
exhuma Apr 25, 2017
44a2914
Remove rfetch.py
exhuma Apr 25, 2017
a717976
Add tests for "cron" and "dump" commands.
exhuma Apr 26, 2017
7b797d3
Add unit-tests for the "fetch" command.
exhuma Apr 28, 2017
a4de0e6
Add tests for the "import" command.
exhuma Apr 30, 2017
7bce645
Prepare main module for unit-testing.
exhuma Apr 30, 2017
b904dcf
Add unit-test for main entry-point.
exhuma Apr 30, 2017
5d9855f
Remove "raise" which caused nonexecution of code
exhuma Apr 30, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[run]
omit =
munininfluxdb/test/*

7 changes: 4 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
*.pyc
*~
venv/
munininfluxdb/test*
.idea/
local/
/*.egg-info
data/
lib/
local/
venv/
/.cache
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,14 @@ Installation & Usage
3. Run ```import``` command:

```
$ sudo ./muninflux import
$ sudo muninflux import
```

4. A cron job will be automatically added after installation to refresh data from munin every 5 minutes (Munin default)
4. Install the CRON job to refresh data from munin every 5 minutes (Munin default)

```
$ sudo muninflux cron install
```

### Some more details

Expand Down
33 changes: 0 additions & 33 deletions muninflux

This file was deleted.

File renamed without changes.
110 changes: 110 additions & 0 deletions munininfluxdb/commands/cron.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from __future__ import print_function
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one of the subcommands of the main command. It is loaded in munininfluxdb/main.py. In this particular case, it contains the cron code which I extracted from the other files. This way, the cron installation and deinstallation can be run separately (as also mentioned in the readme file).

import os
import pwd
import sys

from munininfluxdb.utils import Symbol, absolute_executable


# Cron job comment is used to uninstall and must not be manually deleted from the crontab
CRON_COMMENT = 'Update InfluxDB with fresh values from Munin'
NAME = 'cron'
DESCRIPTION = 'Installs or uninstalls the CRON job'


def get_cron_user():
try:
pwd.getpwnam('munin')
except KeyError:
output = 'root'
else:
output = 'munin'
return output


def uninstall_cron(cron_adapter):
"""
Creates a function which uses *cron_adapter* to remove an entry from the
CRONtab.

See :py:mod:`munininfluxdb.external.cron` for an example of an adapter.
"""
def fun(args):
"""
Main function for the "cron uninstall" command.

:param args: The result from parsing CLI arguments.
"""
if os.geteuid() != 0:
print("It seems you are not root, please run \"muninflux cron uninstall\" again with root privileges")
sys.exit(1)

user = args.user or get_cron_user()
nb = cron_adapter.remove_by_comment(user, CRON_COMMENT)

if nb:
print("{0} Cron job uninstalled for user {1} ({2} entries deleted)".format(Symbol.OK_GREEN, user, nb))
else:
print("No matching job found (searching comment \"{1}\" in crontab for user {2})".format(Symbol.WARN_YELLOW,
CRON_COMMENT, user))
return fun


def install_cron(cron_adapter):
"""
Creates a function which uses *cron_adapter* to add an entry to the CRONtab.

See :py:mod:`munininfluxdb.external.cron` for an example of an adapter.
"""
def fun(args):
"""
Main function for the "cron install" command.

:param args: The result from parsing CLI arguments.
:return: Whether the operation was successful or not.
:rtype: bool
"""
script_path = absolute_executable()
cmd = '%s fetch' % script_path

if os.geteuid() != 0:
print("It seems you are not root, please run \"%s cron install\" again with root privileges")
sys.exit(1)

user = args.user or get_cron_user()
success = cron_adapter.add_with_comment(user, cmd, args.period, CRON_COMMENT)

print("{0} Cron job installed for user {1}".format(Symbol.OK_GREEN, user))
return success
return fun


def setup(parser, injections):
"""
Sets up CLI argument parsing.

The argument *injections* should be a dictionary containing a key 'cron'
mapping to a cron adapter. For an example cron adapter see
``munininfluxdb/external/cron.py``

:param parser: The argument parser for this subcommand.
:param injections: A dictionary containing the key ``'cron'`` mapping to an
implementation of a CRON adapter. See
:py:mod:`munininfluxdb.external.cron` for an example.
"""
parser.add_argument('-u', '--user', default='', metavar='USER',
help='The CRON user')

subparsers = parser.add_subparsers(title='CRON commands')
install_parser = subparsers.add_parser(
'install', description='Installs the CRON job')
uninstall_parser = subparsers.add_parser(
'uninstall', description='Uninstalls the CRON job')

install_parser.add_argument(
'-p', '--period', default=5, type=int,
help="sets the period in minutes between each fetch in the cron job (default: %(default)dmin)")

cron_adapter = injections['cron']
install_parser.set_defaults(func=install_cron(cron_adapter))
uninstall_parser.set_defaults(func=uninstall_cron(cron_adapter))
80 changes: 80 additions & 0 deletions munininfluxdb/commands/dump.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import logging
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one of the subcommands of the main command. It is loaded in munininfluxdb/main.py.


from munininfluxdb import munin
from munininfluxdb import rrd
from munininfluxdb.settings import Settings, Defaults
from munininfluxdb.utils import Symbol


LOG = logging.getLogger(__name__)
NAME = 'dump'
DESCRIPTION = """
The 'dump' command writes out the munin RRD files to XML. These XML files can
then be used by the 'load' command to import them into influxdb.
"""


def retrieve_munin_configuration(settings):
"""
"""
print("Exploring Munin structure")

try:
settings = munin.discover_from_datafile(settings)
except Exception as e:
LOG.debug('Traceback:', exc_info=True)
print(" {0} Could not process datafile ({1}), will read www and RRD cache instead".format(Symbol.NOK_RED, settings.paths['datafile']))

# read /var/cache/munin/www to check what's currently displayed on the dashboard
settings = munin.discover_from_www(settings)
settings = rrd.discover_from_rrd(settings, insert_missing=False)
else:
print(" {0} Found {1}: extracted {2} measurement units".format(Symbol.OK_GREEN, settings.paths['datafile'],
settings.nb_fields))

# for each host, find the /var/lib/munin/<host> directory and check if node name and plugin conf match RRD files
try:
rrd.check_rrd_files(settings)
except Exception as e:
print(" {0} {1}".format(Symbol.NOK_RED, e))
else:
print(" {0} Found {1} RRD files".format(Symbol.OK_GREEN, settings.nb_rrd_files))

return settings


def main(args):
settings = Settings(args)
settings = retrieve_munin_configuration(settings)

# export RRD files as XML for (much) easier parsing (but takes much more time)
print("\nExporting RRD databases:".format(settings.nb_rrd_files))
nb_xml = rrd.export_to_xml(settings)
print(" {0} Exported {1} RRD files to XML ({2})".format(Symbol.OK_GREEN, nb_xml, settings.paths['xml']))


def setup(parser, injections):
"""
Sets up CLI argument parsing.

The argument *injections* is currently unused in this command and is a
placeholder for the future.

:param parser: The argument parser for this subcommand.
"""
parser.add_argument('--xml-temp-path', default=Defaults.MUNIN_XML_FOLDER,
help='set path where to store result of RRD exported files (default: %(default)s)')
parser.add_argument('--keep-temp', action='store_true',
help='instruct to retain temporary files (mostly RRD\'s XML) after generation')
parser.add_argument('-v', '--verbose', type=int, default=1,
help='set verbosity level (0: quiet, 1: default, 2: debug)')

# Munin
munargs = parser.add_argument_group('Munin parameters')
munargs.add_argument('--munin-path', default=Defaults.MUNIN_VAR_FOLDER,
help='path to main Munin folder (default: %(default)s)')
munargs.add_argument('--www', '--munin-www-path', default=Defaults.MUNIN_WWW_FOLDER,
help='path to main Munin folder (default: %(default)s)')
munargs.add_argument('--rrd', '--munin-rrd-path', default=Defaults.MUNIN_RRD_FOLDER,
help='path to main Munin folder (default: %(default)s)')
parser.set_defaults(func=main)
91 changes: 16 additions & 75 deletions bin/fetch.py → munininfluxdb/commands/fetch.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,22 @@
#!/usr/bin/env python
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one of the subcommands of the main command. It is loaded in munininfluxdb/main.py.

from __future__ import print_function
import pwd
import json
import os
import sys
import argparse
from collections import defaultdict

from munininfluxdb.utils import Symbol
from munininfluxdb.settings import Defaults

import influxdb
import storable

try:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before touching munin-influxdb I also sent a PR for storable which makes the module available on pypi. This has been accepted and we can install it via setup.py dependencies. The vendor folder is no longer needed.

import storable
except ImportError:
from vendor import storable
NAME = 'fetch'
DESCRIPTION = """'fetch' command grabs fresh data gathered by a still running Munin installation and send it to InfluxDB.

try:
pwd.getpwnam('munin')
except KeyError:
CRON_USER = 'root'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cron has been extracted to a separate sub-command. So all cron related stuff has been removed from this file and moved to commands/cron.py

else:
CRON_USER = 'munin'
Currently, Munin needs to be still running to update the data in '/var/lib/munin/state-*' files.
"""

# Cron job comment is used to uninstall and must not be manually deleted from the crontab
CRON_COMMENT = 'Update InfluxDB with fresh values from Munin'

def pack_values(config, values):
suffix = ":{0}".format(Defaults.DEFAULT_RRD_INDEX)
Expand Down Expand Up @@ -66,7 +57,9 @@ def read_state_file(filename):
assert 'spoolfetch' in data and 'value' in data
return data['value'], data['spoolfetch']

def main(config_filename=Defaults.FETCH_CONFIG):
def main(args):
config_filename = args.config or Defaults.FETCH_CONFIG

config = None
with open(config_filename) as f:
config = json.load(f)
Expand Down Expand Up @@ -113,68 +106,16 @@ def main(config_filename=Defaults.FETCH_CONFIG):
json.dump(config, f)
print("{0} Updated configuration: {1}".format(Symbol.OK_GREEN, f.name))

def uninstall_cron():
if os.geteuid() != 0:
print("It seems you are not root, please run \"muninflux fetch --uninstall-cron\" again with root privileges".format(sys.argv[0]))
sys.exit(1)

try:
import crontab
except ImportError:
from vendor import crontab

cron = crontab.CronTab(user=CRON_USER)
jobs = list(cron.find_comment(CRON_COMMENT))
cron.remove(*jobs)
cron.write()

return len(jobs)

def install_cron(script_file, period):
if os.geteuid() != 0:
print("It seems you are not root, please run \"muninflux fetch --install-cron\" again with root privileges".format(sys.argv[0]))
sys.exit(1)

try:
import crontab
except ImportError:
from vendor import crontab

cron = crontab.CronTab(user=CRON_USER)
job = cron.new(command=script_file, user=CRON_USER, comment=CRON_COMMENT)
job.minute.every(period)

if job.is_valid() and job.is_enabled():
cron.write()
def setup(parser, injections):
"""
Sets up CLI argument parsing.

return job.is_valid() and job.is_enabled()
The argument *injections* is currently unused in this command and is a
placeholder for the future.

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="""
'fetch' command grabs fresh data gathered by a still running Munin installation and send it to InfluxDB.

Currently, Munin needs to be still running to update the data in '/var/lib/munin/state-*' files.
""")
:param parser: The argument parser for this subcommand.
"""
parser.add_argument('--config', default=Defaults.FETCH_CONFIG,
help='overrides the default configuration file (default: %(default)s)')
cronargs = parser.add_argument_group('cron job management')
cronargs.add_argument('--install-cron', dest='script_path',
help='install a cron job to updated InfluxDB with fresh data from Munin every <period> minutes')
cronargs.add_argument('-p', '--period', default=5, type=int,
help="sets the period in minutes between each fetch in the cron job (default: %(default)min)")
cronargs.add_argument('--uninstall-cron', action='store_true',
help='uninstall the fetch cron job (any matching the initial comment actually)')
args = parser.parse_args()

if args.script_path:
install_cron(args.script_path, args.period)
print("{0} Cron job installed for user {1}".format(Symbol.OK_GREEN, CRON_USER))
elif args.uninstall_cron:
nb = uninstall_cron()
if nb:
print("{0} Cron job uninstalled for user {1} ({2} entries deleted)".format(Symbol.OK_GREEN, CRON_USER, nb))
else:
print("No matching job found (searching comment \"{1}\" in crontab for user {2})".format(Symbol.WARN_YELLOW,
CRON_COMMENT, CRON_USER))
else:
main(args.config)
parser.set_defaults(func=main)
Loading