implement health checks in sahara-dashboard
following things are done in change: * action to verify cluster; * view to display health check in cluster details; * new column for the health overview in clusters list. Implements blueprint: cluster-verification Depends-On: I598753cbb33d7781b646e726ad8d614e924e4876 Change-Id: I6c47d564400872ec4f38bf9d5824f93f4599181c
This commit is contained in:
parent
bf067510da
commit
46d9adb665
|
@ -524,3 +524,7 @@ def job_execution_delete(request, jex_id):
|
|||
|
||||
def job_types_list(request):
|
||||
return client(request).job_types.list()
|
||||
|
||||
|
||||
def verification_update(request, cluster_id, status):
|
||||
return client(request).clusters.verification_update(cluster_id, status)
|
||||
|
|
|
@ -77,6 +77,29 @@ class DeleteCluster(tables.DeleteAction):
|
|||
saharaclient.cluster_delete(request, obj_id)
|
||||
|
||||
|
||||
class CheckClusterAction(tables.BatchAction):
|
||||
name = 'check_cluster'
|
||||
|
||||
@staticmethod
|
||||
def action_present(count):
|
||||
return ungettext_lazy(
|
||||
u"Start Verification",
|
||||
u"Start Verifications",
|
||||
count
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def action_past(count):
|
||||
return ungettext_lazy(
|
||||
u"Started Verification",
|
||||
u"Started Verifications",
|
||||
count
|
||||
)
|
||||
|
||||
def action(self, request, datum_id):
|
||||
saharaclient.verification_update(request, datum_id, status='START')
|
||||
|
||||
|
||||
class UpdateClusterShares(tables.LinkAction):
|
||||
name = "update_shares"
|
||||
verbose_name = _("Update Shares")
|
||||
|
@ -165,6 +188,22 @@ class MakeUnProtected(acl_utils.MakeUnProtected):
|
|||
saharaclient.cluster_update(request, datum_id, **update_kwargs)
|
||||
|
||||
|
||||
def get_health_status_info(cluster):
|
||||
try:
|
||||
return cluster.verification['status']
|
||||
except (AttributeError, KeyError):
|
||||
return 'UNKNOWN'
|
||||
|
||||
|
||||
def get_health_filter(health):
|
||||
mapper = {'GREEN': 'success', 'YELLOW': 'warning',
|
||||
'RED': 'danger', 'CHECKING': 'info'}
|
||||
|
||||
label = mapper.get(health, 'default')
|
||||
return render_to_string('clusters/_health_status.html',
|
||||
{'status': health, 'label': label})
|
||||
|
||||
|
||||
class ClustersTable(tables.DataTable):
|
||||
|
||||
name = tables.Column("name",
|
||||
|
@ -184,6 +223,10 @@ class ClustersTable(tables.DataTable):
|
|||
status=True,
|
||||
filters=(rich_status_filter,))
|
||||
|
||||
health = tables.Column(get_health_status_info,
|
||||
verbose_name=_("Health"),
|
||||
filters=(get_health_filter,))
|
||||
|
||||
instances_count = tables.Column(get_instances_count,
|
||||
verbose_name=_("Instances Count"))
|
||||
|
||||
|
@ -203,4 +246,5 @@ class ClustersTable(tables.DataTable):
|
|||
row_actions = (ScaleCluster,
|
||||
UpdateClusterShares,
|
||||
DeleteCluster, MakePublic, MakePrivate,
|
||||
MakeProtected, MakeUnProtected)
|
||||
MakeProtected, MakeUnProtected,
|
||||
CheckClusterAction)
|
||||
|
|
|
@ -229,8 +229,20 @@ class EventLogTab(tabs.Tab):
|
|||
return kwargs
|
||||
|
||||
|
||||
class HealthChecksTab(tabs.Tab):
|
||||
name = _("Cluster health checks")
|
||||
slug = 'cluster_health_checks'
|
||||
template_name = "clusters/_health_checks_table.html"
|
||||
|
||||
def get_context_data(self, request, **kwargs):
|
||||
cluster_id = self.tab_group.kwargs['cluster_id']
|
||||
kwargs['cluster_id'] = cluster_id
|
||||
kwargs['data_update_url'] = request.get_full_path()
|
||||
return kwargs
|
||||
|
||||
|
||||
class ClusterDetailsTabs(tabs.TabGroup):
|
||||
slug = "cluster_details"
|
||||
tabs = (GeneralTab, ClusterConfigsDetails, NodeGroupsTab, InstancesTab,
|
||||
EventLogTab)
|
||||
EventLogTab, HealthChecksTab)
|
||||
sticky = True
|
||||
|
|
|
@ -73,6 +73,53 @@ class DataProcessingClusterTests(test.TestCase):
|
|||
self.assertEqual(3, step_1["completed"])
|
||||
self.assertEqual(0, len(step_1["events"]))
|
||||
|
||||
@test.create_stubs({api.sahara: ('cluster_get', )})
|
||||
def test_health_checks_tab_sc1(self):
|
||||
cluster = self.clusters.list()[-1]
|
||||
api.sahara.cluster_get(IsA(http.HttpRequest),
|
||||
"cl2").AndReturn(cluster)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse(
|
||||
'horizon:project:data_processing.clusters:verifications',
|
||||
args=["cl2"])
|
||||
res = self.client.get(url)
|
||||
data = jsonutils.loads(res.content)
|
||||
|
||||
self.assertFalse(data['need_update'])
|
||||
check0 = data['checks'][0]
|
||||
check1 = data['checks'][1]
|
||||
self.assertEqual('success', check0['label'])
|
||||
self.assertEqual('danger', check1['label'])
|
||||
|
||||
self.assertEqual('GREEN', check0['status'])
|
||||
self.assertEqual('RED', check1['status'])
|
||||
self.assertEqual('0:07:40', check0['duration'])
|
||||
|
||||
@test.create_stubs({api.sahara: ('cluster_get', )})
|
||||
def test_health_checks_tab_sc2(self):
|
||||
cluster = self.clusters.list()[0]
|
||||
cl1_id = 'ec9a0d28-5cfb-4028-a0b5-40afe23f1533'
|
||||
api.sahara.cluster_get(IsA(http.HttpRequest),
|
||||
cl1_id).AndReturn(cluster)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse(
|
||||
'horizon:project:data_processing.clusters:verifications',
|
||||
args=[cl1_id])
|
||||
res = self.client.get(url)
|
||||
data = jsonutils.loads(res.content)
|
||||
|
||||
self.assertTrue(data['need_update'])
|
||||
check0 = data['checks'][0]
|
||||
check1 = data['checks'][1]
|
||||
self.assertEqual('info', check0['label'])
|
||||
self.assertEqual('danger', check1['label'])
|
||||
|
||||
self.assertEqual('CHECKING', check0['status'])
|
||||
self.assertEqual('RED', check1['status'])
|
||||
self.assertEqual('Houston, we have a problem', check1['description'])
|
||||
|
||||
@test.create_stubs({api.sahara: ('cluster_list',
|
||||
'cluster_delete')})
|
||||
def test_delete(self):
|
||||
|
|
|
@ -170,6 +170,49 @@ class ClusterEventsView(django_base.View):
|
|||
content_type='application/json')
|
||||
|
||||
|
||||
class ClusterHealthChecksView(django_base.View):
|
||||
_date_format = "%Y-%m-%dT%H:%M:%S"
|
||||
_status_in_progress = 'CHECKING'
|
||||
|
||||
def _get_checks(self, cluster):
|
||||
try:
|
||||
return cluster.verification['checks']
|
||||
except (AttributeError, KeyError):
|
||||
return []
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
||||
time_helpers = helpers.Helpers(request)
|
||||
cluster_id = kwargs.get("cluster_id")
|
||||
need_update, not_done_count, checks = False, 0, []
|
||||
mapping_to_label_type = {'red': 'danger', 'yellow': 'warning',
|
||||
'green': 'success', 'checking': 'info'}
|
||||
try:
|
||||
cluster = saharaclient.cluster_get(request, cluster_id)
|
||||
for check in self._get_checks(cluster):
|
||||
check['label'] = mapping_to_label_type.get(
|
||||
check['status'].lower())
|
||||
|
||||
if not check['description']:
|
||||
check['description'] = _("No description")
|
||||
|
||||
if check['status'] == self._status_in_progress:
|
||||
not_done_count += 1
|
||||
check['duration'] = time_helpers.get_duration(
|
||||
check['created_at'], check['updated_at'])
|
||||
checks.append(check)
|
||||
except APIException:
|
||||
need_update = False
|
||||
checks = []
|
||||
if not_done_count > 0:
|
||||
need_update = True
|
||||
context = {"checks": checks,
|
||||
"need_update": need_update}
|
||||
|
||||
return HttpResponse(json.dumps(context),
|
||||
content_type='application/json')
|
||||
|
||||
|
||||
class CreateClusterView(workflows.WorkflowView):
|
||||
workflow_class = create_flow.CreateCluster
|
||||
success_url = \
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
{% load i18n %}
|
||||
|
||||
<h4>{% trans "Cluster health checks" %}</h4>
|
||||
<table id="sahara_health_checks_table" class="table table-bordered datatable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Duration" %}</th>
|
||||
<th>{% trans "Description" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sahara_health_checks_body">
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script type="text/javascript">
|
||||
var data_update_url = "{{ data_update_url }}"
|
||||
$(function() {
|
||||
// Initialize.
|
||||
horizon.verifications.data_update_url = "{{ data_update_url }}"
|
||||
horizon.verifications.update_health_checks();
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,2 @@
|
|||
<span class='label label-{{ label }}'>{{ status }}</span>
|
||||
|
|
@ -93,6 +93,9 @@ urlpatterns = patterns('',
|
|||
url(r'^cluster/(?P<cluster_id>[^/]+)/scale$',
|
||||
cluster_views.ScaleClusterView.as_view(),
|
||||
name='scale'),
|
||||
url(r'^cluster/(?P<cluster_id>[^/]+)/verifications$',
|
||||
cluster_views.ClusterHealthChecksView.as_view(),
|
||||
name='verifications'),
|
||||
url(r'^cluster/(?P<cluster_id>[^/]+)/update_shares$',
|
||||
cluster_views.UpdateClusterSharesView.as_view(),
|
||||
name='update-shares'),
|
||||
|
|
|
@ -415,8 +415,7 @@ class NewClusterConfigAction(c_flow.GeneralConfigAction):
|
|||
|
||||
class Meta(object):
|
||||
name = _("Configure Cluster")
|
||||
help_text_template = (
|
||||
"data_processing.clusters/_configure_general_help.html")
|
||||
help_text_template = "clusters/_configure_general_help.html"
|
||||
|
||||
|
||||
class ClusterGeneralConfig(workflows.Step):
|
||||
|
|
|
@ -102,7 +102,7 @@
|
|||
var form = $(".hidden_create_field").closest("form");
|
||||
var successful = false;
|
||||
form.submit(function (e) {
|
||||
var oldHref = $(".configure-nodegrouptemplate-btn")[0].href;
|
||||
var oldHref = $(".create_cluster_btn")[0].href;
|
||||
var plugin = $("#id_plugin_name option:selected").val();
|
||||
var version = $("#id_" + plugin + "_version option:selected").val();
|
||||
form.find(".close").click();
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
horizon.verifications = {
|
||||
data_update_url: null,
|
||||
|
||||
update_health_checks: function() {
|
||||
var url = this.data_update_url + "/verifications";
|
||||
$.get(url).done(function(data) {
|
||||
horizon.verifications.update_health_checks_view(data.checks);
|
||||
horizon.verifications.schedule_next_update(data);
|
||||
}).fail(function() {
|
||||
horizon.alert("error", gettext("Verification is not available."));
|
||||
});
|
||||
},
|
||||
|
||||
update_health_checks_view: function(checks) {
|
||||
// Clear health checks
|
||||
$("#sahara_health_checks_body").find("tr").remove();
|
||||
|
||||
$(checks).each(function (i, check) {
|
||||
horizon.verifications.create_check_row(check);
|
||||
});
|
||||
},
|
||||
|
||||
create_check_row: function(check) {
|
||||
var check_row_template = "" +
|
||||
"<tr id='%check_id%'>" +
|
||||
"<td>%status%</td>" +
|
||||
"<td>%name%</td>" +
|
||||
"<td>%duration%</td>" +
|
||||
"<td>%description%</td>" +
|
||||
"</tr>";
|
||||
|
||||
var status_template = "" +
|
||||
"<span class='label label-%label_type%'>%status_text%</span>";
|
||||
var status = status_template
|
||||
.replace(/%label_type%/g, check.label)
|
||||
.replace(/%status_text%/g, check.status);
|
||||
|
||||
var row = check_row_template
|
||||
.replace(/%check_id%/g, check.id)
|
||||
.replace(/%name%/g, check.name)
|
||||
.replace(/%duration%/g, check.duration)
|
||||
.replace(/%status%/g, status)
|
||||
.replace(/%description%/g, check.description);
|
||||
$("#sahara_health_checks_body").append(row);
|
||||
},
|
||||
|
||||
schedule_next_update: function(data) {
|
||||
// 2-3 sec delay so that if there are multiple tabs polling the
|
||||
// backend the requests are spread in time
|
||||
var delay = 2000 + Math.floor((Math.random() * 1000) + 1);
|
||||
|
||||
if (data.need_update) {
|
||||
setTimeout(function() {
|
||||
horizon.verifications.update_health_checks(); }, delay);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
|
@ -29,7 +29,8 @@ ADD_INSTALLED_APPS = \
|
|||
"sahara_dashboard.content.data_processing.clusters", ]
|
||||
|
||||
ADD_JS_FILES = [
|
||||
'dashboard/project/data_processing/data_processing.event_log.js'
|
||||
'dashboard/project/data_processing/data_processing.event_log.js',
|
||||
'dashboard/project/data_processing/data_processing.verifications.js'
|
||||
]
|
||||
|
||||
ADD_EXCEPTIONS = {
|
||||
|
|
|
@ -337,7 +337,27 @@ def data(TEST):
|
|||
"updated_at": "2014-06-04T20:02:15",
|
||||
"user_keypair_id": "stackboxkp"
|
||||
}
|
||||
|
||||
cluster1_dict.update({
|
||||
'verification': {
|
||||
'status': 'CHECKING',
|
||||
'checks': [
|
||||
{
|
||||
'status': 'CHECKING',
|
||||
'name': "Stupid check",
|
||||
'description': "Stupid description",
|
||||
"created_at": "2015-03-27T15:51:54",
|
||||
"updated_at": "2015-03-27T15:59:34",
|
||||
},
|
||||
{
|
||||
'status': 'RED',
|
||||
'name': "Stupid check",
|
||||
'description': "Houston, we have a problem",
|
||||
"created_at": "2015-03-27T15:51:54",
|
||||
"updated_at": "2015-03-27T15:59:34",
|
||||
},
|
||||
]
|
||||
}
|
||||
})
|
||||
cluster1 = clusters.Cluster(
|
||||
clusters.ClusterManager(None), cluster1_dict)
|
||||
TEST.clusters.add(cluster1)
|
||||
|
@ -346,6 +366,25 @@ def data(TEST):
|
|||
cluster2_dict.update({
|
||||
"id": "cl2",
|
||||
"name": "cl2_name",
|
||||
'verification': {
|
||||
'status': 'RED',
|
||||
'checks': [
|
||||
{
|
||||
'status': 'GREEN',
|
||||
'name': "Stupid check",
|
||||
'description': "Stupid description",
|
||||
"created_at": "2015-03-27T15:51:54",
|
||||
"updated_at": "2015-03-27T15:59:34",
|
||||
},
|
||||
{
|
||||
'status': 'RED',
|
||||
'name': "Stupid check",
|
||||
'description': "Houston, we have a problem",
|
||||
"created_at": "2015-03-27T15:51:54",
|
||||
"updated_at": "2015-03-27T15:59:34",
|
||||
},
|
||||
]
|
||||
},
|
||||
"provision_progress": [
|
||||
{
|
||||
"created_at": "2015-03-27T15:51:54",
|
||||
|
|
Loading…
Reference in New Issue