Patch 0122 reimplements version checking and fixes
https://fedorahosted.org/freeipa/ticket/5572

Patch 0123 contains unit test for version checking code.

Thanks to Martin^1 for the idea of using CFFI for calling rpm C-API directly.

--
Martin^3 Babinsky
From c7a5d8970d100d071597b4e1d7cef8a27b8cd485 Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Fri, 8 Jan 2016 15:54:00 +0100
Subject: [PATCH 2/3] tests for package version comparison

These tests will ensure that our package version handling code can correctly
decide when to upgrade IPA master.

https://fedorahosted.org/freeipa/ticket/5572
---
 ipatests/test_ipaserver/test_version_comparsion.py | 47 ++++++++++++++++++++++
 1 file changed, 47 insertions(+)
 create mode 100644 ipatests/test_ipaserver/test_version_comparsion.py

diff --git a/ipatests/test_ipaserver/test_version_comparsion.py b/ipatests/test_ipaserver/test_version_comparsion.py
new file mode 100644
index 0000000000000000000000000000000000000000..51d069b23ba389ffce39e948cdbc7a1faaa84563
--- /dev/null
+++ b/ipatests/test_ipaserver/test_version_comparsion.py
@@ -0,0 +1,47 @@
+#
+# Copyright (C) 2015  FreeIPA Contributors see COPYING for license
+#
+
+"""
+tests for correct RPM version comparison
+"""
+
+from ipaplatform.tasks import tasks
+import pytest
+
+version_strings = [
+    ("3.0.el6", "3.0.0.el6", "older"),
+    ("3.0.0.el6", "3.0.0.el6_8.2", "older"),
+    ("3.0.0-42.el6", "3.0.0.el6", "newer"),
+    ("3.0.0-1", "3.0.0-42", "older"),
+    ("3.0.0-42.el6", "3.3.3.fc20", "older"),
+    ("4.2.0-15.el7", "4.2.0-15.el7_2.3", "older"),
+    ("4.2.0-15.el7_2.3", "4.2.0-15.el7_2.3", "equal"),
+    ("4.2.0-1.fc23", "4.2.1.fc23", "older"),
+    ("4.2.3-alpha.fc23", "4.2.3-2.fc23", "older"),  # numeric version elements have
+                                                # precedence over letters
+    ("4.3.90.201601080923GIT55aeea7-0.fc23", "4.3.0-1.fc23", "newer")
+]
+
+
+@pytest.fixture(params=version_strings)
+def versions(request):
+    return request.param
+
+class TestVersionComparsion(object):
+
+    def test_versions(self, versions):
+        version_string1, version_string2, expected_comparison = versions
+
+        ver1 = tasks.parse_ipa_version(version_string1)
+        ver2 = tasks.parse_ipa_version(version_string2)
+
+        if expected_comparison == "newer":
+            assert ver1 > ver2
+        elif expected_comparison == "older":
+            assert ver1 < ver2
+        elif expected_comparison == "equal":
+            assert ver1 == ver2
+        else:
+            raise TypeError(
+                "Unexpected comparison string: {}", expected_comparison)
-- 
2.5.0

From 9677e1a3ca2f5837f1b779780127adf27efa81df Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Fri, 8 Jan 2016 14:17:14 +0100
Subject: [PATCH 1/3] use FFI call to rpmvercmp function for version comparison

Stop using rpm-python to compare package versions since the implicit NSS
initialization upon  the module import breaks NSS handling in IPA code. Call
rpm-libs C-API function via CFFI instead.

Big thanks to Martin Kosek <mko...@redhat.com> for sharing the code snippet
that spurred this patch.

https://fedorahosted.org/freeipa/ticket/5572
---
 freeipa.spec.in             |  2 +-
 ipaplatform/redhat/tasks.py | 59 +++++++++++++++++++++------------------------
 2 files changed, 29 insertions(+), 32 deletions(-)

diff --git a/freeipa.spec.in b/freeipa.spec.in
index 7e956538d0f6c24bab636579303e0c7b5eeec199..7f31d41d16b2a26b404c277595f0994a21123f80 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -214,7 +214,7 @@ Requires: python-pyasn1
 Requires: dbus-python
 Requires: python-dns >= 1.11.1
 Requires: python-kdcproxy >= 0.3
