Bug 1700741

Summary: When dnf plugin is upgraded via Obsolete, it is not run in the transaction phase
Product: Red Hat Enterprise Linux 8 Reporter: Jan Pazdziora (Red Hat) <jpazdziora>
Component: dnfAssignee: Jaroslav Mracek <jmracek>
Status: CLOSED ERRATA QA Contact: Karel Srot <ksrot>
Severity: unspecified Docs Contact:
Priority: low    
Version: 8.0CC: dmach, james.antill, jpazdziora, pkratoch
Target Milestone: rcKeywords: Triaged
Target Release: 8.0Flags: pm-rhel: mirror+
Hardware: Unspecified   
OS: Unspecified   
Whiteboard:
Fixed In Version: dnf-4.2.7-3.el8 Doc Type: If docs needed, set a value
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: 2019-11-05 22:21:49 UTC Type: Bug
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:
Bug Depends On: 1681084    
Bug Blocks:    

Description Jan Pazdziora (Red Hat) 2019-04-17 09:54:56 UTC
Description of problem:

When dnf plugin is shipped via rpm package and that rpm package gets upgraded to newer version, the operation of the plugin is not disturbed -- the transaction() call after the old package was removed is still invoked, running code from the old version.

However, when instead of upgrading the rpm package with the plugin, different package which brings the same dnf plugin is installed, and it removes the previous package via Obsoletes, the transaction() is not invoked.

Version-Release number of selected component (if applicable):

dnf-4.0.9.2-5.el8.noarch

How reproducible:

Deterministic.

Steps to Reproduce:
1. Have plugin code in dnf-plugins/a.py which implements plugin "a" which will show us the list of installed and removed packages:

from dnf import Plugin
from dnfpluginscore import logger

class a(Plugin):

	def resolved(self):
		logger.warning("resolved a0: install_set %s, remove_set %s"
			% ( self.base.transaction.install_set, self.base.transaction.remove_set ))

	def transaction(self):
		logger.warning("transaction a0: install_set %s, remove_set %s"
			% ( self.base.transaction.install_set, self.base.transaction.remove_set ))

2. Have setup.py:

try:
	from setuptools import setup
except ImportError:
	from distutils.core import setup

setup(
	name = 'a-plugin',
	version = '0.0.0',
	py_modules = ['dnf-plugins.a'],
)

3. Build the plugin rpm: python3 setup.py bdist_rpm --binary-only
4. Install the plugin: dnf install -y dist/a-plugin-0.0.0-1.noarch.rpm
5. Install some package, to see what the transaction looks like:
   dnf install -y zsh
6. Observe the output says

Last metadata expiration check: 0:59:09 ago on Wed 17 Apr 2019 04:34:58 AM EDT.
resolved a0: install_set {<hawkey.Package object id 13644, zsh-5.5.1-6.el8.x86_64, BaseOS>}, remove_set set()
Dependencies resolved.
[...]
  Verifying        : zsh-5.5.1-6.el8.x86_64                                 1/1 
Installed products updated.
transaction a0: install_set {<hawkey.Package object id 13644, zsh-5.5.1-6.el8.x86_64, BaseOS>}, remove_set set()

7. Now we update the version of the rpm but also record the new version in the plugin code:

diff --git a/dnf-plugins/a.py b/dnf-plugins/a.py
index 8467b0f..a5da385 100644
--- a/dnf-plugins/a.py
+++ b/dnf-plugins/a.py
@@ -4,9 +4,9 @@ from dnfpluginscore import logger
 class a(Plugin):
 
        def resolved(self):
