Skip to content

Commit c03072c

Browse files
authored
Add Meta endpoints. (#539)
These endpoints are not API specific, they are at the root of the server as /api/meta/versions and /api/meta/heartbeat. - Heartbeat (start time and uptime) is open for anyone authenticated. - Versions (python, django, drf) is available to admins.
1 parent 08e227d commit c03072c

File tree

3 files changed

+80
-11
lines changed

3 files changed

+80
-11
lines changed

mreg/api/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@
55
urlpatterns = [
66
path('token-logout/', views.TokenLogout.as_view()),
77
path('token-auth/', views.ObtainExpiringAuthToken.as_view()),
8+
path('meta/heartbeat', views.MetaHeartbeat.as_view()),
9+
path('meta/versions', views.MetaVersions.as_view()),
810
]

mreg/api/v1/tests/tests.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ def assert_get_and_400(self, path, **kwargs):
121121
def assert_get_and_401(self, path, **kwargs):
122122
return self._assert_get_and_status(path, 401, **kwargs)
123123

124+
def assert_get_and_403(self, path, **kwargs):
125+
return self._assert_get_and_status(path, 403, **kwargs)
126+
124127
def assert_get_and_404(self, path, **kwargs):
125128
return self._assert_get_and_status(path, 404, **kwargs)
126129

@@ -280,6 +283,26 @@ def test_login_with_invalid_credentials(self):
280283
self.assert_post_and_400("/api/token-auth/", {"who": "someone", "why": "because"})
281284
self.assert_post_and_400("/api/token-auth/", {})
282285

286+
class APIMetaTestCase(MregAPITestCase):
287+
"""Test the meta API endpoint."""
288+
289+
def test_meta_versions_admin_200_ok(self):
290+
response = self.assert_get_and_200("/api/meta/versions")
291+
for key in ('rest_framework_version', 'django_version', 'python_version'):
292+
self.assertTrue(key in response.data)
293+
print(key, response.data[key])
294+
295+
def test_meta_versions_user_403_forbidden(self):
296+
self.client = self.get_token_client(superuser=False)
297+
self.assert_get_and_403("/api/meta/versions")
298+
299+
def test_meta_heartbeat_user_200_ok(self):
300+
self.client = self.get_token_client(superuser=False)
301+
response = self.assert_get("/api/meta/heartbeat")
302+
for key in ('uptime', 'start_time'):
303+
self.assertTrue(key in response.data)
304+
print(key, response.data[key])
305+
283306

284307
class APIAutoupdateZonesTestCase(MregAPITestCase):
285308
"""This class tests the autoupdate of zones' updated_at whenever

mreg/api/views.py

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,46 @@
1-
from rest_framework import status
1+
import platform
2+
import time
3+
from typing import Any, cast
4+
5+
import django
6+
from rest_framework import __version__ as res_version
7+
from rest_framework import serializers, status
28
from rest_framework.authtoken.views import ObtainAuthToken
9+
from rest_framework.exceptions import AuthenticationFailed
310
from rest_framework.permissions import IsAuthenticated
11+
from rest_framework.request import Request
412
from rest_framework.response import Response
513
from rest_framework.views import APIView
6-
from rest_framework import serializers
7-
from rest_framework.exceptions import AuthenticationFailed
814

15+
from mreg.api.permissions import IsSuperOrNetworkAdminMember
916
from mreg.models.base import ExpiringToken
1017

18+
start_time = int(time.time())
19+
1120

1221
class ObtainExpiringAuthToken(ObtainAuthToken):
1322

14-
def post(self, request, *args, **kwargs):
15-
serializer = self.serializer_class(data=request.data,
16-
context={'request': request})
23+
def post(self, request: Request, *args: Any, **kwargs: Any):
24+
serializer = self.serializer_class(data=request.data, context={"request": request})
1725
try:
1826
serializer.is_valid(raise_exception=True)
1927
except serializers.ValidationError as err:
20-
if 'username' in request.POST and 'password' in request.POST:
28+
if (
29+
isinstance(request.POST, dict)
30+
and "username" in request.POST
31+
and "password" in request.POST
32+
):
2133
raise AuthenticationFailed()
2234
else:
2335
raise err
2436

25-
user = serializer.validated_data['user']
37+
if (
38+
not isinstance(serializer.validated_data, dict)
39+
or "user" not in serializer.validated_data
40+
):
41+
raise AuthenticationFailed()
42+
43+
user = cast(str, serializer.validated_data["user"])
2644

2745
token, created = ExpiringToken.objects.get_or_create(user=user)
2846

@@ -31,15 +49,41 @@ def post(self, request, *args, **kwargs):
3149
ExpiringToken.objects.filter(user=user).delete()
3250
token, _ = ExpiringToken.objects.get_or_create(user=user)
3351

34-
return Response({'token': token.key})
52+
return Response({"token": token.key})
3553

3654

3755
class TokenLogout(APIView):
3856

39-
permission_classes = (IsAuthenticated, )
57+
permission_classes = (IsAuthenticated,)
4058

41-
def post(self, request):
59+
def post(self, request: Request):
4260
# delete the user on logout to clean up the local user database and
4361
# group memberships. As the user owns the token, it will also be deleted.
4462
request.user.delete()
4563
return Response(status=status.HTTP_200_OK)
64+
65+
66+
class MetaVersions(APIView):
67+
68+
permission_classes = (IsSuperOrNetworkAdminMember,)
69+
70+
def get(self, request: Request):
71+
data = {
72+
"django_version": django.get_version(),
73+
"rest_framework_version": res_version,
74+
"python_version": platform.python_version(),
75+
}
76+
return Response(status=status.HTTP_200_OK, data=data)
77+
78+
79+
class MetaHeartbeat(APIView):
80+
81+
permission_classes = (IsAuthenticated,)
82+
83+
def get(self, request: Request):
84+
uptime = int(start_time - time.time())
85+
data = {
86+
"start_time": start_time,
87+
"uptime": uptime,
88+
}
89+
return Response(status=status.HTTP_200_OK, data=data)

0 commit comments

Comments
 (0)