views.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. """ Port of sentry.api.endpoints.chunk.ChunkUploadEndpoint """
  2. import logging
  3. from io import BytesIO
  4. from gzip import GzipFile
  5. from django.conf import settings
  6. from django.urls import reverse
  7. from django.shortcuts import get_object_or_404
  8. from rest_framework import views, status
  9. from rest_framework.response import Response
  10. from organizations_ext.models import Organization
  11. from .models import FileBlob
  12. from .permissions import ChunkUploadPermission
  13. CHUNK_UPLOAD_BLOB_SIZE = 8 * 1024 * 1024 # 8MB
  14. MAX_CHUNKS_PER_REQUEST = 64
  15. MAX_REQUEST_SIZE = 32 * 1024 * 1024
  16. MAX_CONCURRENCY = settings.DEBUG and 1 or 8
  17. HASH_ALGORITHM = "sha1"
  18. CHUNK_UPLOAD_ACCEPT = (
  19. "debug_files", # DIF assemble
  20. "release_files", # Release files assemble
  21. "pdbs", # PDB upload and debug id override
  22. "sources", # Source artifact bundle upload
  23. )
  24. class GzipChunk(BytesIO):
  25. def __init__(self, file):
  26. data = GzipFile(fileobj=file, mode="rb").read()
  27. self.size = len(data)
  28. self.name = file.name
  29. super().__init__(data)
  30. class ChunkUploadAPIView(views.APIView):
  31. permission_classes = [ChunkUploadPermission]
  32. def get(self, request, organization_slug):
  33. url = settings.GLITCHTIP_URL.geturl() + reverse(
  34. "chunk-upload", args=[organization_slug]
  35. )
  36. return Response(
  37. {
  38. "url": url,
  39. "chunkSize": CHUNK_UPLOAD_BLOB_SIZE,
  40. "chunksPerRequest": MAX_CHUNKS_PER_REQUEST,
  41. "maxFileSize": 2147483648,
  42. "maxRequestSize": MAX_REQUEST_SIZE,
  43. "concurrency": MAX_CONCURRENCY,
  44. "hashAlgorithm": HASH_ALGORITHM,
  45. "compression": ["gzip"],
  46. "accept": CHUNK_UPLOAD_ACCEPT,
  47. }
  48. )
  49. def post(self, request, organization_slug):
  50. logger = logging.getLogger("glitchtip.files")
  51. logger.info("chunkupload.start")
  52. organization = get_object_or_404(
  53. Organization, slug=organization_slug.lower(), users=self.request.user
  54. )
  55. self.check_object_permissions(request, organization)
  56. files = request.data.getlist("file")
  57. files += [GzipChunk(chunk) for chunk in request.data.getlist("file_gzip")]
  58. if len(files) == 0:
  59. # No files uploaded is ok
  60. logger.info("chunkupload.end", extra={"status": status.HTTP_200_OK})
  61. return Response(status=status.HTTP_200_OK)
  62. logger.info("chunkupload.post.files", extra={"len": len(files)})
  63. # Validate file size
  64. checksums = []
  65. size = 0
  66. for chunk in files:
  67. size += chunk.size
  68. if chunk.size > CHUNK_UPLOAD_BLOB_SIZE:
  69. logger.info(
  70. "chunkupload.end", extra={"status": status.HTTP_400_BAD_REQUEST}
  71. )
  72. return Response(
  73. {"error": "Chunk size too large"},
  74. status=status.HTTP_400_BAD_REQUEST,
  75. )
  76. checksums.append(chunk.name)
  77. if size > MAX_REQUEST_SIZE:
  78. logger.info(
  79. "chunkupload.end", extra={"status": status.HTTP_400_BAD_REQUEST}
  80. )
  81. return Response(
  82. {"error": "Request too large"}, status=status.HTTP_400_BAD_REQUEST
  83. )
  84. if len(files) > MAX_CHUNKS_PER_REQUEST:
  85. logger.info(
  86. "chunkupload.end", extra={"status": status.HTTP_400_BAD_REQUEST}
  87. )
  88. return Response(
  89. {"error": "Too many chunks"}, status=status.HTTP_400_BAD_REQUEST
  90. )
  91. try:
  92. FileBlob.from_files(
  93. zip(files, checksums), organization=organization, logger=logger
  94. )
  95. except IOError as err:
  96. logger.info(
  97. "chunkupload.end", extra={"status": status.HTTP_400_BAD_REQUEST}
  98. )
  99. return Response({"error": str(err)}, status=status.HTTP_400_BAD_REQUEST)
  100. logger.info("chunkupload.end", extra={"status": status.HTTP_200_OK})
  101. return Response(status=status.HTTP_200_OK)