On 23/04/15 12:55, Martin Basti wrote:
On 21/04/15 10:31, Martin Basti wrote:
On 21/04/15 08:12, Jan Cholasta wrote:
Hi,

Dne 15.4.2015 v 16:26 Martin Basti napsal(a):
https://fedorahosted.org/freeipa/ticket/4904

Patches attached.

Also ipa-upgradeconfig part is called as a subprocess. This will be
removed after installer modifications.

This patch may cause temporal upgrade issues (corner cases), until
installer part will be finished.

If somebody will be hit by them, please use --skip-version-check for
ipactl and ipa-server-upgrade.

Regarding that option vs. --force: I think the common assumption is that --force ignores *all* non-fatal errors, but you break that assumption in ipactl. IMO --force should both ignore errors in service startup *and* skip version check, and a new option should be added to just ignore errors in service startup (e.g. --ignore-service-failures).
Originally I used --force option to skip detection, but there was objections against it on list.

However, to have option --force, which set true for both --ignore-service-failures and --skip-version-check options, might be better.


ipa-server-upgrade should probably also have --force, even if it does the same thing as --skip-version-check, again because --force is common.


This is a weird API:

+        if data_upgrade.badsyntax:
+            raise admintool.ScriptError(
+                'Bad syntax detected in upgrade file(s).', 1)
+        elif data_upgrade.upgradefailed:
+            raise admintool.ScriptError('IPA upgrade failed.', 1)
+        elif data_upgrade.modified:
+            self.log.info('Data update complete')
+        else:
+ self.log.info('Data update complete, no data were modified')

Why does not IPAUpgrade raise errors instead?

For historical reasons, I can investigate what would break this change, I will send it in separate patch.

+class IPAVersionError(Exception):
+    pass
+
+class PlatformMismatchError(IPAVersionError):
+    pass
+
+class DataUpgradeRequiredError(IPAVersionError):
+    pass
+
+class DataInNewerVersionError(IPAVersionError):
+    pass

I don't like the "IPA" in "IPAVersionError", it does not tell you much about what kind of version is that. Also data version errors should only tell you what is wrong, not how you fix it. IMO better names for these would be e.g. "UpgradeVersionError", "UpgradePlatformError", "UpgradeDataOlderVersionError", "UpgradeDataNewerVersionError". Similar for store_ipa_version and check_ipa_version.

Ok.

Why is it not an error if there is no version in check_ipa_version? IMO it should, even if you then ignore the exception most of the time.
I can raise error in that case and ignore the exception.


Honza

Martin^2

Updated patches attached.



Updated patches attached

--
Martin Basti

From 9ba1dc90b12c70053a926e3dbfbd6aaa4d9fc920 Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Thu, 2 Apr 2015 14:14:15 +0200
Subject: [PATCH 1/3] Server Upgrade: ipa-server-upgrade command

https://fedorahosted.org/freeipa/ticket/4904
---
 freeipa.spec.in                         |  2 +
 install/tools/Makefile.am               |  1 +
 install/tools/ipa-server-upgrade        | 12 ++++++
 install/tools/man/Makefile.am           |  1 +
 install/tools/man/ipa-server-upgrade.1  | 40 ++++++++++++++++++
 ipaserver/install/ipa_server_upgrade.py | 72 +++++++++++++++++++++++++++++++++
 6 files changed, 128 insertions(+)
 create mode 100644 install/tools/ipa-server-upgrade
 create mode 100644 install/tools/man/ipa-server-upgrade.1
 create mode 100644 ipaserver/install/ipa_server_upgrade.py

diff --git a/freeipa.spec.in b/freeipa.spec.in
index 608242b5adbc43efbbf0ae30a6d7a933bebc1084..c661fe574464fdba1b1a8c64710d44a012ec8ede 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -660,6 +660,7 @@ fi
 %{_sbindir}/ipa-replica-manage
 %{_sbindir}/ipa-csreplica-manage
 %{_sbindir}/ipa-server-certinstall
+%{_sbindir}/ipa-server-upgrade
 %{_sbindir}/ipa-ldap-updater
 %{_sbindir}/ipa-otptoken-import
 %{_sbindir}/ipa-compat-manage