-               logger.warning("resolved a0: install_set %s, remove_set %s"
+               logger.warning("resolved a1: install_set %s, remove_set %s"
                        % ( self.base.transaction.install_set, self.base.transaction.remove_set ))
 
        def transaction(self):
-               logger.warning("transaction a0: install_set %s, remove_set %s"
+               logger.warning("transaction a1: install_set %s, remove_set %s"
                        % ( self.base.transaction.install_set, self.base.transaction.remove_set ))
diff --git a/setup.py b/setup.py
index 01d7eb4..98b370b 100644
--- a/setup.py
+++ b/setup.py
@@ -5,7 +5,7 @@ except ImportError:
 
 setup(
        name = 'a-plugin',
-       version = '0.0.0',
+       version = '1.0.0',
        py_modules = ['dnf-plugins.a'],
 )

8. Build rpm: python3 setup.py bdist_rpm --binary-only
9. Upgrade the plugin: dnf upgrade -y dist/a-plugin-1.0.0-1.noarch.rpm 
10. Observe the output says that the old version (a0) code was invoked both before the upgrade (resolved) and after the upgrade (transaction):

resolved a0: install_set {<hawkey.Package object id 46307, a-plugin-1.0.0-1.noarch, @commandline>}, remove_set {<hawkey.Package object id 6, a-plugin-0.0.0-1.noarch, @System>}
[...]
Installed products updated.
transaction a0: install_set {<hawkey.Package object id 46307, a-plugin-1.0.0-1.noarch, @commandline>}, remove_set {<hawkey.Package object id 6, a-plugin-0.0.0-1.noarch, @System>}

11. Run some dnf operation and notice that now the "a1" code is invoked:
    dnf remove -y zsh

resolved a1: install_set set(), remove_set {<hawkey.Package object id 444, zsh-5.5.1-6.el8.x86_64, @System>}
[...]
transaction a1: install_set set(), remove_set {<hawkey.Package object id 444, zsh-5.5.1-6.el8.x86_64, @System>}

12. Now move the plugin to different rpm package, from a-plugin to b-plugin. Since the actual name of the plugin and thus the file where it is stored stays the same, we need to add Obsoletes, to avoid errors like file /usr/lib/python3.6/site-packages/dnf-plugins/a.py from install of b-plugin-1.0.0-1.noarch conflicts with file from package a-plugin-1.0.0-1.noarch:

  diff --git a/dnf-plugins/a.py b/dnf-plugins/a.py
index a5da385..22a1b9e 100644
--- a/dnf-plugins/a.py
+++ b/dnf-plugins/a.py
@@ -4,9 +4,9 @@ from dnfpluginscore import logger
 class a(Plugin):
 
        def resolved(self):
-               logger.warning("resolved a1: install_set %s, remove_set %s"
+               logger.warning("resolved b1: install_set %s, remove_set %s"
                        % ( self.base.transaction.install_set, self.base.transaction.remove_set ))
 
        def transaction(self):
-               logger.warning("transaction a1: install_set %s, remove_set %s"
+               logger.warning("transaction b1: install_set %s, remove_set %s"
                        % ( self.base.transaction.install_set, self.base.transaction.remove_set ))
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..0b08475
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,2 @@
+[bdist_rpm]
+obsoletes = a-plugin
diff --git a/setup.py b/setup.py
index 98b370b..4937d84 100644
--- a/setup.py
+++ b/setup.py
@@ -4,7 +4,7 @@ except ImportError:
        from distutils.core import setup
 
 setup(
-       name = 'a-plugin',
+       name = 'b-plugin',
        version = '1.0.0',
        py_modules = ['dnf-plugins.a'],
 )

13. Build rpm: python3 setup.py bdist_rpm --binary-only
14. Install the new package: dnf install -y dist/b-plugin-1.0.0-1.noarch.rpm

Actual results:

Updating Subscription Management repositories.
Unable to read consumer identity
This system is not registered to Red Hat Subscription Management. You can use subscription-manager to register.
Last metadata expiration check: 0:11:35 ago on Wed 17 Apr 2019 05:36:48 AM EDT.
resolved a1: install_set {<hawkey.Package object id 46306, b-plugin-1.0.0-1.noarch, @commandline>}, remove_set {<hawkey.Package object id 6, a-plugin-1.0.0-1.noarch, @System>}
Dependencies resolved.
================================================================================
 Package           Arch            Version          Repository             Size
================================================================================
Installing:
 b-plugin          noarch          1.0.0-1          @commandline          8.2 k
     replacing  a-plugin.noarch 1.0.0-1

Transaction Summary
================================================================================
Install  1 Package

Total size: 8.2 k
Downloading Packages:
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
  Preparing        :                                                        1/1 
  Installing       : b-plugin-1.0.0-1.noarch                                1/2 
  Obsoleting       : a-plugin-1.0.0-1.noarch                                2/2 
  Running scriptlet: a-plugin-1.0.0-1.noarch                                2/2 
  Verifying        : b-plugin-1.0.0-1.noarch                                1/2 
  Verifying        : a-plugin-1.0.0-1.noarch                                2/2 
Installed products updated.

Installed:
  b-plugin-1.0.0-1.noarch                                                       

Complete!

Expected results:

Updating Subscription Management repositories.
Unable to read consumer identity
This system is not registered to Red Hat Subscription Management. You can use subscription-manager to register.
Last metadata expiration check: 0:11:35 ago on Wed 17 Apr 2019 05:36:48 AM EDT.
resolved a1: install_set {<hawkey.Package object id 46306, b-plugin-1.0.0-1.noarch, @commandline>}, remove_set {<hawkey.Package object id 6, a-plugin-1.0.0-1.noarch, @System>}
Dependencies resolved.
================================================================================
 Package           Arch            Version          Repository             Size
================================================================================
Installing:
 b-plugin          noarch          1.0.0-1          @commandline          8.2 k
     replacing  a-plugin.noarch 1.0.0-1

Transaction Summary
================================================================================
Install  1 Package

Total size: 8.2 k
Downloading Packages:
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
  Preparing        :                                                        1/1 
  Installing       : b-plugin-1.0.0-1.noarch                                1/2 
  Obsoleting       : a-plugin-1.0.0-1.noarch                                2/2 
  Running scriptlet: a-plugin-1.0.0-1.noarch                                2/2 
  Verifying        : b-plugin-1.0.0-1.noarch                                1/2 
  Verifying        : a-plugin-1.0.0-1.noarch                                2/2 
Installed products updated.
transaction a1: install_set {<hawkey.Package object id 46306, b-plugin-1.0.0-1.noarch, @commandline>}, remove_set {<hawkey.Package object id 6, a-plugin-1.0.0-1.noarch, @System>}

Installed:
  b-plugin-1.0.0-1.noarch                                                       

Complete!

In other words, I expect the transaction phase of the original (a-plugin-1.0.0-1) to be still invoked, and the

transaction a1: install_set {<hawkey.Package object id 46306, b-plugin-1.0.0-1.noarch, @commandline>}, remove_set {<hawkey.Package object id 6, a-plugin-1.0.0-1.noarch, @System>}

output to be shown.

Additional info:

Comment 2 Jaroslav Mracek 2019-07-03 17:03:16 UTC
I would like to resolve the issue by enhancing a documentation. Please do you have any suggestion where to put such information?

Comment 3 Jan Pazdziora (Red Hat) 2019-07-08 09:35:34 UTC
I'd likely put it to the dnf plugin API documentation, as a note about behaviour expectations.

Comment 4 Jaroslav Mracek 2019-07-22 07:40:38 UTC
I added a note for the transaction hook into documentation: https://github.com/rpm-software-management/dnf/pull/1435

Comment 9 errata-xmlrpc 2019-11-05 22:21:49 UTC
Since the problem described in this bug report should be
resolved in a recent advisory, it has been closed with a
resolution of ERRATA.

For information on the advisory, and where to find the updated
files, follow the link below.

If the solution does not work for you, open a new bug report.

https://access.redhat.com/errata/RHSA-2019:3583