Agent: Optional middleware to rate limit NOTIFYs
Currently, the Agent naively does an AXFR/backend call for every NOTIFY that it receives. It should be able to get a NOTIFY for a zone, and then ignore successive NOTIFYs for a time period, and then do the zone transfer, so as to catch all of the updates that came in. This middleware accomplishes that by implementing a small locking dictionary that doesn't allow more than one NOTIFY to kick off AXFR per zone, per process for a configurable time period. The Agent gracefully hanldes the situation where it's sleeping on a NOTIFY, and a DELETE zone come through. When the NOTIFY wakes up, and the zone is already gone, it refuses the NOTIFY because the domain doesn't exist. Change-Id: If5655f8da201202482fa8c44af9b1c8496bf3281
This commit is contained in:
@@ -13,7 +13,11 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import mock
|
||||
from dns import zone as dnszone
|
||||
import dns.message
|
||||
import dns.rdatatype
|
||||
import dns.rcode
|
||||
|
||||
from designate import dnsutils
|
||||
from designate.tests import TestCase
|
||||
@@ -95,3 +99,68 @@ class TestUtils(TestCase):
|
||||
|
||||
self.assertEqual(len(SAMPLES), len(zone.recordsets))
|
||||
self.assertEqual('example.com.', zone.name)
|
||||
|
||||
def test_zone_lock(self):
|
||||
# Initialize a ZoneLock
|
||||
lock = dnsutils.ZoneLock(0.1)
|
||||
|
||||
# Ensure there's no lock for different zones
|
||||
for zone_name in ['foo.com.', 'bar.com.', 'example.com.']:
|
||||
self.assertEqual(True, lock.acquire(zone_name))
|
||||
|
||||
# Ensure a lock for successive calls for the same zone
|
||||
self.assertEqual(True, lock.acquire('example2.com.'))
|
||||
self.assertEqual(False, lock.acquire('example2.com.'))
|
||||
|
||||
# Acquire, release, and reacquire
|
||||
self.assertEqual(True, lock.acquire('example3.com.'))
|
||||
lock.release('example3.com.')
|
||||
self.assertEqual(True, lock.acquire('example3.com.'))
|
||||
|
||||
def test_limit_notify_middleware(self):
|
||||
# Set the delay
|
||||
self.config(notify_delay=.1,
|
||||
group='service:agent')
|
||||
|
||||
# Initialize the middlware
|
||||
placeholder_app = None
|
||||
middleware = dnsutils.LimitNotifyMiddleware(placeholder_app)
|
||||
|
||||
# Prepare a NOTIFY
|
||||
zone_name = 'example.com.'
|
||||
notify = dns.message.make_query(zone_name, dns.rdatatype.SOA)
|
||||
notify.flags = 0
|
||||
notify.set_opcode(dns.opcode.NOTIFY)
|
||||
notify.flags |= dns.flags.AA
|
||||
|
||||
# Send the NOTIFY through the middleware
|
||||
# No problem, middleware should return None to pass it on
|
||||
self.assertEqual(middleware.process_request(notify), None)
|
||||
|
||||
@mock.patch('designate.dnsutils.ZoneLock.acquire', return_value=False)
|
||||
def test_limit_notify_middleware_no_acquire(self, acquire):
|
||||
# Set the delay
|
||||
self.config(notify_delay=.1,
|
||||
group='service:agent')
|
||||
|
||||
# Initialize the middlware
|
||||
placeholder_app = None
|
||||
middleware = dnsutils.LimitNotifyMiddleware(placeholder_app)
|
||||
|
||||
# Prepare a NOTIFY
|
||||
zone_name = 'example.com.'
|
||||
notify = dns.message.make_query(zone_name, dns.rdatatype.SOA)
|
||||
notify.flags = 0
|
||||
notify.set_opcode(dns.opcode.NOTIFY)
|
||||
notify.flags |= dns.flags.AA
|
||||
|
||||
# Make a response object to match the middleware's return
|
||||
response = dns.message.make_response(notify)
|
||||
# Provide an authoritative answer
|
||||
response.flags |= dns.flags.AA
|
||||
|
||||
# Send the NOTIFY through the middleware
|
||||
# Lock can't be acquired, a NOTIFY is already being worked on
|
||||
# so just return what would have come back for a successful NOTIFY
|
||||
# This needs to be a one item tuple for the serialization middleware
|
||||
self.assertEqual(middleware.process_request(notify), (response,))
|
||||
|
||||
Reference in New Issue
Block a user