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>
This commit is contained in:
Martin Basti
2015-04-10 15:42:58 +02:00
committed by Jan Cholasta
parent 3942696606
commit 9f049ca144
10 changed files with 187 additions and 35 deletions

View File

@@ -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; \

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.
'''

View File

@@ -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

View File

@@ -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()

View File

@@ -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")

View File

@@ -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")

View File

@@ -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', ]