|
|
e3ffab |
From 5ec39e80a626e833d8fec24b1797b9c25c072784 Mon Sep 17 00:00:00 2001
|
|
|
e3ffab |
From: Jan Cholasta <jcholast@redhat.com>
|
|
|
e3ffab |
Date: Mon, 12 Jan 2015 12:44:21 +0000
|
|
|
e3ffab |
Subject: [PATCH] Fix validation of ipa-restore options
|
|
|
e3ffab |
|
|
|
e3ffab |
Fix restore mode checks. Do some of the existing checks earlier to make them
|
|
|
e3ffab |
effective. Check if --instance and --backend exist both in the filesystem and
|
|
|
e3ffab |
in the backup.
|
|
|
e3ffab |
|
|
|
e3ffab |
Log backup type and restore mode before performing restore.
|
|
|
e3ffab |
|
|
|
e3ffab |
Update ipa-restore man page.
|
|
|
e3ffab |
|
|
|
e3ffab |
https://fedorahosted.org/freeipa/ticket/4797
|
|
|
e3ffab |
|
|
|
e3ffab |
Reviewed-By: Petr Vobornik <pvoborni@redhat.com>
|
|
|
e3ffab |
Reviewed-By: Martin Kosek <mkosek@redhat.com>
|
|
|
e3ffab |
---
|
|
|
e3ffab |
install/tools/man/ipa-restore.1 | 10 +--
|
|
|
e3ffab |
ipaserver/install/ipa_restore.py | 175 +++++++++++++++++++++++----------------
|
|
|
e3ffab |
2 files changed, 108 insertions(+), 77 deletions(-)
|
|
|
e3ffab |
|
|
|
e3ffab |
diff --git a/install/tools/man/ipa-restore.1 b/install/tools/man/ipa-restore.1
|
|
|
e3ffab |
index 31734b259524e4b07312a4009184e725aafc3728..98242e246ab888f326af5ccfa6420fc080261891 100644
|
|
|
e3ffab |
--- a/install/tools/man/ipa-restore.1
|
|
|
e3ffab |
+++ b/install/tools/man/ipa-restore.1
|
|
|
e3ffab |
@@ -64,17 +64,17 @@ Restore the data only. The default is to restore everything in the backup.
|
|
|
e3ffab |
The full path to a GPG keyring. The keyring consists of two files, a public and a private key (.sec and .pub respectively). Specify the path without an extension.
|
|
|
e3ffab |
.TP
|
|
|
e3ffab |
\fB\-\-no\-logs\fR
|
|
|
e3ffab |
-Exclude the IPA service log files in the backup (if they were backed up). Applicable only with a full backup.
|
|
|
e3ffab |
+Exclude the IPA service log files in the backup (if they were backed up).
|
|
|
e3ffab |
.TP
|
|
|
e3ffab |
\fB\-\-online\fR
|
|
|
e3ffab |
-Perform the restore on\-line. Requires the \-\-data option.
|
|
|
e3ffab |
+Perform the restore on\-line. Requires data\-only backup or the \-\-data option.
|
|
|
e3ffab |
.TP
|
|
|
e3ffab |
\fB\-\-instance\fR=\fIINSTANCE\fR
|
|
|
e3ffab |
-The backend to restore within an instance or instances.
|
|
|
e3ffab |
-.TP
|
|
|
e3ffab |
-Restore only the databases in this 389\-ds instance. The default is to restore all found (at most this is the IPA REALM instance and the PKI\-IPA instance).
|
|
|
e3ffab |
+Restore only the databases in this 389\-ds instance. The default is to restore all found (at most this is the IPA REALM instance and the PKI\-IPA instance). Requires data\-only backup or the \-\-data option.
|
|
|
e3ffab |
.TP
|
|
|
e3ffab |
\fB\-\-backend\fR=\fIBACKEND\fR
|
|
|
e3ffab |
+The backend to restore within an instance or instances. Requires data\-only backup or the \-\-data option.
|
|
|
e3ffab |
+.TP
|
|
|
e3ffab |
\fB\-\-v\fR, \fB\-\-verbose\fR
|
|
|
e3ffab |
Print debugging information
|
|
|
e3ffab |
.TP
|
|
|
e3ffab |
diff --git a/ipaserver/install/ipa_restore.py b/ipaserver/install/ipa_restore.py
|
|
|
e3ffab |
index cd98d07f5f7c7b2ea1b1fef9a272229475efcdc9..be487166d9b2319aeee5fcb54bf4779afcac5afa 100644
|
|
|
e3ffab |
--- a/ipaserver/install/ipa_restore.py
|
|
|
e3ffab |
+++ b/ipaserver/install/ipa_restore.py
|
|
|
e3ffab |
@@ -25,6 +25,7 @@ import time
|
|
|
e3ffab |
import pwd
|
|
|
e3ffab |
from ConfigParser import SafeConfigParser
|
|
|
e3ffab |
import ldif
|
|
|
e3ffab |
+import itertools
|
|
|
e3ffab |
|
|
|
e3ffab |
from ipalib import api, errors
|
|
|
e3ffab |
from ipapython import version, ipautil, certdb, dogtag
|
|
|
e3ffab |
@@ -161,32 +162,25 @@ class Restore(admintool.AdminTool):
|
|
|
e3ffab |
|
|
|
e3ffab |
|
|
|
e3ffab |
def validate_options(self):
|
|
|
e3ffab |
+ parser = self.option_parser
|
|
|
e3ffab |
options = self.options
|
|
|
e3ffab |
super(Restore, self).validate_options(needs_root=True)
|
|
|
e3ffab |
- if options.data_only:
|
|
|
e3ffab |
- installutils.check_server_configuration()
|
|
|
e3ffab |
|
|
|
e3ffab |
if len(self.args) < 1:
|
|
|
e3ffab |
- self.option_parser.error(
|
|
|
e3ffab |
- "must provide the backup to restore")
|
|
|
e3ffab |
+ parser.error("must provide the backup to restore")
|
|
|
e3ffab |
elif len(self.args) > 1:
|
|
|
e3ffab |
- self.option_parser.error(
|
|
|
e3ffab |
- "must provide exactly one name for the backup")
|
|
|
e3ffab |
+ parser.error("must provide exactly one name for the backup")
|
|
|
e3ffab |
|
|
|
e3ffab |
dirname = self.args[0]
|
|
|
e3ffab |
if not os.path.isabs(dirname):
|
|
|
e3ffab |
- self.backup_dir = os.path.join(BACKUP_DIR, dirname)
|
|
|
e3ffab |
- else:
|
|
|
e3ffab |
- self.backup_dir = dirname
|
|
|
e3ffab |
-
|
|
|
e3ffab |
+ dirname = os.path.join(BACKUP_DIR, dirname)
|
|
|
e3ffab |
if not os.path.isdir(dirname):
|
|
|
e3ffab |
- raise self.option_parser.error("must provide path to backup directory")
|
|
|
e3ffab |
+ parser.error("must provide path to backup directory")
|
|
|
e3ffab |
|
|
|
e3ffab |
if options.gpg_keyring:
|
|
|
e3ffab |
if (not os.path.exists(options.gpg_keyring + '.pub') or
|
|
|
e3ffab |
- not os.path.exists(options.gpg_keyring + '.sec')):
|
|
|
e3ffab |
- raise admintool.ScriptError('No such key %s' %
|
|
|
e3ffab |
- options.gpg_keyring)
|
|
|
e3ffab |
+ not os.path.exists(options.gpg_keyring + '.sec')):
|
|
|
e3ffab |
+ parser.error("no such key %s" % options.gpg_keyring)
|
|
|
e3ffab |
|
|
|
e3ffab |
|
|
|
e3ffab |
def ask_for_options(self):
|
|
|
e3ffab |
@@ -212,38 +206,88 @@ class Restore(admintool.AdminTool):
|
|
|
e3ffab |
api.bootstrap(in_server=False, context='restore')
|
|
|
e3ffab |
api.finalize()
|
|
|
e3ffab |
|
|
|
e3ffab |
+ self.backup_dir = self.args[0]
|
|
|
e3ffab |
+ if not os.path.isabs(self.backup_dir):
|
|
|
e3ffab |
+ self.backup_dir = os.path.join(BACKUP_DIR, self.backup_dir)
|
|
|
e3ffab |
+
|
|
|
e3ffab |
self.log.info("Preparing restore from %s on %s",
|
|
|
e3ffab |
- self.backup_dir, api.env.host)
|
|
|
e3ffab |
+ self.backup_dir, api.env.host)
|
|
|
e3ffab |
|
|
|
e3ffab |
- if options.instance and options.backend:
|
|
|
e3ffab |
- database = (options.instance, options.backend)
|
|
|
e3ffab |
- if not os.path.exists(paths.SLAPD_INSTANCE_DB_DIR_TEMPLATE %
|
|
|
e3ffab |
- database):
|
|
|
e3ffab |
- raise admintool.ScriptError(
|
|
|
e3ffab |
- "Instance %s with backend %s does not exist" % database)
|
|
|
e3ffab |
+ self.header = os.path.join(self.backup_dir, 'header')
|
|
|
e3ffab |
+
|
|
|
e3ffab |
+ try:
|
|
|
e3ffab |
+ self.read_header()
|
|
|
e3ffab |
+ except IOError as e:
|
|
|
e3ffab |
+ raise admintool.ScriptError("Cannot read backup metadata: %s" % e)
|
|
|
e3ffab |
+
|
|
|
e3ffab |
+ if options.data_only:
|
|
|
e3ffab |
+ restore_type = 'DATA'
|
|
|
e3ffab |
+ else:
|
|
|
e3ffab |
+ restore_type = self.backup_type
|
|
|
e3ffab |
+
|
|
|
e3ffab |
+ instances = [realm_to_serverid(api.env.realm), 'PKI-IPA']
|
|
|
e3ffab |
+ backends = ['userRoot', 'ipaca']
|
|
|
e3ffab |
|
|
|
e3ffab |
- databases = [database]
|
|
|
e3ffab |
+ # These checks would normally be in the validate method but
|
|
|
e3ffab |
+ # we need to know the type of backup we're dealing with.
|
|
|
e3ffab |
+ if restore_type == 'FULL':
|
|
|
e3ffab |
+ if options.online:
|
|
|
e3ffab |
+ raise admintool.ScriptError(
|
|
|
e3ffab |
+ "File restoration cannot be done online")
|
|
|
e3ffab |
+ if options.instance or options.backend:
|
|
|
e3ffab |
+ raise admintool.ScriptError(
|
|
|
e3ffab |
+ "Restore must be in data-only mode when restoring a "
|
|
|
e3ffab |
+ "specific instance or backend")
|
|
|
e3ffab |
else:
|
|
|
e3ffab |
+ installutils.check_server_configuration()
|
|
|
e3ffab |
+
|
|
|
e3ffab |
if options.instance:
|
|
|
e3ffab |
+ instance_dir = (paths.VAR_LIB_SLAPD_INSTANCE_DIR_TEMPLATE %
|
|
|
e3ffab |
+ options.instance)
|
|
|
e3ffab |
+ if not os.path.exists(instance_dir):
|
|
|
e3ffab |
+ raise admintool.ScriptError(
|
|
|
e3ffab |
+ "Instance %s does not exist" % options.instance)
|
|
|
e3ffab |
+
|
|
|
e3ffab |
instances = [options.instance]
|
|
|
e3ffab |
- else:
|
|
|
e3ffab |
- instances = [realm_to_serverid(api.env.realm), 'PKI-IPA']
|
|
|
e3ffab |
|
|
|
e3ffab |
if options.backend:
|
|
|
e3ffab |
+ for instance in instances:
|
|
|
e3ffab |
+ db_dir = (paths.SLAPD_INSTANCE_DB_DIR_TEMPLATE %
|
|
|
e3ffab |
+ (instance, options.backend))
|
|
|
e3ffab |
+ if os.path.exists(db_dir):
|
|
|
e3ffab |
+ break
|
|
|
e3ffab |
+ else:
|
|
|
e3ffab |
+ raise admintool.ScriptError(
|
|
|
e3ffab |
+ "Backend %s does not exist" % options.backend)
|
|
|
e3ffab |
+
|
|
|
e3ffab |
backends = [options.backend]
|
|
|
e3ffab |
+
|
|
|
e3ffab |
+ for instance, backend in itertools.product(instances, backends):
|
|
|
e3ffab |
+ db_dir = (paths.SLAPD_INSTANCE_DB_DIR_TEMPLATE %
|
|
|
e3ffab |
+ (instance, backend))
|
|
|
e3ffab |
+ if os.path.exists(db_dir):
|
|
|
e3ffab |
+ break
|
|
|
e3ffab |
else:
|
|
|
e3ffab |
- backends = ['userRoot', 'ipaca']
|
|
|
e3ffab |
+ raise admintool.ScriptError(
|
|
|
e3ffab |
+ "Cannot restore a data backup into an empty system")
|
|
|
e3ffab |
|
|
|
e3ffab |
- databases = []
|
|
|
e3ffab |
- for instance in instances:
|
|
|
e3ffab |
- for backend in backends:
|
|
|
e3ffab |
- database = (instance, backend)
|
|
|
e3ffab |
- if os.path.exists(paths.SLAPD_INSTANCE_DB_DIR_TEMPLATE %
|
|
|
e3ffab |
- database):
|
|
|
e3ffab |
- databases.append(database)
|
|
|
e3ffab |
+ self.log.info("Performing %s restore from %s backup" %
|
|
|
e3ffab |
+ (restore_type, self.backup_type))
|
|
|
e3ffab |
|
|
|
e3ffab |
- if options.data_only and not databases:
|
|
|
e3ffab |
- raise admintool.ScriptError('No instances to restore to')
|
|
|
e3ffab |
+ if self.backup_host != api.env.host:
|
|
|
e3ffab |
+ raise admintool.ScriptError(
|
|
|
e3ffab |
+ "Host name %s does not match backup name %s" %
|
|
|
e3ffab |
+ (api.env.host, self.backup_host))
|
|
|
e3ffab |
+
|
|
|
e3ffab |
+ if self.backup_ipa_version != str(version.VERSION):
|
|
|
e3ffab |
+ self.log.warning(
|
|
|
e3ffab |
+ "Restoring data from a different release of IPA.\n"
|
|
|
e3ffab |
+ "Data is version %s.\n"
|
|
|
e3ffab |
+ "Server is running %s." %
|
|
|
e3ffab |
+ (self.backup_ipa_version, str(version.VERSION)))
|
|
|
e3ffab |
+ if (not options.unattended and
|
|
|
e3ffab |
+ not user_input("Continue to restore?", False)):
|
|
|
e3ffab |
+ raise admintool.ScriptError("Aborted")
|
|
|
e3ffab |
|
|
|
e3ffab |
create_ds_user()
|
|
|
e3ffab |
pent = pwd.getpwnam(DS_USER)
|
|
|
e3ffab |
@@ -257,46 +301,35 @@ class Restore(admintool.AdminTool):
|
|
|
e3ffab |
|
|
|
e3ffab |
os.chown(self.dir, pent.pw_uid, pent.pw_gid)
|
|
|
e3ffab |
|
|
|
e3ffab |
- self.header = os.path.join(self.backup_dir, 'header')
|
|
|
e3ffab |
-
|
|
|
e3ffab |
cwd = os.getcwd()
|
|
|
e3ffab |
try:
|
|
|
e3ffab |
dirsrv = services.knownservices.dirsrv
|
|
|
e3ffab |
|
|
|
e3ffab |
- try:
|
|
|
e3ffab |
- self.read_header()
|
|
|
e3ffab |
- except IOError as e:
|
|
|
e3ffab |
- raise admintool.ScriptError('Cannot read backup metadata: %s' % e)
|
|
|
e3ffab |
- # These two checks would normally be in the validate method but
|
|
|
e3ffab |
- # we need to know the type of backup we're dealing with.
|
|
|
e3ffab |
- if (self.backup_type != 'FULL' and not options.data_only and
|
|
|
e3ffab |
- not databases):
|
|
|
e3ffab |
- raise admintool.ScriptError('Cannot restore a data backup into an empty system')
|
|
|
e3ffab |
- if (self.backup_type == 'FULL' and not options.data_only and
|
|
|
e3ffab |
- (options.instance or options.backend)):
|
|
|
e3ffab |
- raise admintool.ScriptError('Restore must be in data-only mode when restoring a specific instance or backend.')
|
|
|
e3ffab |
- if self.backup_host != api.env.host:
|
|
|
e3ffab |
- raise admintool.ScriptError(
|
|
|
e3ffab |
- 'Host name %s does not match backup name %s' %
|
|
|
e3ffab |
- (api.env.host, self.backup_host))
|
|
|
e3ffab |
- if self.backup_ipa_version != str(version.VERSION):
|
|
|
e3ffab |
- self.log.warning(
|
|
|
e3ffab |
- "Restoring data from a different release of IPA.\n"
|
|
|
e3ffab |
- "Data is version %s.\n"
|
|
|
e3ffab |
- "Server is running %s." %
|
|
|
e3ffab |
- (self.backup_ipa_version, str(version.VERSION)))
|
|
|
e3ffab |
- if (not options.unattended and
|
|
|
e3ffab |
- not user_input("Continue to restore?", False)):
|
|
|
e3ffab |
- raise admintool.ScriptError("Aborted")
|
|
|
e3ffab |
-
|
|
|
e3ffab |
self.extract_backup(options.gpg_keyring)
|
|
|
e3ffab |
|
|
|
e3ffab |
- for database in databases:
|
|
|
e3ffab |
- ldifname = '%s-%s.ldif' % database
|
|
|
e3ffab |
- ldiffile = os.path.join(self.dir, ldifname)
|
|
|
e3ffab |
- if not os.path.exists(ldiffile):
|
|
|
e3ffab |
+ databases = []
|
|
|
e3ffab |
+ for instance in instances:
|
|
|
e3ffab |
+ for backend in backends:
|
|
|
e3ffab |
+ database = (instance, backend)
|
|
|
e3ffab |
+ ldiffile = os.path.join(self.dir, '%s-%s.ldif' % database)
|
|
|
e3ffab |
+ if os.path.exists(ldiffile):
|
|
|
e3ffab |
+ databases.append(database)
|
|
|
e3ffab |
+
|
|
|
e3ffab |
+ if options.instance:
|
|
|
e3ffab |
+ for instance, backend in databases:
|
|
|
e3ffab |
+ if instance == options.instance:
|
|
|
e3ffab |
+ break
|
|
|
e3ffab |
+ else:
|
|
|
e3ffab |
+ raise admintool.ScriptError(
|
|
|
e3ffab |
+ "Instance %s not found in backup" % options.instance)
|
|
|
e3ffab |
+
|
|
|
e3ffab |
+ if options.backend:
|
|
|
e3ffab |
+ for instance, backend in databases:
|
|
|
e3ffab |
+ if backend == options.backend:
|
|
|
e3ffab |
+ break
|
|
|
e3ffab |
+ else:
|
|
|
e3ffab |
raise admintool.ScriptError(
|
|
|
e3ffab |
- "Instance %s with backend %s not in backup" % database)
|
|
|
e3ffab |
+ "Backend %s not found in backup" % options.backend)
|
|
|
e3ffab |
|
|
|
e3ffab |
# Big fat warning
|
|
|
e3ffab |
if (not options.unattended and
|
|
|
e3ffab |
@@ -315,7 +348,7 @@ class Restore(admintool.AdminTool):
|
|
|
e3ffab |
self.log.info("Disabling all replication.")
|
|
|
e3ffab |
self.disable_agreements()
|
|
|
e3ffab |
|
|
|
e3ffab |
- if options.data_only:
|
|
|
e3ffab |
+ if restore_type != 'FULL':
|
|
|
e3ffab |
if not options.online:
|
|
|
e3ffab |
self.log.info('Stopping Directory Server')
|
|
|
e3ffab |
dirsrv.stop(capture_output=False)
|
|
|
e3ffab |
@@ -332,11 +365,9 @@ class Restore(admintool.AdminTool):
|
|
|
e3ffab |
|
|
|
e3ffab |
|
|
|
e3ffab |
# We do either a full file restore or we restore data.
|
|
|
e3ffab |
- if self.backup_type == 'FULL' and not options.data_only:
|
|
|
e3ffab |
+ if restore_type == 'FULL':
|
|
|
e3ffab |
if 'CA' in self.backup_services:
|
|
|
e3ffab |
create_ca_user()
|
|
|
e3ffab |
- if options.online:
|
|
|
e3ffab |
- raise admintool.ScriptError('File restoration cannot be done online.')
|
|
|
e3ffab |
self.cert_restore_prepare()
|
|
|
e3ffab |
self.file_restore(options.no_logs)
|
|
|
e3ffab |
self.cert_restore()
|
|
|
e3ffab |
@@ -351,7 +382,7 @@ class Restore(admintool.AdminTool):
|
|
|
e3ffab |
for instance, backend in databases:
|
|
|
e3ffab |
self.ldif2db(instance, backend, online=options.online)
|
|
|
e3ffab |
|
|
|
e3ffab |
- if options.data_only:
|
|
|
e3ffab |
+ if restore_type != 'FULL':
|
|
|
e3ffab |
if not options.online:
|
|
|
e3ffab |
self.log.info('Starting Directory Server')
|
|
|
e3ffab |
dirsrv.start(capture_output=False)
|
|
|
e3ffab |
--
|
|
|
e3ffab |
2.1.0
|
|
|
e3ffab |
|