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