Skip to content

Commit

Permalink
SACGF/variantgrid_private#3689 provide a page that up time robot can …
Browse files Browse the repository at this point in the history
…check that checks more
  • Loading branch information
TheMadBug committed Aug 19, 2024
1 parent 332e3e7 commit 941f4b5
Show file tree
Hide file tree
Showing 16 changed files with 195 additions and 6 deletions.
2 changes: 1 addition & 1 deletion eventlog/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.http.response import HttpResponseRedirectBase
from django.urls import resolve

from auth.session_refresh import VariantGridSessionRefresh
from oidc_auth.session_refresh import VariantGridSessionRefresh
from eventlog.models import ViewEvent

IGNORE_SEGMENTS = {"api", "datatable", "citations_json"} # this should be mostly redundant to is_ajax call
Expand Down
10 changes: 10 additions & 0 deletions library/keycloak.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ def __init__(self, connector: Optional[ServerAuth] = None):
self.connector = connector
self.realm = settings.KEY_CLOAK_REALM

def ping(self):
full_url = self.connector.url(f'/auth/admin/realms/{self.realm}/clients')
print(full_url)
response = requests.get(
auth=self.connector.auth,
url=self.connector.url(f'/auth/admin/realms/{self.realm}/clients'),
timeout=MINUTE_SECS,
)
response.raise_for_status()

def change_password(self, user: User):
user_settings = UserSettings.get_for_user(user)
if not user_settings.oauth_sub:
Expand Down
78 changes: 78 additions & 0 deletions library/uptime_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from dataclasses import dataclass
from enum import Enum, IntEnum
from functools import cached_property
from typing import Optional
import django
from django.contrib.auth.models import User

from library.utils import text_utils


###
# Uptime Check is similar to health check but
# The results is to be displayed to a public web page, so it needs to not show sensitive information
# The results are more about if critical systems are currently working or not, so we can get an alert from uptime robot if they're not
###

class UptimeCheckStatus(IntEnum):
OKAY = 1
NON_CRITICAL_FAILURE = 2
CRITICAL_FAILURE = 3
EXCEPTION_WHEN_CHECKING = 4

@property
def pretty_label(self):
return text_utils.pretty_label(self.name)

def severity(self) -> str:
match self:
case UptimeCheckStatus.OKAY: return "S"
case UptimeCheckStatus.NON_CRITICAL_FAILURE: return "E"
case _: return "C"

@dataclass
class UptimeCheckResponse:
name: str
status: UptimeCheckStatus
note: Optional[str] = None


@dataclass
class UptimeCheckOverall:
uptime_checks: list[UptimeCheckResponse]

@cached_property
def status(self) -> UptimeCheckStatus:
return max(uc.status for uc in self.uptime_checks)


uptime_check_signal = django.dispatch.Signal()


def retrieve_uptime_response():
all_uptimes = []

status = UptimeCheckStatus.OKAY
try:
# note that it's quite likely if the database is down, something well before this will cause an exception
# but feels dishonest to say the database is okay without checking anything
User.objects.first()
except:
status = UptimeCheckStatus.CRITICAL_FAILURE
all_uptimes.append(UptimeCheckResponse(
name="Database",
status=status
))

for caller, result in uptime_check_signal.send_robust(sender=None):
if result:
if isinstance(result, Exception):
all_uptimes.append(UptimeCheckResponse(
name=str(caller),
status=UptimeCheckStatus.EXCEPTION_WHEN_CHECKING,
note=f"Exception generating health check by {caller}: {result}"
))
else:
all_uptimes.append(result)

return UptimeCheckOverall(uptime_checks=all_uptimes)
File renamed without changes.
File renamed without changes.
14 changes: 14 additions & 0 deletions oidc_auth/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from django.apps import AppConfig

# TODO rename to OIDCAuth

# noinspection PyUnresolvedReferences
class OIDCAuthConfig(AppConfig):
name = 'oidc_auth'

# noinspection PyUnresolvedReferences
def ready(self):
# pylint: disable=import-outside-toplevel
# imported to activate receivers
from oidc_auth.signals import keycloak_uptime_check # pylint: disable=unused-import
# pylint: enable=import-outside-toplevel
File renamed without changes.
File renamed without changes.
File renamed without changes.
Empty file added oidc_auth/signals/__init__.py
Empty file.
28 changes: 28 additions & 0 deletions oidc_auth/signals/keycloak_uptime_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from typing import Optional

from django.dispatch import receiver
from django.conf import settings

from library.keycloak import Keycloak
from library.uptime_check import uptime_check_signal, UptimeCheckResponse, UptimeCheckStatus


@receiver(signal=uptime_check_signal)
def keycloak_uptime_check(sender, **kwargs):
if not settings.KEYCLOAK_SYNC_DETAILS:
return None

status = UptimeCheckStatus.OKAY
note: Optional[str] = None
try:
Keycloak().ping()
except Exception as ex:
print(str(ex))
status = UptimeCheckStatus.CRITICAL_FAILURE
note = str(ex)

