notify-300 set total message limit of 250k

This commit is contained in:
Kenneth Kehl
2023-04-28 12:37:06 -07:00
parent 5cbeedb000
commit 9de806000a
14 changed files with 193 additions and 89 deletions
+2 -1
View File
@@ -59,7 +59,8 @@ werkzeug = "~=2.2"
prometheus-client = "==0.14.1" prometheus-client = "==0.14.1"
gds-metrics = {version = "==0.2.4", ref = "6f1840a57b6fb1ee40b7e84f2f18ec229de8aa72", git = "https://github.com/alphagov/gds_metrics_python.git"} gds-metrics = {version = "==0.2.4", ref = "6f1840a57b6fb1ee40b7e84f2f18ec229de8aa72", git = "https://github.com/alphagov/gds_metrics_python.git"}
packaging = "==21.3" packaging = "==21.3"
notifications-utils = {editable = true, ref = "main", git = "https://github.com/GSA/notifications-utils.git"} # TODO revert to main
notifications-utils = {editable = true, ref = "notify-300", git = "https://github.com/GSA/notifications-utils.git"}
newrelic = "*" newrelic = "*"
[dev-packages] [dev-packages]
Generated
+74 -74
View File
@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "f99ef7d5dbcae426e1dc5512454ea13d352b3259524b144c55bf2ae135102215" "sha256": "b58a7407ba322daf10ca83357dc35a7af28bac028fa04475d56f8c53d93b3028"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -768,7 +768,7 @@
"notifications-utils": { "notifications-utils": {
"editable": true, "editable": true,
"git": "https://github.com/GSA/notifications-utils.git", "git": "https://github.com/GSA/notifications-utils.git",
"ref": "c7a85c4d1e8f975d349c1555f416ae20f28a2ce1" "ref": "8a7db23114779d71d2eb5344670d38413d188c1d"
}, },
"numpy": { "numpy": {
"hashes": [ "hashes": [
@@ -828,10 +828,10 @@
}, },
"phonenumbers": { "phonenumbers": {
"hashes": [ "hashes": [
"sha256:421b69fd6d6650372000a6c47ab5b5c5d7b438b33f7b317739e728eff1ec1886", "sha256:107469114fd297258a485bdf8238d0522cb392db1257faf2bf23384ecbdb0e8a",
"sha256:fe071b8324473e72a54b52e602d059c15b999ec9900fff9e42c01b422aeca662" "sha256:3e3274d88cab3609b55ff5b93417075dbca2d13064f103fbf562e0ea1dda0f9a"
], ],
"version": "==8.13.10" "version": "==8.13.11"
}, },
"prometheus-client": { "prometheus-client": {
"hashes": [ "hashes": [
@@ -1061,11 +1061,11 @@
}, },
"requests": { "requests": {
"hashes": [ "hashes": [
"sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", "sha256:e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b",
"sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" "sha256:f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059"
], ],
"markers": "python_version >= '3.7' and python_version < '4'", "markers": "python_version >= '3.7'",
"version": "==2.28.2" "version": "==2.29.0"
}, },
"rfc3339-validator": { "rfc3339-validator": {
"hashes": [ "hashes": [
@@ -1267,11 +1267,11 @@
}, },
"werkzeug": { "werkzeug": {
"hashes": [ "hashes": [
"sha256:340335057f72974d9281dbaf52c8090a9f9a59ba304ae814bf0656e6559c0020", "sha256:2f3278e9ef61511cdf82cc28fc5da0f5b501dd8f01ecf5ef6a5d810048f68702",
"sha256:3b6b46926d052b8ebca97c4dc73c12e47bdd07d57ab0600c039c3155450227bc" "sha256:b7b8bc1609f35ae8e45d48a9b58d7a4eb1e41eec148d37e977e5df6ebf3398b2"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.3.0" "version": "==2.3.2"
}, },
"wrapt": { "wrapt": {
"hashes": [ "hashes": [
@@ -1484,60 +1484,60 @@
"toml" "toml"
], ],
"hashes": [ "hashes": [
"sha256:06ddd9c0249a0546997fdda5a30fbcb40f23926df0a874a60a8a185bc3a87d93", "sha256:00f8fd8a5fe1ffc3aef78ea2dbf553e5c0f4664324e878995e38d41f037eb2b3",
"sha256:0743b0035d4b0e32bc1df5de70fba3059662ace5b9a2a86a9f894cfe66569013", "sha256:0b65a6a5484b7f2970393d6250553c05b2ede069e0e18abe907fdc7f3528252e",
"sha256:0f3736a5d34e091b0a611964c6262fd68ca4363df56185902528f0b75dbb9c1f", "sha256:12bc9127c8aca2f7c25c9acca53da3db6799b2999b40f28c2546237b7ea28459",
"sha256:1127b16220f7bfb3f1049ed4a62d26d81970a723544e8252db0efde853268e21", "sha256:1a3e8697cb40f28e5bcfb6f4bda7852d96dbb6f6fd7cc306aba4ae690c9905ab",
"sha256:172db976ae6327ed4728e2507daf8a4de73c7cc89796483e0a9198fd2e47b462", "sha256:1d2a9180beff1922b09bd7389e23454928e108449e646c26da5c62e29b0bf4e3",
"sha256:182eb9ac3f2b4874a1f41b78b87db20b66da6b9cdc32737fbbf4fea0c35b23fc", "sha256:1d3893f285fd76f56651f04d1efd3bdce251c32992a64c51e5d6ec3ba9e3f9c9",
"sha256:1bb1e77a9a311346294621be905ea8a2c30d3ad371fc15bb72e98bfcfae532df", "sha256:2857894c22833d3da6e113623a9b7440159b2295280b4e0d954cadbfa724b85a",
"sha256:1fd78b911aea9cec3b7e1e2622c8018d51c0d2bbcf8faaf53c2497eb114911c1", "sha256:29c7d88468f01a75231797173b52dc66d20a8d91b8bb75c88fc5861268578f52",
"sha256:20d1a2a76bb4eb00e4d36b9699f9b7aba93271c9c29220ad4c6a9581a0320235", "sha256:2d784177a7fb9d0f58d24d3e60638c8b729c3693963bf67fa919120f750db237",
"sha256:21b154aba06df42e4b96fc915512ab39595105f6c483991287021ed95776d934", "sha256:39747afc854a7ee14e5e132da7db179d6281faf97dc51e6d7806651811c47538",
"sha256:2c2e58e45fe53fab81f85474e5d4d226eeab0f27b45aa062856c89389da2f0d9", "sha256:3d6f3c5b6738a494f17c73b4aa3aa899865cc33a74aa85e3b5695943b79ad3ce",
"sha256:2c3b2803e730dc2797a017335827e9da6da0e84c745ce0f552e66400abdfb9a1", "sha256:3fc9cde48de956bfbacea026936fbd4974ff1dc2f83397c6f1968f0142c9d50b",
"sha256:3146b8e16fa60427e03884301bf8209221f5761ac754ee6b267642a2fd354c48", "sha256:4078939c4b7053e14e87c65aa68dbed7867e326e450f94038bfe1a1b22078ff9",
"sha256:344e714bd0fe921fc72d97404ebbdbf9127bac0ca1ff66d7b79efc143cf7c0c4", "sha256:437da7d2fcc35bf45e04b7e9cfecb7c459ec6f6dc17a8558ed52e8d666c2d9ab",
"sha256:387065e420aed3c71b61af7e82c7b6bc1c592f7e3c7a66e9f78dd178699da4fe", "sha256:4522dd9aeb9cc2c4c54ce23933beb37a4e106ec2ba94f69138c159024c8a906a",
"sha256:3f04becd4fcda03c0160d0da9c8f0c246bc78f2f7af0feea1ec0930e7c93fa4a", "sha256:50fda3d33b705b9c01e3b772cfa7d14de8aec2ec2870e4320992c26d057fde12",
"sha256:4a42e1eff0ca9a7cb7dc9ecda41dfc7cbc17cb1d02117214be0561bd1134772b", "sha256:56a674ad18d6b04008283ca03c012be913bf89d91c0803c54c24600b300d9e51",
"sha256:4ea748802cc0de4de92ef8244dd84ffd793bd2e7be784cd8394d557a3c751e21", "sha256:56d74d6fbd5a98a5629e8467b719b0abea9ca01a6b13555d125c84f8bf4ea23d",
"sha256:55416d7385774285b6e2a5feca0af9652f7f444a4fa3d29d8ab052fafef9d00d", "sha256:5c122d120c11a236558c339a59b4b60947b38ac9e3ad30a0e0e02540b37bf536",
"sha256:5d0391fb4cfc171ce40437f67eb050a340fdbd0f9f49d6353a387f1b7f9dd4fa", "sha256:5c6c6e3b8fb6411a2035da78d86516bfcfd450571d167304911814407697fb7a",
"sha256:63cdeaac4ae85a179a8d6bc09b77b564c096250d759eed343a89d91bce8b6367", "sha256:603a2b172126e3b08c11ca34200143089a088cd0297d4cfc4922d2c1c3a892f9",
"sha256:72fcae5bcac3333a4cf3b8f34eec99cea1187acd55af723bcbd559adfdcb5535", "sha256:60feb703abc8d78e9427d873bcf924c9e30cf540a21971ef5a17154da763b60f",
"sha256:7c4ed4e9f3b123aa403ab424430b426a1992e6f4c8fd3cb56ea520446e04d152", "sha256:6a17bf32e9e3333d78606ac1073dd20655dc0752d5b923fa76afd3bc91674ab4",
"sha256:83957d349838a636e768251c7e9979e899a569794b44c3728eaebd11d848e58e", "sha256:700bc9fb1074e0c67c09fe96a803de66663830420781df8dc9fb90d7421d4ccb",
"sha256:87ecc7c9a1a9f912e306997ffee020297ccb5ea388421fe62a2a02747e4d5539", "sha256:72751d117ceaad3b1ea3bcb9e85f5409bbe9fb8a40086e17333b994dbccc0718",
"sha256:8f69770f5ca1994cb32c38965e95f57504d3aea96b6c024624fdd5bb1aa494a1", "sha256:7283f78d07a201ac7d9dc2ac2e4faaea99c4d302f243ee5b4e359f3e170dc008",
"sha256:8f6c930fd70d91ddee53194e93029e3ef2aabe26725aa3c2753df057e296b925", "sha256:856bcb837e96adede31018a0854ce7711a5d6174db1a84e629134970676c54fa",
"sha256:965ee3e782c7892befc25575fa171b521d33798132692df428a09efacaffe8d0", "sha256:864e36947289be05abd83267c4bade35e772526d3e9653444a9dc891faf0d698",
"sha256:974bc90d6f6c1e59ceb1516ab00cf1cdfbb2e555795d49fa9571d611f449bcb2", "sha256:8769a67e8816c7e94d5bf446fc0501641fde78fdff362feb28c2c64d45d0e9b1",
"sha256:981b4df72c93e3bc04478153df516d385317628bd9c10be699c93c26ddcca8ab", "sha256:876e4ef3eff00b50787867c5bae84857a9af4c369a9d5b266cd9b19f61e48ef7",
"sha256:aa784405f0c640940595fa0f14064d8e84aff0b0f762fa18393e2760a2cf5841", "sha256:89e63b38c7b888e00fd42ce458f838dccb66de06baea2da71801b0fc9070bfa0",
"sha256:ae7863a1d8db6a014b6f2ff9c1582ab1aad55a6d25bac19710a8df68921b6e30", "sha256:92b565c51732ea2e7e541709ccce76391b39f4254260e5922e08e00971e88e33",
"sha256:aeae2aa38395b18106e552833f2a50c27ea0000122bde421c31d11ed7e6f9c91", "sha256:9e5eedde6e6e241ec3816f05767cc77e7456bf5ec6b373fb29917f0990e2078f",
"sha256:b2317d5ed777bf5a033e83d4f1389fd4ef045763141d8f10eb09a7035cee774c", "sha256:a5c4f2e44a2ae15fa6883898e756552db5105ca4bd918634cbd5b7c00e19e8a1",
"sha256:be19931a8dcbe6ab464f3339966856996b12a00f9fe53f346ab3be872d03e257", "sha256:ab08af91cf4d847a6e15d7d5eeae5fead1487caf16ff3a2056dbe64d058fd246",
"sha256:be9824c1c874b73b96288c6d3de793bf7f3a597770205068c6163ea1f326e8b9", "sha256:ab08e03add2cf5793e66ac1bbbb24acfa90c125476f5724f5d44c56eeec1d635",
"sha256:c0045f8f23a5fb30b2eb3b8a83664d8dc4fb58faddf8155d7109166adb9f2040", "sha256:ac4861241e693e21b280f07844ae0e0707665e1dfcbf9466b793584984ae45c4",
"sha256:c86bd45d1659b1ae3d0ba1909326b03598affbc9ed71520e0ff8c31a993ad911", "sha256:b3023ce23e41a6f006c09f7e6d62b6c069c36bdc9f7de16a5ef823acc02e6c63",
"sha256:ca0f34363e2634deffd390a0fef1aa99168ae9ed2af01af4a1f5865e362f8623", "sha256:bc47015fc0455753e8aba1f38b81b731aaf7f004a0c390b404e0fcf1d6c1d72f",
"sha256:d298c2815fa4891edd9abe5ad6e6cb4207104c7dd9fd13aea3fdebf6f9b91259", "sha256:c2becddfcbf3d994a8f4f9dd2b6015cae3a3eff50dedc6e4a17c3cccbe8f93d4",
"sha256:d2a3a6146fe9319926e1d477842ca2a63fe99af5ae690b1f5c11e6af074a6b5c", "sha256:cdee9a77fd0ce000781680b6a1f4b721c567f66f2f73a49be1843ff439d634f3",
"sha256:dfd393094cd82ceb9b40df4c77976015a314b267d498268a076e940fe7be6b79", "sha256:cdfb53bef4b2739ff747ebbd76d6ac5384371fd3c7a8af08899074eba034d483",
"sha256:e58c0d41d336569d63d1b113bd573db8363bc4146f39444125b7f8060e4e04f5", "sha256:d4db4e6c115d869cd5397d3d21fd99e4c7053205c33a4ae725c90d19dcd178af",
"sha256:ea3f5bc91d7d457da7d48c7a732beaf79d0c8131df3ab278e6bba6297e23c6c4", "sha256:d9f770c6052d9b5c9b0e824fd8c003fe33276473b65b4f10ece9565ceb62438e",
"sha256:ea53151d87c52e98133eb8ac78f1206498c015849662ca8dc246255265d9c3c4", "sha256:e41a7f44e73b37c6f0132ecfdc1c8b67722f42a3d9b979e6ebc150c8e80cf13a",
"sha256:eb0edc3ce9760d2f21637766c3aa04822030e7451981ce569a1b3456b7053f22", "sha256:ea534200efbf600e60130c48552f99f351cae2906898a9cd924c1c7f2fb02853",
"sha256:f649dd53833b495c3ebd04d6eec58479454a1784987af8afb77540d6c1767abd", "sha256:f19ba9301e6fb0b94ba71fda9a1b02d11f0aab7f8e2455122a4e2921b6703c2f",
"sha256:f760073fcf8f3d6933178d67754f4f2d4e924e321f4bb0dcef0424ca0215eba1", "sha256:f37ae1804596f13d811e0247ffc8219f5261b3565bdf45fcbb4fc091b8e9ff35",
"sha256:fa546d66639d69aa967bf08156eb8c9d0cd6f6de84be9e8c9819f52ad499c910", "sha256:f7668a621afc52db29f6867e0e9c72a1eec9f02c94a7c36599119d557cf6e471",
"sha256:fd214917cabdd6f673a29d708574e9fbdb892cb77eb426d0eae3490d95ca7859", "sha256:f7ffdb3af2a01ce91577f84fc0faa056029fe457f3183007cffe7b11ea78b23c",
"sha256:fff5aaa6becf2c6a1699ae6a39e2e6fb0672c2d42eca8eb0cafa91cf2e9bd312" "sha256:fabd1f4d12dfd6b4f309208c2f31b116dc5900e0b42dbafe4ee1bc7c998ffbb0"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==7.2.3" "version": "==7.2.4"
}, },
"cryptography": { "cryptography": {
"hashes": [ "hashes": [
@@ -2074,11 +2074,11 @@
}, },
"requests": { "requests": {
"hashes": [ "hashes": [
"sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", "sha256:e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b",
"sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" "sha256:f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059"
], ],
"markers": "python_version >= '3.7' and python_version < '4'", "markers": "python_version >= '3.7'",
"version": "==2.28.2" "version": "==2.29.0"
}, },
"requests-mock": { "requests-mock": {
"hashes": [ "hashes": [
@@ -2105,11 +2105,11 @@
}, },
"rich": { "rich": {
"hashes": [ "hashes": [
"sha256:22b74cae0278fd5086ff44144d3813be1cedc9115bdfabbfefd86400cb88b20a", "sha256:2d11b9b8dd03868f09b4fffadc84a6a8cda574e40dc90821bd845720ebb8e89c",
"sha256:b5d573e13605423ec80bdd0cd5f8541f7844a0e71a13f74cf454ccb2f490708b" "sha256:69cdf53799e63f38b95b9bf9c875f8c90e78dd62b2f00c13a911c7a3b9fa4704"
], ],
"markers": "python_full_version >= '3.7.0'", "markers": "python_full_version >= '3.7.0'",
"version": "==13.3.4" "version": "==13.3.5"
}, },
"s3transfer": { "s3transfer": {
"hashes": [ "hashes": [
@@ -2190,11 +2190,11 @@
}, },
"werkzeug": { "werkzeug": {
"hashes": [ "hashes": [
"sha256:340335057f72974d9281dbaf52c8090a9f9a59ba304ae814bf0656e6559c0020", "sha256:2f3278e9ef61511cdf82cc28fc5da0f5b501dd8f01ecf5ef6a5d810048f68702",
"sha256:3b6b46926d052b8ebca97c4dc73c12e47bdd07d57ab0600c039c3155450227bc" "sha256:b7b8bc1609f35ae8e45d48a9b58d7a4eb1e41eec148d37e977e5df6ebf3398b2"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.3.0" "version": "==2.3.2"
}, },
"xmltodict": { "xmltodict": {
"hashes": [ "hashes": [
+30 -6
View File
@@ -30,7 +30,10 @@ from app.models import (
SMS_TYPE, SMS_TYPE,
) )
from app.notifications.process_notifications import persist_notification from app.notifications.process_notifications import persist_notification
from app.notifications.validators import check_service_over_daily_message_limit from app.notifications.validators import (
check_service_over_daily_message_limit,
check_service_over_total_message_limit,
)
from app.serialised_models import SerialisedService, SerialisedTemplate from app.serialised_models import SerialisedService, SerialisedTemplate
from app.service.utils import service_allowed_to_send_to from app.service.utils import service_allowed_to_send_to
from app.utils import DATETIME_FORMAT from app.utils import DATETIME_FORMAT
@@ -59,7 +62,10 @@ def process_job(job_id, sender_id=None):
"Job {} has been cancelled, service {} is inactive".format(job_id, service.id)) "Job {} has been cancelled, service {} is inactive".format(job_id, service.id))
return return
if __sending_limits_for_job_exceeded(service, job, job_id): if __daily_sending_limits_for_job_exceeded(service, job, job_id):
return
if __total_sending_limits_for_job_exceeded(service, job, job_id):
return return
recipient_csv, template, sender_id = get_recipient_csv_and_template_and_sender_id(job) recipient_csv, template, sender_id = get_recipient_csv_and_template_and_sender_id(job)
@@ -134,10 +140,10 @@ def process_row(row, template, job, service, sender_id=None):
return notification_id return notification_id
def __sending_limits_for_job_exceeded(service, job, job_id): def __daily_sending_limits_for_job_exceeded(service, job, job_id):
try: try:
total_sent = check_service_over_daily_message_limit(KEY_TYPE_NORMAL, service) total_daily_sent = check_service_over_daily_message_limit(KEY_TYPE_NORMAL, service)
if total_sent + job.notification_count > service.message_limit: if total_daily_sent + job.notification_count > service.message_limit:
raise TooManyRequestsError(service.message_limit) raise TooManyRequestsError(service.message_limit)
else: else:
return False return False
@@ -146,7 +152,25 @@ def __sending_limits_for_job_exceeded(service, job, job_id):
job.processing_finished = datetime.utcnow() job.processing_finished = datetime.utcnow()
dao_update_job(job) dao_update_job(job)
current_app.logger.info( current_app.logger.info(
"Job {} size {} error. Sending limits {} exceeded".format( "Job {} size {} error. Daily ending limits {} exceeded".format(
job_id, job.notification_count, service.message_limit)
)
return True
def __total_sending_limits_for_job_exceeded(service, job, job_id):
try:
total_sent = check_service_over_total_message_limit(KEY_TYPE_NORMAL, service)
if total_sent + job.notification_count > service.total_message_limit:
raise TooManyRequestsError(service.total_message_limit)
else:
return False
except TooManyRequestsError:
job.job_status = 'total sending limits exceeded'
job.processing_finished = datetime.utcnow()
dao_update_job(job)
current_app.logger.info(
"Job {} size {} error. Total sending limits {} exceeded".format(
job_id, job.notification_count, service.message_limit) job_id, job.notification_count, service.message_limit)
) )
return True return True
+1
View File
@@ -267,6 +267,7 @@ class Config(object):
FREE_SMS_TIER_FRAGMENT_COUNT = 250000 FREE_SMS_TIER_FRAGMENT_COUNT = 250000
DAILY_MESSAGE_LIMIT = 5000 DAILY_MESSAGE_LIMIT = 5000
TOTAL_MESSAGE_LIMIT = 7
HIGH_VOLUME_SERVICE = json.loads(getenv('HIGH_VOLUME_SERVICE', '[]')) HIGH_VOLUME_SERVICE = json.loads(getenv('HIGH_VOLUME_SERVICE', '[]'))
+2
View File
@@ -435,6 +435,8 @@ class Service(db.Model, Versioned):
onupdate=datetime.datetime.utcnow) onupdate=datetime.datetime.utcnow)
active = db.Column(db.Boolean, index=False, unique=False, nullable=False, default=True) active = db.Column(db.Boolean, index=False, unique=False, nullable=False, default=True)
message_limit = db.Column(db.BigInteger, index=False, unique=False, nullable=False) message_limit = db.Column(db.BigInteger, index=False, unique=False, nullable=False)
# TODO nullable if we are adding it late?
total_message_limit = db.Column(db.BigInteger, index=False, unique=False, nullable=False, default=250000)
restricted = db.Column(db.Boolean, index=False, unique=False, nullable=False) restricted = db.Column(db.Boolean, index=False, unique=False, nullable=False)
research_mode = db.Column(db.Boolean, index=False, unique=False, nullable=False, default=False) research_mode = db.Column(db.Boolean, index=False, unique=False, nullable=False, default=False)
email_from = db.Column(db.Text, index=False, unique=True, nullable=False) email_from = db.Column(db.Text, index=False, unique=True, nullable=False)
+21
View File
@@ -5,6 +5,7 @@ from notifications_utils.clients.redis import (
daily_limit_cache_key, daily_limit_cache_key,
daily_total_cache_key, daily_total_cache_key,
rate_limit_cache_key, rate_limit_cache_key,
total_limit_cache_key,
) )
from notifications_utils.recipients import ( from notifications_utils.recipients import (
get_international_phone_info, get_international_phone_info,
@@ -74,6 +75,26 @@ def check_service_over_daily_message_limit(key_type, service):
return int(service_stats) return int(service_stats)
def check_service_over_total_message_limit(key_type, service):
if key_type == KEY_TYPE_TEST or not current_app.config['REDIS_ENABLED']:
return 0
cache_key = total_limit_cache_key(service.id)
service_stats = redis_store.get(cache_key)
if service_stats is None:
# first message of the day, set the cache to 0 and the expiry to 24 hours
service_stats = 0
redis_store.set(cache_key, service_stats, ex=86400)
return service_stats
if int(service_stats) >= service.total_message_limit:
current_app.logger.info(
"service {} has been rate limited for total use sent {} limit {}".format(
service.id, int(service_stats), service.total_message_limit)
)
raise TooManyRequestsError(service.total_message_limit)
return int(service_stats)
def check_application_over_daily_message_total(key_type, service): def check_application_over_daily_message_total(key_type, service):
if key_type == KEY_TYPE_TEST or not current_app.config['REDIS_ENABLED']: if key_type == KEY_TYPE_TEST or not current_app.config['REDIS_ENABLED']:
return 0 return 0
+2
View File
@@ -317,6 +317,7 @@ class DetailedServiceSchema(BaseSchema):
'inbound_sms', 'inbound_sms',
'jobs', 'jobs',
'message_limit', 'message_limit',
'total_message_limit',
'permissions', 'permissions',
'rate_limit', 'rate_limit',
'reply_to_email_addresses', 'reply_to_email_addresses',
@@ -697,6 +698,7 @@ class ServiceHistorySchema(ma.Schema):
updated_at = FlexibleDateTime() updated_at = FlexibleDateTime()
active = fields.Boolean() active = fields.Boolean()
message_limit = fields.Integer() message_limit = fields.Integer()
total_message_limit = fields.Integer()
restricted = fields.Boolean() restricted = fields.Boolean()
email_from = fields.String() email_from = fields.String()
created_by_id = fields.UUID() created_by_id = fields.UUID()
+1
View File
@@ -79,6 +79,7 @@ class SerialisedService(SerialisedModel):
'contact_link', 'contact_link',
'email_from', 'email_from',
'message_limit', 'message_limit',
'total_message_limit',
'permissions', 'permissions',
'rate_limit', 'rate_limit',
'research_mode', 'research_mode',
+3
View File
@@ -13,6 +13,7 @@ from app.notifications.process_notifications import (
) )
from app.notifications.validators import ( from app.notifications.validators import (
check_service_over_daily_message_limit, check_service_over_daily_message_limit,
check_service_over_total_message_limit,
validate_and_format_recipient, validate_and_format_recipient,
validate_template, validate_template,
) )
@@ -47,6 +48,8 @@ def send_one_off_notification(service_id, post_data):
check_service_over_daily_message_limit(KEY_TYPE_NORMAL, service) check_service_over_daily_message_limit(KEY_TYPE_NORMAL, service)
check_service_over_total_message_limit(KEY_TYPE_NORMAL, service)
validate_and_format_recipient( validate_and_format_recipient(
send_to=post_data['to'], send_to=post_data['to'],
key_type=KEY_TYPE_NORMAL, key_type=KEY_TYPE_NORMAL,
@@ -0,0 +1,23 @@
"""
Revision ID: d2db89558026
Revises: 0394_remove_contact_list
Create Date: 2023-04-24 11:35:22.873930
"""
from alembic import op
import sqlalchemy as sa
revision = '0395_add_total_message_limit'
down_revision = '0394_remove_contact_list'
def upgrade():
op.add_column('services', sa.Column('total_message_limit', sa.Integer))
op.add_column('services_history', sa.Column('total_message_limit', sa.Integer))
def downgrade():
op.drop_column('services', 'total_message_limit')
op.drop_column('services_history', 'total_message_limit')
+1
View File
@@ -209,6 +209,7 @@ def sample_service(sample_user):
data = { data = {
'name': service_name, 'name': service_name,
'message_limit': 1000, 'message_limit': 1000,
'total_message_limit': 250000,
'restricted': False, 'restricted': False,
'email_from': email_from, 'email_from': email_from,
'created_by': sample_user 'created_by': sample_user
+2
View File
@@ -107,6 +107,7 @@ def create_service(
email_from=None, email_from=None,
prefix_sms=True, prefix_sms=True,
message_limit=1000, message_limit=1000,
total_message_limit=250000,
organisation_type='federal', organisation_type='federal',
check_if_service_exists=False, check_if_service_exists=False,
go_live_user=None, go_live_user=None,
@@ -123,6 +124,7 @@ def create_service(
service = Service( service = Service(
name=service_name, name=service_name,
message_limit=message_limit, message_limit=message_limit,
total_message_limit=total_message_limit,
restricted=restricted, restricted=restricted,
email_from=email_from if email_from else service_name.lower().replace(' ', '.'), email_from=email_from if email_from else service_name.lower().replace(' ', '.'),
created_by=user if user else create_user(email='{}@digital.cabinet-office.gov.uk'.format(uuid.uuid4())), created_by=user if user else create_user(email='{}@digital.cabinet-office.gov.uk'.format(uuid.uuid4())),
+31 -7
View File
@@ -249,6 +249,7 @@ def test_get_service_by_id(admin_request, sample_service):
'id', 'id',
'inbound_api', 'inbound_api',
'message_limit', 'message_limit',
'total_message_limit',
'name', 'name',
'notes', 'notes',
'organisation', 'organisation',
@@ -362,6 +363,7 @@ def test_create_service(
'name': 'created service', 'name': 'created service',
'user_id': str(sample_user.id), 'user_id': str(sample_user.id),
'message_limit': 1000, 'message_limit': 1000,
'total_message_limit': 250000,
'restricted': False, 'restricted': False,
'active': False, 'active': False,
'email_from': 'created.service', 'email_from': 'created.service',
@@ -426,6 +428,7 @@ def test_create_service_with_domain_sets_organisation(
'name': 'created service', 'name': 'created service',
'user_id': str(sample_user.id), 'user_id': str(sample_user.id),
'message_limit': 1000, 'message_limit': 1000,
'total_message_limit': 250000,
'restricted': False, 'restricted': False,
'active': False, 'active': False,
'email_from': 'created.service', 'email_from': 'created.service',
@@ -448,6 +451,7 @@ def test_create_service_should_create_annual_billing_for_service(
'name': 'created service', 'name': 'created service',
'user_id': str(sample_user.id), 'user_id': str(sample_user.id),
'message_limit': 1000, 'message_limit': 1000,
'total_message_limit': 250000,
'restricted': False, 'restricted': False,
'active': False, 'active': False,
'email_from': 'created.service', 'email_from': 'created.service',
@@ -468,6 +472,7 @@ def test_create_service_should_raise_exception_and_not_create_service_if_annual_
'name': 'created service', 'name': 'created service',
'user_id': str(sample_user.id), 'user_id': str(sample_user.id),
'message_limit': 1000, 'message_limit': 1000,
'total_message_limit': 250000,
'restricted': False, 'restricted': False,
'active': False, 'active': False,
'email_from': 'created.service', 'email_from': 'created.service',
@@ -498,6 +503,7 @@ def test_create_service_inherits_branding_from_organisation(
'name': 'created service', 'name': 'created service',
'user_id': str(sample_user.id), 'user_id': str(sample_user.id),
'message_limit': 1000, 'message_limit': 1000,
'total_message_limit': 250000,
'restricted': False, 'restricted': False,
'active': False, 'active': False,
'email_from': 'created.service', 'email_from': 'created.service',
@@ -516,6 +522,7 @@ def test_should_not_create_service_with_missing_user_id_field(notify_api, fake_u
'email_from': 'service', 'email_from': 'service',
'name': 'created service', 'name': 'created service',
'message_limit': 1000, 'message_limit': 1000,
'total_message_limit': 250000,
'restricted': False, 'restricted': False,
'active': False, 'active': False,
'created_by': str(fake_uuid) 'created_by': str(fake_uuid)
@@ -539,6 +546,7 @@ def test_should_error_if_created_by_missing(notify_api, sample_user):
'email_from': 'service', 'email_from': 'service',
'name': 'created service', 'name': 'created service',
'message_limit': 1000, 'message_limit': 1000,
'total_message_limit': 250000,
'restricted': False, 'restricted': False,
'active': False, 'active': False,
'user_id': str(sample_user.id) 'user_id': str(sample_user.id)
@@ -565,6 +573,7 @@ def test_should_not_create_service_with_missing_if_user_id_is_not_in_database(no
'user_id': fake_uuid, 'user_id': fake_uuid,
'name': 'created service', 'name': 'created service',
'message_limit': 1000, 'message_limit': 1000,
'total_message_limit': 250000,
'restricted': False, 'restricted': False,
'active': False, 'active': False,
'created_by': str(fake_uuid) 'created_by': str(fake_uuid)
@@ -610,6 +619,7 @@ def test_should_not_create_service_with_duplicate_name(notify_api,
'name': sample_service.name, 'name': sample_service.name,
'user_id': str(sample_service.users[0].id), 'user_id': str(sample_service.users[0].id),
'message_limit': 1000, 'message_limit': 1000,
'total_message_limit': 250000,
'restricted': False, 'restricted': False,
'active': False, 'active': False,
'email_from': 'sample.service2', 'email_from': 'sample.service2',
@@ -637,6 +647,7 @@ def test_create_service_should_throw_duplicate_key_constraint_for_existing_email
'name': service_name, 'name': service_name,
'user_id': str(first_service.users[0].id), 'user_id': str(first_service.users[0].id),
'message_limit': 1000, 'message_limit': 1000,
'total_message_limit': 250000,
'restricted': False, 'restricted': False,
'active': False, 'active': False,
'email_from': 'first.service', 'email_from': 'first.service',
@@ -666,6 +677,7 @@ def test_update_service(client, notify_db_session, sample_service):
'created_by': str(sample_service.created_by.id), 'created_by': str(sample_service.created_by.id),
'email_branding': str(brand.id), 'email_branding': str(brand.id),
'organisation_type': 'federal', 'organisation_type': 'federal',
'total_message_limit': 250000,
} }
auth_header = create_admin_authorization_header() auth_header = create_admin_authorization_header()
@@ -684,6 +696,7 @@ def test_update_service(client, notify_db_session, sample_service):
def test_cant_update_service_org_type_to_random_value(client, sample_service): def test_cant_update_service_org_type_to_random_value(client, sample_service):
data = { data = {
'name': 'updated service name', 'name': 'updated service name',
'email_from': 'updated.service.name', 'email_from': 'updated.service.name',
@@ -704,6 +717,7 @@ def test_cant_update_service_org_type_to_random_value(client, sample_service):
def test_update_service_remove_email_branding(admin_request, notify_db_session, sample_service): def test_update_service_remove_email_branding(admin_request, notify_db_session, sample_service):
brand = EmailBranding(colour='#000000', logo='justice-league.png', name='Justice League') brand = EmailBranding(colour='#000000', logo='justice-league.png', name='Justice League')
sample_service.email_branding = brand sample_service.email_branding = brand
sample_service.total_message_limit = 250000
notify_db_session.commit() notify_db_session.commit()
resp = admin_request.post( resp = admin_request.post(
@@ -719,6 +733,7 @@ def test_update_service_change_email_branding(admin_request, notify_db_session,
brand2 = EmailBranding(colour='#111111', logo='avengers.png', name='Avengers') brand2 = EmailBranding(colour='#111111', logo='avengers.png', name='Avengers')
notify_db_session.add_all([brand1, brand2]) notify_db_session.add_all([brand1, brand2])
sample_service.email_branding = brand1 sample_service.email_branding = brand1
sample_service.total_message_limit = 250000
notify_db_session.commit() notify_db_session.commit()
resp = admin_request.post( resp = admin_request.post(
@@ -742,6 +757,7 @@ def test_update_service_flags(client, sample_service):
data = { data = {
'research_mode': True, 'research_mode': True,
'total_message_limit': 250000,
'permissions': [INTERNATIONAL_SMS_TYPE] 'permissions': [INTERNATIONAL_SMS_TYPE]
} }
@@ -780,6 +796,7 @@ def test_update_service_sets_volumes(
service_id=sample_service.id, service_id=sample_service.id,
_data={ _data={
field: value, field: value,
'total_message_limit': 250000,
}, },
_expected_status=expected_status, _expected_status=expected_status,
) )
@@ -804,6 +821,7 @@ def test_update_service_sets_research_consent(
service_id=sample_service.id, service_id=sample_service.id,
_data={ _data={
'consent_to_research': value, 'consent_to_research': value,
'total_message_limit': 250000,
}, },
_expected_status=expected_status, _expected_status=expected_status,
) )
@@ -879,7 +897,8 @@ def test_update_service_permissions_will_add_service_permissions(client, sample_
auth_header = create_admin_authorization_header() auth_header = create_admin_authorization_header()
data = { data = {
'permissions': [EMAIL_TYPE, SMS_TYPE] 'permissions': [EMAIL_TYPE, SMS_TYPE],
'total_message_limit': 250000,
} }
resp = client.post( resp = client.post(
@@ -1003,7 +1022,8 @@ def test_should_not_update_service_with_duplicate_name(notify_api,
email_from='another.name') email_from='another.name')
data = { data = {
'name': service_name, 'name': service_name,
'created_by': str(service.created_by.id) 'created_by': str(service.created_by.id),
'total_message_limit': 250000,
} }
auth_header = create_admin_authorization_header() auth_header = create_admin_authorization_header()
@@ -1034,7 +1054,8 @@ def test_should_not_update_service_with_duplicate_email_from(notify_api,
data = { data = {
'name': service_name, 'name': service_name,
'email_from': email_from, 'email_from': email_from,
'created_by': str(service.created_by.id) 'created_by': str(service.created_by.id),
'total_message_limit': 250000,
} }
auth_header = create_admin_authorization_header() auth_header = create_admin_authorization_header()
@@ -1133,6 +1154,7 @@ def test_default_permissions_are_added_for_user_service(notify_api,
'name': 'created service', 'name': 'created service',
'user_id': str(sample_user.id), 'user_id': str(sample_user.id),
'message_limit': 1000, 'message_limit': 1000,
'total_message_limit': 250000,
'restricted': False, 'restricted': False,
'active': False, 'active': False,
'email_from': 'created.service', 'email_from': 'created.service',
@@ -1905,7 +1927,7 @@ def test_set_sms_prefixing_for_service(
result = admin_request.post( result = admin_request.post(
'service.update_service', 'service.update_service',
service_id=sample_service.id, service_id=sample_service.id,
_data={'prefix_sms': posted_value}, _data={'prefix_sms': posted_value, 'total_message_limit': 250000},
) )
assert result['data']['prefix_sms'] == stored_value assert result['data']['prefix_sms'] == stored_value
@@ -1917,7 +1939,7 @@ def test_set_sms_prefixing_for_service_cant_be_none(
resp = admin_request.post( resp = admin_request.post(
'service.update_service', 'service.update_service',
service_id=sample_service.id, service_id=sample_service.id,
_data={'prefix_sms': None}, _data={'prefix_sms': None, 'total_message_limit': 250000},
_expected_status=400, _expected_status=400,
) )
assert resp['message'] == {'prefix_sms': ['Field may not be null.']} assert resp['message'] == {'prefix_sms': ['Field may not be null.']}
@@ -2256,7 +2278,8 @@ def test_update_service_does_not_call_send_notification_for_live_service(sample_
send_notification_mock = mocker.patch('app.service.rest.send_notification_to_service_users') send_notification_mock = mocker.patch('app.service.rest.send_notification_to_service_users')
data = { data = {
"restricted": True "restricted": True,
'total_message_limit': 250000,
} }
auth_header = create_admin_authorization_header() auth_header = create_admin_authorization_header()
@@ -2275,7 +2298,8 @@ def test_update_service_does_not_call_send_notification_when_restricted_not_chan
send_notification_mock = mocker.patch('app.service.rest.send_notification_to_service_users') send_notification_mock = mocker.patch('app.service.rest.send_notification_to_service_users')
data = { data = {
"name": 'Name of service' "name": 'Name of service',
'total_message_limit': 250000,
} }
auth_header = create_admin_authorization_header() auth_header = create_admin_authorization_header()
-1
View File
@@ -88,7 +88,6 @@ def _notify_db(notify_api, worker_id):
ALEMBIC_CONFIG = os.path.join(BASE_DIR, 'migrations') ALEMBIC_CONFIG = os.path.join(BASE_DIR, 'migrations')
config = Config(ALEMBIC_CONFIG + '/alembic.ini') config = Config(ALEMBIC_CONFIG + '/alembic.ini')
config.set_main_option("script_location", ALEMBIC_CONFIG) config.set_main_option("script_location", ALEMBIC_CONFIG)
with notify_api.app_context(): with notify_api.app_context():
upgrade(config, 'head') upgrade(config, 'head')