Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 3 additions & 11 deletions src/sentry/api/serializers/models/eventattachment.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,10 @@

@register(EventAttachment)
class EventAttachmentSerializer(Serializer):
def get_attrs(self, item_list, user, **kwargs):
files = {
f.id: f
for f in File.objects.filter(id__in=[ea.file_id for ea in item_list if ea.file_id])
}
return {ea: {"file": files[ea.file_id]} for ea in item_list if ea.file_id}

def serialize(self, obj, attrs, user, **kwargs):
file = attrs.get("file")
content_type = obj.content_type or get_mimetype(file)
size = obj.size if obj.size is not None else file.size
sha1 = obj.sha1 or file.checksum
content_type = obj.content_type
size = obj.size or 0
sha1 = obj.sha1
headers = {"Content-Type": content_type}

return {
Expand Down
1 change: 0 additions & 1 deletion src/sentry/event_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2401,7 +2401,6 @@ def save_attachment(
size=file.size,
sha1=file.sha1,
# storage:
file_id=file.file_id,
blob_path=file.blob_path,
)

Expand Down
53 changes: 13 additions & 40 deletions src/sentry/models/eventattachment.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import zstandard
from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.utils import timezone

Expand Down Expand Up @@ -42,7 +41,6 @@ class PutfileResult:
content_type: str
size: int
sha1: str
file_id: int | None = None
blob_path: str | None = None


Expand All @@ -61,8 +59,7 @@ class EventAttachment(Model):
"""Attachment Metadata and Storage

The actual attachment data can be saved in different backing stores:
- Using the :class:`File` model using the `file_id` field.
This stores attachments chunked and deduplicated.
- When the attachment is empty (0-size), `blob_path is None`.
- When the `blob_path` field has a `:` prefix:
It is saved inline in `blob_path` following the `:` prefix.
This happens for "small" and ASCII-only (see `can_store_inline`) attachments.
Expand All @@ -88,8 +85,7 @@ class EventAttachment(Model):

date_added = models.DateTimeField(default=timezone.now, db_index=True)

# the backing blob, either going through the `File` model,
# or directly to a backing blob store
# The `file_id` will be removed in a multi-part migration soon.
file_id = BoundedBigIntegerField(null=True, db_index=True)
blob_path = models.TextField(null=True)

Expand All @@ -114,51 +110,29 @@ def delete(self, *args: Any, **kwargs: Any) -> tuple[int, dict[str, int]]:

if self.blob_path:
if self.blob_path.startswith(":"):
return rv
pass # nothing to do for inline-stored attachments
elif self.blob_path.startswith("eventattachments/v1/"):
storage = get_storage()
storage.delete(self.blob_path)
else:
raise NotImplementedError()

storage.delete(self.blob_path)
return rv

try:
from sentry.models.files.file import File

file = File.objects.get(id=self.file_id)
except ObjectDoesNotExist:
# It's possible that the File itself was deleted
# before we were deleted when the object is in memory
# This seems to be a case that happens during deletion
# code.
pass
else:
file.delete()

return rv

def getfile(self) -> IO[bytes]:
if self.size == 0:
if not self.blob_path:
return BytesIO(b"")

if self.blob_path:
if self.blob_path.startswith(":"):
return BytesIO(self.blob_path[1:].encode())

elif self.blob_path.startswith("eventattachments/v1/"):
storage = get_storage()
compressed_blob = storage.open(self.blob_path)
dctx = zstandard.ZstdDecompressor()
return dctx.stream_reader(compressed_blob, read_across_frames=True)
if self.blob_path.startswith(":"):
return BytesIO(self.blob_path[1:].encode())

else:
raise NotImplementedError()

from sentry.models.files.file import File
elif self.blob_path.startswith("eventattachments/v1/"):
storage = get_storage()
compressed_blob = storage.open(self.blob_path)
dctx = zstandard.ZstdDecompressor()
return dctx.stream_reader(compressed_blob, read_across_frames=True)

file = File.objects.get(id=self.file_id)
return file.getfile()
raise NotImplementedError()

@classmethod
def putfile(cls, project_id: int, attachment: CachedAttachment) -> PutfileResult:
Expand All @@ -171,7 +145,6 @@ def putfile(cls, project_id: int, attachment: CachedAttachment) -> PutfileResult
return PutfileResult(content_type=content_type, size=0, sha1=sha1().hexdigest())

blob = BytesIO(data)

size, checksum = get_size_and_checksum(blob)

if can_store_inline(data):
Expand Down
20 changes: 0 additions & 20 deletions src/sentry/testutils/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@
)
from sentry.models.debugfile import ProjectDebugFile
from sentry.models.environment import Environment
from sentry.models.eventattachment import EventAttachment
from sentry.models.files.control_file import ControlFile
from sentry.models.files.file import File
from sentry.models.group import Group
Expand Down Expand Up @@ -1106,25 +1105,6 @@ def create_file_from_path(path, name=None, **kwargs):
file.putfile(f)
return file

@staticmethod
@assume_test_silo_mode(SiloMode.REGION)
def create_event_attachment(event, file=None, **kwargs):
if file is None:
file = Factories.create_file(
name="log.txt",
size=32,
headers={"Content-Type": "text/plain"},
checksum="dc1e3f3e411979d336c3057cce64294f3420f93a",
)

return EventAttachment.objects.create(
project_id=event.project_id,
event_id=event.event_id,
file_id=file.id,
type=file.type,
**kwargs,
)

@staticmethod
@assume_test_silo_mode(SiloMode.REGION)
def create_dif_file(
Expand Down
5 changes: 0 additions & 5 deletions src/sentry/testutils/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,11 +317,6 @@ def create_file(self, **kwargs):
def create_file_from_path(self, *args, **kwargs):
return Factories.create_file_from_path(*args, **kwargs)

def create_event_attachment(self, event=None, *args, **kwargs):
if event is None:
event = self.event
return Factories.create_event_attachment(event, *args, **kwargs)

def create_dif_file(self, project: Project | None = None, *args, **kwargs):
if project is None:
project = self.project
Expand Down
9 changes: 3 additions & 6 deletions src/sentry/utils/mockdata/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -647,12 +647,9 @@ def generate_events(
project_id=project.id,
event_id=event1.event_id,
name="example-logfile.txt",
file_id=File.objects.get_or_create(
name="example-logfile.txt",
type="text/plain",
checksum="abcde" * 8,
size=13043,
)[0].id,
type="text/plain",
sha1="abcde" * 8,
size=13043,
)

event2 = create_sample_event(
Expand Down
6 changes: 1 addition & 5 deletions tests/sentry/api/endpoints/test_event_attachment_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,7 @@ def create_attachment(self, content: bytes | None = None, group_id: int | None =
attachment = CachedAttachment(
name="hello.png", content_type="image/png; foo=bar", data=data
)
file = EventAttachment.putfile(
self.project.id,
attachment,
)
file = EventAttachment.putfile(self.project.id, attachment)

self.attachment = EventAttachment.objects.create(
event_id=self.event.event_id,
Expand All @@ -50,7 +47,6 @@ def create_attachment(self, content: bytes | None = None, group_id: int | None =
size=file.size,
sha1=file.sha1,
# storage:
file_id=file.file_id,
blob_path=file.blob_path,
)

Expand Down
26 changes: 9 additions & 17 deletions tests/sentry/api/endpoints/test_event_attachments.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
from io import BytesIO

from sentry.models.eventattachment import EventAttachment
from sentry.models.files.file import File
from sentry.testutils.cases import APITestCase
from sentry.testutils.helpers.datetime import before_now
from sentry.testutils.skips import requires_snuba
Expand All @@ -21,14 +18,15 @@ def test_simple(self):
data={"fingerprint": ["group1"], "timestamp": min_ago}, project_id=self.project.id
)

file1 = File.objects.create(name="hello.png", type="event.attachment")
file1.putfile(BytesIO(b"File contents here"))
attachment1 = EventAttachment.objects.create(
project_id=event1.project_id,
event_id=event1.event_id,
type="event.attachment",
name=file1.name,
file_id=file1.id,
name="hello.png",
content_type="image/png",
size=18,
sha1="d3f299af02d6abbe92dd8368bab781824a9702ed",
blob_path=":File contents here",
)

attachment2 = EventAttachment.objects.create(
Expand All @@ -39,7 +37,6 @@ def test_simple(self):
content_type="image/png",
size=1234,
sha1="1234",
# NOTE: we are not actually attaching the `file_id` here
)

path = f"/api/0/projects/{event1.project.organization.slug}/{event1.project.slug}/events/{event1.event_id}/attachments/"
Expand Down Expand Up @@ -82,26 +79,21 @@ def test_is_screenshot(self):
data={"fingerprint": ["group1"], "timestamp": min_ago}, project_id=self.project.id
)

file = File.objects.create(name="screenshot.png", type="image/png")
EventAttachment.objects.create(
event_id=event1.event_id,
project_id=event1.project_id,
file_id=file.id,
name=file.name,
name="screenshot.png",
content_type="image/png",
)
file = File.objects.create(name="crash_screenshot.png")
EventAttachment.objects.create(
event_id=event1.event_id,
project_id=event1.project_id,
file_id=file.id,
name=file.name,
name="crash_screenshot.png",
)
file = File.objects.create(name="foo.png")
EventAttachment.objects.create(
event_id=event1.event_id,
project_id=event1.project_id,
file_id=file.id,
name=file.name,
name="foo.png",
)

path = f"/api/0/projects/{event1.project.organization.slug}/{event1.project.slug}/events/{event1.event_id}/attachments/"
Expand Down
9 changes: 2 additions & 7 deletions tests/sentry/api/endpoints/test_group_attachments.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from io import BytesIO
from urllib.parse import urlencode

from sentry.models.eventattachment import EventAttachment
from sentry.models.files.file import File
from sentry.testutils.cases import APITestCase
from sentry.testutils.helpers.datetime import before_now
from sentry.testutils.skips import requires_snuba
Expand All @@ -15,16 +13,13 @@ def create_attachment(self, type=None, event_id=None, file_name="hello.png", gro
if type is None:
type = "event.attachment"

self.file = File.objects.create(name=file_name, type=type)
self.file.putfile(BytesIO(b"File contents here"))

self.attachment = EventAttachment.objects.create(
event_id=event_id or self.event.event_id,
project_id=self.event.project_id,
group_id=group_id or self.group.id,
file_id=self.file.id,
type=self.file.type,
type=type,
name=file_name,
blob_path=":File contents here",
)

return self.attachment
Expand Down
5 changes: 1 addition & 4 deletions tests/sentry/deletions/test_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from sentry.issues.grouptype import FeedbackGroup, GroupCategory
from sentry.issues.issue_occurrence import IssueOccurrence
from sentry.models.eventattachment import EventAttachment
from sentry.models.files.file import File
from sentry.models.group import Group
from sentry.models.groupassignee import GroupAssignee
from sentry.models.grouphash import GroupHash
Expand Down Expand Up @@ -57,13 +56,11 @@ def setUp(self) -> None:
UserReport.objects.create(
event_id=self.event.event_id, project_id=self.event.project_id, name="With event id"
)
file = File.objects.create(name="hello.png", type="image/png")
EventAttachment.objects.create(
event_id=self.event.event_id,
project_id=self.event.project_id,
file_id=file.id,
type=file.type,
name="hello.png",
content_type="image/png",
)
GroupAssignee.objects.create(group=group, project=self.project, user_id=self.user.id)
GroupHash.objects.create(project=self.project, group=group, hash=uuid4().hex)
Expand Down
4 changes: 1 addition & 3 deletions tests/sentry/deletions/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,11 @@ def test_simple(self):
object_name="object",
project_id=project.id,
)
file_attachment = File.objects.create(name="hello.png", type="image/png")
EventAttachment.objects.create(
event_id=event.event_id,
project_id=event.project_id,
file_id=file_attachment.id,
type=file_attachment.type,
name="hello.png",
type="image/png",
)
hook = self.create_service_hook(
actor=self.user,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from sentry.models.apitoken import ApiToken
from sentry.models.environment import Environment
from sentry.models.eventattachment import EventAttachment
from sentry.models.files.file import File
from sentry.models.group import Group, GroupStatus
from sentry.models.groupassignee import GroupAssignee
from sentry.models.groupbookmark import GroupBookmark
Expand Down Expand Up @@ -1979,14 +1978,12 @@ def test_expand_latest_event_has_attachments(self, _: MagicMock) -> None:
assert "latestEventHasAttachments" not in response.data[0]

# Add 1 attachment
file_attachment = File.objects.create(name="hello.png", type="image/png")
EventAttachment.objects.create(
group_id=event.group.id,
event_id=event.event_id,
project_id=event.project_id,
file_id=file_attachment.id,
type=file_attachment.type,
name="hello.png",
content_type="image/png",
)

response = self.get_response(
Expand Down
Loading
Loading