From c1f79cd311ad62d3adb374921b8c3d303db5add6 Mon Sep 17 00:00:00 2001 From: Jim Hahn Date: Sat, 22 Feb 2020 17:11:20 -0500 Subject: Add frankfurt rules for Actor redesign Note: VcpeTest and VfwTest are not included, as they depend on updates to the APPC and APPC-LCM Actors. Added feature-controlloop-frankfurt. Added HTTP client property files to feature-controlloop-management. Updates per review comments: - pom changes - simplify FrankfurtBase - rename event-svc-http.properties - change "usescases" to "frankfurt" - use blanks for CDS property defaults - trailing spaces in http-client files - add https property to http-client files Added newlines to config files that appear to be missing them (based on feedback from gerrit). Issue-ID: POLICY-2385 Signed-off-by: Jim Hahn Change-Id: Ib4a4d75461c734ae47309e41dc9d099e8815d55d --- .../eventmanager/ControlLoopEventManager2Test.java | 812 ++++++++++++++++++ .../ControlLoopOperationManager2Test.java | 936 +++++++++++++++++++++ .../eventmanager/EventManagerServicesTest.java | 120 +++ .../controlloop/eventmanager/LockDataTest.java | 193 +++++ .../OperationHistoryDataManagerImplTest.java | 379 +++++++++ .../OperationHistoryDataManagerParamsTest.java | 115 +++ .../OperationHistoryDataManagerStubTest.java | 36 + .../controlloop/utils/ControlLoopUtilsTest.java | 193 ++++- 8 files changed, 2781 insertions(+), 3 deletions(-) create mode 100644 controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/ControlLoopEventManager2Test.java create mode 100644 controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/ControlLoopOperationManager2Test.java create mode 100644 controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/EventManagerServicesTest.java create mode 100644 controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/LockDataTest.java create mode 100644 controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerImplTest.java create mode 100644 controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerParamsTest.java create mode 100644 controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerStubTest.java (limited to 'controlloop/common/eventmanager/src/test/java') diff --git a/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/ControlLoopEventManager2Test.java b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/ControlLoopEventManager2Test.java new file mode 100644 index 000000000..522d9f57b --- /dev/null +++ b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/ControlLoopEventManager2Test.java @@ -0,0 +1,812 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.eventmanager; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; +import org.drools.core.WorkingMemory; +import org.junit.Before; +import org.junit.Test; +import org.kie.api.runtime.rule.FactHandle; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.policy.common.utils.coder.Coder; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.common.utils.coder.StandardYamlCoder; +import org.onap.policy.common.utils.io.Serializer; +import org.onap.policy.common.utils.resources.ResourceUtils; +import org.onap.policy.controlloop.ControlLoopEventStatus; +import org.onap.policy.controlloop.ControlLoopException; +import org.onap.policy.controlloop.ControlLoopNotificationType; +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.ControlLoopTargetType; +import org.onap.policy.controlloop.VirtualControlLoopEvent; +import org.onap.policy.controlloop.VirtualControlLoopNotification; +import org.onap.policy.controlloop.actorserviceprovider.ActorService; +import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome; +import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext; +import org.onap.policy.controlloop.drl.legacy.ControlLoopParams; +import org.onap.policy.controlloop.eventmanager.ControlLoopEventManager2.NewEventStatus; +import org.onap.policy.controlloop.eventmanager.ControlLoopOperationManager2.State; +import org.onap.policy.controlloop.ophistory.OperationHistoryDataManager; +import org.onap.policy.controlloop.policy.Policy; +import org.onap.policy.controlloop.policy.PolicyResult; +import org.onap.policy.controlloop.policy.Target; +import org.onap.policy.controlloop.policy.TargetType; +import org.onap.policy.drools.core.lock.LockCallback; +import org.onap.policy.drools.core.lock.LockImpl; +import org.onap.policy.drools.core.lock.LockState; +import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy; +import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate; + +public class ControlLoopEventManager2Test { + private static final UUID REQ_ID = UUID.randomUUID(); + 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"; + private static final String POLICY_VERSION = "1.2.3"; + private static final String MY_TARGET = "my-target"; + private static final String LOCK1 = "my-lock-A"; + private static final String LOCK2 = "my-lock-B"; + private static final Coder yamlCoder = new StandardYamlCoder(); + + @Mock + private WorkingMemory workMem; + @Mock + private Consumer callback1; + @Mock + private Consumer callback2; + @Mock + private Consumer callback3; + @Mock + private FactHandle factHandle; + @Mock + private ActorService actors; + @Mock + private OperationHistoryDataManager dataMgr; + @Mock + private ControlLoopOperationManager2 oper1; + @Mock + private ControlLoopOperationManager2 oper2; + @Mock + private ControlLoopOperationManager2 oper3; + @Mock + private ExecutorService executor; + + private long preCreateTimeMs; + private List locks; + private Target target; + private ToscaPolicy tosca; + private ControlLoopParams params; + private VirtualControlLoopEvent event; + private int updateCount; + private ControlLoopEventManager2 mgr; + + /** + * Sets up. + */ + @Before + public void setUp() throws ControlLoopException, CoderException { + MockitoAnnotations.initMocks(this); + + when(oper1.getHistory()).thenReturn(makeHistory("A")); + when(oper2.getHistory()).thenReturn(makeHistory("B")); + when(oper3.getHistory()).thenReturn(makeHistory("C")); + + when(oper1.getActor()).thenReturn("First"); + when(oper1.getOperation()).thenReturn("OperationA"); + when(oper1.getOperationMessage()).thenReturn("message-A"); + + when(oper2.getActor()).thenReturn("Second"); + when(oper2.getOperation()).thenReturn("OperationB"); + when(oper2.getOperationMessage()).thenReturn("message-B"); + + when(oper3.getActor()).thenReturn("Third"); + when(oper3.getOperation()).thenReturn("OperationC"); + when(oper3.getOperationMessage()).thenReturn("message-C"); + + when(workMem.getFactHandle(any())).thenReturn(factHandle); + + event = new VirtualControlLoopEvent(); + event.setRequestId(REQ_ID); + event.setTarget(ControlLoopOperationManager2.VSERVER_VSERVER_NAME); + event.setAai(new TreeMap<>(Map.of(ControlLoopOperationManager2.VSERVER_VSERVER_NAME, MY_TARGET))); + event.setClosedLoopEventStatus(ControlLoopEventStatus.ONSET); + event.setClosedLoopControlName(CL_NAME); + event.setTargetType(TargetType.VNF.toString()); + + target = new Target(); + target.setType(TargetType.VNF); + + params = new ControlLoopParams(); + params.setClosedLoopControlName(CL_NAME); + params.setPolicyName(POLICY_NAME); + params.setPolicyScope(POLICY_SCOPE); + params.setPolicyVersion(POLICY_VERSION); + + loadPolicy("eventManager/event-mgr-simple.yaml"); + + locks = new ArrayList<>(); + + updateCount = 0; + + preCreateTimeMs = System.currentTimeMillis(); + + mgr = new MyManagerWithOper(params, event, workMem); + } + + @Test + public void testConstructor() { + assertEquals(POLICY_NAME, mgr.getPolicyName()); + + Map orig = event.getAai(); + + event.setAai(addAai(orig, ControlLoopEventManager2.VSERVER_IS_CLOSED_LOOP_DISABLED, "true")); + assertThatThrownBy(() -> new ControlLoopEventManager2(params, event, workMem)) + .hasMessage("is-closed-loop-disabled is set to true on VServer or VNF"); + + event.setAai(addAai(orig, ControlLoopEventManager2.VSERVER_PROV_STATUS, "inactive")); + assertThatThrownBy(() -> new ControlLoopEventManager2(params, event, workMem)) + .hasMessage("prov-status is not ACTIVE on VServer or VNF"); + + // valid + event.setAai(orig); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + // invalid + event.setTarget("unknown-target"); + assertThatThrownBy(() -> new ControlLoopEventManager2(params, event, workMem)); + } + + /** + * Runs through a policy that has several operations. + */ + @Test + public void testMultiOperation() throws Exception { + + loadPolicy("eventManager/event-mgr-multi.yaml"); + + mgr = new MyManagerWithOper(params, event, workMem); + mgr.start(); + + for (ControlLoopOperationManager2 oper : Arrays.asList(oper1, oper2, oper3)) { + assertTrue(mgr.isActive()); + nextStep(oper, true, PolicyResult.SUCCESS); + runRule(); + + assertTrue(mgr.isActive()); + nextStep(oper, false, PolicyResult.SUCCESS); + runRule(); + } + + assertFalse(mgr.isActive()); + } + + @Test + public void testStart() throws Exception { + // start it + mgr.start(); + + // cannot re-start + assertThatCode(() -> mgr.start()).isInstanceOf(IllegalStateException.class) + .hasMessage("manager already started"); + } + + /** + * Tests start() error cases. + */ + @Test + public void testStartErrors() throws Exception { + // wrong jvm + ControlLoopEventManager2 mgr2 = new ControlLoopEventManager2(params, event, workMem); + ControlLoopEventManager2 mgr3 = Serializer.roundTrip(mgr2); + assertThatCode(() -> mgr3.start()).isInstanceOf(IllegalStateException.class) + .hasMessage("manager is no longer active"); + + // no fact handle + when(workMem.getFactHandle(any())).thenReturn(null); + assertThatCode(() -> mgr.start()).isInstanceOf(IllegalStateException.class) + .hasMessage("manager is not in working memory"); + } + + @Test + public void testNextStep_testStartOperationSuccess() throws ControlLoopException { + runOperation(PolicyResult.SUCCESS); + + VirtualControlLoopNotification notif = mgr.getNotification(); + assertEquals(ControlLoopNotificationType.FINAL_SUCCESS, notif.getNotification()); + assertNull(notif.getMessage()); + + assertThatCode(() -> mgr.nextStep()).doesNotThrowAnyException(); + } + + /** + * Tests nextStep() when the next step is invalid, which should cause an exception to + * be thrown by the processor. + */ + @Test + public void testNextStepMissing() throws Exception { + mgr.start(); + + when(oper1.nextStep()).thenThrow(new IllegalArgumentException("expected exception")); + + mgr.nextStep(); + + assertFalse(mgr.isActive()); + + VirtualControlLoopNotification notif = mgr.getNotification(); + assertEquals(ControlLoopNotificationType.FINAL_FAILURE, notif.getNotification()); + assertEquals("Policy processing aborted due to policy error", notif.getMessage()); + assertTrue(notif.getHistory().isEmpty()); + } + + /** + * Tests startOperation() with FINAL_FAILURE_EXCEPTION. + */ + @Test + public void testStartOperationException() throws ControlLoopException { + runOperation(PolicyResult.FAILURE_EXCEPTION); + + VirtualControlLoopNotification notif = mgr.getNotification(); + assertEquals(ControlLoopNotificationType.FINAL_FAILURE, notif.getNotification()); + assertEquals("Exception in processing closed loop", notif.getMessage()); + } + + /** + * Tests startOperation() with FINAL_FAILURE. + */ + @Test + public void testStartOperationFailure() throws ControlLoopException { + runOperation(PolicyResult.FAILURE); + + VirtualControlLoopNotification notif = mgr.getNotification(); + assertEquals(ControlLoopNotificationType.FINAL_FAILURE, notif.getNotification()); + assertNull(notif.getMessage()); + } + + /** + * Tests startOperation() with FINAL_OPENLOOP. + */ + @Test + public void testStartOperationOpenLoop() throws ControlLoopException { + runOperation(PolicyResult.FAILURE_GUARD); + + VirtualControlLoopNotification notif = mgr.getNotification(); + assertEquals(ControlLoopNotificationType.FINAL_OPENLOOP, notif.getNotification()); + assertNull(notif.getMessage()); + } + + @Test + public void testIsActive() throws Exception { + mgr = new ControlLoopEventManager2(params, event, workMem); + assertTrue(mgr.isActive()); + + ControlLoopEventManager2 mgr2 = Serializer.roundTrip(mgr); + assertFalse(mgr2.isActive()); + } + + @Test + public void testUpdated() throws ControlLoopException { + mgr.start(); + + // not the active operation - should be ignored + mgr.updated(oper3); + verify(workMem, never()).update(any(), any()); + + VirtualControlLoopNotification notif; + + // check notification data + when(oper1.getState()).thenReturn(State.LOCK_DENIED); + mgr.updated(oper1); + notif = mgr.getNotification(); + assertNotNull(notif.getHistory()); + + /* + * try the various cases + */ + when(oper1.getState()).thenReturn(State.LOCK_DENIED); + mgr.updated(oper1); + verifyNotification(ControlLoopNotificationType.REJECTED, "The target my-target is already locked"); + + when(oper1.getState()).thenReturn(State.LOCK_LOST); + mgr.updated(oper1); + verifyNotification(ControlLoopNotificationType.OPERATION_FAILURE, "The target my-target is no longer locked"); + + when(oper1.getState()).thenReturn(State.GUARD_STARTED); + mgr.updated(oper1); + verifyNotification(ControlLoopNotificationType.OPERATION, "Sending guard query for First OperationA"); + + when(oper1.getState()).thenReturn(State.GUARD_PERMITTED); + mgr.updated(oper1); + verifyNotification(ControlLoopNotificationType.OPERATION, "Guard result for First OperationA is Permit"); + + when(oper1.getState()).thenReturn(State.GUARD_DENIED); + mgr.updated(oper1); + verifyNotification(ControlLoopNotificationType.OPERATION, "Guard result for First OperationA is Deny"); + + when(oper1.getState()).thenReturn(State.OPERATION_SUCCESS); + mgr.updated(oper1); + verifyNotification(ControlLoopNotificationType.OPERATION_SUCCESS, "message-A"); + + when(oper1.getState()).thenReturn(State.OPERATION_FAILURE); + mgr.updated(oper1); + verifyNotification(ControlLoopNotificationType.OPERATION_FAILURE, "message-A"); + + // should still be active + assertTrue(mgr.isActive()); + + /* + * control loop time + */ + when(oper1.getState()).thenReturn(State.CONTROL_LOOP_TIMEOUT); + mgr.updated(oper1); + verifyNotification(ControlLoopNotificationType.FINAL_FAILURE, "Control Loop timed out"); + + // should now be done + assertFalse(mgr.isActive()); + } + + @Test + public void testDestroy() { + mgr.requestLock(LOCK1, callback1); + mgr.requestLock(LOCK2, callback2); + mgr.requestLock(LOCK1, callback3); + + mgr.destroy(); + + freeLocks(); + + for (LockImpl lock : locks) { + assertTrue(lock.isUnavailable()); + } + } + + /** + * Tests destroy() once it has been started. + */ + @Test + public void testDestroyStarted() throws ControlLoopException { + mgr.start(); + + mgr.requestLock(LOCK1, callback1); + mgr.requestLock(LOCK2, callback2); + mgr.requestLock(LOCK1, callback3); + + mgr.destroy(); + + freeLocks(); + + // should have canceled the operation + verify(oper1).cancel(); + + for (LockImpl lock : locks) { + assertTrue(lock.isUnavailable()); + } + } + + @Test + public void testMakeNotification() throws ControlLoopException { + mgr.start(); + + nextStep(oper1, true, PolicyResult.SUCCESS); + runRule(); + + // check notification while running + VirtualControlLoopNotification notif = mgr.getNotification(); + assertEquals("message-A", notif.getMessage()); + + List history = notif.getHistory(); + assertNotNull(history); + + nextStep(oper1, false, PolicyResult.SUCCESS); + runRule(); + + assertFalse(mgr.isActive()); + + // check notification when complete + notif = mgr.getNotification(); + assertNull(notif.getMessage()); + assertEquals(history, notif.getHistory()); + } + + @Test + public void testOnNewEvent() { + VirtualControlLoopEvent event2 = new VirtualControlLoopEvent(event); + assertEquals(NewEventStatus.FIRST_ONSET, mgr.onNewEvent(event2)); + + event2.setPayload("other payload"); + assertEquals(NewEventStatus.SUBSEQUENT_ONSET, mgr.onNewEvent(event2)); + assertEquals(NewEventStatus.SUBSEQUENT_ONSET, mgr.onNewEvent(event2)); + assertEquals(NewEventStatus.FIRST_ONSET, mgr.onNewEvent(event)); + + event2.setClosedLoopEventStatus(ControlLoopEventStatus.ABATED); + assertEquals(NewEventStatus.FIRST_ABATEMENT, mgr.onNewEvent(event2)); + + assertEquals(NewEventStatus.SUBSEQUENT_ABATEMENT, mgr.onNewEvent(event2)); + assertEquals(NewEventStatus.SUBSEQUENT_ABATEMENT, mgr.onNewEvent(event2)); + + event2.setClosedLoopEventStatus(null); + assertEquals(NewEventStatus.SYNTAX_ERROR, mgr.onNewEvent(event2)); + } + + @Test + public void testDetmControlLoopTimeoutMs() throws Exception { + verifyTimeout(1200 * 1000L); + } + + private void verifyTimeout(long timeMs) { + long end = mgr.getEndTimeMs(); + assertTrue(end >= preCreateTimeMs + timeMs); + assertTrue(end < preCreateTimeMs + timeMs + 5000); + } + + @Test + public void testCheckEventSyntax() { + // initially, it's valid + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setTarget("unknown-target"); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class) + .hasMessage("target field invalid"); + + event.setTarget(null); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class) + .hasMessage("No target field"); + + // abated supersedes previous errors - so it shouldn't throw an exception + event.setClosedLoopEventStatus(ControlLoopEventStatus.ABATED); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setRequestId(null); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class) + .hasMessage("No request ID"); + + event.setClosedLoopControlName(null); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class) + .hasMessage("No control loop name"); + } + + @Test + public void testValidateStatus() { + event.setClosedLoopEventStatus(ControlLoopEventStatus.ONSET); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setClosedLoopEventStatus(ControlLoopEventStatus.ABATED); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setClosedLoopEventStatus(null); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class) + .hasMessage("Invalid value in closedLoopEventStatus"); + } + + @Test + public void testValidateAaiData() { + event.setTargetType("unknown-target-type"); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class) + .hasMessage("The target type is not supported"); + + event.setTargetType(null); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class) + .hasMessage("The Target type is null"); + + event.setAai(null); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class) + .hasMessage("AAI is null"); + + // VM case + event.setTargetType(ControlLoopTargetType.VM); + event.setAai(Map.of(ControlLoopEventManager2.GENERIC_VNF_VNF_ID, MY_TARGET)); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setAai(Map.of()); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class); + + // VNF case + event.setTargetType(ControlLoopTargetType.VNF); + event.setAai(Map.of(ControlLoopEventManager2.GENERIC_VNF_VNF_ID, MY_TARGET)); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setAai(Map.of()); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class); + + // PNF case + event.setTargetType(ControlLoopTargetType.PNF); + event.setAai(Map.of(ControlLoopEventManager2.PNF_NAME, MY_TARGET)); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setAai(Map.of()); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class); + } + + @Test + public void testValidateAaiVmVnfData() { + event.setTargetType(ControlLoopTargetType.VM); + event.setAai(Map.of(ControlLoopEventManager2.GENERIC_VNF_VNF_ID, MY_TARGET)); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setAai(Map.of(ControlLoopEventManager2.VSERVER_VSERVER_NAME, MY_TARGET)); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setAai(Map.of(ControlLoopEventManager2.GENERIC_VNF_VNF_NAME, MY_TARGET)); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setAai(Map.of()); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class).hasMessage( + "generic-vnf.vnf-id or generic-vnf.vnf-name or vserver.vserver-name information missing"); + } + + @Test + public void testValidateAaiPnfData() { + event.setTargetType(ControlLoopTargetType.PNF); + event.setAai(Map.of(ControlLoopEventManager2.PNF_NAME, MY_TARGET)); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setAai(Map.of()); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class) + .hasMessage("AAI PNF object key pnf-name is missing"); + } + + @Test + public void testIsClosedLoopDisabled() { + Map orig = event.getAai(); + + event.setAai(addAai(orig, ControlLoopEventManager2.VSERVER_IS_CLOSED_LOOP_DISABLED, "true")); + assertThatThrownBy(() -> new ControlLoopEventManager2(params, event, workMem)); + + event.setAai(addAai(orig, ControlLoopEventManager2.GENERIC_VNF_IS_CLOSED_LOOP_DISABLED, "true")); + assertThatThrownBy(() -> new ControlLoopEventManager2(params, event, workMem)); + + event.setAai(addAai(orig, ControlLoopEventManager2.PNF_IS_IN_MAINT, "true")); + assertThatThrownBy(() -> new ControlLoopEventManager2(params, event, workMem)); + } + + private Map addAai(Map original, String key, String value) { + Map map = new TreeMap<>(original); + map.put(key, value); + return map; + } + + @Test + public void testIsProvStatusInactive() { + Map orig = event.getAai(); + + event.setAai(addAai(orig, ControlLoopEventManager2.VSERVER_PROV_STATUS, "ACTIVE")); + assertThatCode(() -> new ControlLoopEventManager2(params, event, workMem)).doesNotThrowAnyException(); + + event.setAai(addAai(orig, ControlLoopEventManager2.VSERVER_PROV_STATUS, "inactive")); + assertThatThrownBy(() -> new ControlLoopEventManager2(params, event, workMem)); + + event.setAai(addAai(orig, ControlLoopEventManager2.GENERIC_VNF_PROV_STATUS, "ACTIVE")); + assertThatCode(() -> new ControlLoopEventManager2(params, event, workMem)).doesNotThrowAnyException(); + + event.setAai(addAai(orig, ControlLoopEventManager2.GENERIC_VNF_PROV_STATUS, "inactive")); + assertThatThrownBy(() -> new ControlLoopEventManager2(params, event, workMem)); + } + + @Test + public void testIsAaiTrue() { + Map orig = event.getAai(); + + for (String value : Arrays.asList("yes", "y", "true", "t", "yEs", "trUe")) { + event.setAai(addAai(orig, ControlLoopEventManager2.VSERVER_IS_CLOSED_LOOP_DISABLED, value)); + assertThatThrownBy(() -> new ControlLoopEventManager2(params, event, workMem)); + } + + event.setAai(addAai(orig, ControlLoopEventManager2.VSERVER_IS_CLOSED_LOOP_DISABLED, "false")); + assertThatCode(() -> new ControlLoopEventManager2(params, event, workMem)).doesNotThrowAnyException(); + + event.setAai(addAai(orig, ControlLoopEventManager2.VSERVER_IS_CLOSED_LOOP_DISABLED, "no")); + assertThatCode(() -> new ControlLoopEventManager2(params, event, workMem)).doesNotThrowAnyException(); + } + + @Test + public void testRequestLock() { + final CompletableFuture future1 = mgr.requestLock(LOCK1, callback1); + final CompletableFuture future2 = mgr.requestLock(LOCK2, callback2); + assertSame(future1, mgr.requestLock(LOCK1, callback3)); + + assertEquals(2, locks.size()); + + assertTrue(future1.isDone()); + assertTrue(future2.isDone()); + + verify(callback1, never()).accept(any()); + verify(callback2, never()).accept(any()); + verify(callback3, never()).accept(any()); + + // indicate that the first lock failed + locks.get(0).notifyUnavailable(); + + verify(callback1).accept(any()); + verify(callback2, never()).accept(any()); + verify(callback3).accept(any()); + } + + @Test + public void testMakeOperationManager() throws ControlLoopException { + // use a manager that creates real operation managers + mgr = new MyManager(params, event, workMem); + + assertThatCode(() -> mgr.start()).doesNotThrowAnyException(); + } + + @Test + public void testGetBlockingExecutor() throws Exception { + mgr = new ControlLoopEventManager2(params, event, workMem); + assertThatCode(() -> mgr.getBlockingExecutor()).doesNotThrowAnyException(); + } + + @Test + public void testToString() { + assertNotNull(mgr.toString()); + } + + + private void nextStep(ControlLoopOperationManager2 oper, boolean moreSteps, PolicyResult result) { + when(oper.nextStep()).thenReturn(moreSteps); + when(oper.getOperationResult()).thenReturn(result); + + if (result == PolicyResult.SUCCESS) { + when(oper.getState()).thenReturn(State.OPERATION_SUCCESS); + } else { + when(oper.getState()).thenReturn(State.OPERATION_FAILURE); + } + + mgr.updated(oper); + + updateCount++; + + verify(workMem, times(updateCount)).update(factHandle, mgr); + } + + private void runRule() { + assertTrue(mgr.isActive()); + mgr.nextStep(); + } + + private void runOperation(PolicyResult finalResult) throws ControlLoopException { + mgr.start(); + verify(oper1).start(anyLong()); + + assertTrue(mgr.isActive()); + + nextStep(oper1, true, PolicyResult.SUCCESS); + runRule(); + + nextStep(oper1, false, finalResult); + runRule(); + + assertFalse(mgr.isActive()); + + // should have no effect, because it's done + mgr.updated(oper1); + verify(workMem, times(updateCount)).update(any(), any()); + } + + private void verifyNotification(ControlLoopNotificationType expectedType, String expectedMsg) { + VirtualControlLoopNotification notif = mgr.getNotification(); + assertEquals(expectedType, notif.getNotification()); + assertEquals(expectedMsg, notif.getMessage()); + } + + private List makeHistory(String message) { + ControlLoopOperation clo = new ControlLoopOperation(); + clo.setMessage("history-" + message); + + return List.of(clo); + } + + private void loadPolicy(String fileName) throws CoderException { + ToscaServiceTemplate template = + yamlCoder.decode(ResourceUtils.getResourceAsString(fileName), ToscaServiceTemplate.class); + tosca = template.getToscaTopologyTemplate().getPolicies().get(0).values().iterator().next(); + + params.setToscaPolicy(tosca); + } + + private void freeLocks() { + ArgumentCaptor runCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(executor).execute(runCaptor.capture()); + + runCaptor.getValue().run(); + } + + + private class MyManager extends ControlLoopEventManager2 { + private static final long serialVersionUID = 1L; + + public MyManager(ControlLoopParams params, VirtualControlLoopEvent event, WorkingMemory workMem) + throws ControlLoopException { + + super(params, event, workMem); + } + + @Override + protected ExecutorService getBlockingExecutor() { + return executor; + } + + @Override + protected void makeLock(String targetEntity, String requestId, int holdSec, LockCallback callback) { + LockImpl lock = new LockImpl(LockState.ACTIVE, targetEntity, requestId, holdSec, callback); + locks.add(lock); + callback.lockAvailable(lock); + } + + @Override + public ActorService getActorService() { + return actors; + } + + @Override + public OperationHistoryDataManager getDataManager() { + return dataMgr; + } + } + + + private class MyManagerWithOper extends MyManager { + private static final long serialVersionUID = 1L; + + public MyManagerWithOper(ControlLoopParams params, VirtualControlLoopEvent event, WorkingMemory workMem) + throws ControlLoopException { + + super(params, event, workMem); + } + + @Override + protected ControlLoopOperationManager2 makeOperationManager(ControlLoopEventContext ctx, Policy policy) { + switch (policy.getActor()) { + case "First": + return oper1; + case "Second": + return oper2; + case "Third": + return oper3; + default: + throw new IllegalArgumentException("unknown policy actor " + policy.getActor()); + } + } + } +} diff --git a/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/ControlLoopOperationManager2Test.java b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/ControlLoopOperationManager2Test.java new file mode 100644 index 000000000..a14cc1708 --- /dev/null +++ b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/ControlLoopOperationManager2Test.java @@ -0,0 +1,936 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.eventmanager; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.time.Instant; +import java.util.Map; +import java.util.TreeMap; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.aai.domain.yang.GenericVnf; +import org.onap.policy.aai.AaiCqResponse; +import org.onap.policy.common.utils.time.PseudoExecutor; +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.VirtualControlLoopEvent; +import org.onap.policy.controlloop.actor.guard.GuardActorServiceProvider; +import org.onap.policy.controlloop.actor.guard.GuardOperation; +import org.onap.policy.controlloop.actorserviceprovider.ActorService; +import org.onap.policy.controlloop.actorserviceprovider.Operation; +import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome; +import org.onap.policy.controlloop.actorserviceprovider.Operator; +import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext; +import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams; +import org.onap.policy.controlloop.actorserviceprovider.spi.Actor; +import org.onap.policy.controlloop.ophistory.OperationHistoryDataManager; +import org.onap.policy.controlloop.policy.Policy; +import org.onap.policy.controlloop.policy.PolicyResult; +import org.onap.policy.controlloop.policy.Target; +import org.onap.policy.controlloop.policy.TargetType; + +public class ControlLoopOperationManager2Test { + private static final UUID REQ_ID = UUID.randomUUID(); + private static final String MISMATCH = "mismatch"; + private static final String POLICY_ID = "my-policy"; + private static final String POLICY_ACTOR = "my-actor"; + private static final String POLICY_OPERATION = "my-operation"; + private static final String OTHER_ACTOR = "another-actor"; + private static final String MY_TARGET = "my-target"; + private static final String MY_VNF_ID = "my-vnf-id"; + private static final String PAYLOAD_KEY = "payload-key"; + private static final String PAYLOAD_VALUE = "payload-value"; + private static final long REMAINING_MS = 5000; + private static final int MAX_RUN = 100; + private static final Integer POLICY_RETRY = 3; + private static final Integer POLICY_TIMEOUT = 20; + private static final IllegalArgumentException EXPECTED_EXCEPTION = + new IllegalArgumentException("expected exception"); + + @Captor + private ArgumentCaptor> lockCallback; + + @Mock + private OperationHistoryDataManager dataMgr; + @Mock + private ManagerContext mgrctx; + @Mock + private Operator policyOperator; + @Mock + private Operation policyOperation; + @Mock + private Actor policyActor; + @Mock + private ActorService actors; + @Mock + private AaiCqResponse cqdata; + @Mock + private GenericVnf vnf; + + private CompletableFuture lockFuture; + private CompletableFuture policyFuture; + private Target target; + private Map payload; + private Policy policy; + private VirtualControlLoopEvent event; + private ControlLoopEventContext context; + private PseudoExecutor executor; + private ControlLoopOperationManager2 mgr; + + /** + * Sets up. + */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + lockFuture = new CompletableFuture<>(); + policyFuture = new CompletableFuture<>(); + + when(mgrctx.getActorService()).thenReturn(actors); + when(mgrctx.getDataManager()).thenReturn(dataMgr); + when(mgrctx.requestLock(any(), any())).thenReturn(lockFuture); + + // configure policy operation + when(actors.getActor(POLICY_ACTOR)).thenReturn(policyActor); + when(policyActor.getOperator(POLICY_OPERATION)).thenReturn(policyOperator); + when(policyOperator.buildOperation(any())).thenReturn(policyOperation); + when(policyOperation.start()).thenReturn(policyFuture); + + when(vnf.getVnfId()).thenReturn(MY_VNF_ID); + when(cqdata.getDefaultGenericVnf()).thenReturn(vnf); + + target = new Target(); + target.setType(TargetType.VM); + + payload = Map.of(PAYLOAD_KEY, PAYLOAD_VALUE); + + policy = new Policy(); + policy.setId(POLICY_ID); + policy.setActor(POLICY_ACTOR); + policy.setRecipe(POLICY_OPERATION); + policy.setTarget(target); + policy.setPayload(payload); + policy.setRetry(POLICY_RETRY); + policy.setTimeout(POLICY_TIMEOUT); + + event = new VirtualControlLoopEvent(); + event.setRequestId(REQ_ID); + event.setTarget(ControlLoopOperationManager2.VSERVER_VSERVER_NAME); + event.setAai(new TreeMap<>(Map.of(ControlLoopOperationManager2.VSERVER_VSERVER_NAME, MY_TARGET))); + + context = new ControlLoopEventContext(event); + context.setProperty(AaiCqResponse.CONTEXT_KEY, cqdata); + + executor = new PseudoExecutor(); + + mgr = new ControlLoopOperationManager2(mgrctx, context, policy, executor); + } + + @Test + public void testStart() { + mgr.start(REMAINING_MS); + + // should have determined the target entity by now + assertEquals(MY_TARGET, mgr.getTargetEntity()); + + verify(mgrctx).requestLock(eq(MY_TARGET), any()); + + lockFuture.complete(new OperationOutcome()); + genGuardOutcome(); + policyFuture.complete(genOpOutcome()); + runToCompletion(); + + assertEquals(ControlLoopOperationManager2.State.GUARD_STARTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.GUARD_PERMITTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.OPERATION_SUCCESS, mgr.getState()); + + assertFalse(mgr.nextStep()); + + OperationOutcome outcome = mgr.getOutcomes().peek(); + assertEquals(PolicyResult.SUCCESS, outcome.getResult()); + assertTrue(outcome.isFinalOutcome()); + + verify(mgrctx, times(3)).updated(mgr); + } + + /** + * Tests start() when detmTarget() (i.e., the first task) throws an exception. + */ + @Test + public void testStartDetmTargetException() { + policy.setTarget(null); + mgr.start(REMAINING_MS); + + runToCompletion(); + + assertFalse(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.OPERATION_FAILURE, mgr.getState()); + + // should have called update() for operation-start, but not for any nextStep() + verify(mgrctx).updated(mgr); + } + + /** + * Tests start() when a subsequent task throws an exception. + */ + @Test + public void testStartException() { + when(policyOperation.start()).thenThrow(EXPECTED_EXCEPTION); + + mgr.start(REMAINING_MS); + + lockFuture.complete(new OperationOutcome()); + runToCompletion(); + + assertFalse(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.OPERATION_FAILURE, mgr.getState()); + + // should have called update() for operation-start, but not for any nextStep() + verify(mgrctx).updated(mgr); + } + + /** + * Tests start() when the control loop times out. + */ + @Test + public void testStartClTimeout_testHandleTimeout() throws InterruptedException { + // catch the callback when it times out + CountDownLatch updatedLatch = new CountDownLatch(1); + doAnswer(args -> { + updatedLatch.countDown(); + return null; + }).when(mgrctx).updated(any()); + + long tstart = System.currentTimeMillis(); + + // give it a short timeout + mgr.start(100); + + assertTrue(updatedLatch.await(5, TimeUnit.SECONDS)); + assertTrue(System.currentTimeMillis() - tstart >= 100); + + // don't generate any responses + runToCompletion(); + + // wait for the future to be canceled, via a background thread + CountDownLatch futureLatch = new CountDownLatch(1); + mgr.getFuture().whenComplete((unused, thrown) -> futureLatch.countDown()); + assertTrue(futureLatch.await(5, TimeUnit.SECONDS)); + + // lock should have been canceled + assertTrue(mgr.getFuture().isCancelled()); + + assertFalse(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.CONTROL_LOOP_TIMEOUT, mgr.getState()); + + // should have called update() for operation-start, but not for any nextStep() + verify(mgrctx).updated(mgr); + + // should not have tried to store anything in the DB + verify(dataMgr, never()).store(any(), any(), any()); + } + + @Test + public void testStartOperation() { + mgr.start(REMAINING_MS); + + lockFuture.complete(new OperationOutcome()); + genGuardOutcome(); + runToCompletion(); + + verify(policyOperation).start(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ControlLoopOperationParams.class); + verify(policyOperator).buildOperation(captor.capture()); + + ControlLoopOperationParams params = captor.getValue(); + + assertNotNull(params); + assertEquals(POLICY_ACTOR, params.getActor()); + assertSame(actors, params.getActorService()); + assertNotNull(params.getCompleteCallback()); + assertSame(context, params.getContext()); + assertSame(executor, params.getExecutor()); + assertEquals(POLICY_OPERATION, params.getOperation()); + assertEquals(payload, params.getPayload()); + assertSame(REQ_ID, params.getRequestId()); + assertSame(POLICY_RETRY, params.getRetry()); + assertNotNull(params.getStartCallback()); + assertSame(target, params.getTarget()); + assertEquals(MY_TARGET, params.getTargetEntity()); + assertSame(POLICY_TIMEOUT, params.getTimeoutSec()); + } + + @Test + public void testStartOperationNullPayload() { + policy.setPayload(null); + mgr.start(REMAINING_MS); + + lockFuture.complete(new OperationOutcome()); + genGuardOutcome(); + runToCompletion(); + + verify(policyOperation).start(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ControlLoopOperationParams.class); + verify(policyOperator).buildOperation(captor.capture()); + + ControlLoopOperationParams params = captor.getValue(); + + assertNotNull(params); + assertEquals(POLICY_ACTOR, params.getActor()); + assertSame(actors, params.getActorService()); + assertNotNull(params.getCompleteCallback()); + assertSame(context, params.getContext()); + assertSame(executor, params.getExecutor()); + assertEquals(POLICY_OPERATION, params.getOperation()); + assertTrue(params.getPayload().isEmpty()); + assertSame(REQ_ID, params.getRequestId()); + assertSame(POLICY_RETRY, params.getRetry()); + assertNotNull(params.getStartCallback()); + assertSame(target, params.getTarget()); + assertEquals(MY_TARGET, params.getTargetEntity()); + assertSame(POLICY_TIMEOUT, params.getTimeoutSec()); + } + + @Test + public void testGetOperationMessage() { + // no history yet + assertNull(mgr.getOperationMessage()); + + runCyle(); + assertThat(mgr.getOperationMessage()).contains("actor=my-actor").contains("operation=my-operation"); + } + + @Test + public void testGetOperationResult() { + // no history yet + assertNotNull(mgr.getOperationResult()); + + runCyle(); + assertEquals(PolicyResult.SUCCESS, mgr.getOperationResult()); + } + + /** + * Tests getOperationResult() when it ends in a failure. + */ + @Test + public void testGetOperationResultFailure() { + mgr.start(REMAINING_MS); + + genLockFailure(); + runToCompletion(); + + assertEquals(PolicyResult.FAILURE_GUARD, mgr.getOperationResult()); + } + + /** + * Tests handleException() when the exception is a "cancel". + */ + @Test + public void testHandleExceptionCanceled() { + lockFuture.cancel(false); + + mgr.start(REMAINING_MS); + + runToCompletion(); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.ACTIVE, mgr.getState()); + } + + @Test + public void testCancel() { + mgr.start(REMAINING_MS); + + mgr.cancel(); + assertTrue(mgr.getFuture().isCancelled()); + } + + /** + * Tests cancel() when the operation hasn't been started. + */ + @Test + public void testCancelNotStarted() { + assertNull(mgr.getFuture()); + + mgr.cancel(); + assertNull(mgr.getFuture()); + } + + @Test + public void testLockUnavailable() { + mgr.start(REMAINING_MS); + + runToCompletion(); + + // lock failure outcome + final OperationOutcome outcome = genLockFailure(); + + runToCompletion(); + + assertFalse(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.LOCK_DENIED, mgr.getState()); + + assertEquals(outcome, mgr.getOutcomes().peek()); + + // should have called update() for operation-start, but not for any nextStep() + verify(mgrctx).updated(mgr); + } + + /** + * Tests onStart() and onComplete() with other actors. + */ + @Test + public void testOnStart_testOnComplete() { + mgr.start(REMAINING_MS); + + lockFuture.complete(new OperationOutcome()); + genGuardOutcome(); + + // generate failure outcome for ANOTHER actor - should be ignored + OperationOutcome outcome = mgr.getParams().makeOutcome(); + outcome.setActor(OTHER_ACTOR); + outcome.setResult(PolicyResult.FAILURE); + outcome.setStart(Instant.now()); + mgr.getParams().callbackStarted(new OperationOutcome(outcome)); + + outcome.setEnd(Instant.now()); + mgr.getParams().callbackCompleted(outcome); + + policyFuture.complete(genOpOutcome()); + runToCompletion(); + + // should not include the other actor's outcome + assertEquals(ControlLoopOperationManager2.State.GUARD_STARTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.GUARD_PERMITTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.OPERATION_SUCCESS, mgr.getState()); + + assertFalse(mgr.nextStep()); + + assertEquals(PolicyResult.SUCCESS, mgr.getOutcomes().peek().getResult()); + + verify(mgrctx, times(3)).updated(mgr); + } + + @Test + public void testNextStep() { + mgr.start(REMAINING_MS); + + // only do the lock and the guard + lockFuture.complete(new OperationOutcome()); + genGuardOutcome(); + runToCompletion(); + + assertEquals(ControlLoopOperationManager2.State.GUARD_STARTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.GUARD_PERMITTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertTrue(mgr.nextStep()); + + verify(mgrctx, times(2)).updated(mgr); + } + + /** + * Tests processOutcome() when the lock is denied. + */ + @Test + public void testProcessOutcomeLockDenied() { + mgr.start(REMAINING_MS); + + // unavailable from the start => "denied" + genLockFailure(); + + runToCompletion(); + + assertEquals(ControlLoopOperationManager2.State.LOCK_DENIED, mgr.getState()); + + assertFalse(mgr.nextStep()); + verify(mgrctx).updated(mgr); + + verifyDb(1, PolicyResult.FAILURE_GUARD, "Operation denied by Lock"); + } + + /** + * Tests processOutcome() when the lock is lost. + */ + @Test + public void testProcessOutcomeLockLost() { + mgr.start(REMAINING_MS); + + // indicate lock success initially + lockFuture.complete(new OperationOutcome()); + + // do the guard + genGuardOutcome(); + + // now generate a lock failure => "lost" + genLockFailure(); + + runToCompletion(); + + assertEquals(ControlLoopOperationManager2.State.GUARD_STARTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.GUARD_PERMITTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.LOCK_LOST, mgr.getState()); + + assertFalse(mgr.nextStep()); + verify(mgrctx, times(3)).updated(mgr); + + verifyDb(1, PolicyResult.FAILURE, "Operation aborted by Lock"); + } + + /** + * Tests processOutcome() when the guard is permitted. + */ + @Test + public void testProcessOutcomeGuardPermit() { + mgr.start(REMAINING_MS); + + lockFuture.complete(new OperationOutcome()); + genGuardOutcome(); + + runToCompletion(); + + assertEquals(ControlLoopOperationManager2.State.GUARD_STARTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.GUARD_PERMITTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + verify(mgrctx, times(2)).updated(mgr); + + verify(dataMgr, never()).store(any(), any(), any()); + } + + /** + * Tests processOutcome() when the guard is permitted. + */ + @Test + public void testProcessOutcomeGuardDenied() { + mgr.start(REMAINING_MS); + + lockFuture.complete(new OperationOutcome()); + genGuardOutcome(false); + + runToCompletion(); + + assertEquals(ControlLoopOperationManager2.State.GUARD_STARTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.GUARD_DENIED, mgr.getState()); + + assertFalse(mgr.nextStep()); + verify(mgrctx, times(2)).updated(mgr); + + verifyDb(1, PolicyResult.FAILURE_GUARD, "Operation denied by Guard"); + } + + /** + * Tests processOutcome() when the operation is a success. + */ + @Test + public void testProcessOutcomeOperSuccess() { + mgr.start(REMAINING_MS); + + lockFuture.complete(new OperationOutcome()); + genGuardOutcome(); + genOpOutcome(); + + runToCompletion(); + + assertEquals(ControlLoopOperationManager2.State.GUARD_STARTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.GUARD_PERMITTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.OPERATION_SUCCESS, mgr.getState()); + + assertFalse(mgr.nextStep()); + verify(mgrctx, times(3)).updated(mgr); + + verifyDb(1, PolicyResult.SUCCESS, null); + } + + /** + * Tests processOutcome() when the operation is a failure. + */ + @Test + public void testProcessOutcomeOperFailure() { + mgr.start(REMAINING_MS); + + lockFuture.complete(new OperationOutcome()); + genGuardOutcome(); + genOpOutcome(false); + + runToCompletion(); + + assertEquals(ControlLoopOperationManager2.State.GUARD_STARTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.GUARD_PERMITTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.OPERATION_FAILURE, mgr.getState()); + verifyDb(1, PolicyResult.FAILURE, null); + + assertThat(mgr.toString()).contains("attempts=1"); + + // next failure + genOpOutcome(false); + runToCompletion(); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.OPERATION_FAILURE, mgr.getState()); + verifyDb(2, PolicyResult.FAILURE, null); + + assertThat(mgr.toString()).contains("attempts=2"); + + // and finally a success + genOpOutcome(); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.OPERATION_SUCCESS, mgr.getState()); + verifyDb(3, PolicyResult.SUCCESS, null); + + assertThat(mgr.toString()).contains("attempts=3"); + + assertFalse(mgr.nextStep()); + verify(mgrctx, times(5)).updated(mgr); + } + + @Test + public void testGetOperationHistory() { + // no history yet + assertNull(mgr.getOperationHistory()); + + runCyle(); + assertThat(mgr.getOperationHistory()).contains("actor=my-actor").contains("operation=my-operation") + .contains("outcome=Success"); + } + + @Test + public void testGetHistory() { + // no history yet + assertEquals(0, mgr.getHistory().size()); + + runCyle(); + assertEquals(1, mgr.getHistory().size()); + } + + @Test + public void testDetmTargetVm() { + target.setType(TargetType.VM); + assertNull(mgr.detmTarget()); + assertEquals(MY_TARGET, mgr.getTargetEntity()); + + target.setType(TargetType.VNF); + assertNull(mgr.detmTarget()); + assertEquals(MY_TARGET, mgr.getTargetEntity()); + + target.setType(TargetType.VFMODULE); + assertNull(mgr.detmTarget()); + assertEquals(MY_TARGET, mgr.getTargetEntity()); + + // unsupported type + target.setType(TargetType.VFC); + assertThatIllegalArgumentException().isThrownBy(() -> mgr.detmTarget()) + .withMessage("The target type is not supported"); + + // null type + target.setType(null); + assertThatIllegalArgumentException().isThrownBy(() -> mgr.detmTarget()).withMessage("The target type is null"); + + // null target + policy.setTarget(null); + assertThatIllegalArgumentException().isThrownBy(() -> mgr.detmTarget()).withMessage("The target is null"); + } + + @Test + public void testDetmPnfTarget() { + setTargetPnf(); + assertNull(mgr.detmTarget()); + assertEquals(MY_TARGET, mgr.getTargetEntity()); + + // missing enrichment data + event.getAai().clear(); + assertThatIllegalArgumentException().isThrownBy(() -> mgr.detmTarget()) + .withMessage("AAI section is missing " + ControlLoopOperationManager2.PNF_NAME); + + // wrong target + event.setTarget(MISMATCH); + assertThatIllegalArgumentException().isThrownBy(() -> mgr.detmTarget()) + .withMessage("Target does not match target type"); + } + + @Test + public void testDetmVfModuleTarget() { + // vserver + event.setTarget(ControlLoopOperationManager2.VSERVER_VSERVER_NAME); + event.getAai().clear(); + event.getAai().putAll(Map.of(ControlLoopOperationManager2.VSERVER_VSERVER_NAME, MY_TARGET)); + assertNull(mgr.detmTarget()); + assertEquals(MY_TARGET, mgr.getTargetEntity()); + + // vnf-id + event.setTarget(ControlLoopOperationManager2.GENERIC_VNF_VNF_ID); + event.getAai().clear(); + event.getAai().putAll(Map.of(ControlLoopOperationManager2.GENERIC_VNF_VNF_ID, MY_TARGET)); + assertNull(mgr.detmTarget()); + assertEquals(MY_TARGET, mgr.getTargetEntity()); + + // wrong type + event.setTarget(MISMATCH); + assertThatIllegalArgumentException().isThrownBy(() -> mgr.detmTarget()) + .withMessage("Target does not match target type"); + + // missing enrichment data + event.setTarget(ControlLoopOperationManager2.VSERVER_VSERVER_NAME); + event.getAai().clear(); + assertThatIllegalArgumentException().isThrownBy(() -> mgr.detmTarget()) + .withMessage("Enrichment data is missing " + ControlLoopOperationManager2.VSERVER_VSERVER_NAME); + + // null target + event.setTarget(null); + assertThatIllegalArgumentException().isThrownBy(() -> mgr.detmTarget()).withMessage("Target is null"); + } + + @Test + public void testDetmVnfName() { + setTargetVnfName(); + assertNull(mgr.detmTarget()); + assertEquals(MY_TARGET, mgr.getTargetEntity()); + + // force it to be gotten from the CQ data + event.getAai().clear(); + assertNull(mgr.detmTarget()); + assertEquals(MY_VNF_ID, mgr.getTargetEntity()); + } + + @Test + public void testExtractVnfFromCq() { + // force it to be gotten from the CQ data + setTargetVnfName(); + event.getAai().clear(); + + // missing vnf id in CQ data + when(vnf.getVnfId()).thenReturn(null); + assertThatIllegalArgumentException().isThrownBy(() -> mgr.detmTarget()).withMessage("No vnf-id found"); + + // missing default vnf in CQ data + when(cqdata.getDefaultGenericVnf()).thenReturn(null); + assertThatIllegalArgumentException().isThrownBy(() -> mgr.detmTarget()).withMessage("No vnf-id found"); + } + + @Test + public void testGetState_testGetActor_testGetOperation() { + assertEquals(ControlLoopOperationManager2.State.ACTIVE, mgr.getState()); + assertEquals(POLICY_ACTOR, mgr.getActor()); + assertEquals(POLICY_OPERATION, mgr.getOperation()); + } + + @Test + public void testToString() { + assertThat(mgr.toString()).contains("state").contains("requestId").contains("policyId").contains("attempts"); + } + + /** + * Runs a cycle, from start to completion. + */ + private void runCyle() { + mgr.start(REMAINING_MS); + + lockFuture.complete(new OperationOutcome()); + genGuardOutcome(); + genOpOutcome(); + + runToCompletion(); + + assertTrue(mgr.nextStep()); + assertTrue(mgr.nextStep()); + assertFalse(mgr.nextStep()); + } + + /** + * Runs everything until the executor queue is empty. + */ + private void runToCompletion() { + assertTrue(executor.runAll(MAX_RUN)); + } + + /** + * Generates a failure outcome for the lock, and invokes the callbacks. + * + * @return the generated outcome + */ + private OperationOutcome genLockFailure() { + OperationOutcome outcome = new OperationOutcome(); + outcome.setActor(ControlLoopOperationManager2.LOCK_ACTOR); + outcome.setOperation(ControlLoopOperationManager2.LOCK_OPERATION); + outcome.setResult(PolicyResult.FAILURE); + outcome.setStart(Instant.now()); + outcome.setEnd(Instant.now()); + outcome.setFinalOutcome(true); + + verify(mgrctx).requestLock(eq(MY_TARGET), lockCallback.capture()); + lockCallback.getValue().accept(outcome); + + lockFuture.complete(outcome); + + return outcome; + } + + /** + * Generates an outcome for the guard, and invokes the callbacks. + * + * @return the generated outcome + */ + private OperationOutcome genGuardOutcome() { + return genGuardOutcome(true); + } + + /** + * Generates an outcome for the guard, and invokes the callbacks. + * + * @param permit {@code true} if the guard should be permitted, {@code false} if + * denied + * @return the generated outcome + */ + private OperationOutcome genGuardOutcome(boolean permit) { + OperationOutcome outcome = mgr.getParams().makeOutcome(); + outcome.setActor(GuardActorServiceProvider.NAME); + outcome.setOperation(GuardOperation.NAME); + outcome.setStart(Instant.now()); + mgr.getParams().callbackStarted(new OperationOutcome(outcome)); + + if (!permit) { + outcome.setResult(PolicyResult.FAILURE); + } + + outcome.setEnd(Instant.now()); + mgr.getParams().callbackCompleted(outcome); + + return outcome; + } + + /** + * Generates an outcome for the operation, itself, and invokes the callbacks. + * + * @return the generated outcome + */ + private OperationOutcome genOpOutcome() { + return genOpOutcome(true); + } + + /** + * Generates an outcome for the operation, itself, and invokes the callbacks. + * + * @param success {@code true} if the outcome should be a success, {@code false} if a + * failure + * @return the generated outcome + */ + private OperationOutcome genOpOutcome(boolean success) { + OperationOutcome outcome = mgr.getParams().makeOutcome(); + outcome.setStart(Instant.now()); + mgr.getParams().callbackStarted(new OperationOutcome(outcome)); + + if (success) { + outcome.setFinalOutcome(true); + } else { + outcome.setResult(PolicyResult.FAILURE); + } + + outcome.setEnd(Instant.now()); + mgr.getParams().callbackCompleted(outcome); + + return outcome; + } + + /** + * Configures the data for a PNF target. + */ + private void setTargetPnf() { + event.setTarget(ControlLoopOperationManager2.PNF_NAME); + event.getAai().clear(); + event.getAai().putAll(Map.of(ControlLoopOperationManager2.PNF_NAME, MY_TARGET)); + + target.setType(TargetType.PNF); + } + + /** + * Configures the data for a VNF-NAME target. + */ + private void setTargetVnfName() { + event.setTarget(ControlLoopOperationManager2.GENERIC_VNF_VNF_NAME); + event.getAai().clear(); + event.getAai().putAll(Map.of(ControlLoopOperationManager2.GENERIC_VNF_VNF_ID, MY_TARGET)); + + target.setType(TargetType.VNF); + } + + private void verifyDb(int nrecords, PolicyResult expectedResult, String expectedMsg) { + ArgumentCaptor captor = ArgumentCaptor.forClass(ControlLoopOperation.class); + verify(dataMgr, times(nrecords)).store(any(), any(), captor.capture()); + + ControlLoopOperation oper = captor.getValue(); + + assertEquals(expectedResult.toString(), oper.getOutcome()); + assertEquals(expectedMsg, oper.getMessage()); + } +} diff --git a/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/EventManagerServicesTest.java b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/EventManagerServicesTest.java new file mode 100644 index 000000000..b32fb4438 --- /dev/null +++ b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/EventManagerServicesTest.java @@ -0,0 +1,120 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.eventmanager; + +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Properties; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance; +import org.onap.policy.controlloop.actorserviceprovider.ActorService; +import org.onap.policy.controlloop.ophistory.OperationHistoryDataManagerImpl; +import org.onap.policy.controlloop.ophistory.OperationHistoryDataManagerStub; +import org.onap.policy.drools.utils.PropertyUtil; + +public class EventManagerServicesTest { + private static final String FILEPFX = "eventService/"; + private static final IllegalArgumentException EXPECTED_EXCEPTION = + new IllegalArgumentException("expected exception"); + + private EventManagerServices services; + + /** + * Configures HTTP clients. + */ + @BeforeClass + public static void setUpBeforeClass() throws Exception { + // start with a clean slate + HttpClientFactoryInstance.getClientFactory().destroy(); + + Properties props = + PropertyUtil.getProperties("src/test/resources/eventService/event-svc-http-client.properties"); + HttpClientFactoryInstance.getClientFactory().build(props); + } + + @AfterClass + public static void teatDownBeforeClass() { + HttpClientFactoryInstance.getClientFactory().destroy(); + } + + @After + public void tearDown() { + closeDb(); + } + + @Test + public void testEventManagerServices_testGetActorService() { + // try with guard disabled - should use DB stub + services = new EventManagerServices(FILEPFX + "event-svc-guard-disabled.properties"); + assertTrue(services.getDataManager() instanceof OperationHistoryDataManagerStub); + assertNotNull(services.getActorService()); + + // try with guard enabled - should create a DB connection + services = new EventManagerServices(FILEPFX + "event-svc-with-db.properties"); + assertTrue(services.getDataManager() instanceof OperationHistoryDataManagerImpl); + assertNotNull(services.getActorService()); + } + + @Test + public void testStartActorService() { + // config file not found + assertThatIllegalStateException().isThrownBy(() -> new EventManagerServices("missing-config-file")); + } + + @Test + public void testIsGuardEnabled() { + // cannot check guard + services = new EventManagerServices(FILEPFX + "event-svc-no-guard-actor.properties"); + assertTrue(services.getDataManager() instanceof OperationHistoryDataManagerStub); + + // force exception when checking for guard operator + services = new EventManagerServices(FILEPFX + "event-svc-with-db.properties") { + @Override + public ActorService getActorService() { + ActorService svc = mock(ActorService.class); + when(svc.getActor(any())).thenThrow(EXPECTED_EXCEPTION); + return svc; + } + }; + assertTrue(services.getDataManager() instanceof OperationHistoryDataManagerStub); + } + + @Test + public void testMakeDataManager() { + assertThatThrownBy(() -> new EventManagerServices(FILEPFX + "event-svc-invalid-db.properties")); + } + + + private void closeDb() { + if (services != null) { + services.getDataManager().stop(); + } + } +} diff --git a/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/LockDataTest.java b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/LockDataTest.java new file mode 100644 index 000000000..dc470e7c8 --- /dev/null +++ b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/LockDataTest.java @@ -0,0 +1,193 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.eventmanager; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import java.time.Instant; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome; +import org.onap.policy.controlloop.policy.PolicyResult; +import org.onap.policy.drools.core.lock.Lock; + +public class LockDataTest { + + private static final String ENTITY = "my-entity"; + private static final UUID REQ_ID = UUID.randomUUID(); + + @Mock + private Lock lock; + @Mock + private Consumer callback1; + @Mock + private Consumer callback2; + @Mock + private Consumer callback3; + + private LockData data; + + /** + * Sets up. + */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + data = new LockData(ENTITY, REQ_ID); + } + + @Test + public void testGetFuture() { + CompletableFuture future = data.getFuture(); + assertNotNull(future); + assertFalse(future.isDone()); + } + + @Test + public void testAddUnavailableCallback() { + data.addUnavailableCallback(callback1); + data.addUnavailableCallback(callback2); + + data.lockAvailable(lock); + verify(callback1, never()).accept(any()); + verify(callback2, never()).accept(any()); + + data.lockUnavailable(lock); + verify(callback1).accept(any()); + verify(callback2).accept(any()); + } + + /** + * Tests addUnavailableCallback() when the lock never becomes available. + */ + @Test + public void testAddUnavailableCallbackNeverAvailable() { + data.addUnavailableCallback(callback1); + data.addUnavailableCallback(callback2); + + data.lockUnavailable(lock); + verify(callback1).accept(any()); + verify(callback2).accept(any()); + + data.addUnavailableCallback(callback3); + verify(callback3).accept(any()); + } + + @Test + public void testFree() { + // no lock yet + assertThatCode(() -> data.free()).doesNotThrowAnyException(); + + // no with a lock + data.lockAvailable(lock); + data.free(); + verify(lock).free(); + } + + @Test + public void testLockAvailable() throws Exception { + data.addUnavailableCallback(callback1); + data.addUnavailableCallback(callback2); + + CompletableFuture future = data.getFuture(); + data.lockAvailable(lock); + + assertSame(future, data.getFuture()); + + assertTrue(future.isDone()); + OperationOutcome outcome = future.get(); + assertEquals(ControlLoopOperationManager2.LOCK_ACTOR, outcome.getActor()); + assertEquals(ControlLoopOperationManager2.LOCK_OPERATION, outcome.getOperation()); + assertEquals(ENTITY, outcome.getTarget()); + assertEquals(PolicyResult.SUCCESS, outcome.getResult()); + assertEquals(ControlLoopOperation.SUCCESS_MSG, outcome.getMessage()); + + Instant start = outcome.getStart(); + assertNotNull(start); + + Instant end = outcome.getEnd(); + assertNotNull(end); + assertTrue(start.compareTo(end) <= 0); + + verify(callback1, never()).accept(any()); + verify(callback2, never()).accept(any()); + } + + @Test + public void testLockUnavailable() throws Exception { + data.addUnavailableCallback(callback1); + data.addUnavailableCallback(callback2); + data.addUnavailableCallback(callback3); + + // arrange for callback2 to throw an exception + doThrow(new IllegalStateException("expected exception")).when(callback2).accept(any()); + + CompletableFuture future = data.getFuture(); + assertNotNull(future); + data.lockUnavailable(lock); + + CompletableFuture future2 = data.getFuture(); + assertNotNull(future2); + + assertNotSame(future, future2); + + assertTrue(future.isDone()); + OperationOutcome outcome = future.get(); + + assertTrue(future2.isDone()); + assertSame(outcome, future2.get()); + + assertEquals(ControlLoopOperationManager2.LOCK_ACTOR, outcome.getActor()); + assertEquals(ControlLoopOperationManager2.LOCK_OPERATION, outcome.getOperation()); + assertEquals(ENTITY, outcome.getTarget()); + assertEquals(PolicyResult.FAILURE, outcome.getResult()); + assertEquals(ControlLoopOperation.FAILED_MSG, outcome.getMessage()); + + Instant start = outcome.getStart(); + assertNotNull(start); + + Instant end = outcome.getEnd(); + assertNotNull(end); + assertTrue(start.compareTo(end) <= 0); + + verify(callback1).accept(eq(outcome)); + verify(callback2).accept(eq(outcome)); + verify(callback3).accept(eq(outcome)); + } +} diff --git a/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerImplTest.java b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerImplTest.java new file mode 100644 index 000000000..8e3c1fa9b --- /dev/null +++ b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerImplTest.java @@ -0,0 +1,379 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.ophistory; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.awaitility.Awaitility.await; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.time.Instant; +import java.util.Properties; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import javax.persistence.EntityManagerFactory; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.VirtualControlLoopEvent; +import org.onap.policy.controlloop.ophistory.OperationHistoryDataManagerParams.OperationHistoryDataManagerParamsBuilder; + +public class OperationHistoryDataManagerImplTest { + + private static final IllegalStateException EXPECTED_EXCEPTION = new IllegalStateException("expected exception"); + private static final String MY_TARGET = "my-target"; + private static final String REQ_ID = "my-request-id"; + private static final int BATCH_SIZE = 5; + private static final int MAX_QUEUE_LENGTH = 23; + + private static EntityManagerFactory emf; + + @Mock + private Thread thread; + + private OperationHistoryDataManagerParams params; + private Consumer threadFunction; + private VirtualControlLoopEvent event; + private ControlLoopOperation operation; + private EntityManagerFactory emfSpy; + + // decremented when the thread function completes + private CountDownLatch finished; + + private OperationHistoryDataManagerImpl mgr; + + + /** + * Sets up for all tests. + */ + @BeforeClass + public static void setUpBeforeClass() { + OperationHistoryDataManagerParams params = makeBuilder().build(); + + // capture the entity manager factory for re-use + new OperationHistoryDataManagerImpl(params) { + @Override + protected EntityManagerFactory makeEntityManagerFactory(String opsHistPu, Properties props) { + emf = super.makeEntityManagerFactory(opsHistPu, props); + return emf; + } + }; + } + + /** + * Restores the environment after all tests. + */ + @AfterClass + public static void tearDownAfterClass() { + emf.close(); + } + + /** + * Sets up for an individual test. + */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + event = new VirtualControlLoopEvent(); + event.setRequestId(UUID.randomUUID()); + + operation = new ControlLoopOperation(); + operation.setTarget(MY_TARGET); + + threadFunction = null; + finished = new CountDownLatch(1); + + // prevent the "real" emf from being closed + emfSpy = spy(emf); + doAnswer(ans -> null).when(emfSpy).close(); + + params = makeBuilder().build(); + + mgr = new PseudoThread(); + mgr.start(); + } + + @After + public void tearDown() { + mgr.stop(); + } + + @Test + public void testConstructor() { + // use a thread and manager that haven't been started yet + thread = mock(Thread.class); + mgr = new PseudoThread(); + + // should not start the thread before start() is called + verify(thread, never()).start(); + + mgr.start(); + + // should have started the thread + verify(thread).start(); + + // invalid properties + params.setUrl(null); + assertThatCode(() -> new PseudoThread()).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("data-manager-properties"); + } + + @Test + public void testStart() { + // this should have no effect + mgr.start(); + + mgr.stop(); + + // this should also have no effect + assertThatCode(() -> mgr.start()).doesNotThrowAnyException(); + } + + @Test + public void testStore_testStop() throws InterruptedException { + // store + mgr.store(REQ_ID, event, operation); + + runThread(); + + assertEquals(1, mgr.getRecordsAdded()); + } + + /** + * Tests stop() when the manager isn't running. + */ + @Test + public void testStopNotRunning() { + // use a manager that hasn't been started yet + mgr = new PseudoThread(); + mgr.stop(); + + verify(emfSpy).close(); + } + + /** + * Tests store() when it is already stopped. + */ + @Test + public void testStoreAlreadyStopped() throws InterruptedException { + mgr.stop(); + + // store + mgr.store(REQ_ID, event, operation); + + assertEquals(0, mgr.getRecordsAdded()); + } + + /** + * Tests store() when when the queue is full. + */ + @Test + public void testStoreTooManyItems() throws InterruptedException { + final int nextra = 5; + for (int nitems = 0; nitems < MAX_QUEUE_LENGTH + nextra; ++nitems) { + mgr.store(REQ_ID, event, operation); + } + + runThread(); + + assertEquals(MAX_QUEUE_LENGTH, mgr.getRecordsAdded()); + } + + @Test + public void testRun() throws InterruptedException { + + // trigger thread shutdown when it completes this batch + when(emfSpy.createEntityManager()).thenAnswer(ans -> { + mgr.stop(); + return emf.createEntityManager(); + }); + + + mgr = new RealThread(); + mgr.start(); + + mgr.store(REQ_ID, event, operation); + mgr.store(REQ_ID, event, operation); + mgr.store(REQ_ID, event, operation); + + waitForThread(); + + verify(emfSpy).close(); + + assertEquals(3, mgr.getRecordsAdded()); + } + + private void waitForThread() { + await().atMost(5, TimeUnit.SECONDS).until(() -> !thread.isAlive()); + } + + /** + * Tests run() when the entity manager throws an exception. + */ + @Test + public void testRunException() throws InterruptedException { + AtomicInteger count = new AtomicInteger(0); + + when(emfSpy.createEntityManager()).thenAnswer(ans -> { + if (count.incrementAndGet() == 2) { + // interrupt during one of the attempts + thread.interrupt(); + } + + // throw an exception for each record + throw EXPECTED_EXCEPTION; + }); + + + mgr = new RealThread(); + mgr.start(); + + mgr.store(REQ_ID, event, operation); + mgr.store(REQ_ID, event, operation); + mgr.store(REQ_ID, event, operation); + + waitForThread(); + + verify(emfSpy).close(); + } + + /** + * Tests storeRemainingRecords() when the entity manager throws an exception. + */ + @Test + public void testStoreRemainingRecordsException() throws InterruptedException { + // arrange to throw an exception + when(emfSpy.createEntityManager()).thenThrow(EXPECTED_EXCEPTION); + + mgr.store(REQ_ID, event, operation); + + runThread(); + } + + @Test + public void testStoreRecord() throws InterruptedException { + // no start time + mgr.store(REQ_ID, event, operation); + + // no start time + operation = new ControlLoopOperation(operation); + operation.setStart(Instant.now()); + mgr.store(REQ_ID, event, operation); + + // both start and end times + operation = new ControlLoopOperation(operation); + operation.setEnd(Instant.now()); + mgr.store(REQ_ID, event, operation); + + // only end time + operation = new ControlLoopOperation(operation); + operation.setStart(null); + mgr.store(REQ_ID, event, operation); + + runThread(); + + // all of them should have been stored + assertEquals(4, mgr.getRecordsAdded()); + } + + private void runThread() throws InterruptedException { + if (threadFunction == null) { + return; + } + + Thread thread2 = new Thread(() -> { + threadFunction.accept(emfSpy); + finished.countDown(); + }); + + thread2.setDaemon(true); + thread2.start(); + + mgr.stop(); + + assertTrue(finished.await(5, TimeUnit.SECONDS)); + } + + private static OperationHistoryDataManagerParamsBuilder makeBuilder() { + // @formatter:off + return OperationHistoryDataManagerParams.builder() + .url("jdbc:h2:mem:" + OperationHistoryDataManagerImplTest.class.getSimpleName()) + .userName("sa") + .password("") + .batchSize(BATCH_SIZE) + .maxQueueLength(MAX_QUEUE_LENGTH); + // @formatter:on + } + + /** + * Manager that uses the shared DB. + */ + private class SharedDb extends OperationHistoryDataManagerImpl { + public SharedDb() { + super(params); + } + + @Override + protected EntityManagerFactory makeEntityManagerFactory(String opsHistPu, Properties props) { + // re-use the same factory to avoid re-creating the DB for each test + return emfSpy; + } + } + + /** + * Manager that uses the shared DB and a pseudo thread. + */ + private class PseudoThread extends SharedDb { + + @Override + protected Thread makeThread(EntityManagerFactory emfactory, Consumer command) { + threadFunction = command; + return thread; + } + } + + /** + * Manager that uses the shared DB and catches the thread. + */ + private class RealThread extends SharedDb { + + @Override + protected Thread makeThread(EntityManagerFactory emfactory, Consumer command) { + thread = super.makeThread(emfactory, command); + return thread; + } + } +} diff --git a/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerParamsTest.java b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerParamsTest.java new file mode 100644 index 000000000..aeeac4796 --- /dev/null +++ b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerParamsTest.java @@ -0,0 +1,115 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.ophistory; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.function.Consumer; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.common.parameters.ValidationResult; +import org.onap.policy.controlloop.ophistory.OperationHistoryDataManagerParams.OperationHistoryDataManagerParamsBuilder; + +public class OperationHistoryDataManagerParamsTest { + private static final String CONTAINER = "my-container"; + private static final int BATCH_SIZE = 10; + private static final int MAX_QUEUE_LENGTH = 20; + private static final String MY_PASS = "my-pass"; + private static final String MY_PU = "my-pu"; + private static final String MY_URL = "my-url"; + private static final String MY_USER = "my-user"; + + private OperationHistoryDataManagerParams params; + + @Before + public void setUp() { + params = makeBuilder().build(); + } + + @Test + public void test() { + assertEquals(BATCH_SIZE, params.getBatchSize()); + assertEquals(MAX_QUEUE_LENGTH, params.getMaxQueueLength()); + assertEquals(MY_PASS, params.getPassword()); + assertEquals(OperationHistoryDataManagerParams.DEFAULT_PU, params.getPersistenceUnit()); + assertEquals(MY_URL, params.getUrl()); + assertEquals(MY_USER, params.getUserName()); + + // use specified PU + assertEquals(MY_PU, makeBuilder().persistenceUnit(MY_PU).build().getPersistenceUnit()); + } + + @Test + public void testValidate() { + assertTrue(params.validate(CONTAINER).isValid()); + + testValidateField("url", "null", params2 -> params2.setUrl(null)); + testValidateField("userName", "null", params2 -> params2.setUserName(null)); + testValidateField("password", "null", params2 -> params2.setPassword(null)); + testValidateField("persistenceUnit", "null", params2 -> params2.setPersistenceUnit(null)); + + // check edge cases + params.setBatchSize(0); + assertFalse(params.validate(CONTAINER).isValid()); + + params.setBatchSize(1); + assertTrue(params.validate(CONTAINER).isValid()); + + params.setMaxQueueLength(0); + assertFalse(params.validate(CONTAINER).isValid()); + + params.setMaxQueueLength(1); + assertTrue(params.validate(CONTAINER).isValid()); + + // blank password is ok + params.setPassword(""); + assertTrue(params.validate(CONTAINER).isValid()); + } + + private void testValidateField(String fieldName, String expected, + Consumer makeInvalid) { + + // original params should be valid + ValidationResult result = params.validate(CONTAINER); + assertTrue(fieldName, result.isValid()); + + // make invalid params + OperationHistoryDataManagerParams params2 = makeBuilder().build(); + makeInvalid.accept(params2); + result = params2.validate(CONTAINER); + assertFalse(fieldName, result.isValid()); + assertThat(result.getResult()).contains(CONTAINER).contains(fieldName).contains(expected); + } + + private OperationHistoryDataManagerParamsBuilder makeBuilder() { + // @formatter:off + return OperationHistoryDataManagerParams.builder() + .batchSize(BATCH_SIZE) + .maxQueueLength(MAX_QUEUE_LENGTH) + .password(MY_PASS) + .url(MY_URL) + .userName(MY_USER); + // @formatter:on + } +} diff --git a/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerStubTest.java b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerStubTest.java new file mode 100644 index 000000000..f4a7ff8c5 --- /dev/null +++ b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerStubTest.java @@ -0,0 +1,36 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.ophistory; + +import static org.assertj.core.api.Assertions.assertThatCode; + +import org.junit.Test; + +public class OperationHistoryDataManagerStubTest { + + @Test + public void test() { + OperationHistoryDataManagerStub mgr = new OperationHistoryDataManagerStub(); + + assertThatCode(() -> mgr.store(null, null, null)).doesNotThrowAnyException(); + assertThatCode(() -> mgr.stop()).doesNotThrowAnyException(); + } +} diff --git a/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/utils/ControlLoopUtilsTest.java b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/utils/ControlLoopUtilsTest.java index 2e4811475..2f14954ca 100644 --- a/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/utils/ControlLoopUtilsTest.java +++ b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/utils/ControlLoopUtilsTest.java @@ -19,11 +19,22 @@ package org.onap.policy.controlloop.utils; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; import org.junit.Test; +import org.onap.policy.common.utils.coder.CoderException; import org.onap.policy.common.utils.coder.StandardCoder; import org.onap.policy.controlloop.drl.legacy.ControlLoopParams; import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy; @@ -32,8 +43,7 @@ public class ControlLoopUtilsTest { @Test public void testToControlLoopParams() throws Exception { - String policy = - new String(Files.readAllBytes(Paths.get("src/test/resources/tosca-policy-legacy-vcpe.json"))); + String policy = Files.readString(Paths.get("src/test/resources/tosca-policy-legacy-vcpe.json")); ToscaPolicy toscaPolicy = new StandardCoder().decode(policy, ToscaPolicy.class); ControlLoopParams params = ControlLoopUtils.toControlLoopParams(toscaPolicy); @@ -42,5 +52,182 @@ public class ControlLoopUtilsTest { assertEquals(toscaPolicy.getVersion(), params.getPolicyVersion()); assertEquals(toscaPolicy.getType() + ":" + toscaPolicy.getVersion(), params.getPolicyScope()); assertSame(toscaPolicy, params.getToscaPolicy()); + + assertNull(ControlLoopUtils.toControlLoopParams(null)); + } + + @Test + public void testToObject() { + Map map = Map.of("abc", "def", "ghi", "jkl"); + Properties props = new Properties(); + props.putAll(map); + + // with empty prefix + Map result = ControlLoopUtils.toObject(props, ""); + assertEquals(map, result); + + // with dotted prefix - other items skipped + map = Map.of("pfx.abc", "def", "ghi", "jkl", "pfx.mno", "pqr", "differentpfx.stu", "vwx"); + props.clear(); + props.putAll(Map.of("pfx.abc", "def", "ghi", "jkl", "pfx.mno", "pqr", "differentpfx.stu", "vwx")); + result = ControlLoopUtils.toObject(props, "pfx."); + map = Map.of("abc", "def", "mno", "pqr"); + assertEquals(map, result); + + // undotted prefix - still skips other items + result = ControlLoopUtils.toObject(props, "pfx"); + assertEquals(map, result); + } + + @Test + public void testSetProperty() { + // one, two, and three components in the name, the last two with subscripts + Map map = Map.of("one", "one.abc", "two.def", "two.ghi", "three.jkl.mno[0]", "three.pqr", + "three.jkl.mno[1]", "three.stu"); + Properties props = new Properties(); + props.putAll(map); + + Map result = ControlLoopUtils.toObject(props, ""); + // @formatter:off + map = Map.of( + "one", "one.abc", + "two", Map.of("def", "two.ghi"), + "three", Map.of("jkl", + Map.of("mno", + List.of("three.pqr", "three.stu")))); + // @formatter:on + assertEquals(map, result); + } + + @Test + public void testGetNode() { + Map map = Map.of("abc[0].def", "node.ghi", "abc[0].jkl", "node.mno", "abc[1].def", "node.pqr"); + Properties props = new Properties(); + props.putAll(map); + + Map result = ControlLoopUtils.toObject(props, ""); + // @formatter:off + map = Map.of( + "abc", + List.of( + Map.of("def", "node.ghi", "jkl", "node.mno"), + Map.of("def", "node.pqr") + )); + // @formatter:on + assertEquals(map, result); + + } + + @Test + public void testExpand() { + // add subscripts out of order + Properties props = makeProperties("abc[2]", "expand.def", "abc[1]", "expand.ghi"); + + Map result = ControlLoopUtils.toObject(props, ""); + // @formatter:off + Map map = + Map.of("abc", + Arrays.asList(null, "expand.ghi", "expand.def")); + // @formatter:on + assertEquals(map, result); + + } + + @Test + public void testGetObject() { + // first value is primitive, while second is a map + Properties props = makeProperties("object.abc", "object.def", "object.abc.ghi", "object.jkl"); + + Map result = ControlLoopUtils.toObject(props, ""); + // @formatter:off + Map map = + Map.of("object", + Map.of("abc", + Map.of("ghi", "object.jkl"))); + // @formatter:on + assertEquals(map, result); + } + + @Test + public void testGetArray() { + // first value is primitive, while second is an array + Properties props = makeProperties("array.abc", "array.def", "array.abc[0].ghi", "array.jkl"); + + Map result = ControlLoopUtils.toObject(props, ""); + // @formatter:off + Map map = + Map.of("array", + Map.of("abc", + List.of( + Map.of("ghi", "array.jkl")))); + // @formatter:on + assertEquals(map, result); + } + + @Test + @SuppressWarnings("unchecked") + public void testCompressLists() throws IOException, CoderException { + assertEquals("plain-string", ControlLoopUtils.compressLists("plain-string").toString()); + + // @formatter:off + Map map = + Map.of( + "cmp.abc", "cmp.def", + "cmp.ghi", + Arrays.asList(null, "cmp.list1", null, "cmp.list2", + Map.of("cmp.map", Arrays.asList("cmp.map.list1", "cmp.map1.list2", null)))); + // @formatter:on + + // the data structure needs to be modifiable, so we'll encode/decode it + StandardCoder coder = new StandardCoder(); + map = coder.decode(coder.encode(map), LinkedHashMap.class); + + ControlLoopUtils.compressLists(map); + + // @formatter:off + Map expected = + Map.of( + "cmp.abc", "cmp.def", + "cmp.ghi", + Arrays.asList("cmp.list1", "cmp.list2", + Map.of("cmp.map", Arrays.asList("cmp.map.list1", "cmp.map1.list2")))); + // @formatter:on + assertEquals(expected, map); + } + + /** + * Makes properties containing the specified key/value pairs. The property set returns + * names in the order listed. + * + * @return a new properties containing the specified key/value pairs + */ + private Properties makeProperties(String key1, String value1, String key2, String value2) { + // control the order in which the names are returned + List keyList = List.of(key1, key2); + + Set keySet = new AbstractSet<>() { + @Override + public Iterator iterator() { + return keyList.iterator(); + } + + @Override + public int size() { + return 2; + } + }; + + Properties props = new Properties() { + private static final long serialVersionUID = 1L; + + @Override + public Set stringPropertyNames() { + return keySet; + } + }; + + props.putAll(Map.of(key1, value1, key2, value2)); + + return props; } -} \ No newline at end of file +} -- cgit 1.2.3-korg