Skip to content

Commit 3cb9a1c

Browse files
committed
feat: celery
Add celery task: - prune_inactive_app - close_window
1 parent 83be7a9 commit 3cb9a1c

File tree

7 files changed

+1378
-208
lines changed

7 files changed

+1378
-208
lines changed

controller/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from controller.celery import app as celery_app
2+
3+
__all__ = ("celery_app",)

controller/celery.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import os
2+
3+
from celery import Celery
4+
5+
# Set the default Django settings module for the 'celery' program.
6+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "controller.settings")
7+
8+
app = Celery("controller")
9+
10+
# Using a string here means the worker doesn't have to serialize
11+
# the configuration object to child processes.
12+
# - namespace='CELERY' means all celery-related configuration keys
13+
# should have a `CELERY_` prefix.
14+
app.config_from_object("django.conf:settings", namespace="CELERY")
15+
16+
# Load task modules from all registered Django apps.
17+
app.autodiscover_tasks()

controller/sentry/tasks.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from datetime import timedelta
2+
3+
from celery import shared_task
4+
from celery.utils.log import get_task_logger
5+
from django.conf import settings
6+
from django.db.models import F
7+
from django.utils import timezone
8+
9+
from controller.sentry.models import App
10+
11+
LOGGER = get_task_logger(__name__)
12+
13+
14+
@shared_task()
15+
def prune_inactive_app() -> None:
16+
last_seen = timezone.now() - timedelta(days=settings.APP_AUTO_PRUNE_MAX_AGE_DAY)
17+
apps = App.objects.filter(last_seen__lt=last_seen)
18+
if apps_count := apps.count():
19+
LOGGER.info("Pruning %s apps", apps_count)
20+
apps.delete()
21+
22+
23+
@shared_task()
24+
def close_window() -> None:
25+
apps = App.objects.filter(active_window_end__lt=timezone.now())
26+
apps.update(active_sample_rate=F("default_sample_rate"), active_window_end=None)

controller/sentry/views.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ def retrieve(self, request, *args, **kwargs):
2323
panic = cache.get(settings.PANIC_KEY)
2424
now = timezone.now()
2525
app.last_seen = now
26-
if app.active_window_end and app.active_window_end < now:
27-
app.active_sample_rate = app.default_sample_rate
28-
app.active_window_end = None
2926
app.save()
3027
if panic:
3128
app.active_sample_rate = 0.0

controller/settings.py

Lines changed: 74 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
import os
1313
import sys
1414
from pathlib import Path
15+
from urllib.parse import quote
16+
17+
from celery.schedules import crontab
1518

1619
# Build paths inside the project like this: BASE_DIR / 'subdir'.
1720
BASE_DIR = Path(__file__).resolve().parent.parent
@@ -27,6 +30,7 @@
2730
DEBUG = os.getenv("ENV", "production") != "production"
2831
TESTING = sys.argv[1:2] == ["test"] or os.getenv("TESTING")
2932

33+
# Static and Media
3034
STATIC_URL = os.getenv("STATIC_URL", "/assets/static/")
3135
MEDIA_URL = os.getenv("MEDIA_URL", "/assets/media/")
3236
STATIC_ROOT = os.path.join(BASE_DIR, "assets/static")
@@ -42,7 +46,13 @@
4246
ALLOWED_HOSTS = ["*"]
4347

4448
# Application definition
49+
# URLs
50+
ROOT_URLCONF = "controller.urls"
4551

52+
# WSGI
53+
WSGI_APPLICATION = "controller.wsgi.application"
54+
55+
# Application definition
4656
INSTALLED_APPS = [
4757
"admin_action_tools",
4858
"django.contrib.admin",
@@ -62,35 +72,10 @@
6272
"health_check.db", # stock Django health checkers
6373
"health_check.cache",
6474
"health_check.storage",
75+
"django_celery_results",
6576
"controller.sentry",
6677
]
6778

68-
AUTHENTICATION_BACKENDS = (
69-
# "django.contrib.auth.backends.ModelBackend",
70-
"controller.sentry.auth.ControllerOIDCAuthenticationBackend",
71-
)
72-
73-
OIDC_RP_CLIENT_ID = os.getenv("OIDC_RP_CLIENT_ID")
74-
OIDC_RP_CLIENT_SECRET = os.getenv("OIDC_RP_CLIENT_SECRET")
75-
# "<URL of the OIDC OP authorization endpoint>"
76-
OIDC_OP_AUTHORIZATION_ENDPOINT = os.getenv("OIDC_OP_AUTHORIZATION_ENDPOINT")
77-
# "<URL of the OIDC OP token endpoint>"
78-
OIDC_OP_TOKEN_ENDPOINT = os.getenv("OIDC_OP_TOKEN_ENDPOINT")
79-
# "<URL of the OIDC OP userinfo endpoint>"
80-
OIDC_OP_USER_ENDPOINT = os.getenv("OIDC_OP_USER_ENDPOINT")
81-
# "<URL path to redirect to after login>"
82-
LOGIN_REDIRECT_URL = os.getenv("LOGIN_REDIRECT_URL")
83-
# "<URL path to redirect to after logout>"
84-
LOGOUT_REDIRECT_URL = os.getenv("LOGOUT_REDIRECT_URL")
85-
86-
OIDC_RP_SIGN_ALGO = os.getenv("OIDC_RP_SIGN_ALGO", "RS256")
87-
88-
OIDC_OP_JWKS_ENDPOINT = os.getenv("OIDC_OP_JWKS_ENDPOINT")
89-
90-
91-
DEVELOPER_GROUP = os.getenv("DEVELOPER_GROUP", "Developer")
92-
93-
9479
MIDDLEWARE = [
9580
"django.middleware.security.SecurityMiddleware",
9681
"django.contrib.sessions.middleware.SessionMiddleware",
@@ -101,8 +86,8 @@
10186
"django.middleware.clickjacking.XFrameOptionsMiddleware",
10287
]
10388

