From 80c142ab7cf7eaa403e37cb167695455ff96d733 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Tue, 13 Oct 2020 16:57:45 -0400 Subject: [PATCH] Closes #4918: Add a REST API endpoint which returns NetBox's current operational status --- docs/release-notes/version-2.10.md | 4 ++- netbox/netbox/api/views.py | 46 ++++++++++++++++++++++++++++++ netbox/netbox/tests/test_api.py | 7 ++++- netbox/netbox/urls.py | 2 ++ netbox/netbox/views.py | 1 + 5 files changed, 58 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-2.10.md b/docs/release-notes/version-2.10.md index 69df8c15d..d1564e5e1 100644 --- a/docs/release-notes/version-2.10.md +++ b/docs/release-notes/version-2.10.md @@ -46,6 +46,7 @@ All end-to-end cable paths are now cached using the new CablePath model. This al * [#1692](https://github.com/netbox-community/netbox/issues/1692) - Allow assigment of inventory items to parent items in web UI * [#2179](https://github.com/netbox-community/netbox/issues/2179) - Support the assignment of multiple port numbers for services * [#4897](https://github.com/netbox-community/netbox/issues/4897) - Allow filtering by content type identified as `.` string +* [#4918](https://github.com/netbox-community/netbox/issues/4918) - Add a REST API endpoint (`/api/status/`) which returns NetBox's current operational status * [#4956](https://github.com/netbox-community/netbox/issues/4956) - Include inventory items on primary device view * [#5003](https://github.com/netbox-community/netbox/issues/5003) - CSV import now accepts slug values for choice fields * [#5146](https://github.com/netbox-community/netbox/issues/5146) - Add custom fields support for cables, power panels, rack reservations, and virtual chassis @@ -63,7 +64,8 @@ All end-to-end cable paths are now cached using the new CablePath model. This al ### REST API Changes * Added support for `PUT`, `PATCH`, and `DELETE` operations on list endpoints (bulk update and delete) -* Added `/extras/content-types/` endpoint for Django ContentTypes +* Added the `/extras/content-types/` endpoint for Django ContentTypes +* Added the `/status/` endpoint to convey NetBox's current status * circuits.CircuitTermination: * Added the `/trace/` endpoint * Replaced `connection_status` with `connected_endpoint_reachable` (boolean) diff --git a/netbox/netbox/api/views.py b/netbox/netbox/api/views.py index 3dd512205..887d6b5f8 100644 --- a/netbox/netbox/api/views.py +++ b/netbox/netbox/api/views.py @@ -1,11 +1,18 @@ import logging +import platform +from django import __version__ as DJANGO_VERSION +from django.apps import apps +from django.conf import settings from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.db import transaction from django.db.models import ProtectedError +from django_rq.queues import get_connection from rest_framework import mixins, status from rest_framework.response import Response +from rest_framework.views import APIView from rest_framework.viewsets import GenericViewSet +from rq.worker import Worker from netbox.api import BulkOperationSerializer from netbox.api.exceptions import SerializerNotFound @@ -218,3 +225,42 @@ class ModelViewSet(mixins.CreateModelMixin, logger.info(f"Deleting {model._meta.verbose_name} {instance} (PK: {instance.pk})") return super().perform_destroy(instance) + + +# +# Views +# + +class StatusView(APIView): + """ + Provide a lightweight read-only endpoint for conveying NetBox's current operational status. + """ + permission_classes = [] + + def get(self, request): + # Gather the version number from all installed Django apps + installed_apps = {} + for app_config in apps.get_app_configs(): + app = app_config.module + version = getattr(app, 'VERSION', getattr(app, '__version__', None)) + if version: + if type(version) is tuple: + version = '.'.join(str(n) for n in version) + installed_apps[app_config.name] = version + installed_apps = {k: v for k, v in sorted(installed_apps.items())} + + # Gather installed plugins + plugins = {} + for plugin_name in settings.PLUGINS: + plugin_config = apps.get_app_config(plugin_name) + plugins[plugin_name] = getattr(plugin_config, 'version', None) + plugins = {k: v for k, v in sorted(plugins.items())} + + return Response({ + 'django-version': DJANGO_VERSION, + 'installed-apps': installed_apps, + 'netbox-version': settings.VERSION, + 'plugins': plugins, + 'python-version': platform.python_version(), + 'rq-workers-running': Worker.count(get_connection('default')), + }) diff --git a/netbox/netbox/tests/test_api.py b/netbox/netbox/tests/test_api.py index 0ee2d78dc..2ea12e72f 100644 --- a/netbox/netbox/tests/test_api.py +++ b/netbox/netbox/tests/test_api.py @@ -6,8 +6,13 @@ from utilities.testing import APITestCase class AppTest(APITestCase): def test_root(self): - url = reverse('api-root') response = self.client.get('{}?format=api'.format(url), **self.header) self.assertEqual(response.status_code, 200) + + def test_status(self): + url = reverse('api-status') + response = self.client.get('{}?format=api'.format(url), **self.header) + + self.assertEqual(response.status_code, 200) diff --git a/netbox/netbox/urls.py b/netbox/netbox/urls.py index 4878729b0..250b1ac67 100644 --- a/netbox/netbox/urls.py +++ b/netbox/netbox/urls.py @@ -6,6 +6,7 @@ from drf_yasg import openapi from drf_yasg.views import get_schema_view from extras.plugins.urls import plugin_admin_patterns, plugin_patterns, plugin_api_patterns +from netbox.api.views import StatusView from netbox.views import APIRootView, HomeView, StaticMediaFailureView, SearchView from users.views import LoginView, LogoutView from .admin import admin_site @@ -55,6 +56,7 @@ _patterns = [ path('api/tenancy/', include('tenancy.api.urls')), path('api/users/', include('users.api.urls')), path('api/virtualization/', include('virtualization.api.urls')), + path('api/status/', StatusView.as_view(), name='api-status'), path('api/docs/', schema_view.with_ui('swagger'), name='api_docs'), path('api/redoc/', schema_view.with_ui('redoc'), name='api_redocs'), re_path(r'^api/swagger(?P.json|.yaml)$', schema_view.without_ui(), name='schema_swagger'), diff --git a/netbox/netbox/views.py b/netbox/netbox/views.py index 161bfda74..baf014dbf 100644 --- a/netbox/netbox/views.py +++ b/netbox/netbox/views.py @@ -342,6 +342,7 @@ class APIRootView(APIView): ('ipam', reverse('ipam-api:api-root', request=request, format=format)), ('plugins', reverse('plugins-api:api-root', request=request, format=format)), ('secrets', reverse('secrets-api:api-root', request=request, format=format)), + ('status', reverse('api-status', request=request, format=format)), ('tenancy', reverse('tenancy-api:api-root', request=request, format=format)), ('users', reverse('users-api:api-root', request=request, format=format)), ('virtualization', reverse('virtualization-api:api-root', request=request, format=format)),