views.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. from django.http import Http404, HttpResponse
  2. from django.shortcuts import get_object_or_404
  3. from django.utils.translation import gettext_lazy as _
  4. from drf_spectacular.types import OpenApiTypes
  5. from drf_spectacular.utils import extend_schema
  6. from rest_framework.decorators import action
  7. from rest_framework.exceptions import PermissionDenied
  8. from rest_framework.response import Response
  9. from rest_framework.routers import APIRootView
  10. from rest_framework.viewsets import ReadOnlyModelViewSet
  11. from core import filtersets
  12. from core.choices import DataSourceStatusChoices
  13. from core.jobs import SyncDataSourceJob
  14. from core.models import *
  15. from core.utils import delete_rq_job, enqueue_rq_job, get_rq_jobs, requeue_rq_job, stop_rq_job
  16. from django_rq.queues import get_redis_connection
  17. from django_rq.utils import get_statistics
  18. from django_rq.settings import QUEUES_LIST
  19. from netbox.api.metadata import ContentTypeMetadata
  20. from netbox.api.pagination import LimitOffsetListPagination
  21. from netbox.api.viewsets import NetBoxModelViewSet, NetBoxReadOnlyModelViewSet
  22. from rest_framework import viewsets
  23. from rest_framework.permissions import IsAdminUser
  24. from rq.job import Job as RQ_Job
  25. from rq.worker import Worker
  26. from . import serializers
  27. class CoreRootView(APIRootView):
  28. """
  29. Core API root view
  30. """
  31. def get_view_name(self):
  32. return 'Core'
  33. class DataSourceViewSet(NetBoxModelViewSet):
  34. queryset = DataSource.objects.all()
  35. serializer_class = serializers.DataSourceSerializer
  36. filterset_class = filtersets.DataSourceFilterSet
  37. @action(detail=True, methods=['post'])
  38. def sync(self, request, pk):
  39. """
  40. Enqueue a job to synchronize the DataSource.
  41. """
  42. datasource = get_object_or_404(DataSource, pk=pk)
  43. if not request.user.has_perm('core.sync_datasource', obj=datasource):
  44. raise PermissionDenied(_("This user does not have permission to synchronize this data source."))
  45. # Enqueue the sync job & update the DataSource's status
  46. SyncDataSourceJob.enqueue(instance=datasource, user=request.user)
  47. datasource.status = DataSourceStatusChoices.QUEUED
  48. DataSource.objects.filter(pk=datasource.pk).update(status=datasource.status)
  49. serializer = serializers.DataSourceSerializer(datasource, context={'request': request})
  50. return Response(serializer.data)
  51. class DataFileViewSet(NetBoxReadOnlyModelViewSet):
  52. queryset = DataFile.objects.defer('data')
  53. serializer_class = serializers.DataFileSerializer
  54. filterset_class = filtersets.DataFileFilterSet
  55. class JobViewSet(ReadOnlyModelViewSet):
  56. """
  57. Retrieve a list of job results
  58. """
  59. queryset = Job.objects.all()
  60. serializer_class = serializers.JobSerializer
  61. filterset_class = filtersets.JobFilterSet
  62. class ObjectChangeViewSet(ReadOnlyModelViewSet):
  63. """
  64. Retrieve a list of recent changes.
  65. """
  66. metadata_class = ContentTypeMetadata
  67. queryset = ObjectChange.objects.valid_models()
  68. serializer_class = serializers.ObjectChangeSerializer
  69. filterset_class = filtersets.ObjectChangeFilterSet
  70. class BaseRQViewSet(viewsets.ViewSet):
  71. """
  72. Base class for RQ view sets. Provides a list() method. Subclasses must implement get_data().
  73. """
  74. permission_classes = [IsAdminUser]
  75. serializer_class = None
  76. def get_data(self):
  77. raise NotImplementedError()
  78. @extend_schema(responses={200: OpenApiTypes.OBJECT})
  79. def list(self, request):
  80. data = self.get_data()
  81. paginator = LimitOffsetListPagination()
  82. data = paginator.paginate_list(data, request)
  83. serializer = self.serializer_class(data, many=True, context={'request': request})
  84. return paginator.get_paginated_response(serializer.data)
  85. def get_serializer(self, *args, **kwargs):
  86. """
  87. Return the serializer instance that should be used for validating and
  88. deserializing input, and for serializing output.
  89. """
  90. serializer_class = self.get_serializer_class()
  91. kwargs['context'] = self.get_serializer_context()
  92. return serializer_class(*args, **kwargs)
  93. class BackgroundQueueViewSet(BaseRQViewSet):
  94. """
  95. Retrieve a list of RQ Queues.
  96. Note: Queue names are not URL safe so not returning a detail view.
  97. """
  98. serializer_class = serializers.BackgroundQueueSerializer
  99. lookup_field = 'name'
  100. lookup_value_regex = r'[\w.@+-]+'
  101. def get_view_name(self):
  102. return "Background Queues"
  103. def get_data(self):
  104. return get_statistics(run_maintenance_tasks=True)["queues"]
  105. @extend_schema(responses={200: OpenApiTypes.OBJECT})
  106. def retrieve(self, request, name):
  107. data = self.get_data()
  108. if not data:
  109. raise Http404
  110. for queue in data:
  111. if queue['name'] == name:
  112. serializer = self.serializer_class(queue, context={'request': request})
  113. return Response(serializer.data)
  114. raise Http404
  115. class BackgroundWorkerViewSet(BaseRQViewSet):
  116. """
  117. Retrieve a list of RQ Workers.
  118. """
  119. serializer_class = serializers.BackgroundWorkerSerializer
  120. lookup_field = 'name'
  121. def get_view_name(self):
  122. return "Background Workers"
  123. def get_data(self):
  124. config = QUEUES_LIST[0]
  125. return Worker.all(get_redis_connection(config['connection_config']))
  126. def retrieve(self, request, name):
  127. # all the RQ queues should use the same connection
  128. config = QUEUES_LIST[0]
  129. workers = Worker.all(get_redis_connection(config['connection_config']))
  130. worker = next((item for item in workers if item.name == name), None)
  131. if not worker:
  132. raise Http404
  133. serializer = serializers.BackgroundWorkerSerializer(worker, context={'request': request})
  134. return Response(serializer.data)
  135. class BackgroundTaskViewSet(BaseRQViewSet):
  136. """
  137. Retrieve a list of RQ Tasks.
  138. """
  139. serializer_class = serializers.BackgroundTaskSerializer
  140. def get_view_name(self):
  141. return "Background Tasks"
  142. def get_data(self):
  143. return get_rq_jobs()
  144. def get_task_from_id(self, task_id):
  145. config = QUEUES_LIST[0]
  146. task = RQ_Job.fetch(task_id, connection=get_redis_connection(config['connection_config']))
  147. if not task:
  148. raise Http404
  149. return task
  150. @extend_schema(responses={200: OpenApiTypes.OBJECT})
  151. def retrieve(self, request, pk):
  152. """
  153. Retrieve the details of the specified RQ Task.
  154. """
  155. task = self.get_task_from_id(pk)
  156. serializer = self.serializer_class(task, context={'request': request})
  157. return Response(serializer.data)
  158. @action(methods=["POST"], detail=True)
  159. def delete(self, request, pk):
  160. """
  161. Delete the specified RQ Task.
  162. """
  163. delete_rq_job(pk)
  164. return HttpResponse(status=200)
  165. @action(methods=["POST"], detail=True)
  166. def requeue(self, request, pk):
  167. """
  168. Requeues the specified RQ Task.
  169. """
  170. requeue_rq_job(pk)
  171. return HttpResponse(status=200)
  172. @action(methods=["POST"], detail=True)
  173. def enqueue(self, request, pk):
  174. """
  175. Enqueues the specified RQ Task.
  176. """
  177. enqueue_rq_job(pk)
  178. return HttpResponse(status=200)
  179. @action(methods=["POST"], detail=True)
  180. def stop(self, request, pk):
  181. """
  182. Stops the specified RQ Task.
  183. """
  184. stopped_jobs = stop_rq_job(pk)
  185. if len(stopped_jobs) == 1:
  186. return HttpResponse(status=200)
  187. else:
  188. return HttpResponse(status=204)