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