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-2009 Sun Microsystems, Inc. 025 * Portions Copyright 2012-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.util.ArrayList; 036import java.util.List; 037 038import org.forgerock.i18n.LocalizableMessage; 039import org.forgerock.i18n.slf4j.LocalizedLogger; 040import org.forgerock.opendj.ldap.ResultCode; 041import org.opends.messages.TaskMessages; 042import org.opends.server.api.Backend; 043import org.opends.server.api.Backend.BackendOperation; 044import org.opends.server.api.ClientConnection; 045import org.opends.server.backends.RebuildConfig; 046import org.opends.server.backends.RebuildConfig.RebuildMode; 047import org.opends.server.backends.task.Task; 048import org.opends.server.backends.task.TaskState; 049import org.opends.server.core.DirectoryServer; 050import org.opends.server.core.LockFileManager; 051import org.opends.server.types.Attribute; 052import org.opends.server.types.AttributeType; 053import org.opends.server.types.DN; 054import org.opends.server.types.DirectoryException; 055import org.opends.server.types.Entry; 056import org.opends.server.types.InitializationException; 057import org.opends.server.types.Operation; 058import org.opends.server.types.Privilege; 059 060/** 061 * This class provides an implementation of a Directory Server task that can be 062 * used to rebuild indexes in a backend. 063 */ 064public class RebuildTask extends Task 065{ 066 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 067 068 private String baseDN; 069 private ArrayList<String> indexes; 070 private String tmpDirectory; 071 private RebuildMode rebuildMode = RebuildMode.USER_DEFINED; 072 private boolean isClearDegradedState; 073 074 /** {@inheritDoc} */ 075 @Override 076 public LocalizableMessage getDisplayName() 077 { 078 return TaskMessages.INFO_TASK_REBUILD_NAME.get(); 079 } 080 081 /** {@inheritDoc} */ 082 @Override 083 public void initializeTask() throws DirectoryException 084 { 085 // If the client connection is available, then make sure the associated 086 // client has the INDEX_REBUILD privilege. 087 088 Operation operation = getOperation(); 089 if (operation != null) 090 { 091 ClientConnection clientConnection = operation.getClientConnection(); 092 if (!clientConnection.hasPrivilege(Privilege.LDIF_IMPORT, operation)) 093 { 094 LocalizableMessage message = ERR_TASK_INDEXREBUILD_INSUFFICIENT_PRIVILEGES.get(); 095 throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 096 message); 097 } 098 } 099 100 Entry taskEntry = getTaskEntry(); 101 102 baseDN = asString(taskEntry, ATTR_REBUILD_BASE_DN); 103 tmpDirectory = asString(taskEntry, ATTR_REBUILD_TMP_DIRECTORY); 104 final String val = asString(taskEntry, ATTR_REBUILD_INDEX_CLEARDEGRADEDSTATE); 105 isClearDegradedState = Boolean.parseBoolean(val); 106 107 AttributeType typeIndex = getAttributeTypeOrDefault(ATTR_REBUILD_INDEX); 108 List<Attribute> attrList = taskEntry.getAttribute(typeIndex); 109 indexes = TaskUtils.getMultiValueString(attrList); 110 111 rebuildMode = getRebuildMode(indexes); 112 if (rebuildMode != RebuildMode.USER_DEFINED) 113 { 114 if (indexes.size() != 1) 115 { 116 LocalizableMessage msg = ERR_TASK_INDEXREBUILD_ALL_ERROR.get(); 117 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg); 118 } 119 indexes.clear(); 120 } 121 } 122 123 private String asString(Entry taskEntry, String attrName) 124 { 125 final AttributeType attrType = getAttributeTypeOrDefault(attrName); 126 final List<Attribute> attrList = taskEntry.getAttribute(attrType); 127 return TaskUtils.getSingleValueString(attrList); 128 } 129 130 private RebuildMode getRebuildMode(List<String> indexList) 131 { 132 for (String s : indexList) 133 { 134 if (REBUILD_ALL.equalsIgnoreCase(s)) 135 { 136 return RebuildMode.ALL; 137 } 138 else if (REBUILD_DEGRADED.equalsIgnoreCase(s)) 139 { 140 return RebuildMode.DEGRADED; 141 } 142 } 143 return RebuildMode.USER_DEFINED; 144 } 145 146 /** {@inheritDoc} */ 147 @Override 148 protected TaskState runTask() 149 { 150 RebuildConfig rebuildConfig = new RebuildConfig(); 151 152 try 153 { 154 rebuildConfig.setBaseDN(DN.valueOf(baseDN)); 155 } 156 catch (DirectoryException de) 157 { 158 logger.error(ERR_CANNOT_DECODE_BASE_DN, baseDN, de.getMessageObject()); 159 return TaskState.STOPPED_BY_ERROR; 160 } 161 162 for (final String index : indexes) 163 { 164 rebuildConfig.addRebuildIndex(index); 165 } 166 167 // The degraded state is set(if present in args) 168 // during the initialization. 169 rebuildConfig.isClearDegradedState(isClearDegradedState); 170 boolean isBackendNeedToBeEnabled = false; 171 172 if (tmpDirectory == null) 173 { 174 tmpDirectory = "import-tmp"; 175 } 176 rebuildConfig.setTmpDirectory(tmpDirectory); 177 rebuildConfig.setRebuildMode(rebuildMode); 178 179 final Backend<?> backend = DirectoryServer.getBackendWithBaseDN(rebuildConfig.getBaseDN()); 180 if (backend == null) 181 { 182 logger.error(ERR_NO_BACKENDS_FOR_BASE, baseDN); 183 return TaskState.STOPPED_BY_ERROR; 184 } 185 if (!backend.supports(BackendOperation.INDEXING)) 186 { 187 logger.error(ERR_REBUILDINDEX_WRONG_BACKEND_TYPE); 188 return TaskState.STOPPED_BY_ERROR; 189 } 190 191 // If we are rebuilding one or more system indexes, we have 192 // to acquire exclusive lock. Shared lock in 'cleardegradedstate' mode. 193 String lockFile = LockFileManager.getBackendLockFileName(backend); 194 StringBuilder failureReason = new StringBuilder(); 195 196 // Disable the backend 197 // Except in 'cleardegradedstate' mode we don't need to disable it. 198 if (!isClearDegradedState) 199 { 200 try 201 { 202 TaskUtils.disableBackend(backend.getBackendID()); 203 } 204 catch (DirectoryException e) 205 { 206 logger.traceException(e); 207 208 logger.error(e.getMessageObject()); 209 return TaskState.STOPPED_BY_ERROR; 210 } 211 212 try 213 { 214 if (!LockFileManager.acquireExclusiveLock(lockFile, failureReason)) 215 { 216 logger.error(ERR_REBUILDINDEX_CANNOT_EXCLUSIVE_LOCK_BACKEND, backend.getBackendID(), failureReason); 217 return TaskState.STOPPED_BY_ERROR; 218 } 219 } 220 catch (Exception e) 221 { 222 logger.error(ERR_REBUILDINDEX_CANNOT_EXCLUSIVE_LOCK_BACKEND, backend 223 .getBackendID(), getExceptionMessage(e)); 224 return TaskState.STOPPED_BY_ERROR; 225 } 226 } 227 else 228 { 229 // We just need a shared lock on the backend for this part. 230 try 231 { 232 if (!LockFileManager.acquireSharedLock(lockFile, failureReason)) 233 { 234 logger.error(ERR_REBUILDINDEX_CANNOT_SHARED_LOCK_BACKEND, backend.getBackendID(), failureReason); 235 return TaskState.STOPPED_BY_ERROR; 236 } 237 } 238 catch (Exception e) 239 { 240 logger.error(ERR_REBUILDINDEX_CANNOT_SHARED_LOCK_BACKEND, backend 241 .getBackendID(), getExceptionMessage(e)); 242 return TaskState.STOPPED_BY_ERROR; 243 } 244 } 245 246 TaskState returnCode = TaskState.COMPLETED_SUCCESSFULLY; 247 248 // Launch the rebuild process. 249 try 250 { 251 backend.rebuildBackend(rebuildConfig, DirectoryServer.getInstance().getServerContext()); 252 } 253 catch (InitializationException e) 254 { 255 // This exception catches all 'index not found' 256 // The backend needs to be re-enabled at the end of the process. 257 LocalizableMessage message = 258 ERR_REBUILDINDEX_ERROR_DURING_REBUILD.get(e.getMessage()); 259 logger.traceException(e); 260 logger.error(message); 261 isBackendNeedToBeEnabled = true; 262 returnCode = TaskState.STOPPED_BY_ERROR; 263 } 264 catch (Exception e) 265 { 266 logger.traceException(e); 267 268 logger.error(ERR_REBUILDINDEX_ERROR_DURING_REBUILD, e.getMessage()); 269 returnCode = TaskState.STOPPED_BY_ERROR; 270 } 271 finally 272 { 273 // Release the lock on the backend. 274 try 275 { 276 lockFile = LockFileManager.getBackendLockFileName(backend); 277 failureReason = new StringBuilder(); 278 if (!LockFileManager.releaseLock(lockFile, failureReason)) 279 { 280 logger.warn(WARN_REBUILDINDEX_CANNOT_UNLOCK_BACKEND, backend.getBackendID(), failureReason); 281 returnCode = TaskState.COMPLETED_WITH_ERRORS; 282 } 283 } 284 catch (Throwable t) 285 { 286 logger.warn(WARN_REBUILDINDEX_CANNOT_UNLOCK_BACKEND, backend.getBackendID(), 287 getExceptionMessage(t)); 288 returnCode = TaskState.COMPLETED_WITH_ERRORS; 289 } 290 } 291 292 // The backend must be enabled only if the task is successful 293 // for prevent potential risks of database corruption. 294 if ((returnCode == TaskState.COMPLETED_SUCCESSFULLY || isBackendNeedToBeEnabled) 295 && !isClearDegradedState) 296 { 297 // Enable the backend. 298 try 299 { 300 TaskUtils.enableBackend(backend.getBackendID()); 301 } 302 catch (DirectoryException e) 303 { 304 logger.traceException(e); 305 306 logger.error(e.getMessageObject()); 307 returnCode = TaskState.STOPPED_BY_ERROR; 308 } 309 } 310 311 return returnCode; 312 } 313}