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

This commit is contained in:
Jeremy Stretch 2020-10-13 16:57:45 -04:00
parent c0c5f52ed9
commit 80c142ab7c
5 changed files with 58 additions and 2 deletions

View File

@ -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)

View File

@ -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')),
})

View File

@ -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)

View File

@ -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'),

View File

@ -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)),