@@ -804,6 +805,7 @@ fi
 %{_mandir}/man1/ipa-replica-prepare.1.gz
 %{_mandir}/man1/ipa-server-certinstall.1.gz
 %{_mandir}/man1/ipa-server-install.1.gz
+%{_mandir}/man1/ipa-server-upgrade.1.gz
 %{_mandir}/man1/ipa-dns-install.1.gz
 %{_mandir}/man1/ipa-ca-install.1.gz
 %{_mandir}/man1/ipa-kra-install.1.gz
diff --git a/install/tools/Makefile.am b/install/tools/Makefile.am
index b791a8c748a18602f88522c3a0e3d74499700ae0..e5d45c47966a503da9f25aec13175793a36962e4 100644
--- a/install/tools/Makefile.am
+++ b/install/tools/Makefile.am
@@ -16,6 +16,7 @@ sbin_SCRIPTS =			\
 	ipa-replica-manage	\
 	ipa-csreplica-manage	\
 	ipa-server-certinstall  \
+	ipa-server-upgrade	\
 	ipactl			\
 	ipa-compat-manage	\
 	ipa-nis-manage		\
diff --git a/install/tools/ipa-server-upgrade b/install/tools/ipa-server-upgrade
new file mode 100644
index 0000000000000000000000000000000000000000..747024847a7c9d8837b4968395e2c63648a792cf
--- /dev/null
+++ b/install/tools/ipa-server-upgrade
@@ -0,0 +1,12 @@
+#!/usr/bin/python2
+#
+# Copyright (C) 2015  FreeIPA Contributors see COPYING for license
+#
+
+# Documentation can be found at:
+#     http://freeipa.org/page/LdapUpdate
+#     http://www.freeipa.org/page/V4/Server_Upgrade_Refactoring
+
+from ipaserver.install.ipa_server_upgrade import IPAServerUpgrade
+
+IPAServerUpgrade.run_cli()
diff --git a/install/tools/man/Makefile.am b/install/tools/man/Makefile.am
index 38c049c79fbd2ce22888b47ee576c4574e98c45b..6db1776191ca855986a152dbd4854a0dc1b744d7 100644
--- a/install/tools/man/Makefile.am
+++ b/install/tools/man/Makefile.am
@@ -12,6 +12,7 @@ man1_MANS = 				\
 	ipa-replica-prepare.1		\
 	ipa-server-certinstall.1	\
 	ipa-server-install.1		\
+	ipa-server-upgrade.1		\
 	ipa-dns-install.1		\
 	ipa-adtrust-install.1		\
 	ipa-ca-install.1		\
