Merge branch 'master' into django-1.6

Conflicts:
	cabot/cabotapp/models.py
	cabot/templates/base.html
	cabot/templates/cabotapp/_statuscheck_list.html
	cabot/templates/cabotapp/service_list.html
	cabot/templates/cabotapp/statuscheck_detail.html
	setup.py
This commit is contained in:
Peter Baumgartner 2014-08-04 08:20:36 -06:00
commit 9282ffeb8e
25 changed files with 1466 additions and 138 deletions

View File

@ -1,5 +1,5 @@
from django.contrib import admin
from .models import UserProfile, Service, Shift, ServiceStatusSnapshot, StatusCheck, StatusCheckResult
from .models import UserProfile, Service, Shift, ServiceStatusSnapshot, StatusCheck, StatusCheckResult, Instance
admin.site.register(UserProfile)
admin.site.register(Shift)
@ -7,3 +7,4 @@ admin.site.register(Service)
admin.site.register(ServiceStatusSnapshot)
admin.site.register(StatusCheck)
admin.site.register(StatusCheckResult)
admin.site.register(Instance)

View File

@ -0,0 +1,205 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Instance'
db.create_table('cabotapp_instance', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('name', self.gf('django.db.models.fields.TextField')()),
('last_alert_sent', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
('email_alert', self.gf('django.db.models.fields.BooleanField')(default=False)),
('hipchat_alert', self.gf('django.db.models.fields.BooleanField')(default=True)),
('sms_alert', self.gf('django.db.models.fields.BooleanField')(default=False)),
('telephone_alert', self.gf('django.db.models.fields.BooleanField')(default=False)),
('alerts_enabled', self.gf('django.db.models.fields.BooleanField')(default=True)),
('overall_status', self.gf('django.db.models.fields.TextField')(default='PASSING')),
('old_overall_status', self.gf('django.db.models.fields.TextField')(default='PASSING')),
('hackpad_id', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
('address', self.gf('django.db.models.fields.TextField')(blank=True)),
))
db.send_create_signal('cabotapp', ['Instance'])
# Adding M2M table for field users_to_notify on 'Instance'
db.create_table('cabotapp_instance_users_to_notify', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('instance', models.ForeignKey(orm['cabotapp.instance'], null=False)),
('user', models.ForeignKey(orm['auth.user'], null=False))
))
db.create_unique('cabotapp_instance_users_to_notify', ['instance_id', 'user_id'])
# Adding M2M table for field status_checks on 'Instance'
db.create_table('cabotapp_instance_status_checks', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('instance', models.ForeignKey(orm['cabotapp.instance'], null=False)),
('statuscheck', models.ForeignKey(orm['cabotapp.statuscheck'], null=False))
))
db.create_unique('cabotapp_instance_status_checks', ['instance_id', 'statuscheck_id'])
# Adding M2M table for field instances on 'Service'
db.create_table('cabotapp_service_instances', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('service', models.ForeignKey(orm['cabotapp.service'], null=False)),
('instance', models.ForeignKey(orm['cabotapp.instance'], null=False))
))
db.create_unique('cabotapp_service_instances', ['service_id', 'instance_id'])
def backwards(self, orm):
# Deleting model 'Instance'
db.delete_table('cabotapp_instance')
# Removing M2M table for field users_to_notify on 'Instance'
db.delete_table('cabotapp_instance_users_to_notify')
# Removing M2M table for field status_checks on 'Instance'
db.delete_table('cabotapp_instance_status_checks')
# Removing M2M table for field instances on 'Service'
db.delete_table('cabotapp_service_instances')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'cabotapp.instance': {
'Meta': {'ordering': "['name']", 'object_name': 'Instance'},
'address': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'alerts_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'email_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hackpad_id': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'hipchat_alert': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_alert_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.TextField', [], {}),
'old_overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'sms_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'status_checks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['cabotapp.StatusCheck']", 'symmetrical': 'False', 'blank': 'True'}),
'telephone_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'users_to_notify': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'})
},
'cabotapp.service': {
'Meta': {'ordering': "['name']", 'object_name': 'Service'},
'alerts_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'email_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hackpad_id': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'hipchat_alert': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instances': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['cabotapp.Instance']", 'symmetrical': 'False', 'blank': 'True'}),
'last_alert_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.TextField', [], {}),
'old_overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'sms_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'status_checks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['cabotapp.StatusCheck']", 'symmetrical': 'False', 'blank': 'True'}),
'telephone_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'url': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'users_to_notify': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'})
},
'cabotapp.servicestatussnapshot': {
'Meta': {'object_name': 'ServiceStatusSnapshot'},
'did_send_alert': ('django.db.models.fields.IntegerField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'num_checks_active': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'num_checks_failing': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'num_checks_passing': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'snapshots'", 'to': "orm['cabotapp.Service']"}),
'time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'})
},
'cabotapp.shift': {
'Meta': {'object_name': 'Shift'},
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'end': ('django.db.models.fields.DateTimeField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'start': ('django.db.models.fields.DateTimeField', [], {}),
'uid': ('django.db.models.fields.TextField', [], {}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'cabotapp.statuscheck': {
'Meta': {'ordering': "['name']", 'object_name': 'StatusCheck'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'cached_health': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'calculated_status': ('django.db.models.fields.CharField', [], {'default': "'passing'", 'max_length': '50', 'blank': 'True'}),
'check_type': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
'debounce': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True'}),
'endpoint': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'expected_num_hosts': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True'}),
'frequency': ('django.db.models.fields.IntegerField', [], {'default': '5'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'importance': ('django.db.models.fields.CharField', [], {'default': "'ERROR'", 'max_length': '30'}),
'last_run': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'max_queued_build_time': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'metric': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'name': ('django.db.models.fields.TextField', [], {}),
'password': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_cabotapp.statuscheck_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
'status_code': ('django.db.models.fields.TextField', [], {'default': '200', 'null': 'True'}),
'text_match': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'timeout': ('django.db.models.fields.IntegerField', [], {'default': '30', 'null': 'True'}),
'username': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'value': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'verify_ssl_certificate': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'cabotapp.statuscheckresult': {
'Meta': {'object_name': 'StatusCheckResult'},
'check': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cabotapp.StatusCheck']"}),
'error': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'raw_data': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'succeeded': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
'time_complete': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'})
},
'cabotapp.userprofile': {
'Meta': {'object_name': 'UserProfile'},
'fallback_alert_user': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hipchat_alias': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'mobile_number': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20', 'blank': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
}
}
complete_apps = ['cabotapp']

View File

@ -0,0 +1,158 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Changing field 'StatusCheck.created_by'
db.alter_column('cabotapp_statuscheck', 'created_by_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True))
def backwards(self, orm):
# Changing field 'StatusCheck.created_by'
db.alter_column('cabotapp_statuscheck', 'created_by_id', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['auth.User']))
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'cabotapp.instance': {
'Meta': {'ordering': "['name']", 'object_name': 'Instance'},
'address': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'alerts_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'email_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hackpad_id': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'hipchat_alert': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_alert_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.TextField', [], {}),
'old_overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'sms_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'status_checks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['cabotapp.StatusCheck']", 'symmetrical': 'False', 'blank': 'True'}),
'telephone_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'users_to_notify': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'})
},
'cabotapp.service': {
'Meta': {'ordering': "['name']", 'object_name': 'Service'},
'alerts_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'email_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hackpad_id': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'hipchat_alert': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instances': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['cabotapp.Instance']", 'symmetrical': 'False', 'blank': 'True'}),
'last_alert_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.TextField', [], {}),
'old_overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'sms_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'status_checks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['cabotapp.StatusCheck']", 'symmetrical': 'False', 'blank': 'True'}),
'telephone_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'url': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'users_to_notify': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'})
},
'cabotapp.servicestatussnapshot': {
'Meta': {'object_name': 'ServiceStatusSnapshot'},
'did_send_alert': ('django.db.models.fields.IntegerField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'num_checks_active': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'num_checks_failing': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'num_checks_passing': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'snapshots'", 'to': "orm['cabotapp.Service']"}),
'time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'})
},
'cabotapp.shift': {
'Meta': {'object_name': 'Shift'},
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'end': ('django.db.models.fields.DateTimeField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'start': ('django.db.models.fields.DateTimeField', [], {}),
'uid': ('django.db.models.fields.TextField', [], {}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'cabotapp.statuscheck': {
'Meta': {'ordering': "['name']", 'object_name': 'StatusCheck'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'cached_health': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'calculated_status': ('django.db.models.fields.CharField', [], {'default': "'passing'", 'max_length': '50', 'blank': 'True'}),
'check_type': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}),
'debounce': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True'}),
'endpoint': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'expected_num_hosts': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True'}),
'frequency': ('django.db.models.fields.IntegerField', [], {'default': '5'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'importance': ('django.db.models.fields.CharField', [], {'default': "'ERROR'", 'max_length': '30'}),
'last_run': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'max_queued_build_time': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'metric': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'name': ('django.db.models.fields.TextField', [], {}),
'password': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_cabotapp.statuscheck_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
'status_code': ('django.db.models.fields.TextField', [], {'default': '200', 'null': 'True'}),
'text_match': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'timeout': ('django.db.models.fields.IntegerField', [], {'default': '30', 'null': 'True'}),
'username': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'value': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'verify_ssl_certificate': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'cabotapp.statuscheckresult': {
'Meta': {'object_name': 'StatusCheckResult'},
'check': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cabotapp.StatusCheck']"}),
'error': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'raw_data': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'succeeded': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
'time_complete': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'})
},
'cabotapp.userprofile': {
'Meta': {'object_name': 'UserProfile'},
'fallback_alert_user': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hipchat_alias': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'mobile_number': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20', 'blank': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
}
}
complete_apps = ['cabotapp']

View File

@ -0,0 +1,179 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'InstanceStatusSnapshot'
db.create_table('cabotapp_instancestatussnapshot', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('instance', self.gf('django.db.models.fields.related.ForeignKey')(related_name='snapshots', to=orm['cabotapp.Instance'])),
('time', self.gf('django.db.models.fields.DateTimeField')(db_index=True)),
('num_checks_active', self.gf('django.db.models.fields.IntegerField')(default=0)),
('num_checks_passing', self.gf('django.db.models.fields.IntegerField')(default=0)),
('num_checks_failing', self.gf('django.db.models.fields.IntegerField')(default=0)),
('overall_status', self.gf('django.db.models.fields.TextField')(default='PASSING')),
('did_send_alert', self.gf('django.db.models.fields.IntegerField')(default=False)),
))
db.send_create_signal('cabotapp', ['InstanceStatusSnapshot'])
def backwards(self, orm):
# Deleting model 'InstanceStatusSnapshot'
db.delete_table('cabotapp_instancestatussnapshot')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'cabotapp.instance': {
'Meta': {'ordering': "['name']", 'object_name': 'Instance'},
'address': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'alerts_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'email_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hackpad_id': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'hipchat_alert': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_alert_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.TextField', [], {}),
'old_overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'sms_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'status_checks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['cabotapp.StatusCheck']", 'symmetrical': 'False', 'blank': 'True'}),
'telephone_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'users_to_notify': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'})
},
'cabotapp.instancestatussnapshot': {
'Meta': {'object_name': 'InstanceStatusSnapshot'},
'did_send_alert': ('django.db.models.fields.IntegerField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'snapshots'", 'to': "orm['cabotapp.Instance']"}),
'num_checks_active': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'num_checks_failing': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'num_checks_passing': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'})
},
'cabotapp.service': {
'Meta': {'ordering': "['name']", 'object_name': 'Service'},
'alerts_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'email_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hackpad_id': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'hipchat_alert': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instances': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['cabotapp.Instance']", 'symmetrical': 'False', 'blank': 'True'}),
'last_alert_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.TextField', [], {}),
'old_overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'sms_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'status_checks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['cabotapp.StatusCheck']", 'symmetrical': 'False', 'blank': 'True'}),
'telephone_alert': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'url': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'users_to_notify': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'})
},
'cabotapp.servicestatussnapshot': {
'Meta': {'object_name': 'ServiceStatusSnapshot'},
'did_send_alert': ('django.db.models.fields.IntegerField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'num_checks_active': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'num_checks_failing': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'num_checks_passing': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'overall_status': ('django.db.models.fields.TextField', [], {'default': "'PASSING'"}),
'service': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'snapshots'", 'to': "orm['cabotapp.Service']"}),
'time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'})
},
'cabotapp.shift': {
'Meta': {'object_name': 'Shift'},
'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'end': ('django.db.models.fields.DateTimeField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'start': ('django.db.models.fields.DateTimeField', [], {}),
'uid': ('django.db.models.fields.TextField', [], {}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'cabotapp.statuscheck': {
'Meta': {'ordering': "['name']", 'object_name': 'StatusCheck'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'cached_health': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'calculated_status': ('django.db.models.fields.CharField', [], {'default': "'passing'", 'max_length': '50', 'blank': 'True'}),
'check_type': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}),
'debounce': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True'}),
'endpoint': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'expected_num_hosts': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True'}),
'frequency': ('django.db.models.fields.IntegerField', [], {'default': '5'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'importance': ('django.db.models.fields.CharField', [], {'default': "'ERROR'", 'max_length': '30'}),
'last_run': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'max_queued_build_time': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'metric': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'name': ('django.db.models.fields.TextField', [], {}),
'password': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_cabotapp.statuscheck_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
'status_code': ('django.db.models.fields.TextField', [], {'default': '200', 'null': 'True'}),
'text_match': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'timeout': ('django.db.models.fields.IntegerField', [], {'default': '30', 'null': 'True'}),
'username': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'value': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'verify_ssl_certificate': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
},
'cabotapp.statuscheckresult': {
'Meta': {'object_name': 'StatusCheckResult'},
'check': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cabotapp.StatusCheck']"}),
'error': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'raw_data': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'succeeded': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'time': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
'time_complete': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'})
},
'cabotapp.userprofile': {
'Meta': {'object_name': 'UserProfile'},
'fallback_alert_user': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hipchat_alias': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'mobile_number': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20', 'blank': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
}
}
complete_apps = ['cabotapp']

View File

@ -3,20 +3,24 @@ from django.conf import settings
from django.core.exceptions import ValidationError
from polymorphic import PolymorphicModel
from django.db.models import F
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from celery.exceptions import SoftTimeLimitExceeded
from .jenkins import get_job_status
from .alert import send_alert
from .calendar import get_events
from .graphite import parse_metric
from .alert import send_alert
from .tasks import update_service
from .tasks import update_service, update_instance
from datetime import datetime, timedelta
from django.utils import timezone
from django.db import transaction
import json
import re
import time
import os
import subprocess
import requests
from celery.utils.log import get_task_logger
@ -65,7 +69,11 @@ def calculate_debounced_passing(recent_results, debounce=0):
return False
class Service(models.Model):
class CheckGroupMixin(models.Model):
class Meta:
abstract = True
PASSING_STATUS = 'PASSING'
WARNING_STATUS = 'WARNING'
ERROR_STATUS = 'ERROR'
@ -88,15 +96,16 @@ class Service(models.Model):
)
name = models.TextField()
url = models.TextField(
blank=True,
help_text="URL of service."
)
users_to_notify = models.ManyToManyField(
User,
blank=True,
help_text='Users who should receive alerts.',
)
alerts_enabled = models.BooleanField(
default=True,
help_text='Alert when this service is not healthy.',
)
status_checks = models.ManyToManyField(
'StatusCheck',
blank=True,
@ -113,10 +122,6 @@ class Service(models.Model):
default=False,
help_text='Must be enabled, and check importance set to Critical, to receive telephone alerts.',
)
alerts_enabled = models.BooleanField(
default=True,
help_text='Alert when this service is not healthy.',
)
overall_status = models.TextField(default=PASSING_STATUS)
old_overall_status = models.TextField(default=PASSING_STATUS)
hackpad_id = models.TextField(
@ -126,30 +131,10 @@ class Service(models.Model):
help_text='Gist, Hackpad or Refheap js embed with recovery instructions e.g. https://you.hackpad.com/some_document.js'
)
class Meta:
ordering = ['name']
def __unicode__(self):
return self.name
def update_status(self):
self.old_overall_status = self.overall_status
# Only active checks feed into our calculation
status_checks_failed_count = self.all_failing_checks().count()
self.overall_status = self.most_severe(self.all_failing_checks())
self.snapshot = ServiceStatusSnapshot(
service=self,
num_checks_active=self.active_status_checks().count(),
num_checks_passing=self.active_status_checks(
).count() - status_checks_failed_count,
num_checks_failing=status_checks_failed_count,
overall_status=self.overall_status,
time=timezone.now(),
)
self.snapshot.save()
self.save()
if not (self.overall_status == Service.PASSING_STATUS and self.old_overall_status == Service.PASSING_STATUS):
self.alert()
def most_severe(self, check_list):
failures = [c.importance for c in check_list]
@ -189,7 +174,6 @@ class Service(models.Model):
self.save()
self.snapshot.did_send_alert = True
self.snapshot.save()
# send_alert handles the logic of how exactly alerts should be handled
send_alert(self, duty_officers=get_duty_officers())
@property
@ -201,18 +185,6 @@ class Service(models.Model):
s['time'] = time.mktime(s['time'].timetuple())
return snapshots
def active_status_checks(self):
return self.status_checks.filter(active=True)
def inactive_status_checks(self):
return self.status_checks.filter(active=False)
def all_passing_checks(self):
return self.active_status_checks().filter(calculated_status=self.CALCULATED_PASSING_STATUS)
def all_failing_checks(self):
return self.active_status_checks().exclude(calculated_status=self.CALCULATED_PASSING_STATUS)
def graphite_status_checks(self):
return self.status_checks.filter(polymorphic_ctype__model='graphitestatuscheck')
@ -231,9 +203,110 @@ class Service(models.Model):
def active_jenkins_status_checks(self):
return self.jenkins_status_checks().filter(active=True)
def active_status_checks(self):
return self.status_checks.filter(active=True)
def inactive_status_checks(self):
return self.status_checks.filter(active=False)
def all_passing_checks(self):
return self.active_status_checks().filter(calculated_status=self.CALCULATED_PASSING_STATUS)
def all_failing_checks(self):
return self.active_status_checks().exclude(calculated_status=self.CALCULATED_PASSING_STATUS)
class Service(CheckGroupMixin):
def update_status(self):
self.old_overall_status = self.overall_status
# Only active checks feed into our calculation
status_checks_failed_count = self.all_failing_checks().count()
self.overall_status = self.most_severe(self.all_failing_checks())
self.snapshot = ServiceStatusSnapshot(
service=self,
num_checks_active=self.active_status_checks().count(),
num_checks_passing=self.active_status_checks(
).count() - status_checks_failed_count,
num_checks_failing=status_checks_failed_count,
overall_status=self.overall_status,
time=timezone.now(),
)
self.snapshot.save()
self.save()
if not (self.overall_status == Service.PASSING_STATUS and self.old_overall_status == Service.PASSING_STATUS):
self.alert()
instances = models.ManyToManyField(
'Instance',
blank=True,
help_text='Instances this service is running on.',
)
url = models.TextField(
blank=True,
help_text="URL of service."
)
class Meta:
ordering = ['name']
class Instance(CheckGroupMixin):
def duplicate(self):
checks = self.status_checks.all()
new_instance = self
new_instance.pk = None
new_instance.id = None
new_instance.name = "Copy of %s" % self.name
new_instance.save()
for check in checks:
check.duplicate(inst_set=[new_instance], serv_set=())
return new_instance.pk
def update_status(self):
self.old_overall_status = self.overall_status
# Only active checks feed into our calculation
status_checks_failed_count = self.all_failing_checks().count()
self.overall_status = self.most_severe(self.all_failing_checks())
self.snapshot = InstanceStatusSnapshot(
instance=self,
num_checks_active=self.active_status_checks().count(),
num_checks_passing=self.active_status_checks(
).count() - status_checks_failed_count,
num_checks_failing=status_checks_failed_count,
overall_status=self.overall_status,
time=timezone.now(),
)
self.snapshot.save()
self.save()
class Meta:
ordering = ['name']
address = models.TextField(
blank=True,
help_text="Address (IP/Hostname) of service."
)
def icmp_status_checks(self):
return self.status_checks.filter(polymorphic_ctype__model='icmpstatuscheck')
def active_icmp_status_checks(self):
return self.icmp_status_checks().filter(active=True)
def delete(self, *args, **kwargs):
self.icmp_status_checks().delete()
return super(Instance, self).delete(*args, **kwargs)
class Snapshot(models.Model):
class Meta:
abstract = True
class ServiceStatusSnapshot(models.Model):
service = models.ForeignKey(Service, related_name='snapshots')
time = models.DateTimeField(db_index=True)
num_checks_active = models.IntegerField(default=0)
num_checks_passing = models.IntegerField(default=0)
@ -241,9 +314,17 @@ class ServiceStatusSnapshot(models.Model):
overall_status = models.TextField(default=Service.PASSING_STATUS)
did_send_alert = models.IntegerField(default=False)
class ServiceStatusSnapshot(Snapshot):
service = models.ForeignKey(Service, related_name='snapshots')
def __unicode__(self):
return u"%s: %s" % (self.service.name, self.overall_status)
class InstanceStatusSnapshot(Snapshot):
instance = models.ForeignKey(Instance, related_name='snapshots')
def __unicode__(self):
return u"%s: %s" % (self.instance.name, self.overall_status)
class StatusCheck(PolymorphicModel):
@ -278,7 +359,7 @@ class StatusCheck(PolymorphicModel):
null=True,
help_text='Number of successive failures permitted before check will be marked as failed. Default is 0, i.e. fail on first failure.'
)
created_by = models.ForeignKey(User)
created_by = models.ForeignKey(User, null=True)
calculated_status = models.CharField(
max_length=50, choices=Service.STATUSES, default=Service.CALCULATED_PASSING_STATUS, blank=True)
last_run = models.DateTimeField(null=True)
@ -353,11 +434,11 @@ class StatusCheck(PolymorphicModel):
return self.name
def recent_results(self):
return self.statuscheckresult_set.all().order_by('-time_complete').defer('raw_data')[:10]
return StatusCheckResult.objects.filter(check=self).order_by('-time_complete').defer('raw_data')[:10]
def last_result(self):
try:
return self.recent_results()[0]
return StatusCheckResult.objects.filter(check=self).order_by('-time_complete').defer('raw_data')[0]
except:
return None
@ -365,6 +446,10 @@ class StatusCheck(PolymorphicModel):
start = timezone.now()
try:
result = self._run()
except SoftTimeLimitExceeded as e:
result = StatusCheckResult(check=self)
result.error = u'Error in performing check: Celery soft time limit exceeded'
result.succeeded = False
except Exception as e:
result = StatusCheckResult(check=self)
result.error = u'Error in performing check: %s' % (e,)
@ -383,22 +468,90 @@ class StatusCheck(PolymorphicModel):
raise NotImplementedError('Subclasses should implement')
def save(self, *args, **kwargs):
recent_results = self.recent_results()
if self.pk:
# This should not be necessary
with transaction.commit_manually():
try:
recent_results = list(self.recent_results())
if calculate_debounced_passing(recent_results, self.debounce):
self.calculated_status = Service.CALCULATED_PASSING_STATUS
else:
self.calculated_status = Service.CALCULATED_FAILING_STATUS
self.cached_health = serialize_recent_results(recent_results)
transaction.commit()
except SoftTimeLimitExceeded as e:
# Something weird with postgres
transaction.rollback()
logger.error('Celery time limit exceeded for getting results for %s' % self.pk)
self.calculated_status = Service.CALCULATED_FAILING_STATUS
self.cached_health = '-1'
except Exception as e:
transaction.rollback()
logger.error('Got exception when saving check: %s' % e)
self.calculated_status = Service.CALCULATED_FAILING_STATUS
self.cached_health = '-1'
try:
updated = StatusCheck.objects.get(pk=self.pk)
except StatusCheck.DoesNotExist as e:
logger.error('Cannot find myself (check %s) in the database, presumably have been deleted' % self.pk)
return
else:
self.cached_health = ''
self.calculated_status = Service.CALCULATED_PASSING_STATUS
ret = super(StatusCheck, self).save(*args, **kwargs)
# Update linked services
self.update_related_services()
self.update_related_instances()
return ret
def duplicate(self, inst_set=None, serv_set=None):
new_check = self
new_check.pk = None
new_check.id = None
new_check.save()
if inst_set is not None:
new_check.instance_set = inst_set
if serv_set is not None:
new_check.service_set = serv_set
new_check.save()
return new_check.pk
def update_related_services(self):
services = self.service_set.all()
for service in services:
update_service.delay(service.id)
def update_related_instances(self):
instances = self.instance_set.all()
for instance in instances:
update_instance.delay(instance.id)
class ICMPStatusCheck(StatusCheck):
class Meta(StatusCheck.Meta):
proxy = True
@property
def check_category(self):
return "ICMP/Ping Check"
def _run(self):
result = StatusCheckResult(check=self)
instances = self.instance_set.all()
target = self.instance_set.get().address
# We need to read both STDOUT and STDERR because ping can write to both, depending on the kind of error. Thanks a lot, ping.
ping_process = subprocess.Popen("ping -c 1 " + target, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
response = ping_process.wait()
if response == 0:
result.succeeded = True
else:
output = ping_process.stdout.read()
result.succeeded = False
result.error = output
return result
class GraphiteStatusCheck(StatusCheck):
@ -532,7 +685,6 @@ class HttpStatusCheck(StatusCheck):
result.succeeded = True
return result
class JenkinsStatusCheck(StatusCheck):
class Meta(StatusCheck.Meta):

View File

@ -67,6 +67,16 @@ def update_service(service_or_id):
service.update_status()
@task(ignore_result=True)
def update_instance(instance_or_id):
from .models import Instance
if not isinstance(instance_or_id, Instance):
instance = Instance.objects.get(id=instance_or_id)
else:
instance = instance_or_id
instance.update_status()
@task(ignore_result=True)
def update_shifts():
from .models import update_shifts as _update_shifts

View File

@ -8,7 +8,7 @@ from django.contrib.auth.models import User
from django.test.client import Client
from cabot.cabotapp.models import (
GraphiteStatusCheck, JenkinsStatusCheck,
HttpStatusCheck, Service, StatusCheckResult)
HttpStatusCheck, ICMPStatusCheck, Service, Instance, StatusCheckResult)
from cabot.cabotapp.views import StatusCheckReportForm
from mock import Mock, patch
from twilio import rest
@ -66,6 +66,7 @@ class LocalTestCase(TestCase):
self.service = Service.objects.create(
name='Service',
)
self.service.status_checks.add(
self.graphite_check, self.jenkins_check, self.http_check)
# Passing is most recent
@ -310,6 +311,22 @@ class TestWebInterface(LocalTestCase):
# Still the same
self.assertEqual(reloaded.hackpad_id, snippet_link)
def test_create_instance(self):
instances = Instance.objects.all()
self.assertEqual(len(instances), 0)
self.client.login(username=self.username, password=self.password)
resp = self.client.post(
reverse('create-instance'),
data={
'name': 'My little instance',
},
follow=True,
)
instances = Instance.objects.all()
self.assertEqual(len(instances), 1)
instance = instances[0]
self.assertEqual(len(instance.status_checks.all()), 1)
def test_checks_report(self):
form = StatusCheckReportForm({
'service': self.service.id,

View File

@ -4,10 +4,10 @@ from dateutil.relativedelta import relativedelta
from django.http import HttpResponse, HttpResponseRedirect
from django.core.urlresolvers import reverse_lazy
from django.conf import settings
from .models import (
StatusCheck, GraphiteStatusCheck, JenkinsStatusCheck, HttpStatusCheck,
StatusCheckResult, UserProfile, Service, Shift, get_duty_officers)
from .tasks import run_status_check as _run_status_check
from models import (
StatusCheck, GraphiteStatusCheck, JenkinsStatusCheck, HttpStatusCheck, ICMPStatusCheck,
StatusCheckResult, UserProfile, Service, Instance, Shift, get_duty_officers)
from tasks import run_status_check as _run_status_check
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import (
@ -38,7 +38,7 @@ class LoginRequiredMixin(object):
def subscriptions(request):
""" Simple list of all checks """
t = loader.get_template('cabotapp/subscriptions.html')
services = Service.objects.all().order_by('alerts_enabled')
services = Service.objects.all()
users = User.objects.filter(is_active=True)
c = RequestContext(request, {
'services': services,
@ -55,6 +55,32 @@ def run_status_check(request, pk):
return HttpResponseRedirect(reverse('check', kwargs={'pk': pk}))
def duplicate_icmp_check(request, pk):
pc = StatusCheck.objects.get(pk=pk)
npk = pc.duplicate()
return HttpResponseRedirect(reverse('update-icmp-check', kwargs={'pk': npk}))
def duplicate_instance(request, pk):
instance = Instance.objects.get(pk=pk)
new_instance = instance.duplicate()
return HttpResponseRedirect(reverse('update-instance', kwargs={'pk': new_instance}))
def duplicate_http_check(request, pk):
pc = StatusCheck.objects.get(pk=pk)
npk = pc.duplicate()
return HttpResponseRedirect(reverse('update-http-check', kwargs={'pk': npk}))
def duplicate_graphite_check(request, pk):
pc = StatusCheck.objects.get(pk=pk)
npk = pc.duplicate()
return HttpResponseRedirect(reverse('update-graphite-check', kwargs={'pk': npk}))
def duplicate_jenkins_check(request, pk):
pc = StatusCheck.objects.get(pk=pk)
npk = pc.duplicate()
return HttpResponseRedirect(reverse('update-jenkins-check', kwargs={'pk': npk}))
class StatusCheckResultDetailView(LoginRequiredMixin, DetailView):
model = StatusCheckResult
context_object_name = 'result'
@ -90,7 +116,9 @@ base_widgets = {
class StatusCheckForm(SymmetricalForm):
symmetrical_fields = ('service_set',)
symmetrical_fields = ('service_set', 'instance_set')
service_set = forms.ModelMultipleChoiceField(
queryset=Service.objects.all(),
required=False,
@ -103,6 +131,18 @@ class StatusCheckForm(SymmetricalForm):
)
)
instance_set = forms.ModelMultipleChoiceField(
queryset=Instance.objects.all(),
required=False,
help_text='Link to instance(s).',
widget=forms.SelectMultiple(
attrs={
'data-rel': 'chosen',
'style': 'width: 70%',
},
)
)
class GraphiteStatusCheckForm(StatusCheckForm):
@ -135,6 +175,20 @@ class GraphiteStatusCheckForm(StatusCheckForm):
})
class ICMPStatusCheckForm(StatusCheckForm):
class Meta:
model = ICMPStatusCheck
fields = (
'name',
'frequency',
'importance',
'active',
'debounce',
)
widgets = dict(**base_widgets)
class HttpStatusCheckForm(StatusCheckForm):
class Meta:
@ -196,6 +250,53 @@ class UserProfileForm(forms.ModelForm):
exclude = ('user',)
class InstanceForm(SymmetricalForm):
symmetrical_fields = ('service_set',)
service_set = forms.ModelMultipleChoiceField(
queryset=Service.objects.all(),
required=False,
help_text='Link to service(s).',
widget=forms.SelectMultiple(
attrs={
'data-rel': 'chosen',
'style': 'width: 70%',
},
)
)
class Meta:
model = Instance
template_name = 'instance_form.html'
fields = (
'name',
'address',
'users_to_notify',
'status_checks',
'service_set',
)
widgets = {
'name': forms.TextInput(attrs={'style': 'width: 30%;'}),
'address': forms.TextInput(attrs={'style': 'width: 70%;'}),
'status_checks': forms.SelectMultiple(attrs={
'data-rel': 'chosen',
'style': 'width: 70%',
}),
'service_set': forms.SelectMultiple(attrs={
'data-rel': 'chosen',
'style': 'width: 70%',
}),
'users_to_notify': forms.CheckboxSelectMultiple(),
}
def __init__(self, *args, **kwargs):
ret = super(InstanceForm, self).__init__(*args, **kwargs)
self.fields['users_to_notify'].queryset = User.objects.filter(
is_active=True)
return ret
class ServiceForm(forms.ModelForm):
class Meta:
@ -206,6 +307,7 @@ class ServiceForm(forms.ModelForm):
'url',
'users_to_notify',
'status_checks',
'instances',
'email_alert',
'hipchat_alert',
'sms_alert',
@ -220,6 +322,10 @@ class ServiceForm(forms.ModelForm):
'data-rel': 'chosen',
'style': 'width: 70%',
}),
'instances': forms.SelectMultiple(attrs={
'data-rel': 'chosen',
'style': 'width: 70%',
}),
'users_to_notify': forms.CheckboxSelectMultiple(),
'hackpad_id': forms.TextInput(attrs={'style': 'width:30%;'}),
}
@ -293,38 +399,54 @@ class CheckCreateView(LoginRequiredMixin, CreateView):
if metric:
initial['metric'] = metric
service_id = self.request.GET.get('service')
instance_id = self.request.GET.get('instance')
if service_id:
try:
service = Service.objects.get(id=service_id)
initial['service_set'] = [service]
except Service.DoesNotExist:
pass
if instance_id:
try:
instance = Instance.objects.get(id=instance_id)
initial['instance_set'] = [instance]
except Instance.DoesNotExist:
pass
return initial
def get_success_url(self):
if self.request.GET.get('service') != '':
if self.request.GET.get('service'):
return reverse('service', kwargs={'pk': self.request.GET.get('service')})
else:
if self.request.GET.get('instance'):
return reverse('instance', kwargs={'pk': self.request.GET.get('instance')})
return reverse('checks')
class CheckUpdateView(LoginRequiredMixin, UpdateView):
template_name = 'cabotapp/statuscheck_form.html'
def get_success_url(self):
return reverse('check', kwargs={'pk': self.object.id})
class ICMPCheckCreateView(CheckCreateView):
model = ICMPStatusCheck
form_class = ICMPStatusCheckForm
class GraphiteCheckCreateView(CheckCreateView):
model = GraphiteStatusCheck
form_class = GraphiteStatusCheckForm
class ICMPCheckUpdateView(CheckUpdateView):
model = ICMPStatusCheck
form_class = ICMPStatusCheckForm
class GraphiteCheckUpdateView(CheckUpdateView):
model = GraphiteStatusCheck
form_class = GraphiteStatusCheckForm
class GraphiteCheckCreateView(CheckCreateView):
model = GraphiteStatusCheck
form_class = GraphiteStatusCheckForm
class HttpCheckCreateView(CheckCreateView):
model = HttpStatusCheck
@ -359,7 +481,7 @@ class StatusCheckListView(LoginRequiredMixin, ListView):
context_object_name = 'checks'
def get_queryset(self):
return StatusCheck.objects.all().order_by('name').prefetch_related('service_set')
return StatusCheck.objects.all().order_by('name').prefetch_related('service_set', 'instance_set')
class StatusCheckDeleteView(LoginRequiredMixin, DeleteView):
@ -397,6 +519,15 @@ class UserProfileUpdateView(LoginRequiredMixin, UpdateView):
return profile
class InstanceListView(LoginRequiredMixin, ListView):
model = Instance
context_object_name = 'instances'
def get_queryset(self):
return Instance.objects.all().order_by('name').prefetch_related('status_checks')
class ServiceListView(LoginRequiredMixin, ListView):
model = Service
context_object_name = 'services'
@ -404,6 +535,20 @@ class ServiceListView(LoginRequiredMixin, ListView):
def get_queryset(self):
return Service.objects.all().order_by('name').prefetch_related('status_checks')
class InstanceDetailView(LoginRequiredMixin, DetailView):
model = Instance
context_object_name = 'instance'
def get_context_data(self, **kwargs):
context = super(InstanceDetailView, self).get_context_data(**kwargs)
date_from = date.today() - relativedelta(day=1)
context['report_form'] = StatusCheckReportForm(initial={
'checks': self.object.status_checks.all(),
'service': self.object,
'date_from': date_from,
'date_to': date_from + relativedelta(months=1) - relativedelta(days=1)
})
return context
class ServiceDetailView(LoginRequiredMixin, DetailView):
model = Service
@ -421,6 +566,46 @@ class ServiceDetailView(LoginRequiredMixin, DetailView):
return context
class InstanceCreateView(LoginRequiredMixin, CreateView):
model = Instance
form_class = InstanceForm
def form_valid(self, form):
ret = super(InstanceCreateView, self).form_valid(form)
if self.object.status_checks.filter(polymorphic_ctype__model='icmpstatuscheck').count() == 0:
self.generate_default_ping_check(self.object)
return ret
def generate_default_ping_check(self, obj):
pc = ICMPStatusCheck(
name="Default Ping Check for %s" % obj.name,
frequency=5,
importance=Service.ERROR_STATUS,
debounce=0,
created_by=None,
)
pc.save()
obj.status_checks.add(pc)
def get_success_url(self):
return reverse('instance', kwargs={'pk': self.object.id})
def get_initial(self):
if self.initial:
initial = self.initial
else:
initial = {}
service_id = self.request.GET.get('service')
if service_id:
try:
service = Service.objects.get(id=service_id)
initial['service_set'] = [service]
except Service.DoesNotExist:
pass
return initial
class ServiceCreateView(LoginRequiredMixin, CreateView):
model = Service
form_class = ServiceForm
@ -428,6 +613,12 @@ class ServiceCreateView(LoginRequiredMixin, CreateView):
def get_success_url(self):
return reverse('service', kwargs={'pk': self.object.id})
class InstanceUpdateView(LoginRequiredMixin, UpdateView):
model = Instance
form_class = InstanceForm
def get_success_url(self):
return reverse('instance', kwargs={'pk': self.object.id})
class ServiceUpdateView(LoginRequiredMixin, UpdateView):
model = Service
@ -444,6 +635,13 @@ class ServiceDeleteView(LoginRequiredMixin, DeleteView):
template_name = 'cabotapp/service_confirm_delete.html'
class InstanceDeleteView(LoginRequiredMixin, DeleteView):
model = Instance
success_url = reverse_lazy('instances')
context_object_name = 'instance'
template_name = 'cabotapp/instance_confirm_delete.html'
class ShiftListView(LoginRequiredMixin, ListView):
model = Shift
context_object_name = 'shifts'

