Add releaseLock() method to event manager 60/121060/2
authorJim Hahn <jrh3@att.com>
Mon, 3 May 2021 13:11:30 +0000 (09:11 -0400)
committerJim Hahn <jrh3@att.com>
Mon, 3 May 2021 14:11:09 +0000 (10:11 -0400)
Issue-ID: POLICY-3261
Change-Id: I28a5356ebfc4a6ea1792ef35bc603054208bf73b
Signed-off-by: Jim Hahn <jrh3@att.com>
controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/ActorConstants.java
controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/ControlLoopEventManager.java
controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/StepContext.java
controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/ControlLoopEventManagerTest.java

index 2591a3f..26d5ab8 100644 (file)
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * ONAP
  * ================================================================================
- * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2020-2021 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ public class ActorConstants {
     public static final String CL_TIMEOUT_ACTOR = "-CL-TIMEOUT-";
     public static final String LOCK_ACTOR = "LOCK";
     public static final String LOCK_OPERATION = "Lock";
+    public static final String UNLOCK_OPERATION = "Unlock";
 
     public static final String PAYLOAD_KEY_VF_COUNT = "vfCount";
 
index 248a41b..2c7e133 100644 (file)
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * ONAP
  * ================================================================================
- * Copyright (C) 2017-2020 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2017-2021 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
 package org.onap.policy.controlloop.eventmanager;
 
 import java.io.Serializable;
+import java.time.Instant;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.Map;
@@ -37,8 +38,10 @@ import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.ToString;
 import org.onap.policy.controlloop.ControlLoopException;
+import org.onap.policy.controlloop.ControlLoopOperation;
 import org.onap.policy.controlloop.actorserviceprovider.ActorService;
 import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.OperationResult;
 import org.onap.policy.controlloop.drl.legacy.ControlLoopParams;
 import org.onap.policy.controlloop.ophistory.OperationHistoryDataManager;
 import org.onap.policy.controlloop.ophistory.OperationHistoryDataManagerStub;
@@ -189,13 +192,6 @@ public class ControlLoopEventManager implements StepContext, Serializable {
         return TimeUnit.MILLISECONDS.convert(timeout, TimeUnit.SECONDS);
     }
 
-    /**
-     * Requests a lock. This requests the lock for the time that remains before the
-     * timeout expires. This avoids having to extend the lock.
-     *
-     * @param targetEntity entity to be locked
-     * @return a future that can be used to await the lock
-     */
     @Override
     public synchronized CompletableFuture<OperationOutcome> requestLock(String targetEntity) {
 
@@ -214,6 +210,58 @@ public class ControlLoopEventManager implements StepContext, Serializable {
         return data.getFuture();
     }
 
+    @Override
+    public synchronized CompletableFuture<OperationOutcome> releaseLock(String targetEntity) {
+        LockData data = target2lock.remove(targetEntity);
+
+        if (data == null) {
+            // lock did not exist - immediately return a success
+            OperationOutcome outcome = makeUnlockOutcome(targetEntity);
+            outcome.setEnd(outcome.getStart());
+            onComplete(outcome);
+
+            return CompletableFuture.completedFuture(outcome);
+        }
+
+        /*
+         * previous lock operation may not have completed yet, thus we tack the unlock
+         * operation onto it.
+         *
+         * Note: we must invoke free(), asynchronously (i.e., using whenCompleteAsync()),
+         * as it may block
+         */
+
+        return data.getFuture().whenCompleteAsync((lockOutcome, thrown) -> {
+
+            OperationOutcome outcome = makeUnlockOutcome(targetEntity);
+
+            try {
+                data.free();
+
+            } catch (RuntimeException e) {
+                logger.warn("failed to unlock {}", targetEntity, e);
+                outcome.setResult(OperationResult.FAILURE_EXCEPTION);
+                outcome.setMessage(ControlLoopOperation.FAILED_MSG + ": " + e.getMessage());
+            }
+
+            outcome.setEnd(Instant.now());
+            onComplete(outcome);
+
+        }, getBlockingExecutor());
+    }
+
+    private OperationOutcome makeUnlockOutcome(String targetEntity) {
+        OperationOutcome outcome = new OperationOutcome();
+        outcome.setActor(ActorConstants.LOCK_ACTOR);
+        outcome.setOperation(ActorConstants.UNLOCK_OPERATION);
+        outcome.setTarget(targetEntity);
+        outcome.setResult(OperationResult.SUCCESS);
+        outcome.setMessage(ControlLoopOperation.SUCCESS_MSG);
+        outcome.setFinalOutcome(true);
+        outcome.setStart(Instant.now());
+        return outcome;
+    }
+
     public void onStart(OperationOutcome outcome) {
         outcomes.add(outcome);
     }
index 5251b7a..319cc64 100644 (file)
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * ONAP
  * ================================================================================
- * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2020-2021 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -69,4 +69,17 @@ public interface StepContext {
      * @return a future that can be used to await the lock
      */
     public CompletableFuture<OperationOutcome> requestLock(String targetEntity);
+
+    /**
+     * Releases a lock.
+     * <p/>
+     * Note: once this has been invoked, whether or not the "release" operation succeeds,
+     * subsequent calls to {@link #requestLock(String)} for the same target entity may
+     * always fail, and subsequent calls to {@link #releaseLock(String)} may always
+     * succeed, depending on the implementation.
+     *
+     * @param targetEntity entity to be locked
+     * @return a future that can be used to await the release operation
+     */
+    public CompletableFuture<OperationOutcome> releaseLock(String targetEntity);
 }
index 596400f..b930f57 100644 (file)
@@ -60,6 +60,7 @@ import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
 @RunWith(MockitoJUnitRunner.class)
 public class ControlLoopEventManagerTest {
     private static final UUID REQ_ID = UUID.randomUUID();
+    private static final String EXPECTED_EXCEPTION = "expected exception";
     private static final String CL_NAME = "my-closed-loop-name";
     private static final String POLICY_NAME = "my-policy-name";
     private static final String POLICY_SCOPE = "my-scope";
@@ -184,15 +185,96 @@ public class ControlLoopEventManagerTest {
         // indicate that the first lock failed
         locks.get(0).notifyUnavailable();
 
-        verifyLock(OperationResult.FAILURE);
+        verifyLock(OperationResult.FAILURE, ActorConstants.LOCK_OPERATION);
         assertTrue(mgr.getOutcomes().isEmpty());
     }
 
-    private void verifyLock(OperationResult result) {
+    @Test
+    public void testReleaseLock() {
+        mgr.requestLock(LOCK1);
+        mgr.requestLock(LOCK2);
+
+        // release one lock
+        final CompletableFuture<OperationOutcome> future = mgr.releaseLock(LOCK1);
+
+        // asynchronous, thus should not have executed yet
+        assertThat(future.isDone()).isFalse();
+
+        // asynchronous, thus everything should still be locked
+        for (LockImpl lock : locks) {
+            assertThat(lock.isUnavailable()).isFalse();
+        }
+
+        runExecutor();
+
+        verifyLock(OperationResult.SUCCESS, ActorConstants.UNLOCK_OPERATION);
+        assertThat(mgr.getOutcomes()).isEmpty();
+
+        // first lock should have been released, thus no longer available to the manager
+        assertThat(locks.get(0).isUnavailable()).isTrue();
+
+        // second should still be locked
+        assertThat(locks.get(1).isUnavailable()).isFalse();
+    }
+
+    /**
+     * Tests releaseLock() when there is no lock.
+     */
+    @Test
+    public void testReleaseLockNotLocked() {
+        final CompletableFuture<OperationOutcome> future = mgr.releaseLock(LOCK1);
+
+        // lock didn't exist, so the request should already be complete
+        assertThat(future.isDone()).isTrue();
+
+        verifyLock(OperationResult.SUCCESS, ActorConstants.UNLOCK_OPERATION);
+        assertThat(mgr.getOutcomes()).isEmpty();
+    }
+
+    /**
+     * Tests releaseLock() when lock.free() throws an exception.
+     */
+    @Test
+    public void testReleaseLockException() throws ControlLoopException {
+        mgr = new MyManager(params, REQ_ID) {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void makeLock(String targetEntity, String requestId, int holdSec, LockCallback callback) {
+
+                LockImpl lock = new LockImpl(LockState.ACTIVE, targetEntity, requestId, holdSec, callback) {
+                    private static final long serialVersionUID = 1L;
+
+                    @Override
+                    public boolean free() {
+                        throw new RuntimeException(EXPECTED_EXCEPTION);
+                    }
+                };
+
+                locks.add(lock);
+                callback.lockAvailable(lock);
+            }
+        };
+
+        mgr.requestLock(LOCK1);
+
+        // release the lock
+        final CompletableFuture<OperationOutcome> future = mgr.releaseLock(LOCK1);
+
+        // asynchronous, thus should not have executed yet
+        assertThat(future.isDone()).isFalse();
+
+        runExecutor();
+
+        verifyLock(OperationResult.FAILURE_EXCEPTION, ActorConstants.UNLOCK_OPERATION);
+        assertThat(mgr.getOutcomes()).isEmpty();
+    }
+
+    private void verifyLock(OperationResult result, String lockOperation) {
         OperationOutcome outcome = mgr.getOutcomes().poll();
         assertNotNull(outcome);
         assertEquals(ActorConstants.LOCK_ACTOR, outcome.getActor());
-        assertEquals(ActorConstants.LOCK_OPERATION, outcome.getOperation());
+        assertEquals(lockOperation, outcome.getOperation());
         assertNotNull(outcome.getEnd());
         assertTrue(outcome.isFinalOutcome());
         assertEquals(result, outcome.getResult());
@@ -249,6 +331,7 @@ public class ControlLoopEventManagerTest {
     public void testGetDataManagerDisabled() throws ControlLoopException {
         mgr = new MyManager(params, REQ_ID) {
             private static final long serialVersionUID = 1L;
+
             @Override
             protected String getEnvironmentProperty(String propName) {
                 return ("guard.disabled".equals(propName) ? "true" : null);
@@ -285,8 +368,7 @@ public class ControlLoopEventManagerTest {
         private static ExecutorService executor;
         private static List<LockImpl> locks;
 
-        public MyManager(ControlLoopParams params, UUID requestId)
-                        throws ControlLoopException {
+        public MyManager(ControlLoopParams params, UUID requestId) throws ControlLoopException {
             super(params, requestId);
         }