diff --git a/install/tools/man/ipa-server-upgrade.1 b/install/tools/man/ipa-server-upgrade.1
new file mode 100644
index 0000000000000000000000000000000000000000..02f252ed4cbb625f6d44188f31e701d7c0d4a882
--- /dev/null
+++ b/install/tools/man/ipa-server-upgrade.1
@@ -0,0 +1,40 @@
+.\"
+.\" Copyright (C) 2015  FreeIPA Contributors see COPYING for license
+.\"
+
+.TH "ipa-server-upgrade" "1" "April 02 2015" "FreeIPA" "FreeIPA Manual Pages"
+.SH "NAME"
+ipa\-server\-upgrade \- upgrade IPA server
+.SH "SYNOPSIS"
+ipa\-server\-upgrade [options]
+.SH "DESCRIPTION"
+ipa\-server\-upgrade is used to upgrade IPA server when the IPA packages are being updated. It is not intended to be executed by end\-users.
+
+ipa\-server\-upgrade will:
+
+    * update LDAP schema
+    * process all files with the extension .update in /usr/share/ipa/updates (including update plugins).
+    * upgrade local configurations of IPA services
+
+.SH "OPTIONS"
+.TP
+\fB\-\-version\fR
+Show IPA version
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Show help message and exit
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+Print debugging information
+.TP
+\fB\-q\fR, \fB\-\-quiet\fR
+Output only errors
+.TP
+\fB-\-log-file=FILE\fR
+Log to given file
+.TP
+
+.SH "EXIT STATUS"
+0 if the command was successful
+
+1 if an error occurred
diff --git a/ipaserver/install/ipa_server_upgrade.py b/ipaserver/install/ipa_server_upgrade.py
new file mode 100644
index 0000000000000000000000000000000000000000..c818fc74563c4685448ede76fcf2b379f0951322
--- /dev/null
+++ b/ipaserver/install/ipa_server_upgrade.py
@@ -0,0 +1,72 @@
+#
+# Copyright (C) 2015  FreeIPA Contributors see COPYING for license
+#
+
+import sys
+
+import krbV
+
+from ipalib import api
+from ipaplatform.paths import paths
+from ipapython import admintool, ipautil
+from ipaserver.install import installutils
+from ipaserver.install.upgradeinstance import IPAUpgrade
+
+
+class IPAServerUpgrade(admintool.AdminTool):
+    log_file_name = paths.IPAUPGRADE_LOG
+    command_name = 'ipa-server-upgrade'
+
+    usage = "%prog [options]"
+
+    @classmethod
+    def add_options(cls, parser):
+        super(IPAServerUpgrade, cls).add_options(parser, debug_option=True)
+
+    def validate_options(self):
+        super(IPAServerUpgrade, self).validate_options(needs_root=True)
+
+        try:
+            installutils.check_server_configuration()
+        except RuntimeError as e:
+            print unicode(e)
+            sys.exit(1)
+
+    def setup_logging(self):
+        super(IPAServerUpgrade, self).setup_logging(log_file_mode='a')
+
+    def run(self):
+        super(IPAServerUpgrade, self).run()
+
+        api.bootstrap(in_server=True, context='updates')
+        api.finalize()
+
+        options = self.options
+
+        realm = krbV.default_context().default_realm
+        data_upgrade = IPAUpgrade(realm)
+        data_upgrade.create_instance()
+
+        if data_upgrade.badsyntax:
+            raise admintool.ScriptError(
+                'Bad syntax detected in upgrade file(s).', 1)
+        elif data_upgrade.upgradefailed:
+            raise admintool.ScriptError('IPA upgrade failed.', 1)
+        elif data_upgrade.modified:
+            self.log.info('Data update complete')
+        else:
+            self.log.info('Data update complete, no data were modified')
+
+        # FIXME: remove this when new installer will be ready
+        # execute upgrade of configuration
+        cmd = ['ipa-upgradeconfig', ]
+        if options.verbose:
+            cmd.append('--debug')
+        if options.quiet:
+            cmd.append('--quiet')
+
+        self.log.info('Executing ipa-upgradeconfig, please wait')
+        ipautil.run(cmd)
+
+    def handle_error(self, exception):
+        return installutils.handle_error(exception, self.log_file_name)
-- 
2.1.0

From 0d3d89969908b86ae36cf24e4210eb158702e7f3 Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Fri, 10 Apr 2015 15:42:58 +0200
Subject: [PATCH 2/3] Server Upgrade: Verify version and platform

Verify version and platform before upgrade or ipactl start|restart

Upgrade:
* do not allow upgrade on different platforms
* do not allow upgrade data with higher version than build has

Start:
* do not start services if platform mismatch
* do not start services if upgrade is needed
* do not start services if data with higher version than build has

New ipactl options:
--skip-version-check: do not validate IPA version
--ignore-service-failures (was --force): ignore if a service start fail
      and continue with starting other services
--force: combine --skip-version-check and --ignore-service-failures

https://fedorahosted.org/freeipa/ticket/4904
---
 Makefile                                |  2 +
 install/tools/ipactl                    | 52 +++++++++++++++++++-----
 install/tools/man/ipa-server-upgrade.1  |  3 ++
 install/tools/man/ipactl.8              |  8 +++-
 ipaplatform/__init__.py                 | 22 -----------
 ipaplatform/__init__.py.in              | 12 ++++++
 ipaplatform/base/tasks.py               | 10 +++++
 ipaserver/install/dsinstance.py         |  2 +
 ipaserver/install/installutils.py       | 70 ++++++++++++++++++++++++++++++++-
 ipaserver/install/ipa_server_upgrade.py | 21 ++++++++++
 10 files changed, 168 insertions(+), 34 deletions(-)
 delete mode 100644 ipaplatform/__init__.py
 create mode 100644 ipaplatform/__init__.py.in

