Update the clustering and job processor app to use the ClusterScope setting and not their own settings singleton

This commit is contained in:
Roberto Rosario
2012-09-15 04:56:58 -04:00
parent fa2795dd96
commit a4bbc65508
17 changed files with 244 additions and 218 deletions
+4 -5
View File
@@ -1,3 +1,4 @@
"""
from __future__ import absolute_import
from django.utils.translation import ugettext_lazy as _
@@ -5,14 +6,11 @@ from django.db import transaction, DatabaseError
from scheduler.api import LocalScheduler
from navigation.api import bind_links
from project_tools.api import register_tool
from project_setup.api import register_setup
from .tasks import send_heartbeat, house_keeping
from .links import tool_link, node_list, clustering_config_edit, setup_link
from .models import Node, ClusteringConfig
from .links import tool_link, node_list, clustering_config_edit
from .models import Node
ClusteringConfig()
@transaction.commit_on_success
def add_clustering_jobs():
clustering_scheduler = LocalScheduler('clustering', _(u'Clustering'))
@@ -29,3 +27,4 @@ register_tool(tool_link)
#register_setup(setup_link)
bind_links([Node, 'node_list'], [node_list], menu_name='secondary_menu')
bind_links(['clustering_config_edit'], [clustering_config_edit], menu_name='secondary_menu')
"""
-10
View File
@@ -1,10 +0,0 @@
from __future__ import absolute_import
from django import forms
from .models import ClusteringConfig
class ClusteringConfigForm(forms.ModelForm):
class Meta:
model = ClusteringConfig
-2
View File
@@ -9,5 +9,3 @@ from .icons import icon_tool_link, icon_node_link
tool_link = Link(text=_(u'clustering'), view='node_list', icon=icon_tool_link, permissions=[PERMISSION_NODES_VIEW])
node_list = Link(text=_(u'node list'), view='node_list', icon=icon_node_link, permissions=[PERMISSION_NODES_VIEW])
clustering_config_edit = Link(text=_(u'edit cluster configuration'), view='clustering_config_edit', permissions=[PERMISSION_EDIT_CLUSTER_CONFIGURATION])
setup_link = Link(text=_(u'cluster configuration'), view='clustering_config_edit', permissions=[PERMISSION_EDIT_CLUSTER_CONFIGURATION])
@@ -0,0 +1,39 @@
# -*- 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):
# Deleting model 'ClusteringConfig'
db.delete_table('clustering_clusteringconfig')
def backwards(self, orm):
# Adding model 'ClusteringConfig'
db.create_table('clustering_clusteringconfig', (
('lock_id', self.gf('django.db.models.fields.CharField')(default=1, max_length=1, unique=True)),
('node_heartbeat_interval', self.gf('django.db.models.fields.PositiveIntegerField')(default=10)),
('dead_node_removal_interval', self.gf('django.db.models.fields.PositiveIntegerField')(default=10)),
('node_heartbeat_timeout', self.gf('django.db.models.fields.PositiveIntegerField')(default=60)),
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
))
db.send_create_signal('clustering', ['ClusteringConfig'])
models = {
'clustering.node': {
'Meta': {'object_name': 'Node'},
'cpuload': ('django.db.models.fields.FloatField', [], {'default': '0', 'blank': 'True'}),
'heartbeat': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 9, 15, 0, 0)', 'blank': 'True'}),
'hostname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'memory_usage': ('django.db.models.fields.FloatField', [], {'default': '0', 'blank': 'True'}),
'state': ('django.db.models.fields.CharField', [], {'default': "'d'", 'max_length': '4'})
}
}
complete_apps = ['clustering']
+8 -27
View File
@@ -30,6 +30,14 @@ class NodeManager(models.Manager):
def live_nodes(self):
return self.model.objects.filter(state=NODE_STATE_HEALTHY)
def dead_nodes(self):
from .literals import DEFAULT_NODE_HEARTBEAT_TIMEOUT
return self.model.objects.filter(state=NODE_STATE_HEALTHY).filter(heartbeat__lt=datetime.datetime.now() - datetime.timedelta(seconds=DEFAULT_NODE_HEARTBEAT_TIMEOUT))
def check_dead_nodes(self):
for node in self.dead_nodes():
node.mark_as_dead()
class Node(models.Model):
hostname = models.CharField(max_length=255, verbose_name=_(u'hostname'), unique=True)
@@ -77,30 +85,3 @@ class Node(models.Model):
class Meta:
verbose_name = _(u'node')
verbose_name_plural = _(u'nodes')
class ClusteringConfigManager(models.Manager):
def dead_nodes(self):
return Node.objects.filter(state=NODE_STATE_HEALTHY).filter(heartbeat__lt=datetime.datetime.now() - datetime.timedelta(seconds=self.model.get().node_heartbeat_timeout))
def check_dead_nodes(self):
for node in self.dead_nodes():
node.mark_as_dead()
class ClusteringConfig(Singleton):
node_heartbeat_interval = models.PositiveIntegerField(verbose_name=(u'node heartbeat interval (in seconds)'), help_text=_(u'Interval of time for the node\'s heartbeat update to the cluster.'), default=DEFAULT_NODE_HEARTBEAT_INTERVAL)
node_heartbeat_timeout = models.PositiveIntegerField(verbose_name=(u'node heartbeat timeout (in seconds)'), help_text=_(u'After this amount of time a node without heartbeat updates is considered dead and removed from the cluster node list.'), default=DEFAULT_NODE_HEARTBEAT_TIMEOUT)
dead_node_removal_interval = models.PositiveIntegerField(verbose_name=(u'dead node check and removal interval (in seconds)'), help_text=_(u'Interval of time to check the cluster for unresponsive nodes and remove them from the cluster.'), default=DEFAULT_DEAD_NODE_REMOVAL_INTERVAL)
cluster = ClusteringConfigManager()
def __unicode__(self):
return ugettext('clustering config')
#def clean(self):
# if self.node_heartbeat_interval > self.node_heartbeat_timeout:
# raise ValidationError(_(u'Heartbeat interval cannot be greater than heartbeat timeout or else nodes will always be rated as "dead"'))
class Meta:
verbose_name = verbose_name_plural = _(u'clustering config')
+28
View File
@@ -0,0 +1,28 @@
from __future__ import absolute_import
from django.utils.translation import ugettext_lazy as _
from django.db import transaction, DatabaseError
from scheduler.api import LocalScheduler
from navigation.api import bind_links
from .tasks import send_heartbeat, house_keeping
from .links import tool_link, node_list
from .models import Node
from .settings import NODE_HEARTBEAT_INTERVAL, DEAD_NODE_REMOVAL_INTERVAL
@transaction.commit_on_success
def add_clustering_jobs():
clustering_scheduler = LocalScheduler('clustering', _(u'Clustering'))
try:
# TODO: auto convert setting using JSON loads
clustering_scheduler.add_interval_job('send_heartbeat', _(u'Update a node\'s properties.'), send_heartbeat, seconds=int(NODE_HEARTBEAT_INTERVAL))
clustering_scheduler.add_interval_job('house_keeping', _(u'Check for unresponsive nodes in the cluster list.'), house_keeping, seconds=int(DEAD_NODE_REMOVAL_INTERVAL))
except DatabaseError:
transaction.rollback()
clustering_scheduler.start()
def init_clustering():
add_clustering_jobs()
bind_links([Node, 'node_list'], [node_list], menu_name='secondary_menu')
+7
View File
@@ -7,11 +7,13 @@ from smart_settings import ClusterScope
from .icons import icon_tool_link
from .literals import (DEFAULT_NODE_HEARTBEAT_INTERVAL, DEFAULT_NODE_HEARTBEAT_TIMEOUT,
DEFAULT_DEAD_NODE_REMOVAL_INTERVAL)
from .links import tool_link
label = _(u'Clustering')
description = _(u'Registers nodes into a Citadel (Mayan EDMS cluster).')
dependencies = ['app_registry', 'icons', 'navigation', 'scheduler']
icon = icon_tool_link
tool_links = [tool_link]
settings = [
{
'name': 'NODE_HEARTBEAT_INTERVAL',
@@ -32,3 +34,8 @@ settings = [
'scopes': [ClusterScope()]
},
]
# TODO: implement settings post edit clean like method for sanity checks
#def clean(self):
# if self.node_heartbeat_interval > self.node_heartbeat_timeout:
# raise ValidationError(_(u'Heartbeat interval cannot be greater than heartbeat timeout or else nodes will always be rated as "dead"'))
+2 -2
View File
@@ -4,7 +4,7 @@ import logging
from lock_manager.decorators import simple_locking
from .models import Node, ClusteringConfig
from .models import Node
from .signals import node_heartbeat
LOCK_EXPIRE = 10
@@ -23,5 +23,5 @@ def send_heartbeat():
@simple_locking('house_keeping', 10)
def house_keeping():
logger.debug('starting')
ClusteringConfig.cluster.check_dead_nodes()
Node.objects.check_dead_nodes()
-1
View File
@@ -3,5 +3,4 @@ from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('clustering.views',
url(r'^node/list/$', 'node_list', (), 'node_list'),
url(r'^edit/$', 'clustering_config_edit', (), 'clustering_config_edit'),
)
+1 -33
View File
@@ -13,8 +13,7 @@ from permissions.models import Permission
from common.utils import encapsulate
from acls.models import AccessEntry
from .forms import ClusteringConfigForm
from .models import Node, ClusteringConfig
from .models import Node
from .permissions import PERMISSION_NODES_VIEW, PERMISSION_EDIT_CLUSTER_CONFIGURATION
@@ -70,34 +69,3 @@ def node_workers(request, node_pk):
return render_to_response('generic_list.html', context,
context_instance=RequestContext(request))
def clustering_config_edit(request):
Permission.objects.check_permissions(request.user, [PERMISSION_EDIT_CLUSTER_CONFIGURATION])
cluster_config = ClusteringConfig.get()
post_action_redirect = None
previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', '/')))
next = request.POST.get('next', request.GET.get('next', post_action_redirect if post_action_redirect else request.META.get('HTTP_REFERER', '/')))
if request.method == 'POST':
form = ClusteringConfigForm(data=request.POST)
if form.is_valid():
try:
form.save()
except Exception, exc:
messages.error(request, _(u'Error trying to edit cluster configuration; %s') % exc)
else:
messages.success(request, _(u'Cluster configuration edited successfully.'))
return HttpResponseRedirect(next)
else:
form = ClusteringConfigForm(instance=cluster_config)
return render_to_response('generic_form.html', {
'form': form,
'object': cluster_config,
'title': _(u'Edit cluster configuration')
}, context_instance=RequestContext(request))