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 }