Skip to content

Commit bcef348

Browse files
authored
Merge pull request #93 from geopython/plugins_82
Plugins 82 - Moving to plugin architecture for healthchecks
2 parents c789acd + 0573202 commit bcef348

61 files changed

Lines changed: 4856 additions & 491 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.travis.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ install:
1212

1313
script:
1414
- echo -e "admin\ntest\ntest\nyou@example.com\nyou@example.com" | python GeoHealthCheck/models.py create
15-
- find . -name "*.py" | grep -v docs | xargs flake8
15+
- find . -name "*.py" | grep -v docs | grep -v migrations | xargs flake8
16+
- python GeoHealthCheck/models.py load tests/data/fixtures.json y
17+
- python GeoHealthCheck/models.py run
18+
- python tests/run_tests.py
1619
- cd docs && make html
1720

1821
after-script:

GeoHealthCheck/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@
2626
# OTHER DEALINGS IN THE SOFTWARE.
2727
#
2828
# =================================================================
29-
__version__ = '0.1.0'
29+
__version__ = '0.2.0'

GeoHealthCheck/app.py

Lines changed: 165 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# =================================================================
33
#
44
# Authors: Tom Kralidis <tomkralidis@gmail.com>
5+
# Just van den Broecke <justb4@gmail.com>
56
#
67
# Copyright (c) 2014 Tom Kralidis
78
#
@@ -37,12 +38,14 @@
3738
from flask.ext.babel import Babel, gettext
3839
from flask.ext.login import (LoginManager, login_user, logout_user,
3940
current_user, login_required)
41+
from flask_migrate import Migrate
4042

4143
from __init__ import __version__
42-
from healthcheck import run_test_resource
44+
from healthcheck import sniff_test_resource, run_test_resource
4345
from init import DB
4446
from enums import RESOURCE_TYPES
45-
from models import Resource, Run, Tag, User
47+
from models import Resource, Run, ProbeVars, CheckVars, Tag, User
48+
from factory import Factory
4649
from util import render_template2, send_email
4750
import views
4851

@@ -52,6 +55,8 @@
5255
APP.config.from_pyfile('../instance/config_site.py')
5356
APP.secret_key = APP.config['SECRET_KEY']
5457

58+
MIGRATE = Migrate(APP, DB)
59+
5560
LOGIN_MANAGER = LoginManager()
5661
LOGIN_MANAGER.init_app(APP)
5762

@@ -145,15 +150,15 @@ def cssize_reliability(value, css_type=None):
145150
number = int(value)
146151

147152
if APP.config['GHC_RELIABILITY_MATRIX']['red']['min'] <= number <= \
148-
APP.config['GHC_RELIABILITY_MATRIX']['red']['max']:
153+
APP.config['GHC_RELIABILITY_MATRIX']['red']['max']:
149154
score = 'danger'
150155
panel = 'red'
151156
elif (APP.config['GHC_RELIABILITY_MATRIX']['orange']['min'] <= number <=
152-
APP.config['GHC_RELIABILITY_MATRIX']['orange']['max']):
157+
APP.config['GHC_RELIABILITY_MATRIX']['orange']['max']):
153158
score = 'warning'
154159
panel = 'yellow'
155160
elif (APP.config['GHC_RELIABILITY_MATRIX']['green']['min'] <= number <=
156-
APP.config['GHC_RELIABILITY_MATRIX']['green']['max']):
161+
APP.config['GHC_RELIABILITY_MATRIX']['green']['max']):
157162
score = 'success'
158163
panel = 'green'
159164
else: # should never really get here
@@ -204,7 +209,6 @@ def home():
204209
"""homepage"""
205210

206211
resource_type = None
207-
tag = None
208212

209213
if request.args.get('resource_type') in RESOURCE_TYPES.keys():
210214
resource_type = request.args['resource_type']
@@ -250,7 +254,8 @@ def export():
250254
'min_response_time': round(r.min_response_time, 2),
251255
'average_response_time': round(r.average_response_time, 2),
252256
'max_response_time': round(r.max_response_time, 2),
253-
'reliability': round(r.reliability, 2)
257+
'reliability': round(r.reliability, 2),
258+
'last_report': r.last_run.report
254259
})
255260
return jsonify(json_dict)
256261
elif request.url_rule.rule == '/csv':
@@ -321,7 +326,8 @@ def export_resource(identifier):
321326
'last_run': resource.last_run.checked_datetime.strftime(
322327
'%Y-%m-%dT%H:%M:%SZ'),
323328
'history_csv': history_csv,
324-
'history_json': history_json
329+
'history_json': history_json,
330+
'last_report': resource.last_run.report
325331
}
326332
return jsonify(json_dict)
327333
elif 'csv' in request.url_rule.rule:
@@ -466,7 +472,7 @@ def add():
466472
resource_type=rtype))
467473
return redirect(url_for('add', lang=g.current_lang))
468474

