[2/3] unbound-dhcp-leases-bridge: Only reload if leases have actually changed

Message ID 20240426150919.3766772-2-michael.tremer@ipfire.org
State Staged
Commit 749bf8590204f949facaf21386a17e211dbd5d6d
Headers
Series [1/3] unbound-dhcp-leases-bridge: Implement atomic file replacement |

Commit Message

Michael Tremer April 26, 2024, 3:09 p.m. UTC
  This patches changes that leases will always be written in
alphanumerical order so that we can later compare the newly generated
file with the previous version. If it has not changed, we skip reload
Unbound.

Suggested-by: Nick Howitt <nick@howitts.co.uk>
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
---
 config/unbound/unbound-dhcp-leases-bridge | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)
  

Comments

Nick Howitt April 26, 2024, 3:54 p.m. UTC | #1
Can I just ask here as I don't know python file i/o at all.

The reason I moved filecmp outside the "with tempfile....." was because 
the file was being held open without an EOF so the filecmp always 
failed, making it useless. When you do the f.flush(), foes the EOF get 
added to the temp file, or does the same problem that I had exist here?

On 26/04/2024 16:09, Michael Tremer wrote:
> This patches changes that leases will always be written in
> alphanumerical order so that we can later compare the newly generated
> file with the previous version. If it has not changed, we skip reload
> Unbound.
>
> Suggested-by: Nick Howitt<nick@howitts.co.uk>
> Signed-off-by: Michael Tremer<michael.tremer@ipfire.org>
> ---
>   config/unbound/unbound-dhcp-leases-bridge | 20 ++++++++++++++------
>   1 file changed, 14 insertions(+), 6 deletions(-)
>
> diff --git a/config/unbound/unbound-dhcp-leases-bridge b/config/unbound/unbound-dhcp-leases-bridge
> index 5d5696af0..80c8267e8 100644
> --- a/config/unbound/unbound-dhcp-leases-bridge
> +++ b/config/unbound/unbound-dhcp-leases-bridge
> @@ -22,6 +22,7 @@
>   import argparse
>   import datetime
>   import daemon
> +import filecmp
>   import functools
>   import ipaddress
>   import logging
> @@ -516,24 +517,29 @@ class UnboundConfigWriter(object):
>   
>   	def update_dhcp_leases(self, leases):
>   		# Write out all leases
> -		self.write_dhcp_leases(leases)
> +		if self.write_dhcp_leases(leases):
> +			log.debug("Reloading Unbound...")
>   
> -		log.debug("Reloading Unbound...")
> -
> -		# Reload the configuration without dropping the cache
> -		self._control("reload_keep_cache")
> +			# Reload the configuration without dropping the cache
> +			self._control("reload_keep_cache")
>   
>   	def write_dhcp_leases(self, leases):
>   		log.debug("Writing DHCP leases...")
>   
>   		with tempfile.NamedTemporaryFile(mode="w") as f:
> -			for l in leases:
> +			for l in sorted(leases, key=lambda x: x.ipaddr):
>   				for rr in l.rrset:
>   					f.write("local-data: \"%s\"\n" % " ".join(rr))
>   
>   			# Flush the file
>   			f.flush()
>   
> +			# Compare if the new leases file has changed from the previous version
> +			if filecmp.cmp(f.name, self.path, shallow=False):
> +				log.debug("The generated leases file has not changed")
> +
> +				return False
> +
>   			# Make file readable for everyone
>   			os.fchmod(f.fileno(), stat.S_IRUSR|stat.S_IWUSR|stat.S_IRGRP|stat.S_IROTH)
>   
> @@ -543,6 +549,8 @@ class UnboundConfigWriter(object):
>   			# Move the file to its destination
>   			os.link(f.name, self.path)
>   
> +		return True
> +
>   	def _control(self, *args):
>   		command = ["unbound-control"]
>   		command.extend(args)
  
Michael Tremer April 27, 2024, 8:38 a.m. UTC | #2
Hello,

> On 26 Apr 2024, at 17:54, Nick Howitt <nick@howitts.co.uk> wrote:
> 
> Can I just ask here as I don't know python file i/o at all.