diff --git a/Makefile b/Makefile
index 3225a61b5b80e1ca0968e0c45f18c0ec3645df05..abf58382960099a54b8920dd0e741b9fda17682f 100644
--- a/Makefile
+++ b/Makefile
@@ -157,6 +157,8 @@ version-update: release-update
 		> ipa-client/version.m4
 
 	if [ "$(SUPPORTED_PLATFORM)" != "" ]; then \
+		sed -e s/__PLATFORM__/$(SUPPORTED_PLATFORM)/ \
+			ipaplatform/__init__.py.in > ipaplatform/__init__.py; \
 		rm -f ipaplatform/paths.py ipaplatform/services.py ipaplatform/tasks.py; \
 		ln -s $(SUPPORTED_PLATFORM)/paths.py ipaplatform/paths.py; \
 		ln -s $(SUPPORTED_PLATFORM)/services.py ipaplatform/services.py; \
diff --git a/install/tools/ipactl b/install/tools/ipactl
index b1b0b6e26fa97cdc953c86eee22e160782b57379..f3253e3f667b449e1df523eaa5e55cb33f43b467 100755
--- a/install/tools/ipactl
+++ b/install/tools/ipactl
@@ -90,17 +90,41 @@ def parse_options():
     parser.add_option("-d", "--debug", action="store_true", dest="debug",
                       help="Display debugging information")
     parser.add_option("-f", "--force", action="store_true", dest="force",
-                      help="If any service start fails, do not rollback the"
-                      + " services, continue with the operation")
+                      help="Force IPA to start. Combine options "
+                           "--skip-version-check and --ignore-service-failures")
+    parser.add_option("--ignore-service-failures", action="store_true",
+                      dest="ignore_service_failures",
+                      help="If any service start fails, do not rollback the "
+                           "services, continue with the operation")
+    parser.add_option("--skip-version-check", action="store_true",
+                      dest="skip_version_check", default=False,
+                      help="skip version check")
 
     options, args = parser.parse_args()
     safe_options = parser.get_safe_opts(options)
 
+    if options.force:
+        options.ignore_service_failures = True
+        options.skip_version_check = True
+
     return safe_options, options, args
 
 def emit_err(err):
     sys.stderr.write(err + '\n')
 
+
+def version_check():
+    try:
+        installutils.check_version()
+    except (installutils.UpgradeMissingVersionError,
+            installutils.UpgradeDataOlderVersionError):
+        emit_err("Upgrade required: please run ipa-server-upgrade command")
+        raise IpactlError("Aborting ipactl")
+    except installutils.UpgradeVersionError as e:
+        emit_err("IPA version error: %s" % e)
+        raise IpactlError("Aborting ipactl")
+
+
 def get_config(dirsrv):
     base = DN(('cn', api.env.host), ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn)
     srcfilter = '(ipaConfigString=enabledService)'
@@ -217,6 +241,9 @@ def stop_dirsrv(dirsrv):
 
 def ipa_start(options):
 
+    if not options.skip_version_check:
+        version_check()
+
     if os.path.isfile(tasks.get_svc_list_file()):
         emit_err("Existing service file detected!")
         emit_err("Assuming stale, cleaning and proceeding")
@@ -241,7 +268,7 @@ def ipa_start(options):
         emit_err("Failed to read data from service file: " + str(e))
         emit_err("Shutting down")
 
-        if not options.force:
+        if not options.ignore_service_failures:
             stop_dirsrv(dirsrv)
 
         if isinstance(e, IpactlError):
@@ -261,8 +288,9 @@ def ipa_start(options):
             svchandle.start(capture_output=get_capture_output(svc, options.debug))
         except Exception:
             emit_err("Failed to start %s Service" % svc)
-            #if force start specified, skip rollback and continue with the next service
-            if options.force:
+            # if ignore_service_failures is specified, skip rollback and
+            # continue with the next service
+            if options.ignore_service_failures:
                 emit_err("Forced start, ignoring %s Service, continuing normal operation" % svc)
                 continue
 
@@ -313,6 +341,8 @@ def ipa_stop(options):
 
 
 def ipa_restart(options):
+    if not options.skip_version_check:
+        version_check()
     dirsrv = services.knownservices.dirsrv
     new_svc_list = []
     dirsrv_restart = True
