Merge "Add APPC-LCM actor"
authorRam Krishna Verma <ram_krishna.verma@bell.ca>
Thu, 5 Mar 2020 16:22:53 +0000 (16:22 +0000)
committerGerrit Code Review <gerrit@onap.org>
Thu, 5 Mar 2020 16:22:53 +0000 (16:22 +0000)
models-interactions/model-actors/actor.appc/src/main/java/org/onap/policy/controlloop/actor/appc/AppcActorServiceProvider.java
models-interactions/model-actors/actor.appc/src/test/java/org/onap/policy/controlloop/actor/appc/AppcServiceProviderTest.java
models-interactions/model-actors/actor.appclcm/pom.xml
models-interactions/model-actors/actor.appclcm/src/main/java/org/onap/policy/controlloop/actor/appclcm/AppcLcmActorServiceProvider.java
models-interactions/model-actors/actor.appclcm/src/main/java/org/onap/policy/controlloop/actor/appclcm/AppcLcmOperation.java [new file with mode: 0644]
models-interactions/model-actors/actor.appclcm/src/main/java/org/onap/policy/controlloop/actor/appclcm/ConfigModifyOperation.java [new file with mode: 0644]
models-interactions/model-actors/actor.appclcm/src/test/java/org/onap/policy/controlloop/actor/appclcm/AppcLcmActorServiceProviderTest.java
models-interactions/model-actors/actor.appclcm/src/test/java/org/onap/policy/controlloop/actor/appclcm/AppcLcmOperationTest.java [new file with mode: 0644]
models-interactions/model-actors/actor.appclcm/src/test/resources/configModify.json [new file with mode: 0644]

index 117bd5c..0a03957 100644 (file)
@@ -81,15 +81,6 @@ public class AppcActorServiceProvider extends BidirectionalTopicActor<Bidirectio
                         ModifyConfigOperation::new));
     }
 
-    /**
-     * This actor should take precedence over APPC-LCM.
-     */
-    @Override
-    public int getSequenceNumber() {
-        return -1;
-    }
-
-
     // TODO old code: remove lines down to **HERE**
 
     @Override