There is pretty much no difference to the regular C API, except that Python has a little bit more aggressive buffering.

> The reason I moved filecmp outside the "with tempfile....." was because the file was being held open without an EOF so the filecmp always failed, making it useless. When you do the f.flush(), foes the EOF get added to the temp file, or does the same problem that I had exist here?

Normally, if you call f.write(…) the data might not always be written to disk immediately. There is a small buffer to avoid tiny write operations. flush() manually writes everything to disk. That way, we can keep the file handle open (so that we can later delete the file) and it can be opened again to call compare.

Alternatively, there could have been a compare function that works on the already opened file descriptor.

> On 26/04/2024 16:09, Michael Tremer wrote:
>> This patches changes that leases will always be written in
>> alphanumerical order so that we can later compare the newly generated
>> file with the previous version. If it has not changed, we skip reload
>> Unbound.
>> 
>> Suggested-by: Nick Howitt <nick@howitts.co.uk>
>> Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
>> ---
>> config/unbound/unbound-dhcp-leases-bridge | 20 ++++++++++++++------
>> 1 file changed, 14 insertions(+), 6 deletions(-)
>> 
>> diff --git a/config/unbound/unbound-dhcp-leases-bridge b/config/unbound/unbound-dhcp-leases-bridge
>> index 5d5696af0..80c8267e8 100644
>> --- a/config/unbound/unbound-dhcp-leases-bridge
>> +++ b/config/unbound/unbound-dhcp-leases-bridge
>> @@ -22,6 +22,7 @@
>> import argparse
>> import datetime
>> import daemon
>> +import filecmp
>> import functools
>> import ipaddress
>> import logging
>> @@ -516,24 +517,29 @@ class UnboundConfigWriter(object):
>> 
>> def update_dhcp_leases(self, leases):
>> # Write out all leases
>> - self.write_dhcp_leases(leases)
>> + if self.write_dhcp_leases(leases):
>> + log.debug("Reloading Unbound...")
>> 
>> - log.debug("Reloading Unbound...")
>> -
>> - # Reload the configuration without dropping the cache
>> - self._control("reload_keep_cache")
>> + # Reload the configuration without dropping the cache
>> + self._control("reload_keep_cache")
>> 
>> def write_dhcp_leases(self, leases):
>> log.debug("Writing DHCP leases...")
>> 
>> with tempfile.NamedTemporaryFile(mode="w") as f:
>> - for l in leases:
>> + for l in sorted(leases, key=lambda x: x.ipaddr):
>> for rr in l.rrset:
>> f.write("local-data: \"%s\"\n" % " ".join(rr))
>> 
>> # Flush the file
>> f.flush()
>> 
>> + # Compare if the new leases file has changed from the previous version
>> + if filecmp.cmp(f.name, self.path, shallow=False):
>> + log.debug("The generated leases file has not changed")
>> +
>> + return False
>> +
>> # Make file readable for everyone
>> os.fchmod(f.fileno(), stat.S_IRUSR|stat.S_IWUSR|stat.S_IRGRP|stat.S_IROTH)
>> 
>> @@ -543,6 +549,8 @@ class UnboundConfigWriter(object):
>> # Move the file to its destination
>> os.link(f.name, self.path)
>> 
>> + return True
>> +
>> def _control(self, *args):
>> command = ["unbound-control"]
>> command.extend(args)
>> 
>
  