@@ -379,7 +409,7 @@ def ipa_restart(options):
         emit_err("Failed to restart Directory Service: " + str(e))
         emit_err("Shutting down")
 
-        if not options.force:
+        if not options.ignore_service_failures:
             stop_services(reversed(svc_list))
             stop_dirsrv(dirsrv)
 
@@ -395,8 +425,9 @@ def ipa_restart(options):
                 svchandle.restart(capture_output=get_capture_output(svc, options.debug))
             except Exception:
                 emit_err("Failed to restart %s Service" % svc)
-                #if force start specified, skip rollback and continue with the next service
-                if options.force:
+                # if ignore_service_failures is specified,
+                # skip rollback and continue with the next service
+                if options.ignore_service_failures:
                     emit_err("Forced restart, ignoring %s Service, continuing normal operation" % svc)
                     continue
 
@@ -415,8 +446,9 @@ def ipa_restart(options):
                 svchandle.start(capture_output=get_capture_output(svc, options.debug))
             except Exception:
                 emit_err("Failed to start %s Service" % svc)
-                #if force start specified, skip rollback and continue with the next service
-                if options.force:
+                # if ignore_service_failures is specified, skip rollback and
+                # continue with the next service
+                if options.ignore_service_failures:
                     emit_err("Forced start, ignoring %s Service, continuing normal operation" % svc)
                     continue
 
diff --git a/install/tools/man/ipa-server-upgrade.1 b/install/tools/man/ipa-server-upgrade.1
index 02f252ed4cbb625f6d44188f31e701d7c0d4a882..c4797578f3b0dd14326a1ed93c268330cbd7d429 100644
--- a/install/tools/man/ipa-server-upgrade.1
+++ b/install/tools/man/ipa-server-upgrade.1
@@ -18,6 +18,9 @@ ipa\-server\-upgrade will:
 
 .SH "OPTIONS"
 .TP
+\fB\-\-force\fR
+Skip version check (force upgrade). WARNING: this option may break your system
+.TP
 \fB\-\-version\fR
 Show IPA version
 .TP
diff --git a/install/tools/man/ipactl.8 b/install/tools/man/ipactl.8
index 5a1fd27ad6cb88877589173709c6cf0afa357fe1..136fe9ac6fe7dbff5618543c1958565bab87502a 100644
--- a/install/tools/man/ipactl.8
+++ b/install/tools/man/ipactl.8
@@ -41,5 +41,11 @@ Stop then start all of the services that make up IPA
 \fB\-d\fR, \fB\-\-debug\fR
 Display debugging information
 .TP
-\fB\-f\fR, \fB\-\-force\fR
+\fB\-\-skip\-version\-check\fR
+Skip version check
+.TP
+\fB\-\-ignore\-service\-failures\fR
 If any service start fails, do not rollback the services, continue with the operation
+.TP
+\fB\-f\fR, \fB\-\-force\fR
+Force IPA to start. Combine options --skip-version-check and --ignore-service-failures
diff --git a/ipaplatform/__init__.py b/ipaplatform/__init__.py
deleted file mode 100644
index cf342aa3f24ebcc2b3825a77c1fc66e2be512c45..0000000000000000000000000000000000000000
--- a/ipaplatform/__init__.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Authors:
-#   Tomas Babej <tba...@redhat.com>
-#
-# Copyright (C) 2014  Red Hat
-# see file 'COPYING' for use and warranty information
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-'''
-Module containing platform-specific functionality for every platform.
-'''
diff --git a/ipaplatform/__init__.py.in b/ipaplatform/__init__.py.in
new file mode 100644
index 0000000000000000000000000000000000000000..61f6f3c4a36065486fb4e7a333c47127c1ea0994
--- /dev/null
+++ b/ipaplatform/__init__.py.in
@@ -0,0 +1,12 @@
+#
+# Copyright (C) 2015  FreeIPA Contributors see COPYING for license
+#
+
+'''
+Module containing platform-specific functionality for every platform.
+'''
+
+NAME = "__PLATFORM__"
+
+# FIXME: too much cyclic dependencies
+# from __PLATFORM__ import paths, tasks, services
diff --git a/ipaplatform/base/tasks.py b/ipaplatform/base/tasks.py
index ff71c2bd12be3b775a0ed43c8038ee3924d2c9f6..10c5e835d0d585caa989c7744d3bae9f253de0d3 100644
--- a/ipaplatform/base/tasks.py
+++ b/ipaplatform/base/tasks.py
@@ -24,6 +24,9 @@ This module contains default platform-specific implementations of system tasks.
 
 import pwd
 import grp
