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
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ dependencies = [
"mmh3>=4.0.0",
"msgspec>=0.19.0",
"msgpack>=1.1.0",
"objectstore-client>=0.0.5",
"objectstore-client>=0.0.10",
"openai>=1.3.5",
"orjson>=3.10.10",
"packaging>=24.1",
Expand Down Expand Up @@ -797,7 +797,6 @@ module = [
"tests.sentry.notifications.notifications.organization_request.*",
"tests.sentry.notifications.platform.*",
"tests.sentry.notifications.utils.*",
"tests.sentry.objectstore.*",
"tests.sentry.onboarding_tasks.*",
"tests.sentry.organizations.*",
"tests.sentry.partnerships.*",
Expand Down
10 changes: 4 additions & 6 deletions src/sentry/attachments/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
import sentry_sdk
from django.conf import settings

from sentry.objectstore import Client as ObjectstoreClient
from sentry.objectstore import get_attachments_client
from sentry.objectstore import get_attachments_session
from sentry.options.rollout import in_random_rollout
from sentry.utils.cache import cache_key_for_event
from sentry.utils.imports import import_string
Expand Down Expand Up @@ -81,13 +80,12 @@ def delete_cached_and_ratelimited_attachments(
Non-ratelimited attachments which are already stored in `objectstore` will
be retained there for long-term storage.
"""
client: ObjectstoreClient | None = None
for attachment in attachments:
# deletes from objectstore if no long-term storage is desired
if attachment.rate_limited and attachment.stored_id:
if client is None:
client = get_attachments_client().for_project(project.organization_id, project.id)
client.delete(attachment.stored_id)
get_attachments_session(project.organization_id, project.id).delete(
attachment.stored_id
)

# unconditionally deletes any payloads from the attachment cache
attachment.delete()
Expand Down
30 changes: 11 additions & 19 deletions src/sentry/lang/native/symbolicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from sentry.lang.native.utils import Backoff
from sentry.models.project import Project
from sentry.net.http import Session
from sentry.objectstore import get_attachments_client
from sentry.objectstore import get_attachments_session
from sentry.options.rollout import in_random_rollout
from sentry.utils import metrics

Expand Down Expand Up @@ -180,16 +180,12 @@ def process_minidump(
"objectstore.force-stored-symbolication"
)
if force_stored_attachment:
client = get_attachments_client().for_project(
self.project.organization_id, self.project.id
)
minidump.stored_id = client.put(minidump.data)
session = get_attachments_session(self.project.organization_id, self.project.id)
minidump.stored_id = session.put(minidump.data)

if minidump.stored_id:
client = get_attachments_client().for_project(
self.project.organization_id, self.project.id
)
storage_url = client.object_url(minidump.stored_id)
session = get_attachments_session(self.project.organization_id, self.project.id)
storage_url = session.object_url(minidump.stored_id)
json: dict[str, Any] = {
"platform": platform,
"sources": sources,
Expand All @@ -206,7 +202,7 @@ def process_minidump(
return process_response(res)
finally:
if force_stored_attachment:
client.delete(minidump.stored_id)
session.delete(minidump.stored_id)
minidump.stored_id = None

data = {
Expand All @@ -229,16 +225,12 @@ def process_applecrashreport(self, platform: str, report: CachedAttachment):
"objectstore.force-stored-symbolication"
)
if force_stored_attachment:
client = get_attachments_client().for_project(
self.project.organization_id, self.project.id
)
report.stored_id = client.put(report.data)
session = get_attachments_session(self.project.organization_id, self.project.id)
report.stored_id = session.put(report.data)

if report.stored_id:
client = get_attachments_client().for_project(
self.project.organization_id, self.project.id
)
storage_url = client.object_url(report.stored_id)
session = get_attachments_session(self.project.organization_id, self.project.id)
storage_url = session.object_url(report.stored_id)
json: dict[str, Any] = {
"platform": platform,
"sources": sources,
Expand All @@ -254,7 +246,7 @@ def process_applecrashreport(self, platform: str, report: CachedAttachment):
return process_response(res)
finally:
if force_stored_attachment:
client.delete(report.stored_id)
session.delete(report.stored_id)
report.stored_id = None

data = {
Expand Down
22 changes: 8 additions & 14 deletions src/sentry/models/eventattachment.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from sentry.db.models.fields.bounded import BoundedIntegerField
from sentry.db.models.manager.base_query_set import BaseQuerySet
from sentry.models.files.utils import get_size_and_checksum, get_storage
from sentry.objectstore import get_attachments_client
from sentry.objectstore import get_attachments_session
from sentry.objectstore.metrics import measure_storage_operation
from sentry.options.rollout import in_random_rollout
from sentry.utils import metrics
Expand Down Expand Up @@ -130,15 +130,15 @@ def delete(self, *args: Any, **kwargs: Any) -> tuple[int, dict[str, int]]:
if blob_path.startswith(V2_PREFIX):
try:
organization_id = _get_organization(self.project_id)
get_attachments_client().for_project(
organization_id, self.project_id
).delete(blob_path.removeprefix(V2_PREFIX))
get_attachments_session(organization_id, self.project_id).delete(
blob_path.removeprefix(V2_PREFIX)
)
except Exception:
sentry_sdk.capture_exception()

elif self.blob_path.startswith(V2_PREFIX):
organization_id = _get_organization(self.project_id)
get_attachments_client().for_project(organization_id, self.project_id).delete(
get_attachments_session(organization_id, self.project_id).delete(
self.blob_path.removeprefix(V2_PREFIX)
)

Expand Down Expand Up @@ -169,9 +169,7 @@ def getfile(self) -> IO[bytes]:
elif self.blob_path.startswith(V2_PREFIX):
id = self.blob_path.removeprefix(V2_PREFIX)
organization_id = _get_organization(self.project_id)
response = (
get_attachments_client().for_project(organization_id, self.project_id).get(id)
)
response = get_attachments_session(organization_id, self.project_id).get(id)
return response.payload

raise NotImplementedError()
Expand Down Expand Up @@ -203,9 +201,7 @@ def putfile(cls, project_id: int, attachment: CachedAttachment) -> PutfileResult
if in_random_rollout("objectstore.double_write.attachments"):
try:
organization_id = _get_organization(project_id)
get_attachments_client().for_project(organization_id, project_id).put(
data, id=object_key
)
get_attachments_session(organization_id, project_id).put(data, key=object_key)
metrics.incr("storage.attachments.double_write")
blob_path += V2_PREFIX
except Exception:
Expand All @@ -220,9 +216,7 @@ def putfile(cls, project_id: int, attachment: CachedAttachment) -> PutfileResult

else:
organization_id = _get_organization(project_id)
blob_path = V2_PREFIX + get_attachments_client().for_project(
organization_id, project_id
).put(data)
blob_path = V2_PREFIX + get_attachments_session(organization_id, project_id).put(data)

return PutfileResult(
content_type=content_type, size=size, sha1=checksum, blob_path=blob_path
Expand Down
23 changes: 12 additions & 11 deletions src/sentry/objectstore/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
from datetime import timedelta

from objectstore_client import Client, ClientBuilder, ClientError, MetricsBackend, TimeToLive
from objectstore_client import Client, MetricsBackend, Session, TimeToLive, Usecase
from objectstore_client.metrics import Tags

from sentry.utils import metrics as sentry_metrics

__all__ = ["get_attachments_client", "Client", "ClientBuilder", "ClientError"]

_attachments_client: ClientBuilder | None = None
__all__ = ["get_attachments_session"]


class SentryMetricsBackend(MetricsBackend):
Expand Down Expand Up @@ -35,16 +33,19 @@ def distribution(
sentry_metrics.distribution(name, value, tags=tags, unit=unit)


def get_attachments_client() -> ClientBuilder:
global _attachments_client
if not _attachments_client:
_ATTACHMENTS_CLIENT: Client | None = None
_ATTACHMENTS_USECASE = Usecase("attachments", expiration_policy=TimeToLive(timedelta(days=30)))


def get_attachments_session(org: int, project: int) -> Session:
global _ATTACHMENTS_CLIENT
if not _ATTACHMENTS_CLIENT:
from sentry import options as options_store

options = options_store.get("objectstore.config")
_attachments_client = ClientBuilder(
_ATTACHMENTS_CLIENT = Client(
options["base_url"],
"attachments",
metrics_backend=SentryMetricsBackend(),
default_expiration_policy=TimeToLive(timedelta(days=30)),
)
return _attachments_client

return _ATTACHMENTS_CLIENT.session(_ATTACHMENTS_USECASE, org=org, project=project)
8 changes: 2 additions & 6 deletions src/sentry/reprocessing2.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
from sentry.models.eventattachment import V1_PREFIX, V2_PREFIX, EventAttachment
from sentry.models.files.utils import get_storage
from sentry.models.project import Project
from sentry.objectstore import get_attachments_client
from sentry.objectstore import get_attachments_session
from sentry.options.rollout import in_random_rollout
from sentry.services import eventstore
from sentry.services.eventstore.models import Event, GroupEvent
Expand Down Expand Up @@ -412,11 +412,7 @@ def _maybe_copy_attachment_into_cache(
else:
# otherwise, we store it in objectstore
with attachment.getfile() as fp:
stored_id = (
get_attachments_client()
.for_project(project.organization_id, project.id)
.put(fp)
)
stored_id = get_attachments_session(project.organization_id, project.id).put(fp)
# but we then also make that storage permanent, as otherwise
# the codepaths won’t be cleaning up this stored file.
# essentially this means we are moving the file from the previous storage
Expand Down
8 changes: 3 additions & 5 deletions tests/sentry/api/endpoints/test_event_attachment_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from sentry.attachments.base import CachedAttachment
from sentry.models.activity import Activity
from sentry.models.eventattachment import V1_PREFIX, V2_PREFIX, EventAttachment
from sentry.objectstore import get_attachments_client
from sentry.objectstore import get_attachments_session
from sentry.testutils.cases import APITestCase, PermissionTestCase, TestCase
from sentry.testutils.helpers.datetime import before_now
from sentry.testutils.helpers.features import with_feature
Expand Down Expand Up @@ -97,10 +97,8 @@ def test_doublewrite_objectstore(self) -> None:
assert attachment.blob_path is not None
object_key = attachment.blob_path.removeprefix(V1_PREFIX + V2_PREFIX)
# the file should also be available in objectstore
os_response = (
get_attachments_client()
.for_project(self.organization.id, self.project.id)
.get(object_key)
os_response = get_attachments_session(self.organization.id, self.project.id).get(
object_key
)
assert os_response.payload.read() == ATTACHMENT_CONTENT

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from sentry.models.debugfile import create_files_from_dif_zip
from sentry.models.eventattachment import EventAttachment
from sentry.models.userreport import UserReport
from sentry.objectstore import get_attachments_client
from sentry.objectstore import get_attachments_session
from sentry.services import eventstore
from sentry.testutils.factories import get_fixture_path
from sentry.testutils.helpers.features import Feature
Expand Down Expand Up @@ -467,10 +467,8 @@ def test_process_stored_attachment(
with open(get_fixture_path("native", "threadnames.dmp"), "rb") as f:
attachment_payload = f.read()

stored_id = (
get_attachments_client()
.for_project(default_project.organization_id, project_id)
.put(attachment_payload)
stored_id = get_attachments_session(default_project.organization_id, project_id).put(
attachment_payload
)

with task_runner():
Expand Down
71 changes: 0 additions & 71 deletions tests/sentry/objectstore/test_objectstore.py

This file was deleted.

Loading
Loading