104-
ROOT_URLCONF = "controller.urls"
10589

90+
# template
10691
TEMPLATES = [
10792
{
10893
"BACKEND": "django.template.backends.django.DjangoTemplates",
@@ -120,7 +105,28 @@
120105
},
121106
]
122107

123-
WSGI_APPLICATION = "controller.wsgi.application"
108+
# Authentication
109+
AUTHENTICATION_BACKENDS = (
110+
# "django.contrib.auth.backends.ModelBackend",
111+
"controller.sentry.auth.ControllerOIDCAuthenticationBackend",
112+
)
113+
114+
OIDC_RP_CLIENT_ID = os.getenv("OIDC_RP_CLIENT_ID")
115+
OIDC_RP_CLIENT_SECRET = os.getenv("OIDC_RP_CLIENT_SECRET")
116+
# "<URL of the OIDC OP authorization endpoint>"
117+
OIDC_OP_AUTHORIZATION_ENDPOINT = os.getenv("OIDC_OP_AUTHORIZATION_ENDPOINT")
118+
# "<URL of the OIDC OP token endpoint>"
119+
OIDC_OP_TOKEN_ENDPOINT = os.getenv("OIDC_OP_TOKEN_ENDPOINT")
120+
# "<URL of the OIDC OP userinfo endpoint>"
121+
OIDC_OP_USER_ENDPOINT = os.getenv("OIDC_OP_USER_ENDPOINT")
122+
# "<URL path to redirect to after login>"
123+
LOGIN_REDIRECT_URL = os.getenv("LOGIN_REDIRECT_URL")
124+
# "<URL path to redirect to after logout>"
125+
LOGOUT_REDIRECT_URL = os.getenv("LOGOUT_REDIRECT_URL")
126+
127+
OIDC_RP_SIGN_ALGO = os.getenv("OIDC_RP_SIGN_ALGO", "RS256")
128+
129+
OIDC_OP_JWKS_ENDPOINT = os.getenv("OIDC_OP_JWKS_ENDPOINT")
124130

125131

126132
# Database
@@ -172,7 +178,11 @@
172178

173179
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
174180

181+
182+
# Reuse db connection
175183
CONN_MAX_AGE = None
184+
185+
# CACHE
176186
APP_CACHE_TIMEOUT = 0
177187

178188
if not TESTING:
@@ -190,22 +200,56 @@
190200
}
191201

192202

203+
# App config
193204
DEFAULT_SAMPLE_RATE = float(os.getenv("DEFAULT_SAMPLE_RATE", "0.1"))
205+
194206
DEFAULT_WSGI_IGNORE_PATHS = os.getenv("DEFAULT_WSGI_IGNORE_PATHS", "/health,/healthz,/health/,/healthz/").split(",")
195207

196208

197209
DEFAULT_CELERY_IGNORE_TASKS = []
198210

199-
200211
CACHE_META_INVALIDATION = {
201212
"SERVER_NAME": os.getenv("CACHE_META_SERVER_NAME", "localhost"),
202213
"SERVER_PORT": int(os.getenv("CACHE_META_SERVER_PORT", "8000")),
203214
"HTTP_ACCEPT": os.getenv("CACHE_META_HTTP_ACCEPT", "*/*"),
204215
}
205216

206-
207217
MAX_BUMP_TIME_SEC = int(os.getenv("MAX_BUMP_TIME_SEC", "0"))
208218
if MAX_BUMP_TIME_SEC == 0:
209219
MAX_BUMP_TIME_SEC = 30 * 60 # 30 minutes
210220

221+
# CACHE KEY for panic
211222
PANIC_KEY = "PANIC"
223+
224+
DEVELOPER_GROUP = os.getenv("DEVELOPER_GROUP", "Developer")
225+
226+
APP_AUTO_PRUNE = os.getenv("APP_AUTO_PRUNE", "true").lower() == "true"
227+
APP_AUTO_PRUNE_MAX_AGE_DAY = int(os.getenv("APP_AUTO_PRUNE_MAX_AGE_DAY", "5"))
228+
229+
230+
# Celery
231+
BROKER_USER = quote(os.environ.get("CELERY_BROKER_USER", "rabbitmq"))
232+
BROKER_PASSWORD = quote(os.environ.get("CELERY_BROKER_PASSWORD", "rabbitmq"))
233+
BROKER_HOST = os.environ.get("CELERY_BROKER_HOST", "localhost")
234+
BROKER_PORT = os.environ.get("CELERY_BROKER_PORT", "5672")
235+
BROKER_VHOST = quote(os.environ.get("CELERY_BROKER_VHOST", "/"))
236+
237+
238+
CELERY_ACCEPT_CONTENT = ["json"]
239+
CELERY_ACKS_LATE = True
240+
CELERY_PREFETCH_MULTIPLIER = 1
241+
CELERY_RESULT_BACKEND = "django-db"
242+
CELERY_BROKER_URL = f"amqp://{BROKER_USER}:{BROKER_PASSWORD}@{BROKER_HOST}:{BROKER_PORT}/{BROKER_VHOST}"
243+
244+
CELERY_BEAT_SCHEDULE = {
245+
"close-window": {
246+
"task": "controller.sentry.tasks.close_window",
247+
"schedule": crontab(),
248+
}
249+
}
250+
251+
if APP_AUTO_PRUNE:
252+
CELERY_BEAT_SCHEDULE["prune-inactive"] = {
253+
"task": "controller.sentry.tasks.prune_inactive_app",
254+
"schedule": crontab(minute="0", hour="*"),
255+
}

0 commit comments

Comments
 (0)