Skip to content

Commit

Permalink
feat(nodestore) FilestoreNodeStorage, all credits to #76250
Browse files Browse the repository at this point in the history
  • Loading branch information
rophy committed Dec 15, 2024
1 parent 3273e16 commit 3984e7c
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 6 deletions.
1 change: 1 addition & 0 deletions src/sentry/nodestore/filestore/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .backend import FilestoreNodeStorage # NOQA
63 changes: 63 additions & 0 deletions src/sentry/nodestore/filestore/backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import datetime
import logging
from io import BytesIO

from sentry import options as options_store
from sentry.models.file import get_storage
from sentry.nodestore.base import NodeStorage

logger = logging.getLogger("sentry.nodestore")


class FilestoreNodeStorage(NodeStorage):
"""
A backend that persist nodes to configured File Store.
Intended for "s3" or "gcs", which performnace may not be ideal.
config.yml Configuration reference:
>>> filestore.backend: "s3"
... filestore.options:
... access_key: "xxx"
... secret_key: "xxx"
... endpoint_url: "https://s3.us-east-1.amazonaws.com"
... bucket_name: "sentry"
... location: "/sentry"
"""

def __init__(self, prefix_path=None):
self.prefix_path: str = "nodestore/"
backend = options_store.get("filestore.backend")
if backend not in ["s3", "gcs"]:
logger.warning(
"FilestoreNodeStorage was intended for s3 or gcs, currently using: %s", backend
)
if prefix_path:
self.prefix_path = prefix_path

def _get_bytes(self, id: str):
storage = get_storage()
path = self.node_path(id)
return storage.open(path).read()

def _set_bytes(self, id: str, data: bytes, ttl=0):
storage = get_storage()
path = self.node_path(id)
storage.save(path, BytesIO(data))

def delete(self, id):
storage = get_storage()
path = self.node_path(id)
storage.delete(path)

def cleanup(self, cutoff: datetime.datetime):
"""
This driver does not have managed TTLs. To enable TTLs you will need to enable it on your
bucket.
"""
raise NotImplementedError

def bootstrap(self):
# Nothing for this backend to do during bootstrap
pass

def node_path(self, id: str):
return f"{self.prefix_path}{id}.json"
21 changes: 15 additions & 6 deletions src/sentry/utils/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@ class NoRegistrationExistsError(ValueError):


class Registry(Generic[T]):
def __init__(self):
"""
A simple generic registry that allows for registering and retrieving items by key. Reverse lookup by value is enabled by default.
If you have duplicate values, you may want to disable reverse lookup.
"""

def __init__(self, enable_reverse_lookup=True):
self.registrations: dict[str, T] = {}
self.reverse_lookup: dict[T, str] = {}
self.enable_reverse_lookup = enable_reverse_lookup

def register(self, key: str):
def inner(item: T) -> T:
Expand All @@ -26,13 +32,14 @@ def inner(item: T) -> T:
f"A registration already exists for {key}: {self.registrations[key]}"
)

if item in self.reverse_lookup:
raise AlreadyRegisteredError(
f"A registration already exists for {item}: {self.reverse_lookup[item]}"
)
if self.enable_reverse_lookup:
if item in self.reverse_lookup:
raise AlreadyRegisteredError(
f"A registration already exists for {item}: {self.reverse_lookup[item]}"
)
self.reverse_lookup[item] = key

self.registrations[key] = item
self.reverse_lookup[item] = key

return item

Expand All @@ -44,6 +51,8 @@ def get(self, key: str) -> T:
return self.registrations[key]

def get_key(self, item: T) -> str:
if not self.enable_reverse_lookup:
raise NotImplementedError("Reverse lookup is not enabled")
if item not in self.reverse_lookup:
raise NoRegistrationExistsError(f"No registration exists for {item}")
return self.reverse_lookup[item]
20 changes: 20 additions & 0 deletions tests/sentry/utils/test_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,23 @@ def unregistered_func():

test_registry.register("something else")(unregistered_func)
assert test_registry.get("something else") == unregistered_func

def test_allow_duplicate_values(self):
test_registry = Registry[str](enable_reverse_lookup=False)

@test_registry.register("something")
@test_registry.register("something 2")
def registered_func():
pass

assert test_registry.get("something") == registered_func
assert test_registry.get("something 2") == registered_func

with pytest.raises(NoRegistrationExistsError):
test_registry.get("something else")

with pytest.raises(NotImplementedError):
test_registry.get_key(registered_func)

test_registry.register("something else")(registered_func)
assert test_registry.get("something else") == registered_func

0 comments on commit 3984e7c

Please sign in to comment.