diff --git a/sahara_dashboard/api/sahara.py b/sahara_dashboard/api/sahara.py index 4733010c..6813103c 100644 --- a/sahara_dashboard/api/sahara.py +++ b/sahara_dashboard/api/sahara.py @@ -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) diff --git a/sahara_dashboard/content/data_processing/clusters/clusters/tables.py b/sahara_dashboard/content/data_processing/clusters/clusters/tables.py index 4c2c7962..7819a6e1 100644 --- a/sahara_dashboard/content/data_processing/clusters/clusters/tables.py +++ b/sahara_dashboard/content/data_processing/clusters/clusters/tables.py @@ -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) diff --git a/sahara_dashboard/content/data_processing/clusters/clusters/tabs.py b/sahara_dashboard/content/data_processing/clusters/clusters/tabs.py index cb8832f9..6cc1c512 100644 --- a/sahara_dashboard/content/data_processing/clusters/clusters/tabs.py +++ b/sahara_dashboard/content/data_processing/clusters/clusters/tabs.py @@ -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 diff --git a/sahara_dashboard/content/data_processing/clusters/clusters/tests.py b/sahara_dashboard/content/data_processing/clusters/clusters/tests.py index 31da4e2a..a4dacf47 100644 --- a/sahara_dashboard/content/data_processing/clusters/clusters/tests.py +++ b/sahara_dashboard/content/data_processing/clusters/clusters/tests.py @@ -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): diff --git a/sahara_dashboard/content/data_processing/clusters/clusters/views.py b/sahara_dashboard/content/data_processing/clusters/clusters/views.py index ed4c1d9d..a5e17de1 100644 --- a/sahara_dashboard/content/data_processing/clusters/clusters/views.py +++ b/sahara_dashboard/content/data_processing/clusters/clusters/views.py @@ -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 = \ diff --git a/sahara_dashboard/content/data_processing/clusters/templates/clusters/_health_checks_table.html b/sahara_dashboard/content/data_processing/clusters/templates/clusters/_health_checks_table.html new file mode 100644 index 00000000..d02400f1 --- /dev/null +++ b/sahara_dashboard/content/data_processing/clusters/templates/clusters/_health_checks_table.html @@ -0,0 +1,24 @@ +{% load i18n %} + +
{% trans "Status" %} | +{% trans "Name" %} | +{% trans "Duration" %} | +{% trans "Description" %} | +
---|