You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by je...@apache.org on 2015/02/16 12:44:53 UTC

[22/37] allura git commit: [#4542] ticket:715 Add rate limits

[#4542] ticket:715 Add rate limits


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/5c82af91
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/5c82af91
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/5c82af91

Branch: refs/heads/ib/4542
Commit: 5c82af9102913513a368f3c9db3adf5397e5f42e
Parents: e7ace57
Author: Igor Bondarenko <je...@gmail.com>
Authored: Mon Feb 2 12:01:13 2015 +0000
Committer: Igor Bondarenko <je...@gmail.com>
Committed: Mon Feb 16 10:17:38 2015 +0000

----------------------------------------------------------------------
 Allura/allura/model/webhook.py       | 23 ++++++++++++++++++-
 Allura/allura/tests/test_webhooks.py | 38 +++++++++++++++++++++++++++++++
 Allura/allura/webhooks.py            |  6 ++++-
 Allura/development.ini               |  6 +++++
 4 files changed, 71 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/5c82af91/Allura/allura/model/webhook.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/webhook.py b/Allura/allura/model/webhook.py
index 05eb436..09cc7fa 100644
--- a/Allura/allura/model/webhook.py
+++ b/Allura/allura/model/webhook.py
@@ -15,7 +15,12 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
-from ming.odm import FieldProperty
+import datetime as dt
+
+from ming.odm import FieldProperty, session
+from paste.deploy.converters import asint
+from tg import config
+
 from allura.model import Artifact
 
 
@@ -28,6 +33,7 @@ class Webhook(Artifact):
     type = FieldProperty(str)
     hook_url = FieldProperty(str)
     secret = FieldProperty(str)
+    last_sent = FieldProperty(dt.datetime, if_missing=None)
 
     def url(self):
         return '{}{}/{}/{}'.format(
@@ -41,3 +47,18 @@ class Webhook(Artifact):
         ac_ids = [ac._id for ac in project.app_configs]
         hooks = cls.query.find(dict(type=type, app_config_id={'$in': ac_ids}))
         return hooks.all()
+
+    def enforce_limit(self):
+        '''Returns False if limit is reached, otherwise True'''
+        if self.last_sent is None:
+            return True
+        now = dt.datetime.utcnow()
+        config_type = self.type.replace('-', '_')
+        limit = asint(config.get('webhook.%s.limit' % config_type, 30))
+        if (now - self.last_sent) > dt.timedelta(seconds=limit):
+            return True
+        return False
+
+    def update_limit(self):
+        self.last_sent = dt.datetime.utcnow()
+        session(self).flush(self)

http://git-wip-us.apache.org/repos/asf/allura/blob/5c82af91/Allura/allura/tests/test_webhooks.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/test_webhooks.py b/Allura/allura/tests/test_webhooks.py
index fa0305f..99e5f91 100644
--- a/Allura/allura/tests/test_webhooks.py
+++ b/Allura/allura/tests/test_webhooks.py
@@ -1,6 +1,7 @@
 import json
 import hmac
 import hashlib
+import datetime as dt
 
 from mock import Mock, patch
 from nose.tools import (
@@ -12,6 +13,7 @@ from nose.tools import (
 from formencode import Invalid
 from ming.odm import session
 from pylons import tmpl_context as c
+from tg import config
 
 from allura import model as M
 from allura.lib import helpers as h
@@ -431,6 +433,18 @@ class TestRepoPushWebhookSender(TestWebhookBase):
             self.wh._id,
             sender.get_payload.return_value)
 
+    @patch('allura.webhooks.log', autospec=True)
+    @patch('allura.webhooks.send_webhook', autospec=True)
+    def test_send_limit_reached(self, send_webhook, log):
+        sender = RepoPushWebhookSender()
+        sender.get_payload = Mock()
+        self.wh.enforce_limit = Mock(return_value=False)
+        with h.push_config(c, app=self.git):
+            sender.send(arg1=1, arg2=2)
+        assert_equal(send_webhook.post.call_count, 0)
+        log.warn.assert_called_once_with(
+            'Webhook fires too often: %s. Skipping', self.wh)
+
     @patch('allura.webhooks.send_webhook', autospec=True)
     def test_send_no_configured_webhooks(self, send_webhook):
         self.wh.delete()
@@ -467,3 +481,27 @@ class TestModels(TestWebhookBase):
     def test_webhook_url(self):
         assert_equal(self.wh.url(),
             '/adobe/adobe-1/admin/webhooks/repo-push/{}'.format(self.wh._id))
+
+    def test_webhook_enforce_limit(self):
+        self.wh.last_sent = None
+        assert_equal(self.wh.enforce_limit(), True)
+        # default value
+        self.wh.last_sent = dt.datetime.utcnow() - dt.timedelta(seconds=31)
+        assert_equal(self.wh.enforce_limit(), True)
+        self.wh.last_sent = dt.datetime.utcnow() - dt.timedelta(seconds=15)
+        assert_equal(self.wh.enforce_limit(), False)
+        # value from config
+        with h.push_config(config, **{'webhook.repo_push.limit': 100}):
+            self.wh.last_sent = dt.datetime.utcnow() - dt.timedelta(seconds=101)
+            assert_equal(self.wh.enforce_limit(), True)
+            self.wh.last_sent = dt.datetime.utcnow() - dt.timedelta(seconds=35)
+            assert_equal(self.wh.enforce_limit(), False)
+
+    @patch('allura.model.webhook.dt', autospec=True)
+    def test_update_limit(self, dt_mock):
+        _now = dt.datetime(2015, 02, 02, 13, 39)
+        dt_mock.datetime.utcnow.return_value = _now
+        assert_equal(self.wh.last_sent, None)
+        self.wh.update_limit()
+        session(self.wh).expunge(self.wh)
+        assert_equal(M.Webhook.query.get(_id=self.wh._id).last_sent, _now)

http://git-wip-us.apache.org/repos/asf/allura/blob/5c82af91/Allura/allura/webhooks.py
----------------------------------------------------------------------
diff --git a/Allura/allura/webhooks.py b/Allura/allura/webhooks.py
index 2393acd..e13086d 100644
--- a/Allura/allura/webhooks.py
+++ b/Allura/allura/webhooks.py
@@ -272,7 +272,11 @@ class WebhookSender(object):
         if webhooks:
             payload = self.get_payload(**kw)
             for webhook in webhooks:
-                send_webhook.post(webhook._id, payload)
+                if webhook.enforce_limit():
+                    webhook.update_limit()
+                    send_webhook.post(webhook._id, payload)
+                else:
+                    log.warn('Webhook fires too often: %s. Skipping', webhook)
 
 
 class RepoPushWebhookSender(WebhookSender):

http://git-wip-us.apache.org/repos/asf/allura/blob/5c82af91/Allura/development.ini
----------------------------------------------------------------------
diff --git a/Allura/development.ini b/Allura/development.ini
index c6425a8..39a9efa 100644
--- a/Allura/development.ini
+++ b/Allura/development.ini
@@ -129,6 +129,12 @@ user_prefs_storage.ldap.fields.display_name = cn
 # Limit the number of emails a user can claim.
 user_prefs.maximum_claimed_emails = 20
 
+# Limit rate of webhook firing (in seconds, default = 30)
+# Option format: webhook.<hook type>.limit,
+# all '-' in hook type must be changed to '_'
+# e.g. for repo-push webhook:
+# webhook.repo_push.limit = 10
+
 # Additional fields for admin project/user search
 # Note: whitespace after comma is important!
 # search.project.additional_search_fields = private, url, title