+
+from pkg_resources import parse_version
+
 from ipaplatform.paths import paths
 from ipapython.ipa_log_manager import log_mgr
 from ipapython import ipautil
@@ -208,5 +211,12 @@ class BaseTaskNamespace(object):
         else:
             log.debug('user %s exists', name)
 
+    def parse_ipa_version(self, version):
+        """
+        :param version: textual version
+        :return: object implementing proper __cmp__ method for version compare
+        """
+        return parse_version(version)
+
 
 task_namespace = BaseTaskNamespace()
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index 8a76e773f0a464529331d9e2e459c9cc5ea0522e..da00bcf8236fb07fdf46019a196e8aab8cfb9f1d 100644
--- a/ipaserver/install/dsinstance.py
+++ b/ipaserver/install/dsinstance.py
@@ -511,6 +511,8 @@ class DsInstance(service.Service):
                                    sub_dict=self.sub_dict)
         files = ld.get_all_files(ldapupdate.UPDATES_DIR)
         ld.update(files)
+        installutils.store_version()
+
 
     def __add_referint_module(self):
         self._ldap_mod("referint-conf.ldif")
diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py
index 787a1207abfbd378074719a88734338ef79485c8..8a4f2cadab89390df0363bdd9adf4c023cadfd8c 100644
--- a/ipaserver/install/installutils.py
+++ b/ipaserver/install/installutils.py
@@ -35,6 +35,8 @@ from dns.exception import DNSException
 import ldap
 from nss.error import NSPRError
 
+import ipaplatform
+
 from ipapython import ipautil, sysrestore, admintool, dogtag, version
 from ipapython.admintool import ScriptError
 from ipapython.ipa_log_manager import root_logger, log_mgr
@@ -42,9 +44,10 @@ from ipalib.util import validate_hostname
 from ipapython import config
 from ipalib import errors, x509
 from ipapython.dn import DN
-from ipaserver.install import certs, service
+from ipaserver.install import certs, service, sysupgrade
 from ipaplatform import services
 from ipaplatform.paths import paths
