├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.rst ├── ping ├── __init__.py ├── checks.py ├── decorators.py ├── defaults.py ├── models.py ├── tasks.py ├── urls.py └── views.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .DS_Store 3 | django_ping.egg-info/ 4 | /dist -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Garrett Pennington 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Django Ping 2 | =========== 3 | 4 | Django Ping is utility that provides a lightweight endpoint for availability and uptime monitoring services. It 5 | also provides hooks for testing stack components and reporting them via JSON. 6 | 7 | Current Version: 0.3.0 8 | https://github.com/gpennington/django-ping 9 | 10 | Installation 11 | ------------ 12 | 13 | Use pip to download and install:: 14 | 15 | pip install django-ping 16 | 17 | Add Django Ping to url conf:: 18 | 19 | url(r'^ping/', include('ping.urls')), 20 | 21 | Basic Configuration 22 | ------------------- 23 | 24 | Hitting the endpoint returns a simple status 200 response. 25 | You can customize the message by adding to your Django settings:: 26 | 27 | PING_DEFAULT_RESPONSE = "All systems go!" 28 | PING_DEFAULT_MIMETYPE = 'text/html' 29 | 30 | Hitting the url:: 31 | 32 | /ping 33 | 34 | displays:: 35 | 36 | All systems go! 37 | 38 | Advanced Configuration 39 | ---------------------- 40 | 41 | Enable Status Checks 42 | ~~~~~~~~~~~~~~~~~~~~ 43 | 44 | Adding a ``checks=true`` parameter to the url tells Django Ping to run 45 | a series of status checks and reports the results. 46 | 47 | For example:: 48 | 49 | /ping?checks=true 50 | 51 | displays:: 52 | 53 | Your site is up! 54 | db_sessions True 55 | db_site True 56 | 57 | By default, Django Ping tests that your Database is responding 58 | by using supplying two tests. You can supply your own tests 59 | to make sure everything is responding as expected. If you don't 60 | use database sessions or the contrib.sites app, you can removed 61 | remove the ones you don't need. 62 | 63 | To customize, include a tuple in your Django settings:: 64 | 65 | PING_CHECKS = ( 66 | 'ping.checks.check_database_sessions', 67 | #'ping.checks.check_database_sites', 68 | 'my_app.module.check_function', 69 | 'my_other_app.some_module.some_function' 70 | ) 71 | 72 | 73 | Specifying a ``fmt`` parameter to ``json`` returns more detailed and serialized data. 74 | For example:: 75 | 76 | /ping?fmt=json 77 | 78 | displays:: 79 | 80 | { 81 | "db_sessions": true, 82 | "db_site": true 83 | } 84 | 85 | Custom Status Checks 86 | ~~~~~~~~~~~~~~~~~~~~ 87 | 88 | Checks should accept the request object and return 89 | two values. The name/key to be displayed 90 | and the value of the check. The value should be anything 91 | that can be serialized.:: 92 | 93 | def check_sample(request): 94 | #...do some things... 95 | return 'foo', True 96 | #or 97 | return 'bar', float(123.456) 98 | #or even 99 | return 'baz', ['one', 'two', 'three', {'a':1, 'b':2, 'c':3}] 100 | 101 | Then, add that to the ``PING_CHECKS`` tuple to display:: 102 | 103 | { 104 | 'foo', true 105 | } 106 | 107 | You can send ``POST`` requests too. As an example:: 108 | 109 | def check_create_user(request): 110 | from django.contrib.auth.models import User 111 | try: 112 | if request.method == 'POST': 113 | username = request.GET.get('username') 114 | u, created = User.objects.get_or_create(username=username) 115 | if created: 116 | return 'create_user', "User: %s has been created" % u.username 117 | else: 118 | return 'create_user', "User: %s already exists" % u.username 119 | else: 120 | return 'create_user', "User cannot be created with GET" 121 | except: 122 | return 'create_user', "User not created" 123 | 124 | 125 | Included Status Checks 126 | ~~~~~~~~~~~~~~~~~~~~~~ 127 | 128 | Django Ping includes a few checks to test various components 129 | live. 130 | 131 | **check_database_sessions** - Hits your database and attempts to retrieve a single session. 132 | 133 | **check_database_sites** - Hits your database and attempts to retrieve a single site instance. 134 | 135 | **check_cache_set** - Attempts to cache a value using the current cache backend defined. 136 | 137 | **check_cache_get** - Attempts to retrieve a cached value using the current cache backend defined. 138 | 139 | **check_celery** - Adds a task to the queue and checks for celery to complete it. 140 | 141 | 142 | Authentication 143 | ~~~~~~~~~~~~~~ 144 | 145 | You can require HTTP Basic authentication to access the ping endpoint, 146 | set ``PING_BASIC_AUTH`` to ``True`` in your Django settings. 147 | 148 | Provide in the request the username/password of a valid User. 149 | 150 | Complete Settings List 151 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 152 | 153 | Check ``ping.defaults`` for default values. 154 | 155 | PING_RESPONSE = "Some string" 156 | PING_MIMETYPE = 'text/html' or valid content type 157 | PING_DEFAULT_CHECKS = tuple of paths to check methods 158 | PING_BASIC_AUTH = Boolean (default is False) 159 | PING_CELERY_TIMEOUT = In seconds as integers (5 is default) 160 | 161 | 162 | What's Next? 163 | ------------ 164 | 165 | Go sign up for a monitoring service or role your own. 166 | 167 | https://www.pingdom.com/ 168 | 169 | http://www.panopta.com/ 170 | 171 | http://binarycanary.com/ 172 | -------------------------------------------------------------------------------- /ping/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gpennington/django-ping/5210ed68bbad6dffb273ecdf08363e7b7de36603/ping/__init__.py -------------------------------------------------------------------------------- /ping/checks.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.utils.importlib import import_module 3 | from django.core.exceptions import ImproperlyConfigured 4 | 5 | from ping.defaults import * 6 | 7 | def checks(request): 8 | """ 9 | Iterates through a tuple of systems checks, 10 | then returns a key name for the check and the value 11 | for that check. 12 | """ 13 | response_dict = {} 14 | 15 | #Taken straight from Django 16 | #If there is a better way, I don't know it 17 | for path in getattr(settings, 'PING_CHECKS', PING_DEFAULT_CHECKS): 18 | i = path.rfind('.') 19 | module, attr = path[:i], path[i+1:] 20 | try: 21 | mod = import_module(module) 22 | except ImportError as e: 23 | raise ImproperlyConfigured('Error importing module %s: "%s"' % (module, e)) 24 | try: 25 | func = getattr(mod, attr) 26 | except AttributeError: 27 | raise ImproperlyConfigured('Module "%s" does not define a "%s" callable' % (module, attr)) 28 | 29 | key, value = func(request) 30 | response_dict[key] = value 31 | 32 | return response_dict 33 | 34 | #DEFAULT SYSTEM CHECKS 35 | 36 | #Database 37 | def check_database_sessions(request): 38 | from django.contrib.sessions.models import Session 39 | try: 40 | session = Session.objects.all()[0] 41 | return 'db_sessions', True 42 | except: 43 | return 'db_sessions', False 44 | 45 | def check_database_sites(request): 46 | from django.contrib.sites.models import Site 47 | try: 48 | session = Site.objects.all()[0] 49 | return 'db_site', True 50 | except: 51 | return 'db_site', False 52 | 53 | 54 | #Caching 55 | CACHE_KEY = 'django-ping-test' 56 | CACHE_VALUE = 'abc123' 57 | 58 | def check_cache_set(request): 59 | from django.core.cache import cache 60 | try: 61 | cache.set(CACHE_KEY, CACHE_VALUE, 30) 62 | return 'cache_set', True 63 | except: 64 | return 'cache_set', False 65 | 66 | def check_cache_get(request): 67 | from django.core.cache import cache 68 | try: 69 | data = cache.get(CACHE_KEY) 70 | if data == CACHE_VALUE: 71 | return 'cache_get', True 72 | else: 73 | return 'cache_get', False 74 | except: 75 | return 'cache_get', False 76 | 77 | 78 | #User 79 | def check_user_exists(request): 80 | from django.contrib.auth.models import User 81 | try: 82 | username = request.GET.get('username') 83 | u = User.objects.get(username=username) 84 | return 'user_exists', True 85 | except: 86 | return 'user_exists', False 87 | 88 | 89 | #Celery 90 | def check_celery(request): 91 | from datetime import datetime, timedelta 92 | from time import sleep, time 93 | from ping.tasks import sample_task 94 | 95 | now = time() 96 | datetimenow = datetime.now() 97 | expires = datetimenow + timedelta(seconds=getattr(settings, 'PING_CELERY_TIMEOUT', PING_CELERY_TIMEOUT)) 98 | 99 | try: 100 | task = sample_task.apply_async(expires=expires) 101 | while expires > datetime.now(): 102 | if task.ready() and task.result == True: 103 | finished = str(time() - now) 104 | return 'celery', { 'success': True, 'time':finished } 105 | sleep(0.25) 106 | return 'celery', { 'success': False } 107 | except Exception: 108 | return 'celery', { 'success': False } 109 | -------------------------------------------------------------------------------- /ping/decorators.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | import base64 3 | 4 | from django.http import HttpResponse 5 | from django.contrib.auth import authenticate, login 6 | from django.conf import settings 7 | 8 | from ping.defaults import PING_BASIC_AUTH 9 | 10 | def http_basic_auth(func): 11 | """ 12 | Attempts to login user with u/p provided in HTTP_AUTHORIZATION header. 13 | If successful, returns the view, otherwise returns a 401. 14 | If PING_BASIC_AUTH is False, then just return the view function 15 | 16 | Modified code by: 17 | http://djangosnippets.org/users/bthomas/ 18 | from 19 | http://djangosnippets.org/snippets/1304/ 20 | """ 21 | @wraps(func) 22 | def _decorator(request, *args, **kwargs): 23 | if getattr(settings, 'PING_BASIC_AUTH', PING_BASIC_AUTH): 24 | from django.contrib.auth import authenticate, login 25 | if request.META.has_key('HTTP_AUTHORIZATION'): 26 | authmeth, auth = request.META['HTTP_AUTHORIZATION'].split(' ', 1) 27 | if authmeth.lower() == 'basic': 28 | auth = auth.strip().decode('base64') 29 | username, password = auth.split(':', 1) 30 | user = authenticate(username=username, password=password) 31 | if user: 32 | login(request, user) 33 | return func(request, *args, **kwargs) 34 | else: 35 | return HttpResponse("Invalid Credentials", status=401) 36 | else: 37 | return HttpResponse("No Credentials Provided", status=401) 38 | else: 39 | return func(request, *args, **kwargs) 40 | return _decorator -------------------------------------------------------------------------------- /ping/defaults.py: -------------------------------------------------------------------------------- 1 | PING_DEFAULT_RESPONSE = "Your site is up!" 2 | PING_DEFAULT_MIMETYPE = 'text/html' 3 | 4 | PING_DEFAULT_CHECKS = ( 5 | 'ping.checks.check_database_sessions', 6 | 'ping.checks.check_database_sites', 7 | ) 8 | 9 | PING_BASIC_AUTH = False 10 | 11 | PING_CELERY_TIMEOUT = 5 -------------------------------------------------------------------------------- /ping/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models -------------------------------------------------------------------------------- /ping/tasks.py: -------------------------------------------------------------------------------- 1 | from celery.task import task 2 | 3 | @task() 4 | def sample_task(): 5 | return True -------------------------------------------------------------------------------- /ping/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import patterns, include, url 2 | from ping.views import status 3 | 4 | urlpatterns = patterns('', 5 | url(r'^$', status, name='status'), 6 | ) 7 | 8 | -------------------------------------------------------------------------------- /ping/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | from django.conf import settings 3 | from django.utils import simplejson 4 | from django.contrib.auth.decorators import login_required 5 | from django.views.decorators.csrf import csrf_exempt 6 | 7 | from ping.defaults import * 8 | from ping.checks import checks 9 | from ping.decorators import http_basic_auth 10 | 11 | @csrf_exempt 12 | @http_basic_auth 13 | def status(request): 14 | """ 15 | Returns a simple HttpResponse 16 | """ 17 | 18 | response = "

