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