+from ipaplatform.tasks import tasks
 
 # Used to determine install status
 IPA_MODULES = [
@@ -67,6 +70,27 @@ class HostReverseLookupError(HostLookupError):
 class HostnameLocalhost(HostLookupError):
     pass
 
+
+class UpgradeVersionError(Exception):
+    pass
+
+
+class UpgradePlatformError(UpgradeVersionError):
+    pass
+
+
+class UpgradeDataOlderVersionError(UpgradeVersionError):
+    pass
+
+
+class UpgradeDataNewerVersionError(UpgradeVersionError):
+    pass
+
+
+class UpgradeMissingVersionError(UpgradeVersionError):
+    pass
+
+
 class ReplicaConfig:
     def __init__(self, top_dir=None):
         self.realm_name = ""
@@ -1037,3 +1061,47 @@ def load_external_cert(files, subject_base):
     ca_file.flush()
 
     return cert_file, ca_file
+
+
+def store_version():
+    """Store current data version and platform. This is required for check if
+    upgrade is required.
+    """
+    sysupgrade.set_upgrade_state('ipa', 'data_version',
+                                 version.VENDOR_VERSION)
+    sysupgrade.set_upgrade_state('ipa', 'platform', ipaplatform.NAME)
+
+
+def check_version():
+    """
+    :raise UpgradePlatformError: if platform is not the same
+    :raise UpgradeDataOlderVersionError: if data needs to be upgraded
+    :raise UpgradeDataNewerVersionError: older version of IPA was detected than data
+    :raise UpgradeMissingVersionError: if platform or version is missing
+    """
+    platform = sysupgrade.get_upgrade_state('ipa', 'platform')
+    if platform is not None:
+        if platform != ipaplatform.NAME:
+            raise UpgradePlatformError(
+                "platform mismatch (expected '%s', current '%s')" % (
+                platform, ipaplatform.NAME)
+            )
+    else:
+        raise UpgradeMissingVersionError("no platform stored")
+
+    data_version = sysupgrade.get_upgrade_state('ipa', 'data_version')
+    if data_version is not None:
+        parsed_data_ver = tasks.parse_ipa_version(data_version)
+        parsed_ipa_ver = tasks.parse_ipa_version(version.VENDOR_VERSION)
+        if parsed_data_ver < parsed_ipa_ver:
+            raise UpgradeDataOlderVersionError(
+                "data needs to be upgraded (expected version '%s', current "
+                "version '%s')" % (version.VENDOR_VERSION, data_version)
+            )
+        elif parsed_data_ver > parsed_ipa_ver:
+            raise UpgradeDataNewerVersionError(
+                "data are in newer version than IPA (data version '%s', IPA "
+                "version '%s')" % (data_version, version.VENDOR_VERSION)
+            )
+    else:
+        raise UpgradeMissingVersionError("no data_version stored")
diff --git a/ipaserver/install/ipa_server_upgrade.py b/ipaserver/install/ipa_server_upgrade.py
index c818fc74563c4685448ede76fcf2b379f0951322..a971f7f00d297c4f55a3075858e8a095f06cee30 100644
--- a/ipaserver/install/ipa_server_upgrade.py
+++ b/ipaserver/install/ipa_server_upgrade.py
@@ -22,6 +22,10 @@ class IPAServerUpgrade(admintool.AdminTool):
     @classmethod
     def add_options(cls, parser):
         super(IPAServerUpgrade, cls).add_options(parser, debug_option=True)
+        parser.add_option("--force", action="store_true",
+                          dest="force", default=False,
+                          help="skip version check (force "
+                               "upgrade). WARNING: this may break your system")
 
     def validate_options(self):
         super(IPAServerUpgrade, self).validate_options(needs_root=True)
@@ -43,6 +47,20 @@ class IPAServerUpgrade(admintool.AdminTool):
 
         options = self.options
 
+        if not options.skip_force:
+            # check IPA version and data version
+            try:
+                installutils.check_version()
+            except (installutils.UpgradePlatformError,
+                    installutils.UpgradeDataNewerVersionError) as e:
+                raise admintool.ScriptError(
+                    'Unable to execute IPA upgrade: %s' % e, 1)
+            except installutils.UpgradeMissingVersionError as e:
+                self.log.info("Missing version: %s", e)
+            except installutils.UpgradeVersionError:
+                # Ignore other errors
+                pass
+
         realm = krbV.default_context().default_realm
         data_upgrade = IPAUpgrade(realm)
         data_upgrade.create_instance()
@@ -57,6 +75,9 @@ class IPAServerUpgrade(admintool.AdminTool):
         else:
             self.log.info('Data update complete, no data were modified')
 
+        # store new data version after upgrade
+        installutils.store_version()
+
         # FIXME: remove this when new installer will be ready
         # execute upgrade of configuration
         cmd = ['ipa-upgradeconfig', ]
-- 
2.1.0

From 06ea02f771505110ac7332ee97a0021c1cb2411d Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Fri, 10 Apr 2015 15:47:25 +0200
Subject: [PATCH 3/3] Server Upgrade: use ipa-server-upgrade in RPM upgrade

https://fedorahosted.org/freeipa/ticket/4904
---
 freeipa.spec.in | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/freeipa.spec.in b/freeipa.spec.in
index c661fe574464fdba1b1a8c64710d44a012ec8ede..8b58b0e0525357241ec95ae40b3e7b4b8c6ce118 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -513,8 +513,7 @@ fi
 %posttrans server
 # This must be run in posttrans so that updates from previous
 # execution that may no longer be shipped are not applied.
-/usr/sbin/ipa-ldap-updater --upgrade --quiet >/dev/null || :
-/usr/sbin/ipa-upgradeconfig --quiet >/dev/null || :
+/usr/sbin/ipa-server-upgrade --quiet >/dev/null || :
 
 # Restart IPA processes. This must be also run in postrans so that plugins
 # and software is in consistent state
-- 
2.1.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