%s

" % getattr(settings, 'PING_DEFAULT_RESPONSE', PING_DEFAULT_RESPONSE) 19 | mimetype = getattr(settings, 'PING_DEFAULT_MIMETYPE', PING_DEFAULT_MIMETYPE) 20 | 21 | if request.GET.get('checks') == 'true': 22 | response_dict = checks(request) 23 | response += "
" 24 | for key, value in sorted(response_dict.items()): 25 | response += "
%s
" % str(key) 26 | response += "
%s
" % str(value) 27 | response += "
" 28 | 29 | if request.GET.get('fmt') == 'json': 30 | try: 31 | response = simplejson.dumps(response_dict) 32 | except UnboundLocalError: 33 | response_dict = checks(request) 34 | response = simplejson.dumps(response_dict) 35 | response = simplejson.dumps(response_dict, sort_keys=True) 36 | mimetype = 'application/json' 37 | 38 | return HttpResponse(response, mimetype=mimetype, status=200) -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | version = '0.2.0' 4 | 5 | setup(name='django-ping', 6 | version=version, 7 | description="Django Monitoring and Availability Utility", 8 | long_description="", 9 | classifiers=[ 10 | "Development Status :: 3 - Alpha", 11 | "Environment :: Web Environment", 12 | "Intended Audience :: End Users/Desktop", 13 | "Natural Language :: English", 14 | "Operating System :: OS Independent", 15 | "Programming Language :: Python", 16 | "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", 17 | "Topic :: Utilities", 18 | "License :: OSI Approved :: MIT License", 19 | ], 20 | keywords='', 21 | author='Garrett Pennington', 22 | url='', 23 | license='MIT', 24 | packages=find_packages(), 25 | install_requires = [], 26 | include_package_data=True, 27 | zip_safe=False, 28 | ) --------------------------------------------------------------------------------