From 77dcb75fb0bf9c03a591297519a97f2f3b69dbf0 Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Wed, 05 Sep 2007 06:51:34 +0000
Subject: [PATCH] Implement support for delete and modify operations in the task backend as follows:

---
 opends/src/messages/messages/backend.properties                                                    |   21 +
 opends/tests/unit-tests-testng/src/server/org/opends/server/backends/task/TaskBackendTestCase.java |  409 +++++++++++++++++++++++++++++++
 opends/tests/unit-tests-testng/src/server/org/opends/server/tasks/TasksTestCase.java               |   30 ++
 opends/tests/unit-tests-testng/src/server/org/opends/server/tasks/DummyTask.java                   |   64 ++++
 opends/src/server/org/opends/server/backends/task/TaskBackend.java                                 |  208 +++++++++++----
 5 files changed, 669 insertions(+), 63 deletions(-)

diff --git a/opends/src/messages/messages/backend.properties b/opends/src/messages/messages/backend.properties
index 3e6d6b1..00aee65 100644
--- a/opends/src/messages/messages/backend.properties
+++ b/opends/src/messages/messages/backend.properties
@@ -944,5 +944,22 @@
 SEVERE_WARN_TRUSTSTORE_SET_PERMISSIONS_FAILED_328=Failed to set permissions \
  on trust store file %s
 SEVERE_ERR_ROOT_CONTAINER_NOT_INITIALIZED_329=The root container for backend \
-  %s has not been initialized preventing this backend from processing the \
-  requested operation
+ %s has not been initialized preventing this backend from processing the \
+ requested operation
+SEVERE_ERR_TASKBE_MODIFY_CANNOT_LOCK_ENTRY_330=Unable to obtain a write lock \
+ on entry %s
+SEVERE_ERR_TASKBE_MODIFY_INVALID_ENTRY_331=Entry %s cannot be modified \
+ because it does not represent a task entry.  Only task entries may be \
+ modified in the task backend
+SEVERE_ERR_TASKBE_MODIFY_NO_SUCH_TASK_332=Entry %s cannot be modified because \
+ it does not represent a valid task in the server
+SEVERE_ERR_TASKBE_MODIFY_COMPLETED_333=Entry %s cannot be modified because \
+ the assoicated task has completed running.  Completed tasks cannot be modified
+SEVERE_ERR_TASKBE_MODIFY_RECURRING_334=Entry %s cannot be modified because \
+ the server does not currently support modifying recurring task entries
+SEVERE_ERR_TASKBE_MODIFY_RUNNING_335=The task associated with entry %s is \
+ currently running.  The only modification allowed for running tasks is to \
+ replace the value of the ds-task-state attribute with "cancel"
+INFO_TASKBE_RUNNING_TASK_CANCELLED_336=Task processing was interrupted by a \
+ modify request to cancel the task
+
diff --git a/opends/src/server/org/opends/server/backends/task/TaskBackend.java b/opends/src/server/org/opends/server/backends/task/TaskBackend.java
index f716e0e..afe3b7c 100644
--- a/opends/src/server/org/opends/server/backends/task/TaskBackend.java
+++ b/opends/src/server/org/opends/server/backends/task/TaskBackend.java
@@ -33,6 +33,7 @@
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.locks.Lock;
 