return UptimeCheckResponse(
name="KeyCloak",
status=status,
note=note
)
30 changes: 30 additions & 0 deletions snpdb/templates/snpdb/uptime_check.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{% extends "uicore/page/base_external.html" %}
{% load ui_utils %}
{% block content %}
<div class="container mt-4">
<div class="jumbotron">
<h1>Uptime Status</h1>
<table class="table">
<thead>
<tr>
<th>System</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for uptime_check in uptime_response.uptime_checks %}
<tr>
<td>{{ uptime_check.name }}</td>
<td>{{ uptime_check.status.severity | severity_icon }}{{ uptime_check.status.pretty_label }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if uptime_response.status == 1 %}
<div class="alert alert-success">Everything is OKAY</div>
{% else %}
<div class="alert alert-danger">{{ uptime_response.status.severity | severity_icon }}Overall Status {{ uptime_response.status.pretty_label }}</div>
{% endif %}
</div>
</div>
{% endblock %}
5 changes: 4 additions & 1 deletion snpdb/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,12 @@
perm_path('autocomplete/Lab/', views_autocomplete.LabAutocompleteView.as_view(), name='lab_autocomplete'),
perm_path('autocomplete/VCF/', views_autocomplete.VCFAutocompleteView.as_view(), name='vcf_autocomplete'),

#Previews
# Previews
perm_path('preview/<str:db>/<str:idx>', preview_view, name='preview_data'),

# For Uptime Robot
perm_path('uptime_check', views.view_uptime, name='uptime_check'),

# Debug dev help
perm_path('ajax_hello_world/<str:data>', views.ajax_hello_world, name='ajax_hello_world'),
]
Expand Down
7 changes: 7 additions & 0 deletions snpdb/views/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
from genes.custom_text_gene_list import create_custom_text_gene_list
from genes.forms import CustomGeneListForm, UserGeneListForm, GeneAndTranscriptForm
from genes.models import GeneListCategory, CustomTextGeneList, GeneList
from library import uptime_check
from library.constants import WEEK_SECS, HOUR_SECS
from library.django_utils import add_save_message, get_model_fields, set_form_read_only, require_superuser, \
get_field_counts
Expand Down Expand Up @@ -1668,3 +1669,9 @@ def view_contig(request, contig_accession):
"genome_build": genome_build,
}
return render(request, "snpdb/genomics/view_contig.html", context)


@login_not_required
def view_uptime(request):
uptime_response = uptime_check.retrieve_uptime_response()
return render(request, "snpdb/uptime_check.html", {"uptime_response": uptime_response})
7 changes: 4 additions & 3 deletions variantgrid/settings/env/shariantcommon.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,17 @@
]

# Keycloak
INSTALLED_APPS = ["oidc_auth"] + INSTALLED_APPS

AUTHENTICATION_BACKENDS = (
'auth.backend.VariantGridOIDCAuthenticationBackend',
'oidc_auth.backend.VariantGridOIDCAuthenticationBackend',
'django.contrib.auth.backends.ModelBackend', # default
'guardian.backends.ObjectPermissionBackend',
)

MIDDLEWARE += (
'auth.session_refresh.VariantGridSessionRefresh',
'auth.oidc_error_handler.HandleOIDC400Middleware',
'oidc_auth.session_refresh.VariantGridSessionRefresh',
'oidc_auth.oidc_error_handler.HandleOIDC400Middleware',
)

REST_FRAMEWORK = {
Expand Down
20 changes: 19 additions & 1 deletion variantopedia/templates/variantopedia/view_allele.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,25 @@
{% settings_value 'CLINGEN_ALLELE_REGISTRY_DOMAIN' as clingen_url %}

<div class="container">
{% page_help page_id='variantdetails/view_allele_help' title='Allele' %}
{% page_help_embedded title='Allele' %}
<p>
An allele is a genetic change independent of a genome builds. Variants from different builds link to the same allele.
</p>

<p>
Variant classifications are compared across alleles, so classification differences can be detected regardless of what build it was originally classified against. Classifying a variant will create an allele, and linked variants for all genome builds supported by the server.
</p>

<p>
Alleles generally have 1 (and only 1) ClinGen Allele ID, from the <a href="http://reg.clinicalgenome.org">ClinGen Allele Registry</a>, which we use to perform liftover between builds.
</p>
{% settings_value "LIFTOVER_BCFTOOLS_ENABLED" as bcftools_enabled %}
{% if bcftools_enabled %}
<p>
If ClinGen fails, we use <a href="https://samtools.github.io/bcftools/">BCF tools</a>.
</p>
{% endif %}
{% end_page_help_embedded %}

{% labelled label="Internal Allele ID" %}
{{ allele_card.allele.pk }} {% admin_link allele_card.allele %}
Expand Down

0 comments on commit 941f4b5

Please sign in to comment.