From 112d3fb86b07ed4450d17883cde78a0baf7ef53c Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Mon, 29 Sep 2014 14:03:46 +0000 Subject: [PATCH] Hold back further updates after failed updates for a while Partly addresses #10603 --- src/ddns/database.py | 43 ++++++++++++++++++++++++++++++++++++++--- src/ddns/providers.py | 45 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/src/ddns/database.py b/src/ddns/database.py index 9301187..1e613c9 100644 --- a/src/ddns/database.py +++ b/src/ddns/database.py @@ -97,12 +97,49 @@ class DDNSDatabase(object): return self.add_update(hostname, "failure", message=message) - def last_update(self, hostname, status="success"): + def last_update(self, hostname, status=None): + """ + Returns the timestamp of the last update (with the given status code). + """ c = self._db.cursor() try: - c.execute("SELECT timestamp FROM updates WHERE hostname = ? AND status = ? \ - ORDER BY timestamp DESC LIMIT 1", (hostname, status)) + if status: + c.execute("SELECT timestamp FROM updates WHERE hostname = ? AND status = ? \ + ORDER BY timestamp DESC LIMIT 1", (hostname, status)) + else: + c.execute("SELECT timestamp FROM updates WHERE hostname = ? \ + ORDER BY timestamp DESC LIMIT 1", (hostname,)) + + for row in c: + return row[0] + finally: + c.close() + + def last_update_status(self, hostname): + """ + Returns the update status of the last update. + """ + c = self._db.cursor() + + try: + c.execute("SELECT status FROM updates WHERE hostname = ? \ + ORDER BY timestamp DESC LIMIT 1", (hostname,)) + + for row in c: + return row[0] + finally: + c.close() + + def last_update_failure_message(self, hostname): + """ + Returns the reason string for the last failed update (if any). + """ + c = self._db.cursor() + + try: + c.execute("SELECT message FROM updates WHERE hostname = ? AND status = ? \ + ORDER BY timestamp DESC LIMIT 1", (hostname, "failure")) for row in c: return row[0] diff --git a/src/ddns/providers.py b/src/ddns/providers.py index 797ef3d..16a6e7b 100644 --- a/src/ddns/providers.py +++ b/src/ddns/providers.py @@ -63,6 +63,10 @@ class DDNSProvider(object): # the IP address has changed. holdoff_days = 30 + # holdoff time for update failures - Number of days no update + # is tried after the last one has failed. + holdoff_failure_days = 0.5 + # True if the provider is able to remove records, too. # Required to remove AAAA records if IPv6 is absent again. can_remove_records = True @@ -149,8 +153,8 @@ class DDNSProvider(object): if force: logger.debug(_("Updating %s forced") % self.hostname) - # Do nothing if no update is required - elif not self.requires_update: + # Do nothing if the last update has failed or no update is required + elif self.has_failure or not self.requires_update: return # Execute the update. @@ -207,6 +211,41 @@ class DDNSProvider(object): return False + @property + def has_failure(self): + """ + Returns True when the last update has failed and no retry + should be performed, yet. + """ + last_status = self.db.last_update_status(self.hostname) + + # Return False if the last update has not failed. + if not last_status == "failure": + return False + + # Determine when the holdoff time ends + last_update = self.db.last_update(self.hostname, status=last_status) + holdoff_end = last_update + datetime.timedelta(days=self.holdoff_failure_days) + + now = datetime.datetime.utcnow() + if now < holdoff_end: + failure_message = self.db.last_update_failure_message(self.hostname) + + logger.warning(_("An update has not been performed because earlier updates failed for %s") \ + % self.hostname) + + if failure_message: + logger.warning(_("Last failure message:")) + + for line in failure_message.splitlines(): + logger.warning(" %s" % line) + + logger.warning(_("Further updates will be withheld until %s") % holdoff_end) + + return True + + return False + def ip_address_changed(self, protos): """ Returns True if this host is already up to date @@ -243,7 +282,7 @@ class DDNSProvider(object): return False # Get the timestamp of the last successfull update - last_update = self.db.last_update(self.hostname) + last_update = self.db.last_update(self.hostname, status="success") # If no timestamp has been recorded, no update has been # performed. An update should be performed now. -- 2.39.2