Просмотр исходного кода

Closes #4918: Add a REST API endpoint which returns NetBox's current operational status

Jeremy Stretch 5 лет назад
Родитель
Сommit
80c142ab7c

+ 3 - 1
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
 * [#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
 * [#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 `<app>.<model>` string
 * [#4897](https://github.com/netbox-community/netbox/issues/4897) - Allow filtering by content type identified as `<app>.<model>` 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
 * [#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
 * [#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
 * [#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
 ### REST API Changes
 
 
 * Added support for `PUT`, `PATCH`, and `DELETE` operations on list endpoints (bulk update and delete)
 * 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:
 * circuits.CircuitTermination:
   * Added the `/trace/` endpoint
   * Added the `/trace/` endpoint
   * Replaced `connection_status` with `connected_endpoint_reachable` (boolean)
   * Replaced `connection_status` with `connected_endpoint_reachable` (boolean)

+ 46 - 0
netbox/netbox/api/views.py

@@ -1,11 +1,18 @@
 import logging
 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.core.exceptions import ObjectDoesNotExist, PermissionDenied
 from django.db import transaction
 from django.db import transaction
 from django.db.models import ProtectedError
 from django.db.models import ProtectedError
+from django_rq.queues import get_connection
 from rest_framework import mixins, status
 from rest_framework import mixins, status
 from rest_framework.response import Response
 from rest_framework.response import Response
+from rest_framework.views import APIView
 from rest_framework.viewsets import GenericViewSet
 from rest_framework.viewsets import GenericViewSet
+from rq.worker import Worker
 
 
 from netbox.api import BulkOperationSerializer
 from netbox.api import BulkOperationSerializer
 from netbox.api.exceptions import SerializerNotFound
 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})")
         logger.info(f"Deleting {model._meta.verbose_name} {instance} (PK: {instance.pk})")
 
 
         return super().perform_destroy(instance)
         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')),
+        })

+ 6 - 1
netbox/netbox/tests/test_api.py

@@ -6,8 +6,13 @@ from utilities.testing import APITestCase
 class AppTest(APITestCase):
 class AppTest(APITestCase):
 
 
     def test_root(self):
     def test_root(self):
-
         url = reverse('api-root')
         url = reverse('api-root')
         response = self.client.get('{}?format=api'.format(url), **self.header)
         response = self.client.get('{}?format=api'.format(url), **self.header)
 
 
         self.assertEqual(response.status_code, 200)
         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)

+ 2 - 0
netbox/netbox/urls.py

@@ -6,6 +6,7 @@ from drf_yasg import openapi
 from drf_yasg.views import get_schema_view
 from drf_yasg.views import get_schema_view
 
 
 from extras.plugins.urls import plugin_admin_patterns, plugin_patterns, plugin_api_patterns
 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 netbox.views import APIRootView, HomeView, StaticMediaFailureView, SearchView
 from users.views import LoginView, LogoutView
 from users.views import LoginView, LogoutView
 from .admin import admin_site
 from .admin import admin_site
@@ -55,6 +56,7 @@ _patterns = [
     path('api/tenancy/', include('tenancy.api.urls')),
     path('api/tenancy/', include('tenancy.api.urls')),
     path('api/users/', include('users.api.urls')),
     path('api/users/', include('users.api.urls')),
     path('api/virtualization/', include('virtualization.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/docs/', schema_view.with_ui('swagger'), name='api_docs'),
     path('api/redoc/', schema_view.with_ui('redoc'), name='api_redocs'),
     path('api/redoc/', schema_view.with_ui('redoc'), name='api_redocs'),
     re_path(r'^api/swagger(?P<format>.json|.yaml)$', schema_view.without_ui(), name='schema_swagger'),
     re_path(r'^api/swagger(?P<format>.json|.yaml)$', schema_view.without_ui(), name='schema_swagger'),

+ 1 - 0
netbox/netbox/views.py

@@ -342,6 +342,7 @@ class APIRootView(APIView):
             ('ipam', reverse('ipam-api:api-root', request=request, format=format)),
             ('ipam', reverse('ipam-api:api-root', request=request, format=format)),
             ('plugins', reverse('plugins-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)),
             ('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)),
             ('tenancy', reverse('tenancy-api:api-root', request=request, format=format)),
             ('users', reverse('users-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)),
             ('virtualization', reverse('virtualization-api:api-root', request=request, format=format)),