Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Link in renewal #503

Merged
merged 4 commits into from
Dec 2, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.16 on 2024-11-20 17:23

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('allocations', '0009_chargebudget'),
]

operations = [
migrations.AddField(
model_name='allocation',
name='low_allocation_warning_issued',
field=models.DateTimeField(null=True),
),
]
1 change: 1 addition & 0 deletions allocations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class Allocation(models.Model):
su_allocated = models.FloatField(null=True)
su_used = models.FloatField(null=True)
balance_service_version = models.IntegerField(default=2, null=False)
low_allocation_warning_issued = models.DateTimeField(null=True)

def as_dict(self):
return Allocation.to_dict(self)
Expand Down
93 changes: 92 additions & 1 deletion allocations/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from django.forms.models import model_to_dict
from django.utils import timezone
from django.utils.html import strip_tags
from django.urls import reverse
from keycloak.exceptions import KeycloakClientError

from allocations.models import Allocation, Charge
Expand Down Expand Up @@ -60,13 +61,14 @@ def _send_expiration_warning_mail(alloc, today):
f"day{'s' if time_until_expiration.days != 1 else ''}"
)

project_url = f'https://chameleoncloud.org{reverse("projects:view_project", args=[alloc.project.id])}'
docs_url = (
"https://chameleoncloud.readthedocs.io/en/latest/user/project.html"
"#recharge-or-extend-your-allocation "
)
email_body = f"""
<p>
The allocation for project <b>{charge_code}</b>
The allocation for project <a href="{project_url}"><b>{charge_code}</b></a>
will expire <b>{time_description}.</b> See our
<a href={docs_url}>Documentation</a>
on how to recharge or extend your allocation.
Expand Down Expand Up @@ -140,6 +142,95 @@ def warn_user_for_expiring_allocation():
)


def _send_low_allocation_warning(alloc, percent_used):
charge_code = alloc.project.charge_code
project_url = f'https://chameleoncloud.org{reverse("projects:view_project", args=[alloc.project.id])}'
docs_url = (
"https://chameleoncloud.readthedocs.io/en/latest/user/project.html"
"#recharge-or-extend-your-allocation "
)
email_body = f"""
<p>
The allocation for project <a href="{project_url}"><b>{charge_code}</b></a>
has used <b>{percent_used}%</b> of allocated SUs. See our
<a href={docs_url}>Documentation</a>
on how to recharge or extend your allocation.
</p>
"""

mail_sent = None
email = alloc.project.pi.email
if not email:
LOG.warning(
f"PI for project {charge_code} has no email; "
"cannot send low usage warning"
)
return None
try:
mail_sent = send_mail(
subject=f"NOTICE: Your allocation for project {charge_code} has used "
f"{percent_used}% of allocated SUs!",
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[email],
message=strip_tags(" ".join(email_body.split()).strip()),
html_message=email_body,
)
except Exception:
pass

return mail_sent


@task
def warn_user_for_low_allocations():
"""
Sends an email to users when their allocation is 90% utilized
"""
# NOTE this avoids sending a low allocation email if we've already
# sent an expiration warning email, as if the allocation is soon expiring
# low remaining SUs does not matter.
active_allocations = Allocation.objects.filter(
status="active",
low_allocation_warning_issued__isnull=True,
expiration_warning_issued__isnull=True,
)

emails_sent = 0
# Iterate over all active allocations that haven't been issued warnings
for alloc in active_allocations:
# Calculate the percentage of used SUs
balances = project_balances([alloc.project.id])[0]
# Skip edge cases
if balances["total"] is None:
continue
percentage_used = round(100.0 * balances["total"] / balances["allocated"], 2)

# Ignore if allocation hasn't used 90% of SUs.
if percentage_used < 90:
continue
# Otherwise send warning mail
mail_sent = _send_low_allocation_warning(alloc, percentage_used)
charge_code = alloc.project.charge_code

# If we successfully sent mail, log it in the database
if mail_sent:
emails_sent += 1
LOG.info(f"Warned PI about low allocation {alloc.id}")
try:
with transaction.atomic():
alloc.low_allocation_warning_issued = datetime.now(timezone.utc)
alloc.save()
except Exception:
LOG.error(
f"Failed to update ORM with low warning timestamp "
f"for project {charge_code}."
)
else:
LOG.error(
f"Failed to send expiration warning email for project {charge_code}"
)


def _deactivate_allocation(alloc):
balance = project_balances([alloc.project.id])
if not balance:
Expand Down
4 changes: 4 additions & 0 deletions chameleon/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,10 @@
"task": "allocations.tasks.warn_user_for_expiring_allocation",
"schedule": crontab(minute=0, hour=7),
},
"warn-user-for-low-allocation": {
"task": "allocations.tasks.warn_user_for_low_allocations",
"schedule": crontab(minute=30, hour=7),
},
"check_charge": {
"task": "allocations.tasks.check_charge",
"schedule": crontab(
Expand Down
Loading