@@ -495,57 +496,6 @@
 
 
   /**
-   * Indicates whether an entry with the specified DN exists in the backend.
-   * The default implementation obtains a read lock and calls
-   * <CODE>getEntry</CODE>, but backend implementations may override this with a
-   * more efficient version that does not require a lock.  The caller is not
-   * required to hold any locks on the specified DN.
-   *
-   * @param  entryDN  The DN of the entry for which to determine existence.
-   *
-   * @return  <CODE>true</CODE> if the specified entry exists in this backend,
-   *          or <CODE>false</CODE> if it does not.
-   *
-   * @throws  DirectoryException  If a problem occurs while trying to make the
-   *                              determination.
-   */
-  public boolean entryExists(DN entryDN)
-         throws DirectoryException
-  {
-    if (entryDN == null)
-    {
-      return false;
-    }
-
-    if (entryDN.equals(taskRootDN) || entryDN.equals(scheduledTaskParentDN) ||
-        entryDN.equals(recurringTaskParentDN))
-    {
-      return true;
-    }
-
-    DN parentDN = entryDN.getParentDNInSuffix();
-    if (parentDN == null)
-    {
-      return false;
-    }
-
-    if (parentDN.equals(scheduledTaskParentDN))
-    {
-      return (taskScheduler.getScheduledTaskEntry(entryDN) != null);
-    }
-    else if (parentDN.equals(recurringTaskParentDN))
-    {
-      return (taskScheduler.getRecurringTaskEntry(entryDN) != null);
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-
-
-  /**
    * Adds the provided entry to this backend.  This method must ensure that the
    * entry is appropriate for the backend and that no entry already exists with
    * the same DN.
@@ -701,10 +651,158 @@
   public void replaceEntry(Entry entry, ModifyOperation modifyOperation)
          throws DirectoryException
   {
-    // FIXME -- We need to support this.
-    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
-            Message.raw("Modify operations are not yet supported in " +
-                        "the task backend"));
+    DN entryDN = entry.getDN();
+
+    Lock entryLock = null;
+    if (! taskScheduler.holdsSchedulerLock())
+    {
+      for (int i=0; i < 3; i++)
+      {
+        entryLock = LockManager.lockWrite(entryDN);
+        if (entryLock != null)
+        {
+          break;
+        }
+      }
+
+      if (entryLock == null)
+      {
+        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+                                     ERR_TASKBE_MODIFY_CANNOT_LOCK_ENTRY.get(
+                                          String.valueOf(entryDN)));
+      }
+    }
+
+    try
+    {
+      // Get the parent for the provided entry DN.  It must be either the
+      // scheduled or recurring task parent DN.
+      DN parentDN = entryDN.getParentDNInSuffix();
+      if (parentDN == null)
+      {
+        Message message =
+            ERR_TASKBE_MODIFY_INVALID_ENTRY.get(String.valueOf(entryDN));
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
+      }
+      else if (parentDN.equals(scheduledTaskParentDN))
+      {
+        // It's a scheduled task.  Make sure that it exists.
+        Task t = taskScheduler.getScheduledTask(entryDN);
+        if (t == null)
+        {
+          Message message =
+              ERR_TASKBE_MODIFY_NO_SUCH_TASK.get(String.valueOf(entryDN));
+          throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message);
+        }
+
+
+        // Look at the state of the task.  We will allow anything to be altered
+        // for a pending task.  For a running task, we will only allow the state
+        // to be altered in order to cancel it.  We will not allow any
+        // modifications for completed tasks.
+        TaskState state = t.getTaskState();
+        if (TaskState.isPending(state))
+        {
+          Task newTask =
+                    taskScheduler.entryToScheduledTask(entry, modifyOperation);
+          taskScheduler.removePendingTask(t.getTaskID());
+          taskScheduler.scheduleTask(newTask, true);
+          return;
+        }
+        else if (TaskState.isRunning(state))
+        {
+          // If the task is running, we will only allow it to be cancelled.
+          // This will only be allowed using the replace modification type on
+          // the ds-task-state attribute if the value starts with "cancel" or
+          // "stop".  In that case, we'll cancel the task.
+          boolean acceptable = true;
+          for (Modification m : modifyOperation.getModifications())
+          {
+            if (m.isInternal())
+            {
+              continue;
+            }
+
+            if (m.getModificationType() != ModificationType.REPLACE)
+            {
+              acceptable = false;
+              break;
+            }
+
+            Attribute a = m.getAttribute();
+            AttributeType at = a.getAttributeType();
+            if (! at.hasName(ATTR_TASK_STATE))
+            {
+              acceptable = false;
+              break;
+            }
+
+            Iterator<AttributeValue> iterator = a.getValues().iterator();
+            if (! iterator.hasNext())
+            {
+              acceptable = false;
+              break;
+            }
+
+            AttributeValue v = iterator.next();
+            String valueString = toLowerCase(v.getStringValue());
+            if (! (valueString.startsWith("cancel") ||
+                   valueString.startsWith("stop")))
+            {
+              acceptable = false;
+              break;
+            }
+
+            if (iterator.hasNext())
+            {
+              acceptable = false;
+              break;
+            }
+          }
+
+          if (acceptable)
+          {
+            Message message = INFO_TASKBE_RUNNING_TASK_CANCELLED.get();
+            t.interruptTask(TaskState.STOPPED_BY_ADMINISTRATOR, message);
+            return;
+          }
+          else
+          {
+            Message message =
+                 ERR_TASKBE_MODIFY_RUNNING.get(String.valueOf(entryDN));
+            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+                                         message);
+          }
+        }
+        else
+        {
+          Message message =
+              ERR_TASKBE_MODIFY_COMPLETED.get(String.valueOf(entryDN));
+          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+                                       message);
+        }
+      }
+      else if (parentDN.equals(recurringTaskParentDN))
+      {
+        // We don't currently support altering recurring tasks.
+        Message message =
+            ERR_TASKBE_MODIFY_RECURRING.get(String.valueOf(entryDN));
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
+      }
+      else
+      {
+        Message message =
+            ERR_TASKBE_MODIFY_INVALID_ENTRY.get(String.valueOf(entryDN));
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
+      }
+    }
+    finally
+    {
+      if (entryLock != null)
+      {
+        LockManager.unlock(entryDN, entryLock);
+      }
+    }
   }
 
 
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/task/TaskBackendTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/task/TaskBackendTestCase.java
new file mode 100644
index 0000000..dbd75ae
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/task/TaskBackendTestCase.java
@@ -0,0 +1,409 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.backends.task;
+
+
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import org.opends.server.TestCaseUtils;
+import org.opends.server.backends.BackendTestCase;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.tasks.TasksTestCase;
+import org.opends.server.types.DN;
+
+import static org.testng.Assert.*;
+
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.*;
+
+
+
+/**
+ * A set of test cases that can be used to test the task backend.
+ */
+public class TaskBackendTestCase
+       extends BackendTestCase
+{
+  /**
+   * Ensures that the Directory Server is running, and that we are allowed to
+   * schedule the dummy task.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @BeforeClass()
+  public void startServer()
+         throws Exception
+  {
+    TestCaseUtils.startServer();
+    TestCaseUtils.initializeTestBackend(true);
+
+    TestCaseUtils.dsconfig(
+      "set-global-configuration-prop",
+      "--add", "allowed-task:org.opends.server.tasks.DummyTask");
+  }
+
+
+
+  /**
+   * Remove the dummy task from the set of allowed tasks.
+   */
+  @AfterClass()
+  public void cleanUp()
+         throws Exception
+  {
+    TestCaseUtils.dsconfig(
+      "set-global-configuration-prop",
+      "--remove", "allowed-task:org.opends.server.tasks.DummyTask");
+  }
+
+
+
+  /**
+   * Tests to ensure that we can delete a task that is scheduled but hasn't
+   * yet started.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testDeletePendingTask()
+         throws Exception
+  {
+    // Schedule a task to start one hour from now that will simply sleep for
+    // 30 seconds.
+    String taskID = "testDeletePendingTask";
+    String taskDN = "ds-task-id=" + taskID + ",cn=Scheduled Tasks,cn=tasks";
+
+    long startTime = System.currentTimeMillis() + 3600000L;
+    SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
+    dateFormat.setTimeZone(TimeZone.getTimeZone(TIME_ZONE_UTC));
+    String startTimeStr = dateFormat.format(new Date(startTime));
+
+    TestCaseUtils.addEntry(
+      "dn: " + taskDN,
+      "objectClass: top",
+      "objectClass: ds-task",
+      "objectClass: extensibleObject",
+      "ds-task-id: " + taskID,
+      "ds-task-class-name: org.opends.server.tasks.DummyTask",
+      "ds-task-scheduled-start-time: " + startTimeStr,
+      "ds-task-dummy-sleep-time: 30000");
+    assertTrue(DirectoryServer.entryExists(DN.decode(taskDN)));
+
+    Task task = TasksTestCase.getTask(DN.decode(taskDN));
+    assertTrue(TaskState.isPending(task.getTaskState()));
+
+    // Perform a modification to delete that task.
+    int resultCode = TestCaseUtils.applyModifications(
+      "dn: " + taskDN,
+      "changetype: delete");
+    assertEquals(resultCode, 0);
+    assertFalse(DirectoryServer.entryExists(DN.decode(taskDN)));
+  }
+
+
+
+  /**
+   * Tests to ensure that we cannot delete a task that is currently running.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testDeleteRunningTask()
+         throws Exception
+  {
+    // Schedule a task to start immediately that will simply sleep for 30
+    // seconds.
+    String taskID = "testDeleteRunningTask";
+    String taskDN = "ds-task-id=" + taskID + ",cn=Scheduled Tasks,cn=tasks";
+
+    TestCaseUtils.addEntry(
+      "dn: " + taskDN,
+      "objectClass: top",
+      "objectClass: ds-task",
+      "objectClass: extensibleObject",
+      "ds-task-id: " + taskID,
+      "ds-task-class-name: org.opends.server.tasks.DummyTask",
+      "ds-task-dummy-sleep-time: 30000");
+    assertTrue(DirectoryServer.entryExists(DN.decode(taskDN)));
+
+
+    // Wait until we're sure that the task has started running.
+    long startTime = System.currentTimeMillis();
+    Task task = TasksTestCase.getTask(DN.decode(taskDN));
+    while (TaskState.isPending(task.getTaskState()))
+    {
+      Thread.sleep(10);
+      if (System.currentTimeMillis() > (startTime + 30000L))
+      {
+        throw new AssertionError("Waited too long for the task to start");
+      }
+    }
+
+    assertTrue(TaskState.isRunning(task.getTaskState()));
+
+
+    // Perform a modification to delete that task.
+    int resultCode = TestCaseUtils.applyModifications(
+      "dn: " + taskDN,
+      "changetype: delete");
+    assertFalse(resultCode == 0);
+    assertTrue(DirectoryServer.entryExists(DN.decode(taskDN)));
+  }
+
+
+
+  /**
+   * Tests to ensure that we can delete a task that has completed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testDeleteCompletedTask()
+         throws Exception
+  {
+    // Schedule a task to start immediately that will simply sleep for 30
+    // seconds.
+    String taskID = "testDeleteCompltedTask";
+    String taskDN = "ds-task-id=" + taskID + ",cn=Scheduled Tasks,cn=tasks";
+
+    TestCaseUtils.addEntry(
+      "dn: " + taskDN,
+      "objectClass: top",
+      "objectClass: ds-task",
+      "objectClass: extensibleObject",
+      "ds-task-id: " + taskID,
+      "ds-task-class-name: org.opends.server.tasks.DummyTask");
+    assertTrue(DirectoryServer.entryExists(DN.decode(taskDN)));
+
+
+    // Wait until the task has completed.
+    Task task = TasksTestCase.getCompletedTask(DN.decode(taskDN));
+    assertTrue(TaskState.isDone(task.getTaskState()));
+
+
+    // Perform a modification to delete that task.
+    int resultCode = TestCaseUtils.applyModifications(
+      "dn: " + taskDN,
+      "changetype: delete");
+    assertEquals(resultCode, 0);
+    assertFalse(DirectoryServer.entryExists(DN.decode(taskDN)));
+  }
+
+
+
+  /**
+   * Tests to ensure that we can modify a task that is scheduled but hasn't
+   * yet started to change the task state as well as other attributes in the
+   * task entry.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testModifyPendingTask()
+         throws Exception
+  {
+    // Schedule a task to start one hour from now that will simply sleep for
+    // 30 seconds.
+    String taskID = "testModifyPendingTask";
+    String taskDN = "ds-task-id=" + taskID + ",cn=Scheduled Tasks,cn=tasks";
+
+    long startTime = System.currentTimeMillis() + 3600000L;
+    SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
+    dateFormat.setTimeZone(TimeZone.getTimeZone(TIME_ZONE_UTC));
+    String startTimeStr = dateFormat.format(new Date(startTime));
+
+    TestCaseUtils.addEntry(
+      "dn: " + taskDN,
+      "objectClass: top",
+      "objectClass: ds-task",
+      "objectClass: extensibleObject",
+      "ds-task-id: " + taskID,
+      "ds-task-class-name: org.opends.server.tasks.DummyTask",
+      "ds-task-scheduled-start-time: " + startTimeStr,
+      "ds-task-dummy-sleep-time: 30000");
+    assertTrue(DirectoryServer.entryExists(DN.decode(taskDN)));
+
+    Task task = TasksTestCase.getTask(DN.decode(taskDN));
+    assertTrue(TaskState.isPending(task.getTaskState()));
+
+    // Perform a modification to update a non-state attribute.
+    int resultCode = TestCaseUtils.applyModifications(
+      "dn: " + taskDN,
+      "changetype: modify",
+      "add: description",
+      "description: foo");
+    assertEquals(resultCode, 0);
+
+
+    // Perform a modification to update the task state.
+    resultCode = TestCaseUtils.applyModifications(
+      "dn: " + taskDN,
+      "changetype: modify",
+      "replace: ds-task-state",
+      "ds-task-state: " + TaskState.CANCELED_BEFORE_STARTING.toString());
+    assertEquals(resultCode, 0);
+
+    // Delete the task.
+    resultCode = TestCaseUtils.applyModifications(
+      "dn: " + taskDN,
+      "changetype: delete");
+    assertEquals(resultCode, 0);
+    assertFalse(DirectoryServer.entryExists(DN.decode(taskDN)));
+  }
+
+
+
+  /**
+   * Tests to ensure that we cannot modify a task that is currently running
+   * other than to change its state.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testModifyRunningTask()
+         throws Exception
+  {
+    // Schedule a task to start immediately that will simply sleep for 30
+    // seconds.
+    String taskID = "testModifyRunningTask";
+    String taskDN = "ds-task-id=" + taskID + ",cn=Scheduled Tasks,cn=tasks";
+
+    TestCaseUtils.addEntry(
+      "dn: " + taskDN,
+      "objectClass: top",
+      "objectClass: ds-task",
+      "objectClass: extensibleObject",
+      "ds-task-id: " + taskID,
+      "ds-task-class-name: org.opends.server.tasks.DummyTask",
+      "ds-task-dummy-sleep-time: 30000");
+    assertTrue(DirectoryServer.entryExists(DN.decode(taskDN)));
+
+
+    // Wait until we're sure that the task has started running.
+    long startTime = System.currentTimeMillis();
+    Task task = TasksTestCase.getTask(DN.decode(taskDN));
+    while (TaskState.isPending(task.getTaskState()))
+    {
+      Thread.sleep(10);
+      if (System.currentTimeMillis() > (startTime + 30000L))
+      {
+        throw new AssertionError("Waited too long for the task to start");
+      }
+    }
+
+    assertTrue(TaskState.isRunning(task.getTaskState()));
+
+
+    // Perform a modification to change something other than the state.
+    int resultCode = TestCaseUtils.applyModifications(
+      "dn: " + taskDN,
+      "changetype: modify",
+      "replace: description",
+      "description: foo");
+    assertFalse(resultCode == 0);
+
+
+    // Perform a modification to cancel the task.
+    resultCode = TestCaseUtils.applyModifications(
+      "dn: " + taskDN,
+      "changetype: modify",
+      "replace: ds-task-state",
+      "ds-task-state: cancel");
+    assertEquals(resultCode, 0);
+
+
+    // We may have to wait for the task to register as done, but it should
+    // definitely be done before it would have stopped normally.
+    task = TasksTestCase.getCompletedTask(DN.decode(taskDN));
+    assertTrue((System.currentTimeMillis() - startTime) < 30000L);
+    assertTrue(TaskState.isDone(task.getTaskState()));
+
+
+    // Perform a modification to delete that task.
+    resultCode = TestCaseUtils.applyModifications(
+      "dn: " + taskDN,
+      "changetype: delete");
+    assertEquals(resultCode, 0);
+    assertFalse(DirectoryServer.entryExists(DN.decode(taskDN)));
+  }
+
+
+
+  /**
+   * Tests to ensure that we cannot modify a task that has completed.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testModifyCompletedTask()
+         throws Exception
+  {
+    // Schedule a task to start immediately that will simply sleep for 30
+    // seconds.
+    String taskID = "testModifyCompltedTask";
+    String taskDN = "ds-task-id=" + taskID + ",cn=Scheduled Tasks,cn=tasks";
+
+    TestCaseUtils.addEntry(
+      "dn: " + taskDN,
+      "objectClass: top",
+      "objectClass: ds-task",
+      "objectClass: extensibleObject",
+      "ds-task-id: " + taskID,
+      "ds-task-class-name: org.opends.server.tasks.DummyTask");
+    assertTrue(DirectoryServer.entryExists(DN.decode(taskDN)));
+
+
+    // Wait until the task has completed.
+    Task task = TasksTestCase.getCompletedTask(DN.decode(taskDN));
+    assertTrue(TaskState.isDone(task.getTaskState()));
+
+
+    // Perform a modification to update a non-state attribute.
+    int resultCode = TestCaseUtils.applyModifications(
+      "dn: " + taskDN,
+      "changetype: modify",
+      "add: description",
+      "description: foo");
+    assertFalse(resultCode == 0);
+
+
+    // Perform a modification to delete that task.
+    resultCode = TestCaseUtils.applyModifications(
+      "dn: " + taskDN,
+      "changetype: delete");
+    assertEquals(resultCode, 0);
+    assertFalse(DirectoryServer.entryExists(DN.decode(taskDN)));
+  }
+}
+
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/tasks/DummyTask.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/tasks/DummyTask.java
index 05a6916..2442335 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/tasks/DummyTask.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/tasks/DummyTask.java
@@ -28,9 +28,15 @@
 
 
 
+import java.util.List;
+
+import org.opends.messages.Message;
 import org.opends.server.backends.task.Task;
 import org.opends.server.backends.task.TaskState;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeValue;
 import org.opends.server.types.DirectoryException;
+import org.opends.server.types.Entry;
 
 
 
@@ -41,6 +47,15 @@
 public class DummyTask
        extends Task
 {
+  // The length of time that the task should sleep before completing.
+  private long sleepTime;
+
+  // The task state to use when interrupting the task.  This will be null unless
+  // the task gets interrupted.
+  private volatile TaskState interruptedState;
+
+
+
   /**
    * {@inheritDoc}
    */
@@ -48,7 +63,25 @@
   public void initializeTask()
          throws DirectoryException
   {
-    // No implementation is required.
+    sleepTime = 0;
+    interruptedState = null;
+
+    Entry taskEntry = getTaskEntry();
+    if (taskEntry != null)
+    {
+      List<Attribute> attrList =
+           taskEntry.getAttribute("ds-task-dummy-sleep-time");
+      if (attrList != null)
+      {
+        for (Attribute a : attrList)
+        {
+          for (AttributeValue v : a.getValues())
+          {
+            sleepTime = Long.parseLong(v.getStringValue());
+          }
+        }
+      }
+    }
   }
 
 
@@ -58,7 +91,34 @@
    */
   protected TaskState runTask()
   {
-    return TaskState.COMPLETED_SUCCESSFULLY;
+    long stopTime = System.currentTimeMillis() + sleepTime;
+    while ((interruptedState == null) &&
+           (System.currentTimeMillis() < stopTime))
+    {
+      try
+      {
+        Thread.sleep(10);
+      } catch (Exception e) {}
+    }
+
+    if (interruptedState == null)
+    {
+      return TaskState.COMPLETED_SUCCESSFULLY;
+    }
+    else
+    {
+      return interruptedState;
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void interruptTask(TaskState taskState, Message interruptMessage)
+  {
+    interruptedState = taskState;
   }
 }
 
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/tasks/TasksTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/tasks/TasksTestCase.java
index 666ea9e..08968ff 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/tasks/TasksTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/tasks/TasksTestCase.java
@@ -157,8 +157,8 @@
 
 
   /**
-   * Retrieves the specified task from the server, waiting for it to finish all
-   * the running its going to do before returning.
+   * Retrieves the specified task from the server, regardless of its current
+   * state.
    *
    * @param  taskEntryDN  The DN of the entry for the task to retrieve.
    *
@@ -166,8 +166,9 @@
    *
    * @throws  Exception  If an unexpected problem occurs.
    */
-  protected Task getCompletedTask(DN taskEntryDN)
-          throws Exception
+  @Test(enabled=false) // This isn't a test method, but TestNG thinks it is.
+  public static Task getTask(DN taskEntryDN)
+         throws Exception
   {
     TaskBackend taskBackend =
          (TaskBackend) DirectoryServer.getBackend(DN.decode("cn=tasks"));
@@ -188,6 +189,27 @@
                                taskEntryDN.toString());
     }
 
+    return task;
+  }
+
+
+
+  /**
+   * Retrieves the specified task from the server, waiting for it to finish all
+   * the running its going to do before returning.
+   *
+   * @param  taskEntryDN  The DN of the entry for the task to retrieve.
+   *
+   * @return  The requested task entry.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(enabled=false) // This isn't a test method, but TestNG thinks it is.
+  public static Task getCompletedTask(DN taskEntryDN)
+          throws Exception
+  {
+    Task task = getTask(taskEntryDN);
+
     if (! TaskState.isDone(task.getTaskState()))
     {
       long stopWaitingTime = System.currentTimeMillis() + 20000L;

--
Gitblit v1.10.0