Skip to content

Commit c2c31d5

Browse files
author
rnorthover
committed
CAA record support and documentation
Additionally: * Exponential backoff and retry logic for `blocked` tasks * Automatic retry after 5 seconds for throttled jobs
1 parent 12a6fed commit c2c31d5

File tree

4 files changed

+66
-12
lines changed

4 files changed

+66
-12
lines changed

docs/tm/records/records.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ ALIASRecord
1818
:members:
1919
:undoc-members:
2020

21+
CAARecord
22+
==========
23+
.. autoclass:: dyn.tm.records.CAARecord
24+
:members:
25+
:undoc-members:
26+
2127
CERTRecord
2228
==========
2329
.. autoclass:: dyn.tm.records.CERTRecord

dyn/core.py

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
"""
77
import base64
88
import copy
9-
import time
109
import locale
1110
import logging
11+
import re
1212
import threading
13+
import time
1314
from datetime import datetime
1415

1516
from . import __version__
@@ -114,6 +115,7 @@ def __init__(self, host=None, port=443, ssl=True, history=False,
114115
self._encoding = locale.getdefaultlocale()[-1] or 'UTF-8'
115116
self._token = self._conn = self._last_response = None
116117
self._permissions = None
118+
self._tasks = {}
117119

118120
@classmethod
119121
def new_session(cls, *args, **kwargs):
@@ -243,6 +245,40 @@ def _handle_error(self, uri, method, raw_args):
243245
"""
244246
return None
245247

248+
def _retry(self, msgs, final=False):
249+
"""Retry logic around throttled or blocked tasks"""
250+
251+
throttle_err = 'RATE_LIMIT_EXCEEDED'
252+
throttled = any(throttle_err == err['ERR_CD'] for err in msgs)
253+
254+
if throttled:
255+
# We're rate limited, so wait 5 seconds and try again
256+
return dict(retry=True, wait=5, final=final)
257+
258+
blocked_err = 'Operation blocked by current task'
259+
blocked = any(blocked_err in err['INFO'] for err in msgs)
260+
261+
pat = re.compile(r'^task_id:\s+(\d+)$')
262+
if blocked:
263+
try:
264+
# Get the task id
265+
task = next(pat.match(i['INFO']).group(1) for i in msgs
266+
if pat.match(i.get('INFO', '')))
267+
except:
268+
# Task id could not be recovered
269+
wait = 1
270+
else:
271+
# Exponential backoff for individual blocked tasks
272+
wait = self._tasks.get(task, 1)
273+
self._tasks[task] = wait * 2 + 1
274+
275+
# Give up if final or wait > 30 seconds
276+
return dict(retry=True, wait=wait, final=wait > 30 or final)
277+
278+
# Neither blocked nor throttled?
279+
return dict(retry=False, wait=0, final=True)
280+
281+
246282
def _handle_response(self, response, uri, method, raw_args, final):
247283
"""Handle the processing of the API's response"""
248284
body = response.read()
@@ -258,12 +294,15 @@ def _handle_response(self, response, uri, method, raw_args, final):
258294
ret_val['status']))
259295

260296
self._meta_update(uri, method, ret_val)
261-
# Handle retrying if ZoneProp is blocking the current task
262-
error_msg = 'Operation blocked by current task'
263-
if ret_val['status'] == 'failure' and error_msg in \
264-
ret_val['msgs'][0]['INFO'] and not final:
265-
time.sleep(8)
266-
return self.execute(uri, method, raw_args, final=True)
297+
298+
retry = {}
299+
# Try to retry?
300+
if ret_val['status'] == 'failure' and not final:
301+
retry = self._retry(ret_val['msgs'], final)
302+
303+
if retry.get('retry', False):
304+
time.sleep(retry['wait'])
305+
return self.execute(uri, method, raw_args, final=retry['final'])
267306
else:
268307
return self._process_response(ret_val, method)
269308

dyn/tm/records.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,7 +1240,16 @@ def __repr__(self):
12401240
return self.__str__()
12411241

12421242
class CAARecord(DNSRecord):
1243-
"""
1243+
"""Certification Authority Authorization (CAA) Resource Record
1244+
1245+
This record allows a DNS domain name holder to specify one or more
1246+
Certification Authorities (CAs) authorized to issue certificates for that
1247+
domain. CAA Resource Records allow a public Certification Authority to
1248+
implement additional controls to reduce the risk of unintended certificate
1249+
mis-issue. This document defines the syntax of the CAA record and rules for
1250+
processing CAA records by certificate issuers.
1251+
1252+
see: https://tools.ietf.org/html/rfc6844
12441253
"""
12451254

12461255
def __init__(self, zone, fqdn, *args, **kwargs):
@@ -1306,7 +1315,7 @@ def rdata(self):
13061315

13071316
@property
13081317
def flags(self):
1309-
self.pull()
1318+
self._pull()
13101319
return self._flags
13111320

13121321
@flags.setter
@@ -1318,7 +1327,7 @@ def flags(self, value):
13181327

13191328
@property
13201329
def tag(self):
1321-
self.pull()
1330+
self._pull()
13221331
return self._tag
13231332

13241333
@tag.setter
@@ -1330,7 +1339,7 @@ def tag(self, value):
13301339

13311340
@property
13321341
def value(self):
1333-
self.pull()
1342+
self._pull()
13341343
return self._value
13351344

13361345
@value.setter

dyn/tm/zones.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from dyn.tm.errors import (DynectCreateError, DynectGetError,
1010
DynectInvalidArgumentError)
1111
from dyn.tm.records import (ARecord, AAAARecord, ALIASRecord, CDSRecord,
12-
CAA, CDNSKEYRecord, CSYNCRecord, CERTRecord,
12+
CAARecord, CDNSKEYRecord, CSYNCRecord, CERTRecord,
1313
CNAMERecord, DHCIDRecord, DNAMERecord,
1414
DNSKEYRecord, DSRecord, KEYRecord, KXRecord,
1515
LOCRecord, IPSECKEYRecord, MXRecord, NAPTRRecord,

0 commit comments

Comments
 (0)