View File

@ -6,6 +6,8 @@ CELERY_IMPORTS = ('cabot.cabotapp.tasks', )
CELERYBEAT_SCHEDULER = "djcelery.schedulers.DatabaseScheduler"
CELERY_TASK_SERIALIZER = "json"
CELERY_ACCEPT_CONTENT = ['json', 'msgpack', 'yaml']
CELERYD_TASK_SOFT_TIME_LIMIT = 120
CELERYD_TASK_TIME_LIMIT = 150
CELERYBEAT_SCHEDULE = {
'run-all-checks': {

View File

@ -92,6 +92,7 @@ MIDDLEWARE_CLASSES = (
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.transaction.TransactionMiddleware',
)
ROOT_URLCONF = 'cabot.urls'

View File

@ -41,6 +41,9 @@
</div>
<div class="navbar-collapse collapse" id="navbar-main">
<ul class="nav navbar-nav">
<li>
<a href="{% url "instances" %}"><i class="fa fa-desktop"></i> Instances</a>
</li>
<li>
<a href="{% url "services" %}"><i class="fa fa-gears"></i> Services</a>
</li>
@ -54,15 +57,21 @@
<li>
<a href="{% url "create-service" %}"><i class="fa fa-gears"></i> Service</a>
</li>
<li>
<a href="{% url "create-instance" %}"><i class="fa fa-desktop"></i> Instance</a>
</li>
<li class="divider"></li>
<li>
<a href="{% url "create-check" %}?service={{ service.id }}" class=""><i class="glyphicon glyphicon-signal" title="Add new metric check"></i> Graphite check</a>
<a href="{% url "create-check" %}?service={{ service.id }}&instance={{ instance.id }}" class=""><i class="glyphicon glyphicon-signal" title="Add new metric check"></i> Graphite check</a>
</li>
<li>
<a href="{% url "create-http-check" %}?service={{ service.id }}" class="" title="Add new Http check"><i class="glyphicon glyphicon-arrow-up"></i> Http check</a>
<a href="{% url "create-http-check" %}?service={{ service.id }}&instance={{ instance.id }}" class="" title="Add new Http check"><i class="glyphicon glyphicon-arrow-up"></i> Http check</a>
</li>
<li>
<a href="{% url "create-jenkins-check" %}?service={{ service.id }}" class="" title="Add new Jenkins check"><i class="glyphicon glyphicon-ok"></i> Jenkins check</a>
<a href="{% url "create-jenkins-check" %}?service={{ service.id }}&instance={{ instance.id }}" class="" title="Add new Jenkins check"><i class="glyphicon glyphicon-ok"></i> Jenkins check</a>
</li>
<li>
<a href="{% url "create-icmp-check" %}?service={{ service.id }}&instance={{ instance.id }}" class="" title="Add new ICMP check"><i class="glyphicon glyphicon-transfer"></i> ICMP check</a>
</li>
</ul>
</li>

View File

@ -0,0 +1,43 @@
<table class="table bootstrap-datatable datatable">
<thead>
<tr>
{% if not instances %}
<div class="col-xs-11 col-xs-offset-1">
<hr></hr>
No instances configured
</div>
</tr>
{% else %}
<th>Name</th>
<th>Overall</th>
<th>Active checks</th>
<th>Disabled checks</th>
<th></th>
</tr>
</thead>
<tbody>
{% for instance in instances %}
<tr class="enabled">
<td>
<a href="{% url "instance" pk=instance.id %}" title="Alerts {% if instance.alerts_enabled %}enabled{% else %}disabled{% endif %}">{{instance.name}} </a>
</td>
<td>
<span class="label label-{% if instance.overall_status == instance.PASSING_STATUS %}success{% else %}danger{% endif %}">{{ instance.overall_status|lower|capfirst }}</span>
</td>
<td>
<span class="label label-{% if instance.active_status_checks.all.count > 0 %}{% if instance.overall_status != instance.PASSING_STATUS %}danger{% else %}success{% endif %}{% else %}default{% endif %}">{{ instance.active_status_checks.all.count }}</span>
</td>
<td>
<span class="label label-{% if instance.inactive_status_checks.all.count > 0 %}warning{% else %}default{% endif %}">{{ instance.inactive_status_checks.all.count }}</span>
</td>
<td class="text-right">
<a class="btn btn-xs" href="{% url "update-instance" pk=instance.id %}" role="button">
<i class="glyphicon glyphicon-edit"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
{% endif %}
</table>

View File

@ -0,0 +1,43 @@
<table class="table bootstrap-datatable datatable">
<thead>
<tr>
{% if not services %}
<div class="col-xs-11 col-xs-offset-1">
<hr></hr>
No services configured
</div>
</tr>
{% else %}
<th>Name</th>
<th>Overall</th>
<th>Active checks</th>
<th>Disabled checks</th>
<th></th>
</tr>
</thead>
<tbody>
{% for service in services %}
<tr class="{% if service.alerts_enabled %}enabled{% else %}warning{% endif %}">
<td>
<a href="{% url "service" pk=service.id %}" title="Alerts {% if service.alerts_enabled %}enabled{% else %}disabled{% endif %}">{{service.name}} </a>
</td>
<td>
<span class="label label-{% if not service.alerts_enabled %}warning{% else %}{% if service.overall_status == service.PASSING_STATUS %}success{% else %}danger{% endif %}{% endif %}">{% if service.alerts_enabled %}{{ service.overall_status|lower|capfirst }}{% else %}Disabled{% endif %}</span>
</td>
<td>
<span class="label label-{% if service.active_status_checks.all.count > 0 %}{% if service.overall_status != service.PASSING_STATUS %}danger{% else %}success{% endif %}{% else %}default{% endif %}">{{ service.active_status_checks.all.count }}</span>
</td>
<td>
<span class="label label-{% if service.inactive_status_checks.all.count > 0 %}warning{% else %}default{% endif %}">{{ service.inactive_status_checks.all.count }}</span>
</td>
<td class="text-right">
<a class="btn btn-xs" href="{% url "update-service" pk=service.id %}" role="button">
<i class="glyphicon glyphicon-edit"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
{% endif %}
</table>

View File

@ -2,18 +2,21 @@
<div class="row">
<div class="col-xs-12">
<div class="col-xs-1"><h3><i class="{% if checks_type == "All" %}fa fa-cog{% else %}glyphicon glyphicon-{% if checks_type == "Http" %}arrow-up{% elif checks_type == "Jenkins" %}ok{% else %}signal{% endif %}{% endif %}"></i></h3></div>
<div class="col-xs-1"><h3><i class="{% if checks_type == "All" %}fa fa-cog{% else %}glyphicon glyphicon-{% if checks_type == "Http" %}arrow-up{% elif checks_type == "Jenkins" %}ok{% elif checks_type == "ICMP" %}transfer{% else %}signal{% endif %}{% endif %}"></i></h3></div>
<div class="col-xs-8"><h3>{{ checks_type }} checks</h3></div>
<div class="col-xs-3 text-right">
<h3>
&nbsp;{% if checks_type == "All" or checks_type == "Graphite" %}
<a href="{% url "create-check" %}?service={{ service.id }}" class=""><i class="glyphicon glyphicon-plus" title="Add new metric check"></i><i class="glyphicon glyphicon-signal" title="Add new metric check"></i></a>
<a href="{% url "create-check" %}?instance={{ instance.id }}&service={{ service.id }}" class=""><i class="glyphicon glyphicon-plus" title="Add new metric check"></i><i class="glyphicon glyphicon-signal" title="Add new metric check"></i></a>
{% endif %}
{% if checks_type == "All" or checks_type == "Http" %}
&nbsp;<a href="{% url "create-http-check" %}?service={{ service.id }}" class="" title="Add new Http check"><i class="glyphicon glyphicon-plus"></i><i class="glyphicon glyphicon-arrow-up"></i></a>
&nbsp;<a href="{% url "create-http-check" %}?instance={{ instance.id }}&service={{ service.id }}" class="" title="Add new Http check"><i class="glyphicon glyphicon-plus"></i><i class="glyphicon glyphicon-arrow-up"></i></a>
{% endif %}
{% if checks_type == "All" or checks_type == "Jenkins" %}
&nbsp;<a href="{% url "create-jenkins-check" %}?service={{ service.id }}" class="" title="Add new Jenkins check"><i class="glyphicon glyphicon-plus"></i><i class="glyphicon glyphicon-ok"></i></a>
&nbsp;<a href="{% url "create-jenkins-check" %}?instance={{ instance.id }}&service={{ service.id }}" class="" title="Add new Jenkins check"><i class="glyphicon glyphicon-plus"></i><i class="glyphicon glyphicon-ok"></i></a>
{% endif %}
{% if checks_type == "All" or checks_type == "ICMP" %}
&nbsp;<a href="{% url "create-icmp-check" %}?instance={{ instance.id }}&service={{ service.id }}" class="" title="Add new ICMP check"><i class="glyphicon glyphicon-plus"></i><i class="glyphicon glyphicon-transfer"></i></a>
{% endif %}
</h3>
</div>
@ -37,6 +40,7 @@
<th>Test description</th>
<th>Importance</th>
<th>Service(s)</th>
<th>Instance(s)</th>
<th></th>
</tr>
</thead>
@ -62,11 +66,11 @@
</td>
{% if checks_type == "All" %}
<td class="text-center">
<i class="glyphicon glyphicon-{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}signal{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}arrow-up{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}ok{% endif %}"></i>
<i class="glyphicon glyphicon-{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}signal{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}arrow-up{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}ok{% elif check.polymorphic_ctype.model == 'icmpstatuscheck' %}transfer{% endif %}"></i>
</td>
{% endif %}
<td title="">
{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}{{ check.metric|truncatechars:70 }} {{ check.check_type }} {{ check.value }}{% if check.expected_num_hosts %} (from {{ check.expected_num_hosts }} hosts){% endif %}{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}Status code {{ check.status_code }} from {{ check.endpoint }}{% if check.text_match %}; match text /{{ check.text_match }}/{% endif %}{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}Monitor job {{ check.name }}{% if check.max_queued_build_time %}; check no build waiting for >{{ check.max_queued_build_time }} minutes{% endif %}{% endif %}
{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}{{ check.metric|truncatechars:70 }} {{ check.check_type }} {{ check.value }}{% if check.expected_num_hosts %} (from {{ check.expected_num_hosts }} hosts){% endif %}{% elif check.polymorphic_ctype.model == 'icmpstatuscheck' %}ICMP Reply from {{ check.instance_set.all.0.address }}{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}Status code {{ check.status_code }} from {{ check.endpoint }}{% if check.text_match %}; match text /{{ check.text_match }}/{% endif %}{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}Monitor job {{ check.name }}{% if check.max_queued_build_time %}; check no build waiting for >{{ check.max_queued_build_time }} minutes{% endif %}{% endif %}
</td>
<td>{{ check.get_importance_display }}</td>
<td>
@ -81,10 +85,25 @@
<span class="label label-warning">No service</span>
{% endif %}
</td>
<td>
{% for instance in check.instance_set.all %}
<a href="{% url "instance" pk=instance.id %}">{{ instance.name }}</a>
{% if forloop.last %}
{% else %}
/
{% endif %}
{% endfor %}
{% if not check.instance_set.all %}
<span class="label label-warning">No instance</span>
{% endif %}
</td>
<td class="text-right">
<a class="btn btn-xs" href="{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}{% url "update-check" pk=check.id %}{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}{% url "update-http-check" pk=check.id %}{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}{% url "update-jenkins-check" pk=check.id %}{% endif %}">
<a class="btn btn-xs" href="{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}{% url "update-check" pk=check.id %}{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}{% url "update-http-check" pk=check.id %}{% elif check.polymorphic_ctype.model == 'icmpstatuscheck' %}{% url "update-icmp-check" pk=check.id %}{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}{% url "update-jenkins-check" pk=check.id %}{% endif %}">
<i class="glyphicon glyphicon-edit"></i><span class="break"></span>
</a>
<a class="btn btn-xs" href="{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}{% url "duplicate-graphite-check" pk=check.id %}{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}{% url "duplicate-http-check" pk=check.id %}{% elif check.polymorphic_ctype.model == 'icmpstatuscheck' %}{% url "duplicate-icmp-check" pk=check.id %}{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}{% url "duplicate-jenkins-check" pk=check.id %}{% endif %}">
<i class="fa fa-copy"></i><span class="break"></span>
</a>
<a class="btn btn-xs" href="{% url "run-check" pk=check.id %}">
<i class="glyphicon glyphicon-refresh"></i><span class="break"></span>
</a>

View File

@ -0,0 +1,18 @@
{% extends 'base.html' %}
{% block content %}
<h1>Delete service</h1>
<form action="." method="post">{% csrf_token %}
<button type="submit" class="btn btn-danger">Delete {{ object }}</button>
</form>
{% endblock %}
{% load compress %}
{% block js %}
{{ block.super }}
{% compress js %}
<script type="text/coffeescript">
</script>
{% endcompress %}
{% endblock %}

View File

@ -0,0 +1,209 @@
{% extends 'base.html' %}
{% block title %}{{ block.super }} - {{ instance.name }}{% endblock title %}
{% block content %}
<div class="row">
<div class="col-xs-12">
<div class="col-xs-1"><h2><i class="fa fa-desktop"></i></h2></div>
<div class="col-xs-5"><h2><span class="break"></span>{{ instance.name }}</h2></div>
<div class="col-xs-4 text-right"><h2><span class="label label-{% if instance.overall_status == instance.PASSING_STATUS %}success{% else %}danger{% endif %}">{{ instance.overall_status|lower|capfirst }}</span> </h2></div>
<div class="col-xs-2 text-right"><h2><a class = "" href="{% url "duplicate-instance" instance.id %}"><i class="fa fa-copy"></i> </a><a class = "" href="{% url update-instance instance.id %}"><i class="glyphicon glyphicon-edit"></i> </a></h2></div>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-6 col-xs-12">
<div class="col-xs-1"><h3><i class="fa fa-cog"></i></h3></div>
<div class="col-xs-11"><h3>Configuration</h3></div>
<div class="col-xs-12">
<div class="col-xs-1"><h5><i class="glyphicon glyphicon-home"></i></h5></div>
<div class="col-xs-3"><h5><span class="break"></span>Address</h5></div>
<div class="col-xs-8"><h5>{{ instance.address|urlize|default:"None configured" }}</h5></div>
</div>
<div class="col-xs-12">
<div class="col-xs-1"><h5><i class="glyphicon glyphicon-user"></i></h5></div>
<div class="col-xs-3"><h5><span class="break"></span>Users watching</h5></div>
<div class="col-xs-8"><h5>
{% if not instance.users_to_notify.all %}
No users subscribed
{% else %}
{{ instance.users_to_notify.all|join:", " }}
{% endif %}
</h5></div>
</div>
<div class="col-xs-12">
<div class="col-xs-1"><h5><i class="fa fa-exclamation-triangle"></i></h5></div>
<div class="col-xs-3"><h5><span class="break"></span>Alert types</h5></div>
<div class="col-xs-8">
<h5>
{% if instance.email_alert %}<i class="fa fa-envelope"></i> Email{% endif %}
{% if instance.hipchat_alert %}<i class="fa fa-comment"></i> Hipchat{% endif %}
{% if instance.sms_alert %}<i class="fa fa-mobile"></i> SMS{% endif %}
{% if instance.telephone_alert %}<i class="fa fa-phone"></i> Telephone{% endif %}
</h5>
</div>
</div>
</div>
<div class="col-xs-12 col-md-6">
<div class="col-xs-1"><h3><i class="fa fa-bar-chart-o"></i></h3></div>
<div class="col-xs-11"><h3>Status (24 hours)</h3></div>
<div class="col-xs-12">
<div id="graph" style="height: 150px; margin: 1 0px;"></div>
<div id="timeline"></div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="col-xs-1"><h3><i class="fa fa-gears"></i></h3></div>
<div class="col-xs-8"><h3>Services</h3></div>
</div>
{% include 'cabotapp/_service_list.html' with services=instance.service_set.all %}
</div>
</div>
<hr>
{% include 'cabotapp/_statuscheck_list.html' with checks=instance.graphite_status_checks.all instance=instance checks_type="Graphite" %}
<hr>
{% include 'cabotapp/_statuscheck_list.html' with checks=instance.http_status_checks.all instance=instance checks_type="Http" %}
<hr>
{% include 'cabotapp/_statuscheck_list.html' with checks=instance.jenkins_status_checks.all instance=instance checks_type="Jenkins" %}
<hr>
{% include 'cabotapp/_statuscheck_list.html' with checks=instance.icmp_status_checks.all instance=instance checks_type="ICMP" %}
<hr>
<div class="row">
<div class="col-xs-12">
<div class="col-xs-1"><h3><i class="fa fa-table"></i></h3></div>
<div class="col-xs-11">
<h3>Status check report</h3>
</div>
<div class="col-xs-12">
<form action="{% url "checks-report" %}" method="get">
<div class="form-group">
<div class="col-xs-12">
{{ report_form.instance }}
<label class="col-xs-2 control-label">{{ report_form.checks.label_tag }}</label>
<div class="col-xs-10">{{ report_form.checks }}</div>
</div>
</div>
<div class="form-group">
<div class="col-xs-12">
<label class="col-xs-2 control-label">{{ report_form.date_from.label_tag }}</label>
<div class="col-xs-10">{{ report_form.date_from }}</div>
</div>
</div>
<div class="form-group">
<div class="col-xs-12">
<label class="col-xs-2 control-label">{{ report_form.date_to.label_tag }}</label>
<div class="col-xs-10">{{ report_form.date_to }}</div>
</div>
</div>
<div class="col-xs-12">
<div class="form-group">
<div class="col-xs-6 col-xs-offset-2">
<button type="submit" class="btn btn-primary">Get report</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<hr>
{% endblock content %}
{% block js %}
{% load compress %}
{% load jsonify %}
{{ block.super }}
<script type="text/javascript">
window.INSTANCE_HISTORY = {{ instance.recent_snapshots|jsonify }}
</script>
<script type="text/javascript" src="{{ STATIC_URL }}arachnys/js/d3.js"></script>
{% compress js %}
<script type="text/javascript" src="{{ STATIC_URL }}arachnys/js/rickshaw.js"></script>
<script type="text/coffeescript">
$(document).ready ->
data = window.INSTANCE_HISTORY
labels = {
num_checks_active: 'blue'
num_checks_failing: '#f00'
}
processedData = formatDataForRickshaw data, labels
drawRickshaw processedData.series, labels, processedData.events
formatDataForRickshaw = (data, labels) ->
series = {}
events = []
for label, color of labels
series[label] = {
color: color
name: label
data: []
}
for slice in data
if slice.did_send_alert
events.push {time: slice.time, message: 'Sent alert'}
for label, color of labels
series[label].data.push
x: slice.time
y: slice[label]
ret = []
for line, val of series
ret.push val
return {
series: ret
events: events
}
drawRickshaw = (data, labels, events = []) ->
rickshawLine = new Rickshaw.Graph
renderer: 'line'
element: document.querySelector('#graph')
series: data
height: 140
rickshawLine.render()
hoverDetail = new Rickshaw.Graph.HoverDetail({graph: rickshawLine})
xAxis = new Rickshaw.Graph.Axis.Time({graph: rickshawLine})
xAxis.render()
yAxis = new Rickshaw.Graph.Axis.Y({graph: rickshawLine})
yAxis.render()
window.annotator = annotator = new Rickshaw.Graph.Annotate({
graph: rickshawLine
element: document.getElementById('timeline')
})
for evt in events
annotator.add evt.time, evt.message
annotator.update()
</script>
<script type="text/javascript">
$(function(){
$(':input.datepicker').datepicker({
dateFormat: 'yy-mm-dd',
buttonImage: '{{ STATIC_URL }}theme/img/calendar.gif',
buttonImageOnly: true,
showOn: 'button'
});
});
</script>
{% endcompress %}
{% endblock js %}

View File

@ -0,0 +1,36 @@
{% extends 'base.html' %}
{% block content %}
<div class="row">
<div class="col-xs-12">
<div class="col-xs-10 col-xs-offset-2">
<h2>{% if form.instance.id %}Edit instance{% else %}New instance{% endif %}</h2>
</div>
</div>
</div>
<form class="form-horizontal" action="" method="post" role="form">
{% include "cabotapp/_base_form.html" %}
<div class="col-xs-12">
<div class="form-group">
<div class="col-xs-6 col-xs-offset-2">
<button type="submit" class="btn btn-primary">Submit</button>
<a href="{% url "dashboard" %}" class="btn">Back to dashboard</a>
</div>
{% if form.instance.id %}
<div class="col-xs-4">
<a class="btn btn-danger" href="{% url "delete-instance" form.instance.id %}">Delete instance</a>
</div>
{% endif %}
</div>
</div>
</form>
{% endblock %}
{% load compress %}
{% block js %}
{{ block.super }}
{% compress js %}
<script type="text/coffeescript">
</script>
{% endcompress %}
{% endblock %}

View File

@ -0,0 +1,26 @@
{% extends 'base.html' %}
{% block content %}
<div class="row sortable ui-sortable">
<div class="col-xs-12" data-original-title="">
<div class="col-xs-1"><h2><i class="fa fa-desktop"></i></h2></div>
<div class="col-xs-10"><h2><span class="break"></span>Instances</h2></div>
<div class="col-xs-1 text-right">
<h2><a href="{% url "create-instance" %}" title="New instance"><i class="glyphicon glyphicon-plus"></i></a></h2>
</div>
</div>
<div class="col-xs-12">
{% include 'cabotapp/_instance_list.html' %}
</div>
</div>
{% endblock content %}
{% block js %}
{% load compress %}
{{ block.super }}
{% compress js %}
<script type="text/coffeescript">
</script>
{% endcompress %}
{% endblock js %}

View File

@ -55,7 +55,18 @@
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="col-xs-1"><h3><i class="fa fa-desktop"></i></h3></div>
<div class="col-xs-8"><h3>Instances</h3></div>
<div class="col-xs-3 text-right">
<h3>
<a href="{% url "create-instance" %}?service={{ service.id }}" class=""><i class="glyphicon glyphicon-plus" title="Add new instance for this service"></i><i class="fa fa-desktop" title=""></i></a>
</h3>
</div>
{% include 'cabotapp/_instance_list.html' with instances=service.instances.all %}
</div>
</div>
<hr>
{% include 'cabotapp/_statuscheck_list.html' with checks=service.graphite_status_checks.all service=service checks_type="Graphite" %}

View File

@ -10,44 +10,7 @@
</div>
</div>
<div class="col-xs-12">
{% if not services %}
No services
{% else %}
<table class="table bootstrap-datatable datatable">
<thead>
<tr>
<th>Name</th>
<th>Overall</th>
<th>Active checks</th>
<th>Disabled checks</th>
<th></th>
</tr>
</thead>
<tbody>
{% for service in services %}
<tr class="{% if service.alerts_enabled %}enabled{% else %}warning{% endif %}">
<td>
<a href="{% url "service" pk=service.id %}" title="Alerts {% if service.alerts_enabled %}enabled{% else %}disabled{% endif %}">{{service.name}} </a>
</td>
<td>
<span class="label label-{% if not service.alerts_enabled %}warning{% else %}{% if service.overall_status == service.PASSING_STATUS %}success{% else %}danger{% endif %}{% endif %}">{% if service.alerts_enabled %}{{ service.overall_status|lower|capfirst }}{% else %}Disabled{% endif %}</span>
</td>
<td>
<span class="label label-{% if service.active_status_checks.all.count > 0 %}{% if service.overall_status != service.PASSING_STATUS %}danger{% else %}success{% endif %}{% else %}default{% endif %}">{{ service.active_status_checks.all.count }}</span>
</td>
<td>
<span class="label label-{% if service.inactive_status_checks.all.count > 0 %}warning{% else %}default{% endif %}">{{ service.inactive_status_checks.all.count }}</span>
</td>
<td class="text-right">
<a class="btn btn-xs" href="{% url "update-service" pk=service.id %}" role="button">
<i class="glyphicon glyphicon-edit"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% include 'cabotapp/_service_list.html' %}
</div>
</div>
{% endblock content %}

View File

@ -6,14 +6,16 @@
<div class="row">
<div class="col-xs-12">
<div class="col-xs-1"><h2><i class="glyphicon glyphicon-{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}signal{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}arrow-up{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}ok{% endif %}"></i></h2></div>
<div class="col-xs-1"><h2><i class="glyphicon glyphicon-{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}signal{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}arrow-up{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}ok{% elif check.polymorphic_ctype.model == 'icmpstatuscheck' %}transfer{% endif %}"></i></h2></div>
<div class="col-xs-5"><h2>{{ check.name }}</h2></div>
<div class="col-xs-4 text-right"><h2><span class="label label-{% if check.calculated_status == 'passing' %}success{% else %}danger{% endif %}">{{ check.calculated_status|capfirst }}</span></h2></div>
<div class="col-xs-2 text-right"><h2>
{% if check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}
<a href="{% jenkins_human_url check.name %}" class=""><i class="glyphicon glyphicon-link"></i></a>
{% endif %}
<a href="{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}{% url "update-check" pk=check.id %}{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}{% url "update-http-check" pk=check.id %}{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}{% url "update-jenkins-check" pk=check.id %}{% endif %}" class=""><i class="glyphicon glyphicon-edit"></i></a> <a href="{% url "run-check" pk=check.id %}"><i class="glyphicon glyphicon-refresh"></i></a>
<a href="{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}{% url "update-check" pk=check.id %}{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}{% url "update-http-check" pk=check.id %}{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}{% url "update-jenkins-check" pk=check.id %}{% elif check.polymorphic_ctype.model == 'icmpstatuscheck' %}{% url "update-icmp-check" pk=check.id %}{% endif %}" class=""><i class="glyphicon glyphicon-edit"></i>
<a href="{% if check.polymorphic_ctype.model == 'graphitestatuscheck' %}{% url "duplicate-graphite-check" pk=check.id %}{% elif check.polymorphic_ctype.model == 'httpstatuscheck' %}{% url "duplicate-http-check" pk=check.id %}{% elif check.polymorphic_ctype.model == 'jenkinsstatuscheck' %}{% url "duplicate-jenkins-check" pk=check.id %}{% elif check.polymorphic_ctype.model == 'icmpstatuscheck' %}{% url "duplicate-icmp-check" pk=check.id %}{% endif %}" class=""><i class="fa fa-copy"></i>
</a> <a href="{% url "run-check" pk=check.id %}"><i class="glyphicon glyphicon-refresh"></i></a>
</h2></div>
</div>
</div>
@ -49,7 +51,7 @@
</td>
<td>{{ result.time_complete }}</td>
<td>{{ result.took }}</td>
<td>{{ result.error|default:"" }}</td>
<td>{% autoescape off %}{{ result.error|default:"" }}{% endautoescape %}</td>
</tr>
{% endfor %}
</tbody>

View File

@ -1,12 +1,17 @@
from django.conf.urls import patterns, include, url
from cabot.cabotapp.views import (
run_status_check, graphite_api_data, twiml_callback, checks_run_recently,
duplicate_icmp_check, duplicate_graphite_check, duplicate_http_check, duplicate_jenkins_check, duplicate_instance,
GraphiteCheckCreateView, GraphiteCheckUpdateView,
HttpCheckCreateView, HttpCheckUpdateView,
ICMPCheckCreateView, ICMPCheckUpdateView,
JenkinsCheckCreateView, JenkinsCheckUpdateView,
StatusCheckDeleteView, StatusCheckListView, StatusCheckDetailView,
StatusCheckResultDetailView, StatusCheckReportView)
from cabot.cabotapp.views import (ServiceListView, ServiceDetailView,
from cabot.cabotapp.views import (InstanceListView, InstanceDetailView,
InstanceUpdateView, InstanceCreateView, InstanceDeleteView,
ServiceListView, ServiceDetailView,
ServiceUpdateView, ServiceCreateView, ServiceDeleteView,
UserProfileUpdateView, ShiftListView, subscriptions)
from django.contrib import admin
@ -43,6 +48,21 @@ urlpatterns = patterns('',
url(r'^service/(?P<pk>\d+)/',
view=ServiceDetailView.as_view(), name='service'),
url(r'^instances/', view=InstanceListView.as_view(),
name='instances'),
url(r'^instance/create/', view=InstanceCreateView.as_view(),
name='create-instance'),
url(r'^instance/update/(?P<pk>\d+)/',
view=InstanceUpdateView.as_view(
), name='update-instance'),
url(r'^instance/duplicate/(?P<pk>\d+)/',
view=duplicate_instance, name='duplicate-instance'),
url(r'^instance/delete/(?P<pk>\d+)/',
view=InstanceDeleteView.as_view(
), name='delete-instance'),
url(r'^instance/(?P<pk>\d+)/',
view=InstanceDetailView.as_view(), name='instance'),
url(r'^checks/$', view=StatusCheckListView.as_view(),
name='checks'),
url(r'^check/run/(?P<pk>\d+)/',
@ -55,22 +75,39 @@ urlpatterns = patterns('',
url(r'^checks/report/$',
view=StatusCheckReportView.as_view(), name='checks-report'),
url(r'^icmpcheck/create/', view=ICMPCheckCreateView.as_view(),
name='create-icmp-check'),
url(r'^icmpcheck/update/(?P<pk>\d+)/',
view=ICMPCheckUpdateView.as_view(
), name='update-icmp-check'),
url(r'^icmpcheck/duplicate/(?P<pk>\d+)/',
view=duplicate_icmp_check, name='duplicate-icmp-check'),
url(r'^graphitecheck/create/',
view=GraphiteCheckCreateView.as_view(
), name='create-check'),
url(r'^graphitecheck/update/(?P<pk>\d+)/',
view=GraphiteCheckUpdateView.as_view(
), name='update-check'),
url(r'^graphitecheck/duplicate/(?P<pk>\d+)/',
view=duplicate_graphite_check, name='duplicate-graphite-check'),
url(r'^httpcheck/create/', view=HttpCheckCreateView.as_view(),
name='create-http-check'),
url(r'^httpcheck/update/(?P<pk>\d+)/',
view=HttpCheckUpdateView.as_view(
), name='update-http-check'),
url(r'^httpcheck/duplicate/(?P<pk>\d+)/',
view=duplicate_http_check, name='duplicate-http-check'),
url(r'^jenkins_check/create/', view=JenkinsCheckCreateView.as_view(),
name='create-jenkins-check'),
url(r'^jenkins_check/update/(?P<pk>\d+)/',
view=JenkinsCheckUpdateView.as_view(
), name='update-jenkins-check'),
url(r'^jenkins_check/duplicate/(?P<pk>\d+)/',
view=duplicate_jenkins_check, name='duplicate-jenkins-check'),
url(r'^result/(?P<service_id>\d+)/twiml_callback/',
view=twiml_callback, name='twiml-callback'),

View File

@ -3,16 +3,5 @@
import os
import gevent
from psycogreen.gevent import patch_psycopg
bind = '127.0.0.1:%s' % os.environ['PORT']
workers = 3
worker_class = 'gevent'
def post_fork(server, worker):
gevent.monkey.patch_all()
patch_psycopg()
worker.log.info('Made psycopg green')