index a2bf018..068f0b5 100644 (file)
@@ -133,7 +133,7 @@ public class AppcServiceProviderTest extends BasicActor {
     @Test
     public void testConstructor() {
         AppcActorServiceProvider prov = new AppcActorServiceProvider();
-        assertEquals(-1, prov.getSequenceNumber());
+        assertEquals(0, prov.getSequenceNumber());
 
         // verify that it has the operators we expect
         var expected = Arrays.asList(ModifyConfigOperation.NAME).stream().sorted().collect(Collectors.toList());
index 16c5575..7538d66 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0"?>
 <!--
   ============LICENSE_START=======================================================
-  Copyright (C) 2017-2018 AT&T Intellectual Property. All rights reserved.
+  Copyright (C) 2017-2018, 2020 AT&T Intellectual Property. All rights reserved.
   Modifications Copyright (C) 2019 Nordix Foundation.
   ================================================================================
   Licensed under the Apache License, Version 2.0 (the "License");
       <artifactId>junit</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.powermock</groupId>
+      <artifactId>powermock-api-mockito2</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 </project>
index 47898f7..1d04cb5 100644 (file)
@@ -5,6 +5,7 @@
  * Copyright (C) 2017-2020 AT&T Intellectual Property. All rights reserved.
  * Modifications copyright (c) 2018 Nokia
  * Modifications Copyright (C) 2019 Nordix Foundation.
+ * Modifications 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.
@@ -38,14 +39,22 @@ import org.onap.policy.appclcm.AppcLcmOutput;
 import org.onap.policy.appclcm.AppcLcmResponseCode;
 import org.onap.policy.controlloop.ControlLoopOperation;
 import org.onap.policy.controlloop.VirtualControlLoopEvent;
-import org.onap.policy.controlloop.actorserviceprovider.impl.ActorImpl;
+import org.onap.policy.controlloop.actorserviceprovider.impl.BidirectionalTopicActor;
+import org.onap.policy.controlloop.actorserviceprovider.impl.BidirectionalTopicOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.BidirectionalTopicActorParams;
 import org.onap.policy.controlloop.policy.Policy;
 import org.onap.policy.controlloop.policy.PolicyResult;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class AppcLcmActorServiceProvider extends ActorImpl {
+public class AppcLcmActorServiceProvider extends BidirectionalTopicActor<BidirectionalTopicActorParams> {
 
+    /*
+     * Confirmed by Daniel, should be 'APPC'.
+     * The actor name defined in the yaml for both legacy operations and lcm operations is still “APPC”. Perhaps in a
+     * future review it would be better to distinguish them as two separate actors in the yaml but it should be okay for
+     * now.
+     */
     private static final String NAME = "APPC";
 
     private static final Logger logger = LoggerFactory.getLogger(AppcLcmActorServiceProvider.class);
@@ -76,8 +85,22 @@ public class AppcLcmActorServiceProvider extends ActorImpl {
             new ImmutableMap.Builder<String, List<String>>().put(RECIPE_RESTART, ImmutableList.of(APPC_VM_ID))
                     .put(RECIPE_MODIFY, ImmutableList.of(APPC_REQUEST_PARAMS, APPC_CONFIG_PARAMS)).build();
 
+    /**
+     * Constructs the object.
+     */
     public AppcLcmActorServiceProvider() {
-        super(NAME);
+        super(NAME, BidirectionalTopicActorParams.class);
+
+        addOperator(new BidirectionalTopicOperator(NAME, ConfigModifyOperation.NAME, this,
+                AppcLcmOperation.SELECTOR_KEYS, ConfigModifyOperation::new));
+    }
+
+    /**
+     * This actor should take precedence.
+     */
+    @Override
+    public int getSequenceNumber() {
+        return -1;
     }
 
     @Override
@@ -100,7 +123,6 @@ public class AppcLcmActorServiceProvider extends ActorImpl {
         return ImmutableList.copyOf(payloads.getOrDefault(recipe, Collections.emptyList()));
     }
 
-
     /**
      * Constructs an APPC request conforming to the lcm API. The actual request is constructed and
      * then placed in a wrapper object used to send through DMAAP.
@@ -191,8 +213,8 @@ public class AppcLcmActorServiceProvider extends ActorImpl {
 
     private static String parsePayload(Map<String, String> payload) {
         StringBuilder payloadString = new StringBuilder("{");
-        payload
-            .forEach((key, value) -> payloadString.append("\"").append(key).append("\": ").append(value).append(","));
+        payload.forEach(
+            (key, value) -> payloadString.append("\"").append(key).append("\": ").append(value).append(","));
         return payloadString.substring(0, payloadString.length() - 1) + "}";
     }
 
diff --git a/models-interactions/model-actors/actor.appclcm/src/main/java/org/onap/policy/controlloop/actor/appclcm/AppcLcmOperation.java b/models-interactions/model-actors/actor.appclcm/src/main/java/org/onap/policy/controlloop/actor/appclcm/AppcLcmOperation.java
new file mode 100644 (file)
index 0000000..c0b8331
--- /dev/null
@@ -0,0 +1,235 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * AppcLcmOperation
+ * ================================================================================
+ * 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.actor.appclcm;
+
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import org.onap.aai.domain.yang.GenericVnf;
+import org.onap.policy.aai.AaiConstants;
+import org.onap.policy.aai.AaiCqResponse;
+import org.onap.policy.appclcm.AppcLcmBody;
+import org.onap.policy.appclcm.AppcLcmCommonHeader;
+import org.onap.policy.appclcm.AppcLcmDmaapWrapper;
+import org.onap.policy.appclcm.AppcLcmInput;
+import org.onap.policy.appclcm.AppcLcmResponseCode;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.impl.BidirectionalTopicOperation;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.BidirectionalTopicConfig;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.topic.SelectorKey;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AppcLcmOperation extends BidirectionalTopicOperation<AppcLcmDmaapWrapper, AppcLcmDmaapWrapper> {
+
+    private static final Logger logger = LoggerFactory.getLogger(AppcLcmOperation.class);
+    private static final StandardCoder coder = new StandardCoder();
+    public static final String VNF_ID_KEY = "vnf-id";
+
+    /**
+     * Keys used to match the response with the request listener. The sub request ID is a
+     * UUID, so it can be used to uniquely identify the response.
+     * <p/>
+     * Note: if these change, then {@link #getExpectedKeyValues(int, Request)} must be
+     * updated accordingly.
+     */
+    public static final List<SelectorKey> SELECTOR_KEYS = List.of(new SelectorKey("common-header", "sub-request-id"));
+
+    /**
+     * Constructs the object.
+     *
+     * @param params operation parameters
+     * @param config configuration for this operation
+     */
+    public AppcLcmOperation(ControlLoopOperationParams params, BidirectionalTopicConfig config) {
+        super(params, config, AppcLcmDmaapWrapper.class);
+    }
+
+    /**
+     * Ensures that A&AI customer query has been performed, and then runs the guard query.
+     * Starts the GUARD using startGuardAsync.
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
+        if (params != null) {
+            ControlLoopOperationParams cqParams = params.toBuilder().actor(AaiConstants.ACTOR_NAME)
+                    .operation(AaiCqResponse.OPERATION).payload(null).retry(null).timeoutSec(null).build();
+
+            // run Custom Query and Guard, in parallel
+            return allOf(() -> params.getContext().obtain(AaiCqResponse.CONTEXT_KEY, cqParams), this::startGuardAsync);
+        }
+        return startGuardAsync();
+    }
+
+    @Override
+    protected AppcLcmDmaapWrapper makeRequest(int attempt) {
+        AaiCqResponse cq = params.getContext().getProperty(AaiCqResponse.CONTEXT_KEY);
+
+        GenericVnf genvnf = cq.getGenericVnfByModelInvariantId(params.getTarget().getResourceID());
+        if (genvnf == null) {
+            logger.info("{}: target entity could not be found for {}", getFullName(), params.getRequestId());
+            throw new IllegalArgumentException("target vnf-id could not be found");
+        }
+
+        return makeRequest(attempt, genvnf.getVnfId());
+    }
+
+    /**
+     * Makes a request, given the target VNF. This is a support function for
+     * {@link #makeRequest(int)}.
+     *
+     * @param attempt attempt number
+     * @param targetVnf target VNF
+     * @return a new request
+     */
+    protected AppcLcmDmaapWrapper makeRequest(int attempt, String targetVnf) {
+        VirtualControlLoopEvent onset = params.getContext().getEvent();
+        String subRequestId = UUID.randomUUID().toString();
+
+        AppcLcmCommonHeader header = new AppcLcmCommonHeader();
+        header.setOriginatorId(onset.getRequestId().toString());
+        header.setRequestId(onset.getRequestId());
+        header.setSubRequestId(subRequestId);
+
+        AppcLcmInput inputRequest = new AppcLcmInput();
+        inputRequest.setCommonHeader(header);
+        inputRequest.setAction(getName());
+
+        /*
+         * Action Identifiers are required for APPC LCM requests. For R1, the recipes supported by
+         * Policy only require a vnf-id.
+         */
+        if (inputRequest.getActionIdentifiers() != null) {
+            inputRequest.getActionIdentifiers().put(VNF_ID_KEY, targetVnf);
+        } else {
+            inputRequest.setActionIdentifiers(Map.of(VNF_ID_KEY, targetVnf));
+        }
+
+        /*
+         * For R1, the payloads will not be required for the Restart, Rebuild, or Migrate recipes.
+         * APPC will populate the payload based on A&AI look up of the vnd-id provided in the action
+         * identifiers. The payload is set when converPayload() is called.
+         */
+        if (operationSupportsPayload()) {
+            convertPayload(params.getPayload(), inputRequest);
+        } else {
+            inputRequest.setPayload(null);
+        }
+
+        AppcLcmBody body = new AppcLcmBody();
+        body.setInput(inputRequest);
+
+        AppcLcmRecipeFormatter recipeFormatter = new AppcLcmRecipeFormatter(getName());
+        inputRequest.setAction(recipeFormatter.getBodyRecipe());
+
+        AppcLcmDmaapWrapper dmaapRequest = new AppcLcmDmaapWrapper();
+        dmaapRequest.setBody(body);
+        dmaapRequest.setVersion("2.0");
+        dmaapRequest.setCorrelationId(onset.getRequestId() + "-" + subRequestId);
+        dmaapRequest.setRpcName(recipeFormatter.getUrlRecipe());
+        dmaapRequest.setType("request");
+
+        body.setInput(inputRequest);
+        dmaapRequest.setBody(body);
+        return dmaapRequest;
+    }
+
+    /**
+     * Converts a payload. The original value is assumed to be a JSON string, which is
+     * decoded into an object.
+     *
+     * @param source source from which to get the values
+     * @param map where to place the decoded values
+     */
+    private static void convertPayload(Map<String, Object> source, AppcLcmInput request) {
+        String encodedPayloadString = null;
+        try {
+            encodedPayloadString = coder.encode(source);
+            request.setPayload(encodedPayloadString);
+        } catch (CoderException e) {
+            logger.error("Cannot convert payload. Error encoding source as a string.", e);
+            throw new IllegalArgumentException("Cannot convert payload. Error encoding source as a string.");
+        }
+    }
+
+    /**
+     * Note: these values must match {@link #SELECTOR_KEYS}.
+     */
+    @Override
+    protected List<String> getExpectedKeyValues(int attempt, AppcLcmDmaapWrapper request) {
+        return List.of(request.getBody().getInput().getCommonHeader().getSubRequestId());
+    }
+
+    @Override
+    protected Status detmStatus(String rawResponse, AppcLcmDmaapWrapper response) {
+        if (response == null || response.getBody() == null || response.getBody().getOutput() == null
+                || response.getBody().getOutput().getStatus() == null) {
+            throw new IllegalArgumentException("APPC-LCM response is missing the response status");
+        }
+
+        String code = AppcLcmResponseCode.toResponseValue(response.getBody().getOutput().getStatus().getCode());
+
+        if (code == null) {
+            throw new IllegalArgumentException(
+                    "unknown APPC-LCM response status code: " + response.getBody().getOutput().getStatus().getCode());
+        }
+
+        switch (code) {
+            case AppcLcmResponseCode.SUCCESS:
+                return Status.SUCCESS;
+            case AppcLcmResponseCode.FAILURE:
+                return Status.FAILURE;
+            case AppcLcmResponseCode.ERROR:
+            case AppcLcmResponseCode.REJECT:
+                throw new IllegalArgumentException("APPC-LCM request was not accepted, code=" + code);
+            case AppcLcmResponseCode.ACCEPTED:
+            default:
+                return Status.STILL_WAITING;
+        }
+    }
+
+    /**
+     * Sets the message to the status description, if available.
+     */
+    @Override
+    public OperationOutcome setOutcome(OperationOutcome outcome, PolicyResult result, AppcLcmDmaapWrapper response) {
+        if (response == null || response.getBody() == null || response.getBody().getOutput() == null
+                || response.getBody().getOutput().getStatus() == null
+                || response.getBody().getOutput().getStatus().getMessage() == null) {
+            return setOutcome(outcome, result);
+        }
+
+        outcome.setResult(result);
+        outcome.setMessage(response.getBody().getOutput().getStatus().getMessage());
+        return outcome;
+    }
+
+    protected boolean operationSupportsPayload() {
+        return params.getPayload() != null && !params.getPayload().isEmpty();
+    }
+}
diff --git a/models-interactions/model-actors/actor.appclcm/src/main/java/org/onap/policy/controlloop/actor/appclcm/ConfigModifyOperation.java b/models-interactions/model-actors/actor.appclcm/src/main/java/org/onap/policy/controlloop/actor/appclcm/ConfigModifyOperation.java
new file mode 100644 (file)
index 0000000..02645af
--- /dev/null
@@ -0,0 +1,43 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * AppcLcmOperation
+ * ================================================================================
+ * 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.actor.appclcm;
+
+import org.onap.policy.controlloop.actorserviceprovider.parameters.BidirectionalTopicConfig;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ConfigModifyOperation extends AppcLcmOperation {
+
+    private static final Logger logger = LoggerFactory.getLogger(ConfigModifyOperation.class);
+
+    public static final String NAME = "ConfigModify";
+
+    /**
+     * Constructs the object.
+     *
+     * @param params operation parameters
+     * @param config configuration for this operation
+     */
+    public ConfigModifyOperation(ControlLoopOperationParams params, BidirectionalTopicConfig config) {
+        super(params, config);
+    }
+}
index ef69bc1..90fc8a5 100644 (file)
@@ -27,9 +27,10 @@ import static org.junit.Assert.assertNull;
 
 import java.time.Instant;
 import java.util.AbstractMap;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.UUID;
-
+import java.util.stream.Collectors;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -174,6 +175,18 @@ public class AppcLcmActorServiceProviderTest {
         HttpServletServerFactoryInstance.getServerFactory().destroy();
     }
 
+    @Test
+    public void testConstructor() {
+        AppcLcmActorServiceProvider prov = new AppcLcmActorServiceProvider();
+        assertEquals(-1, prov.getSequenceNumber());
+
+        // verify that it has the operators we expect
+        var expected = Arrays.asList(ConfigModifyOperation.NAME).stream().sorted().collect(Collectors.toList());
+        var actual = prov.getOperationNames().stream().sorted().collect(Collectors.toList());
+
+        assertEquals(expected.toString(), actual.toString());
+    }
+
     /**
      * A test to construct an APPC LCM restart request.
      */
diff --git a/models-interactions/model-actors/actor.appclcm/src/test/java/org/onap/policy/controlloop/actor/appclcm/AppcLcmOperationTest.java b/models-interactions/model-actors/actor.appclcm/src/test/java/org/onap/policy/controlloop/actor/appclcm/AppcLcmOperationTest.java
new file mode 100644 (file)
index 0000000..d2af4e7
--- /dev/null
@@ -0,0 +1,192 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * AppcLcmOperation
+ * ================================================================================
+ * 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.actor.appclcm;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeast;
+
+import java.util.List;
+import java.util.UUID;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.onap.policy.appclcm.AppcLcmBody;
+import org.onap.policy.appclcm.AppcLcmCommonHeader;
+import org.onap.policy.appclcm.AppcLcmDmaapWrapper;
+import org.onap.policy.appclcm.AppcLcmInput;
+import org.onap.policy.appclcm.AppcLcmOutput;
+import org.onap.policy.appclcm.AppcLcmResponseStatus;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.impl.BidirectionalTopicOperation.Status;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.powermock.reflect.Whitebox;
+
+public class AppcLcmOperationTest {
+    private AppcLcmInput mockInput;
+    private AppcLcmOutput mockOutput;
+    private AppcLcmBody mockBody;
+    private AppcLcmDmaapWrapper mockInputWrapper;
+    private AppcLcmDmaapWrapper mockOutputWrapper;
+    private OperationOutcome mockOperationOutcome;
+    private AppcLcmOperation operation;
+    private AppcLcmResponseStatus mockResponseStatus;
+    private AppcLcmCommonHeader mockCommonHeader;
+    private ControlLoopOperationParams mockParams;
+    private ControlLoopEventContext mockContext;
+    private VirtualControlLoopEvent mockEvent;
+
+    /**
+     * Setup mocks for testing.
+     */
+    @Before
+    public void setup() {
+        mockInput = Mockito.mock(AppcLcmInput.class);
+        mockOutput = Mockito.mock(AppcLcmOutput.class);
+        mockBody = Mockito.mock(AppcLcmBody.class);
+        mockContext = Mockito.mock(ControlLoopEventContext.class);
+        mockEvent = Mockito.mock(VirtualControlLoopEvent.class);
+        mockInputWrapper = Mockito.mock(AppcLcmDmaapWrapper.class);
+        mockOutputWrapper = Mockito.mock(AppcLcmDmaapWrapper.class);
+        mockOperationOutcome = Mockito.mock(OperationOutcome.class);
+        mockResponseStatus = Mockito.mock(AppcLcmResponseStatus.class);
+        mockCommonHeader = Mockito.mock(AppcLcmCommonHeader.class);
+        mockParams = Mockito.mock(ControlLoopOperationParams.class);
+        operation = Mockito.mock(AppcLcmOperation.class);
+    }
+
+    @Test
+    public void testStartPreprocessorAsync() {
+        Mockito.doCallRealMethod().when(operation).startPreprocessorAsync();
+        assertNull(operation.startPreprocessorAsync());
+    }
+
+    @Ignore
+    @Test
+    public void testMakeRequest() {
+        UUID randomId = UUID.randomUUID();
+        Mockito.doCallRealMethod().when(operation).makeRequest(1, "sampleTargetVnf");
+        Mockito.when(mockParams.getRequestId()).thenReturn(randomId);
+        Mockito.when(mockParams.getPayload()).thenReturn(null);
+        Mockito.when(mockParams.getContext()).thenReturn(mockContext);
+        Mockito.when(mockParams.getOperation()).thenReturn("Config-Modify");
+        Mockito.when(mockContext.getEvent()).thenReturn(mockEvent);
+        Mockito.when(mockEvent.getRequestId()).thenReturn(randomId);
+        Whitebox.setInternalState(operation, "params", mockParams);
+        assertNotNull(operation.makeRequest(1, "sampleTargetVnf"));
+        Mockito.verify(mockParams, atLeast(1)).getRequestId();
+        Mockito.verify(mockParams, atLeast(1)).getPayload();
+        Mockito.verify(mockParams, atLeast(1)).getContext();
+        Mockito.verify(mockContext, atLeast(1)).getEvent();
+        Mockito.verify(mockEvent, atLeast(1)).getRequestId();
+    }
+
+    @Test
+    public void testGetExpectedKeyValues() {
+        Mockito.doCallRealMethod().when(operation).getExpectedKeyValues(1, mockInputWrapper);
+        Mockito.when(mockInputWrapper.getBody()).thenReturn(mockBody);
+        Mockito.when(mockBody.getInput()).thenReturn(mockInput);
+        Mockito.when(mockInput.getCommonHeader()).thenReturn(mockCommonHeader);
+        Mockito.when(mockCommonHeader.getSubRequestId()).thenReturn("sampleSubRequestId");
+
+        List<String> retList = operation.getExpectedKeyValues(1, mockInputWrapper);
+        assertNotNull(retList);
+        assertEquals(1, retList.size());
+
+        Mockito.verify(mockInputWrapper, atLeast(1)).getBody();
+        Mockito.verify(mockBody, atLeast(1)).getInput();
+        Mockito.verify(mockInput, atLeast(1)).getCommonHeader();
+        Mockito.verify(mockCommonHeader, atLeast(1)).getSubRequestId();
+    }
+
+    @Test
+    public void testDetmStatus() {
+        Mockito.doCallRealMethod().when(operation).detmStatus("testResponse", mockOutputWrapper);
+        Mockito.when(mockOutputWrapper.getBody()).thenReturn(mockBody);
+        Mockito.when(mockBody.getOutput()).thenReturn(mockOutput);
+        Mockito.when(mockOutput.getStatus()).thenReturn(mockResponseStatus);
+        Mockito.when(mockResponseStatus.getCode()).thenReturn(100);
+        Status retStatus = operation.detmStatus("testResponse", mockOutputWrapper);
+        assertEquals(Status.STILL_WAITING, retStatus);
+
+        Mockito.when(mockResponseStatus.getCode()).thenReturn(400);
+        retStatus = operation.detmStatus("testResponse", mockOutputWrapper);
+        assertEquals(Status.SUCCESS, retStatus);
+
+        Mockito.when(mockResponseStatus.getCode()).thenReturn(450);
+        retStatus = operation.detmStatus("testResponse", mockOutputWrapper);
+        assertEquals(Status.FAILURE, retStatus);
+
+        Mockito.when(mockOutput.getStatus()).thenReturn(null);
+        assertThatIllegalArgumentException().isThrownBy(() -> operation.detmStatus("testResponse", mockOutputWrapper));
+
+        Mockito.when(mockResponseStatus.getCode()).thenReturn(200);
+        assertThatIllegalArgumentException().isThrownBy(() -> operation.detmStatus("testResponse", mockOutputWrapper));
+
+        Mockito.verify(mockOutputWrapper, atLeast(1)).getBody();
+        Mockito.verify(mockBody, atLeast(1)).getOutput();
+        Mockito.verify(mockOutput, atLeast(1)).getStatus();
+        Mockito.verify(mockResponseStatus, atLeast(1)).getCode();
+    }
+
+    @Test
+    public void testSetOutcome() {
+        Mockito.doCallRealMethod().when(operation).setOutcome(mockOperationOutcome, PolicyResult.SUCCESS,
+                mockOutputWrapper);
+        Mockito.doCallRealMethod().when(operation).setOutcome(mockOperationOutcome, PolicyResult.FAILURE,
+                mockOutputWrapper);
+
+        Mockito.doCallRealMethod().when(mockOperationOutcome).setResult(any(PolicyResult.class));
+        Mockito.doCallRealMethod().when(mockOperationOutcome).setMessage(any(String.class));
+        Mockito.doCallRealMethod().when(mockOperationOutcome).getResult();
+        Mockito.doCallRealMethod().when(mockOperationOutcome).getMessage();
+
+        Mockito.when(mockOutputWrapper.getBody()).thenReturn(mockBody);
+        Mockito.when(mockBody.getOutput()).thenReturn(mockOutput);
+        Mockito.when(mockOutput.getStatus()).thenReturn(mockResponseStatus);
+        Mockito.when(mockResponseStatus.getMessage()).thenReturn(null);
+
+        OperationOutcome result = operation.setOutcome(mockOperationOutcome, PolicyResult.SUCCESS, mockOutputWrapper);
+        assertNull(result);
+        Mockito.verify(operation).setOutcome(mockOperationOutcome, PolicyResult.SUCCESS, mockOutputWrapper);
+
+        Mockito.when(mockOutput.getStatus()).thenReturn(mockResponseStatus);
+        Mockito.when(mockResponseStatus.getMessage()).thenReturn("sampleMessage");
+        result = operation.setOutcome(mockOperationOutcome, PolicyResult.FAILURE, mockOutputWrapper);
+        assertEquals(PolicyResult.FAILURE, result.getResult());
+        assertNotNull(result.getMessage());
+
+        Mockito.verify(mockOutputWrapper, atLeast(1)).getBody();
+        Mockito.verify(mockBody, atLeast(1)).getOutput();
+        Mockito.verify(mockOutput, atLeast(1)).getStatus();
+        Mockito.verify(mockResponseStatus, atLeast(1)).getMessage();
+        Mockito.verify(operation, atLeast(1)).setOutcome(mockOperationOutcome, PolicyResult.SUCCESS, mockOutputWrapper);
+        Mockito.verify(operation, atLeast(1)).setOutcome(mockOperationOutcome, PolicyResult.FAILURE, mockOutputWrapper);
+    }
+
+}
diff --git a/models-interactions/model-actors/actor.appclcm/src/test/resources/configModify.json b/models-interactions/model-actors/actor.appclcm/src/test/resources/configModify.json
new file mode 100644 (file)
index 0000000..2e75b29
--- /dev/null
@@ -0,0 +1,22 @@
+{
+    "common-header": {
+        "timestamp": 981593700981,
+        "api-ver": "2.0",
+        "originator-id": "2d011587-a311-45e6-a75d-67fcd3dfae1a",
+        "request-id": "9a06c485-ebf1-4780-a183-6a1d862eebeb",
+        "flags": {
+            "mode": "NORMAL",
+            "force": "FALSE"
+        }
+    },
+    "action": "configModify",
+    "action-identifiers": {
+        "vnf-id": "sampleVNFId",
+        "vnfc-name": "sampleVNFCName",
+        "vserver-id": "sampleServerId"
+    },
+    "payload": {
+        "vnf-host-ip-address": "127.0.0.1",
+        "generic-vnf.vnf-id": "my-vnf"
+    }
+}
\ No newline at end of file