From cbdf437729ca4d010147acfb208ecd90ef65777c Mon Sep 17 00:00:00 2001 From: Jim Hahn Date: Mon, 3 May 2021 09:11:30 -0400 Subject: Add releaseLock() method to event manager Issue-ID: POLICY-3261 Change-Id: I28a5356ebfc4a6ea1792ef35bc603054208bf73b Signed-off-by: Jim Hahn --- .../controlloop/eventmanager/ActorConstants.java | 3 +- .../eventmanager/ControlLoopEventManager.java | 64 +++++++++++++-- .../controlloop/eventmanager/StepContext.java | 15 +++- .../eventmanager/ControlLoopEventManagerTest.java | 92 ++++++++++++++++++++-- 4 files changed, 159 insertions(+), 15 deletions(-) (limited to 'controlloop/common/eventmanager/src') diff --git a/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/ActorConstants.java b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/ActorConstants.java index 2591a3fa0..26d5ab8bf 100644 --- a/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/ActorConstants.java +++ b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/ActorConstants.java @@ -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"; diff --git a/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/ControlLoopEventManager.java b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/ControlLoopEventManager.java index 248a41be6..2c7e133af 100644 --- a/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/ControlLoopEventManager.java +++ b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/ControlLoopEventManager.java @@ -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 requestLock(String targetEntity) { @@ -214,6 +210,58 @@ public class ControlLoopEventManager implements StepContext, Serializable { return data.getFuture(); } + @Override + public synchronized CompletableFuture 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); } diff --git a/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/StepContext.java b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/StepContext.java index 5251b7acc..319cc64a9 100644 --- a/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/StepContext.java +++ b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/StepContext.java @@ -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 requestLock(String targetEntity); + + /** + * Releases a lock. + *

+ * 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 releaseLock(String targetEntity); } diff --git a/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/ControlLoopEventManagerTest.java b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/ControlLoopEventManagerTest.java index 596400f94..b930f57f8 100644 --- a/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/ControlLoopEventManagerTest.java +++ b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/ControlLoopEventManagerTest.java @@ -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 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 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 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 locks; - public MyManager(ControlLoopParams params, UUID requestId) - throws ControlLoopException { + public MyManager(ControlLoopParams params, UUID requestId) throws ControlLoopException { super(params, requestId); } -- cgit 1.2.3-korg