Nick Howitt April 30, 2024, 10:41 a.m. UTC | #3
On 26/04/2024 16:09, Michael Tremer wrote:
> This patches changes that leases will always be written in
> alphanumerical order so that we can later compare the newly generated
> file with the previous version. If it has not changed, we skip reload
> Unbound.
>
> Suggested-by: Nick Howitt<nick@howitts.co.uk>
> Signed-off-by: Michael Tremer<michael.tremer@ipfire.org>
> ---
>   config/unbound/unbound-dhcp-leases-bridge | 20 ++++++++++++++------
>   1 file changed, 14 insertions(+), 6 deletions(-)
>
> diff --git a/config/unbound/unbound-dhcp-leases-bridge b/config/unbound/unbound-dhcp-leases-bridge
> index 5d5696af0..80c8267e8 100644
> --- a/config/unbound/unbound-dhcp-leases-bridge
> +++ b/config/unbound/unbound-dhcp-leases-bridge
> @@ -22,6 +22,7 @@
>   import argparse
>   import datetime
>   import daemon
> +import filecmp
>   import functools
>   import ipaddress
>   import logging
> @@ -516,24 +517,29 @@ class UnboundConfigWriter(object):
>   
>   	def update_dhcp_leases(self, leases):
>   		# Write out all leases
> -		self.write_dhcp_leases(leases)
> +		if self.write_dhcp_leases(leases):
> +			log.debug("Reloading Unbound...")
>   
> -		log.debug("Reloading Unbound...")
> -
> -		# Reload the configuration without dropping the cache
> -		self._control("reload_keep_cache")
> +			# Reload the configuration without dropping the cache
> +			self._control("reload_keep_cache")
>   
>   	def write_dhcp_leases(self, leases):
>   		log.debug("Writing DHCP leases...")
>   
>   		with tempfile.NamedTemporaryFile(mode="w") as f:
> -			for l in leases:
> +			for l in sorted(leases, key=lambda x: x.ipaddr):
>   				for rr in l.rrset:
>   					f.write("local-data: \"%s\"\n" % " ".join(rr))
>   
>   			# Flush the file
>   			f.flush()
>   
> +			# Compare if the new leases file has changed from the previous version
> +			if filecmp.cmp(f.name, self.path, shallow=False):
> +				log.debug("The generated leases file has not changed")
> +
> +				return False
> +
>   			# Make file readable for everyone
>   			os.fchmod(f.fileno(), stat.S_IRUSR|stat.S_IWUSR|stat.S_IRGRP|stat.S_IROTH)
>   
> @@ -543,6 +549,8 @@ class UnboundConfigWriter(object):
>   			# Move the file to its destination
>   			os.link(f.name, self.path)
>   
> +		return True
> +
>   	def _control(self, *args):
>   		command = ["unbound-control"]
>   		command.extend(args)
|Signed-off-by: Nick Howitt <nick@howitts.co.uk>|
  

Patch

diff --git a/config/unbound/unbound-dhcp-leases-bridge b/config/unbound/unbound-dhcp-leases-bridge
index 5d5696af0..80c8267e8 100644
--- a/config/unbound/unbound-dhcp-leases-bridge
+++ b/config/unbound/unbound-dhcp-leases-bridge
@@ -22,6 +22,7 @@ 
 import argparse
 import datetime
 import daemon
+import filecmp
 import functools
 import ipaddress
 import logging
@@ -516,24 +517,29 @@  class UnboundConfigWriter(object):
 
 	def update_dhcp_leases(self, leases):
 		# Write out all leases
-		self.write_dhcp_leases(leases)
+		if self.write_dhcp_leases(leases):
+			log.debug("Reloading Unbound...")
 
-		log.debug("Reloading Unbound...")
-
-		# Reload the configuration without dropping the cache
-		self._control("reload_keep_cache")
+			# Reload the configuration without dropping the cache
+			self._control("reload_keep_cache")
 
 	def write_dhcp_leases(self, leases):
 		log.debug("Writing DHCP leases...")
 
 		with tempfile.NamedTemporaryFile(mode="w") as f:
-			for l in leases:
+			for l in sorted(leases, key=lambda x: x.ipaddr):
 				for rr in l.rrset:
 					f.write("local-data: \"%s\"\n" % " ".join(rr))
 
 			# Flush the file
 			f.flush()
 
+			# Compare if the new leases file has changed from the previous version
+			if filecmp.cmp(f.name, self.path, shallow=False):
+				log.debug("The generated leases file has not changed")
+
+				return False
+
 			# Make file readable for everyone
 			os.fchmod(f.fileno(), stat.S_IRUSR|stat.S_IWUSR|stat.S_IRGRP|stat.S_IROTH)
 
@@ -543,6 +549,8 @@  class UnboundConfigWriter(object):
 			# Move the file to its destination
 			os.link(f.name, self.path)
 
+		return True
+
 	def _control(self, *args):
 		command = ["unbound-control"]
 		command.extend(args)