-Requires: rpm-python
+Requires: rpm-devel
 
 %description -n python2-ipaserver
 IPA is an integrated solution to provide centrally managed Identity (users,
diff --git a/ipaplatform/redhat/tasks.py b/ipaplatform/redhat/tasks.py
index a0b4060cb26bab66248c4397c24b4d58bf1bf8d6..938e0720768c9eb4fe8b3bd31884472495a6be28 100644
--- a/ipaplatform/redhat/tasks.py
+++ b/ipaplatform/redhat/tasks.py
@@ -30,13 +30,13 @@ import stat
 import socket
 import sys
 import base64
+from cffi import FFI
 from functools import total_ordering
 
 from subprocess import CalledProcessError
 from nss.error import NSPRError
 from pyasn1.error import PyAsn1Error
 from six.moves import urllib
-import rpm
 
 from ipapython.ipa_log_manager import root_logger, log_mgr
 from ipapython import ipautil
@@ -49,33 +49,30 @@ from ipaplatform.redhat.authconfig import RedHatAuthConfig
 from ipaplatform.base.tasks import BaseTaskNamespace
 
 
-# copied from rpmUtils/miscutils.py
-def stringToVersion(verstring):
-    if verstring in [None, '']:
-        return (None, None, None)
-    i = verstring.find(':')
-    if i != -1:
-        try:
-            epoch = str(long(verstring[:i]))
-        except ValueError:
-            # look, garbage in the epoch field, how fun, kill it
-            epoch = '0' # this is our fallback, deal
-    else:
-        epoch = '0'
-    j = verstring.find('-')
-    if j != -1:
-        if verstring[i + 1:j] == '':
-            version = None
-        else:
-            version = verstring[i + 1:j]
-        release = verstring[j + 1:]
-    else:
-        if verstring[i + 1:] == '':
-            version = None
-        else:
-            version = verstring[i + 1:]
-        release = None
-    return (epoch, version, release)
+def compare_rpm_version_strings(ver1, ver2):
+    """
+    compare two RPM version string using a FFI call to rpmvercmp() API function
+
+    :param ver1: version string of the first package
+    :param ver2: version string of the second package
+    :returns the same as rpmvercmp(), i.e 1 when ver1 is newer than ver 2,
+        0 when they are the same and -1 when ver2 is newer that ver1
+
+    Note that we cannot really use rpm python bindings for this, since
+    importing them messes with NSS library initialization and breaks our
+    certificate manipulation/validation functions
+    """
+
+    ffi = FFI()
+    ffi.cdef("""
+int rpmvercmp (const char *a, const char *b);
+""")
+
+    C = ffi.dlopen("rpm")
+    arg1 = ffi.new("char[]", ver1)
+    arg2 = ffi.new("char[]", ver2)
+
+    return C.rpmvercmp(arg1, arg2)
 
 
 log = log_mgr.get_logger(__name__)
@@ -101,15 +98,15 @@ def selinux_enabled():
 class IPAVersion(object):
 
     def __init__(self, version):
-        self.version_tuple = stringToVersion(version)
+        self.version = version
 
     def __eq__(self, other):
         assert isinstance(other, IPAVersion)
-        return rpm.labelCompare(self.version_tuple, other.version_tuple) == 0
+        return compare_rpm_version_strings(self.version, other.version) == 0
 
     def __lt__(self, other):
         assert isinstance(other, IPAVersion)
-        return rpm.labelCompare(self.version_tuple, other.version_tuple) == -1
+        return compare_rpm_version_strings(self.version, other.version) == -1
 
 
 class RedHatTaskNamespace(BaseTaskNamespace):
-- 
2.5.0

From b22f783fbef9919fe8b80186a039b44a3b9d366f Mon Sep 17 00:00:00 2001
From: Martin Babinsky <mbabi...@redhat.com>
Date: Fri, 8 Jan 2016 14:17:14 +0100
Subject: [PATCH] use FFI call to rpmvercmp function for version comparison

Stop using rpm-python to compare package versions since the implicit NSS
initialization upon  the module import breaks NSS handling in IPA code. Call
rpm-libs C-API function via CFFI instead.

Big thanks to Martin Kosek <mko...@redhat.com> for sharing the code snippet
that spurred this patch.

https://fedorahosted.org/freeipa/ticket/5572
---
 freeipa.spec.in             |  2 +-
 ipaplatform/redhat/tasks.py | 59 +++++++++++++++++++++------------------------
 2 files changed, 29 insertions(+), 32 deletions(-)

diff --git a/freeipa.spec.in b/freeipa.spec.in
index bbf3db7a1cfc6d5a7a1b43edeb0a799875386f77..9d954b93e180317a7e74fe9cfbc5056f59c0d0b8 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -159,7 +159,7 @@ Requires: p11-kit
 Requires: systemd-python
 Requires: %{etc_systemd_dir}
 Requires: gzip
-Requires: rpm-python
+Requires: rpm-devel
 
 Conflicts: %{alt_name}-server
 Obsoletes: %{alt_name}-server < %{version}
diff --git a/ipaplatform/redhat/tasks.py b/ipaplatform/redhat/tasks.py
index 2e894d776dcd5542e6c11cc0210add8ad9d90298..ffc338b85eb1eb3ac8542e625c7aa46825e28e0f 100644
--- a/ipaplatform/redhat/tasks.py
+++ b/ipaplatform/redhat/tasks.py
@@ -30,7 +30,7 @@ import socket
 import sys
 import urllib
 import base64
-import rpm
+from cffi import FFI
 from functools import total_ordering
 
 from subprocess import CalledProcessError
@@ -48,33 +48,30 @@ from ipaplatform.redhat.authconfig import RedHatAuthConfig
 from ipaplatform.base.tasks import BaseTaskNamespace
 
 
-# copied from rpmUtils/miscutils.py
-def stringToVersion(verstring):
-    if verstring in [None, '']:
-        return (None, None, None)
-    i = verstring.find(':')
-    if i != -1:
-        try:
-            epoch = str(long(verstring[:i]))
-        except ValueError:
-            # look, garbage in the epoch field, how fun, kill it
-            epoch = '0' # this is our fallback, deal
-    else:
-        epoch = '0'
-    j = verstring.find('-')
-    if j != -1:
-        if verstring[i + 1:j] == '':
-            version = None
-        else:
-            version = verstring[i + 1:j]
-        release = verstring[j + 1:]
-    else:
-        if verstring[i + 1:] == '':
-            version = None
-        else:
-            version = verstring[i + 1:]
-        release = None
-    return (epoch, version, release)
+def compare_rpm_version_strings(ver1, ver2):
+    """
+    compare two RPM version string using a FFI call to rpmvercmp() API function
+
+    :param ver1: version string of the first package
+    :param ver2: version string of the second package
+    :returns the same as rpmvercmp(), i.e 1 when ver1 is newer than ver 2,
+        0 when they are the same and -1 when ver2 is newer that ver1
+
+    Note that we cannot really use rpm python bindings for this, since
+    importing them messes with NSS library initialization and breaks our
+    certificate manipulation/validation functions
+    """
+
+    ffi = FFI()
+    ffi.cdef("""
+int rpmvercmp (const char *a, const char *b);
+""")
+
+    C = ffi.dlopen("rpm")
+    arg1 = ffi.new("char[]", ver1)
+    arg2 = ffi.new("char[]", ver2)
+
+    return C.rpmvercmp(arg1, arg2)
 
 
 log = log_mgr.get_logger(__name__)
@@ -100,15 +97,15 @@ def selinux_enabled():
 class IPAVersion(object):
 
     def __init__(self, version):
-        self.version_tuple = stringToVersion(version)
+        self.version = version
 
     def __eq__(self, other):
         assert isinstance(other, IPAVersion)
-        return rpm.labelCompare(self.version_tuple, other.version_tuple) == 0
+        return compare_rpm_version_strings(self.version, other.version) == 0
 
     def __lt__(self, other):
         assert isinstance(other, IPAVersion)
-        return rpm.labelCompare(self.version_tuple, other.version_tuple) == -1
+        return compare_rpm_version_strings(self.version, other.version) == -1
 
 
 class RedHatTaskNamespace(BaseTaskNamespace):
-- 
2.5.0

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to