001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2008 Sun Microsystems, Inc. 025 * Portions Copyright 2014-2015 ForgeRock AS 026 */ 027package org.opends.server.tasks; 028 029import static org.opends.messages.TaskMessages.*; 030import static org.opends.messages.ToolMessages.*; 031import static org.opends.server.config.ConfigConstants.*; 032import static org.opends.server.core.DirectoryServer.*; 033import static org.opends.server.util.StaticUtils.*; 034 035import java.io.File; 036import java.util.HashMap; 037import java.util.List; 038import java.util.Map; 039 040import org.forgerock.i18n.LocalizableMessage; 041import org.forgerock.i18n.slf4j.LocalizedLogger; 042import org.forgerock.opendj.config.server.ConfigException; 043import org.forgerock.opendj.ldap.ResultCode; 044import org.opends.messages.Severity; 045import org.opends.messages.TaskMessages; 046import org.opends.server.api.Backend; 047import org.opends.server.api.Backend.BackendOperation; 048import org.opends.server.api.ClientConnection; 049import org.opends.server.backends.task.Task; 050import org.opends.server.backends.task.TaskState; 051import org.opends.server.config.ConfigEntry; 052import org.opends.server.core.DirectoryServer; 053import org.opends.server.core.LockFileManager; 054import org.opends.server.types.Attribute; 055import org.opends.server.types.AttributeType; 056import org.opends.server.types.BackupDirectory; 057import org.opends.server.types.BackupInfo; 058import org.opends.server.types.DN; 059import org.opends.server.types.DirectoryException; 060import org.opends.server.types.Entry; 061import org.opends.server.types.Operation; 062import org.opends.server.types.Privilege; 063import org.opends.server.types.RestoreConfig; 064 065/** 066 * This class provides an implementation of a Directory Server task that can 067 * be used to restore a binary backup of a Directory Server backend. 068 */ 069public class RestoreTask extends Task 070{ 071 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 072 073 074 /** Stores mapping between configuration attribute name and its label. */ 075 private static Map<String,LocalizableMessage> argDisplayMap = new HashMap<>(); 076 static { 077 argDisplayMap.put(ATTR_BACKUP_DIRECTORY_PATH, INFO_RESTORE_ARG_BACKUP_DIR.get()); 078 argDisplayMap.put(ATTR_BACKUP_ID, INFO_RESTORE_ARG_BACKUP_ID.get()); 079 argDisplayMap.put(ATTR_TASK_RESTORE_VERIFY_ONLY, INFO_RESTORE_ARG_VERIFY_ONLY.get()); 080 } 081 082 083 /** The task arguments. */ 084 private File backupDirectory; 085 private String backupID; 086 private boolean verifyOnly; 087 088 private RestoreConfig restoreConfig; 089 090 /** {@inheritDoc} */ 091 @Override 092 public LocalizableMessage getDisplayName() { 093 return INFO_TASK_RESTORE_NAME.get(); 094 } 095 096 /** {@inheritDoc} */ 097 @Override 098 public LocalizableMessage getAttributeDisplayName(String name) { 099 return argDisplayMap.get(name); 100 } 101 102 /** {@inheritDoc} */ 103 @Override 104 public void initializeTask() throws DirectoryException 105 { 106 // If the client connection is available, then make sure the associated 107 // client has the BACKEND_RESTORE privilege. 108 Operation operation = getOperation(); 109 if (operation != null) 110 { 111 ClientConnection clientConnection = operation.getClientConnection(); 112 if (! clientConnection.hasPrivilege(Privilege.BACKEND_RESTORE, operation)) 113 { 114 LocalizableMessage message = ERR_TASK_RESTORE_INSUFFICIENT_PRIVILEGES.get(); 115 throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 116 message); 117 } 118 } 119 120 121 Entry taskEntry = getTaskEntry(); 122 123 AttributeType typeBackupDirectory = getAttributeTypeOrDefault(ATTR_BACKUP_DIRECTORY_PATH); 124 AttributeType typebackupID = getAttributeTypeOrDefault(ATTR_BACKUP_ID); 125 AttributeType typeVerifyOnly = getAttributeTypeOrDefault(ATTR_TASK_RESTORE_VERIFY_ONLY); 126 127 List<Attribute> attrList; 128 129 attrList = taskEntry.getAttribute(typeBackupDirectory); 130 String backupDirectoryPath = TaskUtils.getSingleValueString(attrList); 131 backupDirectory = new File(backupDirectoryPath); 132 if (! backupDirectory.isAbsolute()) 133 { 134 backupDirectory = 135 new File(DirectoryServer.getInstanceRoot(), backupDirectoryPath); 136 } 137 138 attrList = taskEntry.getAttribute(typebackupID); 139 backupID = TaskUtils.getSingleValueString(attrList); 140 141 attrList = taskEntry.getAttribute(typeVerifyOnly); 142 verifyOnly = TaskUtils.getBoolean(attrList, false); 143 144 } 145 146 /** 147 * Acquire an exclusive lock on a backend. 148 * @param backend The backend on which the lock is to be acquired. 149 * @return true if the lock was successfully acquired. 150 */ 151 private boolean lockBackend(Backend<?> backend) 152 { 153 try 154 { 155 String lockFile = LockFileManager.getBackendLockFileName(backend); 156 StringBuilder failureReason = new StringBuilder(); 157 if (! LockFileManager.acquireExclusiveLock(lockFile, failureReason)) 158 { 159 logger.error(ERR_RESTOREDB_CANNOT_LOCK_BACKEND, backend.getBackendID(), failureReason); 160 return false; 161 } 162 } 163 catch (Exception e) 164 { 165 logger.error(ERR_RESTOREDB_CANNOT_LOCK_BACKEND, backend.getBackendID(), getExceptionMessage(e)); 166 return false; 167 } 168 return true; 169 } 170 171 /** 172 * Release a lock on a backend. 173 * @param backend The backend on which the lock is held. 174 * @return true if the lock was successfully released. 175 */ 176 private boolean unlockBackend(Backend<?> backend) 177 { 178 try 179 { 180 String lockFile = LockFileManager.getBackendLockFileName(backend); 181 StringBuilder failureReason = new StringBuilder(); 182 if (! LockFileManager.releaseLock(lockFile, failureReason)) 183 { 184 logger.warn(WARN_RESTOREDB_CANNOT_UNLOCK_BACKEND, backend.getBackendID(), failureReason); 185 return false; 186 } 187 } 188 catch (Exception e) 189 { 190 logger.warn(WARN_RESTOREDB_CANNOT_UNLOCK_BACKEND, backend.getBackendID(), getExceptionMessage(e)); 191 return false; 192 } 193 return true; 194 } 195 196 /** {@inheritDoc} */ 197 @Override 198 public void interruptTask(TaskState interruptState, LocalizableMessage interruptReason) 199 { 200 if (TaskState.STOPPED_BY_ADMINISTRATOR.equals(interruptState) && 201 restoreConfig != null) 202 { 203 addLogMessage(Severity.INFORMATION, TaskMessages.INFO_TASK_STOPPED_BY_ADMIN.get( 204 interruptReason)); 205 setTaskInterruptState(interruptState); 206 restoreConfig.cancel(); 207 } 208 } 209 210 /** {@inheritDoc} */ 211 @Override 212 public boolean isInterruptable() { 213 return true; 214 } 215 216 /** {@inheritDoc} */ 217 @Override 218 protected TaskState runTask() 219 { 220 // Open the backup directory and make sure it is valid. 221 BackupDirectory backupDir; 222 try 223 { 224 backupDir = BackupDirectory.readBackupDirectoryDescriptor( 225 backupDirectory.getPath()); 226 } 227 catch (Exception e) 228 { 229 logger.error(ERR_RESTOREDB_CANNOT_READ_BACKUP_DIRECTORY, backupDirectory, getExceptionMessage(e)); 230 return TaskState.STOPPED_BY_ERROR; 231 } 232 233 234 // If a backup ID was specified, then make sure it is valid. If none was 235 // provided, then choose the latest backup from the archive. 236 if (backupID != null) 237 { 238 BackupInfo backupInfo = backupDir.getBackupInfo(backupID); 239 if (backupInfo == null) 240 { 241 logger.error(ERR_RESTOREDB_INVALID_BACKUP_ID, backupID, backupDirectory); 242 return TaskState.STOPPED_BY_ERROR; 243 } 244 } 245 else 246 { 247 BackupInfo latestBackup = backupDir.getLatestBackup(); 248 if (latestBackup == null) 249 { 250 logger.error(ERR_RESTOREDB_NO_BACKUPS_IN_DIRECTORY, backupDirectory); 251 return TaskState.STOPPED_BY_ERROR; 252 } 253 else 254 { 255 backupID = latestBackup.getBackupID(); 256 } 257 } 258 259 // Get the DN of the backend configuration entry from the backup. 260 DN configEntryDN = backupDir.getConfigEntryDN(); 261 262 ConfigEntry configEntry; 263 try 264 { 265 // Get the backend configuration entry. 266 configEntry = DirectoryServer.getConfigEntry(configEntryDN); 267 } 268 catch (ConfigException e) 269 { 270 logger.traceException(e); 271 logger.error(ERR_RESTOREDB_NO_BACKENDS_FOR_DN, backupDirectory, configEntryDN); 272 return TaskState.STOPPED_BY_ERROR; 273 } 274 275 // Get the backend ID from the configuration entry. 276 String backendID = TaskUtils.getBackendID(configEntry); 277 278 // Get the backend. 279 Backend<?> backend = DirectoryServer.getBackend(backendID); 280 if (!backend.supports(BackendOperation.RESTORE)) 281 { 282 logger.error(ERR_RESTOREDB_CANNOT_RESTORE, backend.getBackendID()); 283 return TaskState.STOPPED_BY_ERROR; 284 } 285 286 // Create the restore config object from the information available. 287 restoreConfig = new RestoreConfig(backupDir, backupID, verifyOnly); 288 289 // Notify the task listeners that a restore is going to start 290 // this must be done before disabling the backend to allow 291 // listener to get access to the backend configuration 292 // and to take appropriate actions. 293 DirectoryServer.notifyRestoreBeginning(backend, restoreConfig); 294 295 // Disable the backend. 296 if ( !verifyOnly) 297 { 298 try 299 { 300 TaskUtils.disableBackend(backendID); 301 } catch (DirectoryException e) 302 { 303 logger.traceException(e); 304 305 logger.error(e.getMessageObject()); 306 return TaskState.STOPPED_BY_ERROR; 307 } 308 } 309 310 // From here we must make sure to re-enable the backend before returning. 311 boolean errorsEncountered = false; 312 try 313 { 314 // Acquire an exclusive lock for the backend. 315 if (verifyOnly || lockBackend(backend)) 316 { 317 // From here we must make sure to release the backend exclusive lock. 318 try 319 { 320 // Perform the restore. 321 try 322 { 323 backend.restoreBackup(restoreConfig); 324 } 325 catch (DirectoryException de) 326 { 327 DirectoryServer.notifyRestoreEnded(backend, restoreConfig, false); 328 logger.error(ERR_RESTOREDB_ERROR_DURING_BACKUP, backupID, backupDir.getPath(), de.getMessageObject()); 329 errorsEncountered = true; 330 } 331 catch (Exception e) 332 { 333 DirectoryServer.notifyRestoreEnded(backend, restoreConfig, false); 334 logger.error(ERR_RESTOREDB_ERROR_DURING_BACKUP, backupID, backupDir.getPath(), getExceptionMessage(e)); 335 errorsEncountered = true; 336 } 337 } 338 finally 339 { 340 // Release the exclusive lock on the backend. 341 if (!verifyOnly && !unlockBackend(backend)) 342 { 343 errorsEncountered = true; 344 } 345 } 346 } 347 } 348 finally 349 { 350 // Enable the backend. 351 if (! verifyOnly) 352 { 353 try 354 { 355 TaskUtils.enableBackend(backendID); 356 // it is necessary to retrieve the backend structure again 357 // because disabling and enabling it again may have resulted 358 // in a new backend being registered to the server. 359 backend = DirectoryServer.getBackend(backendID); 360 } catch (DirectoryException e) 361 { 362 logger.traceException(e); 363 364 logger.error(e.getMessageObject()); 365 errorsEncountered = true; 366 } 367 } 368 DirectoryServer.notifyRestoreEnded(backend, restoreConfig, true); 369 } 370 371 if (errorsEncountered) 372 { 373 return TaskState.COMPLETED_WITH_ERRORS; 374 } 375 else 376 { 377 return getFinalTaskState(); 378 } 379 } 380}