469-
[title, success, response_time, message, start_time] = run_test_resource(
475+
[title, success, response_time, message, start_time] = sniff_test_resource(
470476
APP.config, resource_type, url)
471477

472478
if not success:
@@ -486,19 +492,59 @@ def add():
486492

487493
resource_to_add = Resource(current_user, resource_type, title, url,
488494
tags=tag_list)
489-
run_to_add = Run(resource_to_add, success, response_time, message,
490-
start_time)
495+
496+
probe_to_add = None
497+
checks_to_add = []
498+
499+
# Always add a default Probe and Check(s) from the GHC_PROBE_DEFAULTS conf
500+
if resource_type in APP.config['GHC_PROBE_DEFAULTS']:
501+
resource_settings = APP.config['GHC_PROBE_DEFAULTS'][resource_type]
502+
probe_class = resource_settings['probe_class']
503+
if probe_class:
504+
# Add the default Probe
505+
probe_obj = Factory.create_obj(probe_class)
506+
probe_to_add = ProbeVars(
507+
resource_to_add, probe_class,
508+
probe_obj.get_default_parameter_values())
509+
510+
# Add optional default (parameterized) Checks to add to this Probe
511+
checks_info = probe_obj.get_checks_info()
512+
checks_param_info = probe_obj.get_plugin_vars()['CHECKS_AVAIL']
513+
for check_class in checks_info:
514+
check_param_info = checks_param_info[check_class]
515+
if 'default' in checks_info[check_class]:
516+
if checks_info[check_class]['default']:
517+
# Filter out params for Check with fixed values
518+
param_defs = check_param_info['PARAM_DEFS']
519+
param_vals = {}
520+
for param in param_defs:
521+
if param_defs[param]['value']:
522+
param_vals[param] = param_defs[param]['value']
523+
check_vars = CheckVars(
524+
probe_to_add, check_class, param_vals)
525+
checks_to_add.append(check_vars)
526+
527+
result = run_test_resource(resource_to_add)
528+
529+
run_to_add = Run(resource_to_add, result)
491530

492531
DB.session.add(resource_to_add)
532+
if probe_to_add:
533+
DB.session.add(probe_to_add)
534+
for check_to_add in checks_to_add:
535+
DB.session.add(check_to_add)
493536
DB.session.add(run_to_add)
537+
494538
try:
495539
DB.session.commit()
496540
msg = gettext('Service registered')
497541
flash('%s (%s, %s)' % (msg, resource_type, url), 'success')
498542
except Exception as err:
499543
DB.session.rollback()
500544
flash(str(err), 'danger')
501-
return redirect(url_for('home', lang=g.current_lang))
545+
return redirect(url_for('home', lang=g.current_lang))
546+
else:
547+
return edit_resource(resource_to_add.identifier)
502548

503549

504550
@APP.route('/resource/<int:resource_identifier>/update', methods=['POST'])
@@ -544,6 +590,26 @@ def update(resource_identifier):
544590
resource.tags.remove(tag_to_delete)
545591

546592
update_counter += 1
593+
elif key == 'probes':
594+
# Remove all existing ProbeVars for Resource
595+
for probe_var in resource.probe_vars:
596+
resource.probe_vars.remove(probe_var)
597+
598+
# Add ProbeVars anew each with optional CheckVars
599+
for probe in value:
600+
print('adding Probe class=%s parms=%s' %
601+
(probe['probe_class'], str(probe)))
602+
probe_vars = ProbeVars(resource, probe['probe_class'],
603+
probe['parameters'])
604+
for check in probe['checks']:
605+
check_vars = CheckVars(
606+
probe_vars, check['check_class'],
607+
check['parameters'])
608+
probe_vars.check_vars.append(check_vars)
609+
610+
resource.probe_vars.append(probe_vars)
611+
612+
update_counter += 1
547613

548614
elif getattr(resource, key) != resource_identifier_dict[key]:
549615
# Update other resource attrs, mainly 'name'
@@ -562,10 +628,10 @@ def update(resource_identifier):
562628
if err:
563629
status = str(err)
564630

565-
return str({'status': status})
631+
return jsonify({'status': status})
566632

567633

568-
@APP.route('/resource/<int:resource_identifier>/test')
634+
@APP.route('/resource/<int:resource_identifier>/test', methods=['GET', 'POST'])
569635
@login_required
570636
def test(resource_identifier):
571637
"""test a resource"""
@@ -574,17 +640,35 @@ def test(resource_identifier):
574640
flash(gettext('Resource not found'), 'danger')
575641
return redirect(request.referrer)
576642

577-
[title, success, response_time, message, start_time] = run_test_resource(
578-
APP.config, resource.resource_type, resource.url)
643+
result = run_test_resource(
644+
resource)
579645

580-
if message not in ['OK', None, 'None']:
581-
msg = gettext('ERROR')
582-
flash('%s: %s' % (msg, message), 'danger')
583-
else:
584-
flash(gettext('Resource tested successfully'), 'success')
646+
if request.method == 'GET':
647+
if result.message not in ['OK', None, 'None']:
648+
msg = gettext('ERROR')
649+
flash('%s: %s' % (msg, result.message), 'danger')
650+
else:
651+
flash(gettext('Resource tested successfully'), 'success')
585652

586-
return redirect(url_for('get_resource_by_id', lang=g.current_lang,
587-
identifier=resource_identifier))
653+
return redirect(url_for('get_resource_by_id', lang=g.current_lang,
654+
identifier=resource_identifier))
655+
elif request.method == 'POST':
656+
return jsonify(result.get_report())
657+
658+
659+
@APP.route('/resource/<int:resource_identifier>/edit')
660+
@login_required
661+
def edit_resource(resource_identifier):
662+
"""edit a resource"""
663+
resource = Resource.query.filter_by(identifier=resource_identifier).first()
664+
if resource is None:
665+
flash(gettext('Resource not found'), 'danger')
666+
return redirect(request.referrer)
667+
668+
probes_avail = views.get_probes_avail(resource.resource_type)
669+
670+
return render_template('edit_resource.html', lang=g.current_lang,
671+
resource=resource, probes_avail=probes_avail)
588672

589673

590674
@APP.route('/resource/<int:resource_identifier>/delete')
@@ -619,6 +703,45 @@ def delete(resource_identifier):
619703
return redirect(url_for(request.referrer))
620704

621705

706+
@APP.route('/probe/<string:probe_class>/edit_form')
707+
@login_required
708+
def get_probe_edit_form(probe_class):
709+
"""get the form to edit a Probe"""
710+
711+
probe_obj = Factory.create_obj(probe_class)
712+
probe_info = probe_obj.get_plugin_vars()
713+
probe_vars = ProbeVars(
714+
None, probe_class, probe_obj.get_default_parameter_values())
715+
# checks_avail = probe_obj.expand_check_vars(probe_obj.CHECKS_AVAIL)
716+
# for check_class in checks_avail:
717+
# check_obj = Factory.create_obj(check_class)
718+
# # check_info = check_obj.get_plugin_vars()
719+
# check_vars = CheckVars(
720+
# probe_vars, check_class, check_obj.get_default_parameter_values())
721+
# probe_vars.check_vars.append(check_vars)
722+
print(str(probe_vars))
723+
724+
return render_template('includes/probe_edit_form.html',
725+
lang=g.current_lang,
726+
probe=probe_vars, probe_info=probe_info)
727+
728+
729+
@APP.route('/check/<string:check_class>/edit_form')
730+
@login_required
731+
def get_check_edit_form(check_class):
732+
"""get the form to edit a Check"""
733+
734+
check_obj = Factory.create_obj(check_class)
735+
check_info = check_obj.get_plugin_vars()
736+
check_vars = CheckVars(
737+
None, check_class, check_obj.get_default_parameter_values())
738+
739+
print(str(check_info))
740+
return render_template('includes/check_edit_form.html',
741+
lang=g.current_lang,
742+
check=check_vars, check_info=check_info)
743+
744+
622745
@APP.route('/login', methods=['GET', 'POST'])
623746
def login():
624747
"""login"""
@@ -655,7 +778,7 @@ def recover():
655778
if request.method == 'GET':
656779
return render_template('recover_password.html')
657780
username = request.form['username']
658-
registered_user = User.query.filter_by(username=username,).first()
781+
registered_user = User.query.filter_by(username=username, ).first()
659782
if registered_user is None:
660783
flash(gettext('Invalid username'), 'danger')
661784
return redirect(url_for('recover', lang=g.current_lang))
@@ -679,8 +802,26 @@ def recover():
679802
return redirect(url_for('home', lang=g.current_lang))
680803

681804

805+
#
806+
# REST Interface Calls
807+
#
808+
809+
810+
@APP.route('/api/v1.0/probes-avail/')
811+
@APP.route('/api/v1.0/probes-avail/<resource_type>')
812+
def api_probes_avail(resource_type=None):
813+
"""
814+
Get available (configured) Probes for this
815+
installation, optional for resource type
816+
"""
817+
818+
probes = views.get_probes_avail(resource_type)
819+
return jsonify(probes)
820+
821+
682822
if __name__ == '__main__': # run locally, for fun
683823
import sys
824+
684825
HOST = '0.0.0.0'
685826
PORT = 8000
686827
if len(sys.argv) > 1:

GeoHealthCheck/check.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from plugin import Plugin
2+
from result import CheckResult
3+
4+
5+
class Check(Plugin):
6+
"""
7+
Base class for specific Plugin implementations to perform
8+
a check on results from a Probe.
9+
"""
10+
11+
def __init__(self):
12+
Plugin.__init__(self)
13+
self.probe = None
14+
15+
# Lifecycle
16+
def init(self, probe, check_vars):
17+
"""
18+
Initialize Checker with parent Probe and parameters dict.
19+
:return:
20+
"""
21+
22+
self.probe = probe
23+
self.check_vars = check_vars
24+
self._parameters = check_vars.parameters
25+
self._result = CheckResult(self, check_vars)
26+
self._result.start()
27+
28+
# Lifecycle
29+
def set_result(self, success, message):
30+
self._result.set(success, message)
31+
self._result.stop()
32+
33+
# Lifecycle
34+
def perform(self):
35+
"""
36+
Perform this Check's specific check. TODO: return Result object.
37+
:return:
38+
"""
39+
pass

0 commit comments

Comments
 (0)