summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Basti <mbasti@redhat.com>2015-04-10 15:42:58 +0200
committerJan Cholasta <jcholast@redhat.com>2015-05-04 11:16:26 +0000
commit9f049ca14403f3696d54d186e6b1b15181f055df (patch)
tree80c4c6d0b2d9e0b584322a931a729560e274c174
parent39426966063b3f3deced90ef3f5d0a87801d7eab (diff)
downloadfreeipa-9f049ca14403f3696d54d186e6b1b15181f055df.tar.gz
freeipa-9f049ca14403f3696d54d186e6b1b15181f055df.tar.xz
freeipa-9f049ca14403f3696d54d186e6b1b15181f055df.zip
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 Reviewed-By: Jan Cholasta <jcholast@redhat.com> Reviewed-By: David Kupka <dkupka@redhat.com>
-rw-r--r--Makefile2
-rwxr-xr-xinstall/tools/ipactl57
-rw-r--r--install/tools/man/ipa-server-upgrade.16
-rw-r--r--install/tools/man/ipactl.88
-rw-r--r--ipaplatform/__init__.py22
-rw-r--r--ipaplatform/__init__.py.in12
-rw-r--r--ipaplatform/base/tasks.py10
-rw-r--r--ipaserver/install/dsinstance.py2
-rw-r--r--ipaserver/install/installutils.py70
-rw-r--r--ipaserver/install/ipa_server_upgrade.py33
10 files changed, 187 insertions, 35 deletions
diff --git a/Makefile b/Makefile
index 3225a61b5..abf583829 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 b1b0b6e26..b37f55575 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,11 @@ def stop_dirsrv(dirsrv):
def ipa_start(options):
+ if not options.skip_version_check:
+ version_check()
+ else:
+ print "Skipping 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 +270,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 +290,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 +343,11 @@ def ipa_stop(options):
def ipa_restart(options):
+ if not options.skip_version_check:
+ version_check()
+ else:
+ print "Skipping version check"
+
dirsrv = services.knownservices.dirsrv
new_svc_list = []
dirsrv_restart = True
@@ -379,7 +414,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 +430,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 +451,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 02f252ed4..b3d89bde7 100644
--- a/install/tools/man/ipa-server-upgrade.1
+++ b/install/tools/man/ipa-server-upgrade.1
@@ -18,6 +18,12 @@ ipa\-server\-upgrade will:
.SH "OPTIONS"
.TP
+\fB\-\-skip\-version-\check\fR
+Skip version check. WARNING: this option may break your system
+.TP
+\fB\-\-force\fR
+Force upgrade (alias for --skip-version-check)
+.TP
\fB\-\-version\fR
Show IPA version
.TP
diff --git a/install/tools/man/ipactl.8 b/install/tools/man/ipactl.8
index 5a1fd27ad..136fe9ac6 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 cf342aa3f..000000000
--- a/ipaplatform/__init__.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Authors:
-# Tomas Babej <tbabej@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 000000000..61f6f3c4a
--- /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 ff71c2bd1..10c5e835d 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 8a76e773f..da00bcf82 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 787a1207a..8a4f2cada 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 6d77fddc3..148d1fe7e 100644
--- a/ipaserver/install/ipa_server_upgrade.py
+++ b/ipaserver/install/ipa_server_upgrade.py
@@ -21,11 +21,21 @@ class ServerUpgrade(admintool.AdminTool):
@classmethod
def add_options(cls, parser):
- super(ServerUpgrade, cls).add_options(parser, debug_option=True)
+ super(ServerUpgrade, cls).add_options(parser)
+ parser.add_option("--force", action="store_true",
+ dest="force", default=False,
+ help="force upgrade (alias for --skip-version-check)")
+ parser.add_option("--skip-version-check", action="store_true",
+ dest="skip_version_check", default=False,
+ help="skip version check. WARNING: this may break "
+ "your system")
def validate_options(self):
super(ServerUpgrade, self).validate_options(needs_root=True)
+ if self.options.force:
+ self.options.skip_version_check = True
+
try:
installutils.check_server_configuration()
except RuntimeError as e:
@@ -43,6 +53,24 @@ class ServerUpgrade(admintool.AdminTool):
options = self.options
+ if not options.skip_version_check:
+ # 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
+ else:
+ self.log.info("Skipping version check")
+ self.log.warning("Upgrade without version check may break your "
+ "system")
+
realm = krbV.default_context().default_realm
data_upgrade = IPAUpgrade(realm)
data_upgrade.create_instance()
@@ -57,6 +85,9 @@ class ServerUpgrade(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', ]