Merge "Implement validation and hierarchical get"
authorPamela Dragosh <pdragosh@research.att.com>
Wed, 12 Feb 2020 19:27:42 +0000 (19:27 +0000)
committerGerrit Code Review <gerrit@onap.org>
Wed, 12 Feb 2020 19:27:42 +0000 (19:27 +0000)
49 files changed:
models-interactions/model-actors/actor.sdnc/pom.xml
models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperation.java [moved from models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperator.java with 88% similarity]
models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperation.java [moved from models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperator.java with 85% similarity]
models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProvider.java
models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperation.java [new file with mode: 0644]
models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperator.java [deleted file]
models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperatorTest.java
models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicOperator.java [deleted file]
models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicSdncOperator.java [new file with mode: 0644]
models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperatorTest.java
models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProviderTest.java
models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncOperatorTest.java
models-interactions/model-actors/actor.test/pom.xml [new file with mode: 0644]
models-interactions/model-actors/actor.test/src/main/java/org/onap/policy/controlloop/actor/test/BasicHttpOperation.java [new file with mode: 0644]
models-interactions/model-actors/actor.test/src/test/java/org/onap/policy/controlloop/actor/test/BasicHttpOperationTest.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandler.java [deleted file]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operation.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operator.java
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Util.java
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContext.java
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImpl.java
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperation.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperator.java
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartial.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartial.java
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParams.java
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParams.java
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParams.java
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/spi/Actor.java
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandlerTest.java [deleted file]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/UtilTest.java
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContextTest.java
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImplTest.java
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpActorTest.java
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperationTest.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperatorTest.java
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartialTest.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartialTest.java
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParamsTest.java
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParamsTest.java
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParamsTest.java
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/PipelineControllerFutureTest.java
models-interactions/model-actors/actorServiceProvider/src/test/resources/logback-test.xml
models-interactions/model-actors/pom.xml
models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/AaiConstants.java [new file with mode: 0644]
models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/AaiCqResponse.java
models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/AaiManager.java
models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/util/AaiException.java
models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/util/Serialization.java

index 04040db..4bb03ec 100644 (file)
   ============LICENSE_END=========================================================
   -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <modelVersion>4.0.0</modelVersion>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
 
-  <parent>
-   <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
-    <artifactId>model-actors</artifactId>
-    <version>2.2.1-SNAPSHOT</version>
-  </parent>
+    <parent>
+        <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+        <artifactId>model-actors</artifactId>
+        <version>2.2.1-SNAPSHOT</version>
+    </parent>
 
-  <artifactId>actor.sdnc</artifactId>
+    <artifactId>actor.sdnc</artifactId>
 
-  <dependencies>
-    <dependency>
-     <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
-      <artifactId>actorServiceProvider</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
-      <artifactId>sdnc</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
-      <artifactId>events</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
-      <artifactId>aai</artifactId>
-      <version>${project.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.onap.policy.common</groupId>
-      <artifactId>policy-endpoints</artifactId>
-      <version>${policy.common.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.onap.policy.models.policy-models-interactions</groupId>
-      <artifactId>simulators</artifactId>
-      <version>${project.version}</version>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
+    <dependencies>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+            <artifactId>actorServiceProvider</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+            <artifactId>sdnc</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+            <artifactId>events</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+            <artifactId>aai</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.common</groupId>
+            <artifactId>policy-endpoints</artifactId>
+            <version>${policy.common.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+            <artifactId>actor.test</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions</groupId>
+            <artifactId>simulators</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-api-mockito2</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
 </project>
@@ -23,6 +23,8 @@ package org.onap.policy.controlloop.actor.sdnc;
 import java.util.UUID;
 import org.apache.commons.lang3.StringUtils;
 import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
 import org.onap.policy.sdnc.SdncHealRequest;
 import org.onap.policy.sdnc.SdncHealRequestHeaderInfo;
 import org.onap.policy.sdnc.SdncHealRequestInfo;
@@ -34,7 +36,7 @@ import org.onap.policy.sdnc.SdncHealVfModuleRequestInput;
 import org.onap.policy.sdnc.SdncHealVnfInfo;
 import org.onap.policy.sdnc.SdncRequest;
 
-public class BandwidthOnDemandOperator extends SdncOperator {
+public class BandwidthOnDemandOperation extends SdncOperation {
     public static final String NAME = "BandwidthOnDemand";
 
     public static final String URI = "/GENERIC-RESOURCE-API:vf-module-topology-operation";
@@ -46,14 +48,17 @@ public class BandwidthOnDemandOperator extends SdncOperator {
     /**
      * Constructs the object.
      *
-     * @param actorName name of the actor with which this operator is associated
+     * @param params operation parameters
+     * @param operator operator that created this operation
      */
-    public BandwidthOnDemandOperator(String actorName) {
-        super(actorName, NAME);
+    public BandwidthOnDemandOperation(ControlLoopOperationParams params, HttpOperator operator) {
+        super(params, operator);
     }
 
     @Override
-    protected SdncRequest constructRequest(ControlLoopEventContext context) {
+    protected SdncRequest makeRequest(int attempt) {
+        ControlLoopEventContext context = params.getContext();
+
         String serviceInstance = context.getEnrichment().get(SERVICE_ID_KEY);
         if (StringUtils.isBlank(serviceInstance)) {
             throw new IllegalArgumentException("missing enrichment data, " + SERVICE_ID_KEY);
@@ -23,6 +23,8 @@ package org.onap.policy.controlloop.actor.sdnc;
 import java.util.UUID;
 import org.apache.commons.lang3.StringUtils;
 import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
 import org.onap.policy.sdnc.SdncHealNetworkInfo;
 import org.onap.policy.sdnc.SdncHealRequest;
 import org.onap.policy.sdnc.SdncHealRequestHeaderInfo;
@@ -30,7 +32,7 @@ import org.onap.policy.sdnc.SdncHealRequestInfo;
 import org.onap.policy.sdnc.SdncHealServiceInfo;
 import org.onap.policy.sdnc.SdncRequest;
 
-public class RerouteOperator extends SdncOperator {
+public class RerouteOperation extends SdncOperation {
     public static final String NAME = "Reroute";
 
     public static final String URI = "/GENERIC-RESOURCE-API:network-topology-operation";
@@ -42,14 +44,17 @@ public class RerouteOperator extends SdncOperator {
     /**
      * Constructs the object.
      *
-     * @param actorName name of the actor with which this operator is associated
+     * @param params operation parameters
+     * @param operator operator that created this operation
      */
-    public RerouteOperator(String actorName) {
-        super(actorName, NAME);
+    public RerouteOperation(ControlLoopOperationParams params, HttpOperator operator) {
+        super(params, operator);
     }
 
     @Override
-    protected SdncRequest constructRequest(ControlLoopEventContext context) {
+    protected SdncRequest makeRequest(int attempt) {
+        ControlLoopEventContext context = params.getContext();
+
         String serviceInstance = context.getEnrichment().get(SERVICE_ID_KEY);
         if (StringUtils.isBlank(serviceInstance)) {
             throw new IllegalArgumentException("missing enrichment data, " + SERVICE_ID_KEY);
index 8dc8ba5..99a4fda 100644 (file)
@@ -30,6 +30,7 @@ import java.util.UUID;
 import org.onap.policy.controlloop.ControlLoopOperation;
 import org.onap.policy.controlloop.VirtualControlLoopEvent;
 import org.onap.policy.controlloop.actorserviceprovider.impl.HttpActor;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
 import org.onap.policy.controlloop.policy.Policy;
 import org.onap.policy.sdnc.SdncHealNetworkInfo;
 import org.onap.policy.sdnc.SdncHealRequest;
@@ -76,8 +77,11 @@ public class SdncActorServiceProvider extends HttpActor {
     public SdncActorServiceProvider() {
         super(NAME);
 
-        addOperator(new RerouteOperator(NAME));
-        addOperator(new BandwidthOnDemandOperator(NAME));
+        addOperator(HttpOperator.makeOperator(NAME, RerouteOperation.NAME,
+                        RerouteOperation::new));
+
+        addOperator(HttpOperator.makeOperator(NAME, BandwidthOnDemandOperation.NAME,
+                        BandwidthOnDemandOperation::new));
     }
 
 
diff --git a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperation.java b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperation.java
new file mode 100644 (file)
index 0000000..9d42c49
--- /dev/null
@@ -0,0 +1,85 @@
+/*-
+ * ============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.actor.sdnc;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperation;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.sdnc.SdncRequest;
+import org.onap.policy.sdnc.SdncResponse;
+
+/**
+ * Superclass for SDNC Operators.
+ */
+public abstract class SdncOperation extends HttpOperation<SdncResponse> {
+
+    /**
+     * Constructs the object.
+     *
+     * @param params operation parameters
+     * @param operator operator that created this operation
+     */
+    public SdncOperation(ControlLoopOperationParams params, HttpOperator operator) {
+        super(params, operator, SdncResponse.class);
+    }
+
+    @Override
+    protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+        SdncRequest request = makeRequest(attempt);
+
+        Entity<SdncRequest> entity = Entity.entity(request, MediaType.APPLICATION_JSON);
+
+        Map<String, Object> headers = makeHeaders();
+
+        headers.put("Accept", MediaType.APPLICATION_JSON);
+        String url = makeUrl();
+
+        logRestRequest(url, request);
+
+        // @formatter:off
+        return handleResponse(outcome, url,
+            callback -> operator.getClient().post(callback, makePath(), entity, headers));
+        // @formatter:on
+    }
+
+    /**
+     * Makes the request.
+     *
+     * @param attempt current attempt, starting with "1"
+     * @return a new request to be posted
+     */
+    protected abstract SdncRequest makeRequest(int attempt);
+
+    /**
+     * Checks that the response has an "output" and that the output indicates success.
+     */
+    @Override
+    protected boolean isSuccess(Response rawResponse, SdncResponse response) {
+        return response.getResponseOutput() != null && "200".equals(response.getResponseOutput().getResponseCode());
+    }
+}
diff --git a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperator.java b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperator.java
deleted file mode 100644 (file)
index 479ee90..0000000
+++ /dev/null
@@ -1,148 +0,0 @@
-/*-
- * ============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.actor.sdnc;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import org.onap.policy.common.endpoints.http.client.HttpClient;
-import org.onap.policy.common.utils.coder.CoderException;
-import org.onap.policy.common.utils.coder.StandardCoder;
-import org.onap.policy.controlloop.actorserviceprovider.AsyncResponseHandler;
-import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
-import org.onap.policy.controlloop.actorserviceprovider.Util;
-import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
-import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
-import org.onap.policy.controlloop.policy.PolicyResult;
-import org.onap.policy.sdnc.SdncRequest;
-import org.onap.policy.sdnc.SdncResponse;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Superclass for SDNC Operators.
- */
-public abstract class SdncOperator extends HttpOperator {
-    private static final Logger logger = LoggerFactory.getLogger(SdncOperator.class);
-
-    /**
-     * Constructs the object.
-     *
-     * @param actorName name of the actor with which this operator is associated
-     * @param name operation name
-     */
-    public SdncOperator(String actorName, String name) {
-        super(actorName, name);
-    }
-
-    @Override
-    protected CompletableFuture<OperationOutcome> startOperationAsync(ControlLoopOperationParams params, int attempt,
-                    OperationOutcome outcome) {
-
-        SdncRequest request = constructRequest(params.getContext());
-        return postRequest(params, outcome, request);
-    }
-
-    /**
-     * Constructs the request.
-     *
-     * @param context associated event context
-     * @return a new request
-     */
-    protected abstract SdncRequest constructRequest(ControlLoopEventContext context);
-
-    /**
-     * Posts the request and and arranges to retrieve the response.
-     *
-     * @param params operation parameters
-     * @param outcome updated with the response
-     * @param sdncRequest request to be posted
-     * @return the result of the request
-     */
-    private CompletableFuture<OperationOutcome> postRequest(ControlLoopOperationParams params, OperationOutcome outcome,
-                    SdncRequest sdncRequest) {
-        Map<String, Object> headers = new HashMap<>();
-
-        headers.put("Accept", "application/json");
-        String sdncUrl = getClient().getBaseUrl();
-
-        Util.logRestRequest(sdncUrl, sdncRequest);
-
-        Entity<SdncRequest> entity = Entity.entity(sdncRequest, MediaType.APPLICATION_JSON);
-
-        ResponseHandler handler = new ResponseHandler(params, outcome, sdncUrl);
-        return handler.handle(getClient().post(handler, getPath(), entity, headers));
-    }
-
-    private class ResponseHandler extends AsyncResponseHandler<Response> {
-        private final String sdncUrl;
-
-        public ResponseHandler(ControlLoopOperationParams params, OperationOutcome outcome, String sdncUrl) {
-            super(params, outcome);
-            this.sdncUrl = sdncUrl;
-        }
-
-        /**
-         * Handles the response.
-         */
-        @Override
-        protected OperationOutcome doComplete(Response rawResponse) {
-            String strResponse = HttpClient.getBody(rawResponse, String.class);
-
-            Util.logRestResponse(sdncUrl, strResponse);
-
-            SdncResponse response;
-            try {
-                response = makeDecoder().decode(strResponse, SdncResponse.class);
-            } catch (CoderException e) {
-                logger.warn("Sdnc Heal cannot decode response with http error code {}", rawResponse.getStatus(), e);
-                return SdncOperator.this.setOutcome(getParams(), getOutcome(), PolicyResult.FAILURE_EXCEPTION);
-            }
-
-            if (response.getResponseOutput() != null && "200".equals(response.getResponseOutput().getResponseCode())) {
-                return SdncOperator.this.setOutcome(getParams(), getOutcome(), PolicyResult.SUCCESS);
-
-            } else {
-                logger.info("Sdnc Heal Restcall failed with http error code {}", rawResponse.getStatus());
-                return SdncOperator.this.setOutcome(getParams(), getOutcome(), PolicyResult.FAILURE);
-            }
-        }
-
-        /**
-         * Handles exceptions.
-         */
-        @Override
-        protected OperationOutcome doFailed(Throwable thrown) {
-            logger.info("Sdnc Heal Restcall threw an exception", thrown);
-            return SdncOperator.this.setOutcome(getParams(), getOutcome(), PolicyResult.FAILURE_EXCEPTION);
-        }
-    }
-
-    // these may be overridden by junit tests
-
-    protected StandardCoder makeDecoder() {
-        return new StandardCoder();
-    }
-}
index 02931a4..0623df2 100644 (file)
@@ -26,45 +26,51 @@ import static org.junit.Assert.assertNotNull;
 import java.util.Map;
 import org.junit.Before;
 import org.junit.Test;
-import org.onap.policy.common.utils.coder.CoderException;
 import org.onap.policy.sdnc.SdncRequest;
 
-public class BandwidthOnDemandOperatorTest extends BasicOperator {
+public class BandwidthOnDemandOperatorTest extends BasicSdncOperator {
 
-    private BandwidthOnDemandOperator oper;
+    private BandwidthOnDemandOperation oper;
 
+    public BandwidthOnDemandOperatorTest() {
+        super(DEFAULT_ACTOR, BandwidthOnDemandOperation.NAME);
+    }
 
     /**
      * Set up.
      */
     @Before
-    public void setUp() {
-        makeContext();
-        oper = new BandwidthOnDemandOperator(ACTOR);
+    public void setUp() throws Exception {
+        super.setUp();
+        oper = new BandwidthOnDemandOperation(params, operator);
     }
 
     @Test
     public void testBandwidthOnDemandOperator() {
-        assertEquals(ACTOR, oper.getActorName());
-        assertEquals(BandwidthOnDemandOperator.NAME, oper.getName());
+        assertEquals(DEFAULT_ACTOR, oper.getActorName());
+        assertEquals(BandwidthOnDemandOperation.NAME, oper.getName());
     }
 
     @Test
-    public void testConstructRequest() throws CoderException {
-        SdncRequest request = oper.constructRequest(context);
+    public void testMakeRequest() throws Exception {
+        SdncRequest request = oper.makeRequest(1);
         assertEquals("my-service", request.getNsInstanceId());
         assertEquals(REQ_ID, request.getRequestId());
-        assertEquals(BandwidthOnDemandOperator.URI, request.getUrl());
+        assertEquals(BandwidthOnDemandOperation.URI, request.getUrl());
         assertNotNull(request.getHealRequest().getRequestHeaderInfo().getSvcRequestId());
 
         verifyRequest("bod.json", request);
 
-        verifyMissing(oper, BandwidthOnDemandOperator.SERVICE_ID_KEY, "service");
+        verifyMissing(BandwidthOnDemandOperation.SERVICE_ID_KEY, "service", BandwidthOnDemandOperation::new);
+
+        // perform the operation
+        makeContext();
+        verifyRequest("bod.json", verifyOperation(oper));
     }
 
     @Override
     protected Map<String, String> makeEnrichment() {
-        return Map.of(BandwidthOnDemandOperator.SERVICE_ID_KEY, "my-service", BandwidthOnDemandOperator.VNF_ID,
+        return Map.of(BandwidthOnDemandOperation.SERVICE_ID_KEY, "my-service", BandwidthOnDemandOperation.VNF_ID,
                         "my-vnf");
     }
 }
diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicOperator.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicOperator.java
deleted file mode 100644 (file)
index b9028d4..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-/*-
- * ============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.actor.sdnc;
-
-import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
-import static org.junit.Assert.assertEquals;
-
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.UUID;
-import org.onap.policy.common.utils.coder.CoderException;
-import org.onap.policy.common.utils.coder.StandardCoder;
-import org.onap.policy.common.utils.resources.ResourceUtils;
-import org.onap.policy.controlloop.VirtualControlLoopEvent;
-import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
-
-/**
- * Superclass for various operator tests.
- */
-public abstract class BasicOperator {
-    protected static final UUID REQ_ID = UUID.randomUUID();
-    protected static final String ACTOR = "my-actor";
-
-    protected Map<String, String> enrichment;
-    protected VirtualControlLoopEvent event;
-    protected ControlLoopEventContext context;
-
-    /**
-     * Pretty-prints a request and verifies that the result matches the expected JSON.
-     *
-     * @param <T> request type
-     * @param expectedJsonFile name of the file containing the expected JSON
-     * @param request request to verify
-     * @throws CoderException if the request cannot be pretty-printed
-     */
-    protected <T> void verifyRequest(String expectedJsonFile, T request) throws CoderException {
-        String json = new StandardCoder().encode(request, true);
-        String expected = ResourceUtils.getResourceAsString(expectedJsonFile);
-
-        // strip request id, because it changes each time
-        final String stripper = "svc-request-id[^,]*";
-        json = json.replaceFirst(stripper, "").trim();
-        expected = expected.replaceFirst(stripper, "").trim();
-
-        assertEquals(expected, json);
-    }
-
-    /**
-     * Verifies that an exception is thrown if a field is missing from the enrichment
-     * data.
-     *
-     * @param oper operator to construct the request
-     * @param fieldName name of the field to be removed from the enrichment data
-     * @param expectedText text expected in the exception message
-     */
-    protected void verifyMissing(SdncOperator oper, String fieldName, String expectedText) {
-        makeContext();
-        enrichment.remove(fieldName);
-
-        assertThatIllegalArgumentException().isThrownBy(() -> oper.constructRequest(context))
-                        .withMessageContaining("missing").withMessageContaining(expectedText);
-    }
-
-    protected void makeContext() {
-        // need a mutable map, so make a copy
-        enrichment = new TreeMap<>(makeEnrichment());
-
-        event = new VirtualControlLoopEvent();
-        event.setRequestId(REQ_ID);
-        event.setAai(enrichment);
-
-        context = new ControlLoopEventContext(event);
-    }
-
-    protected abstract Map<String, String> makeEnrichment();
-}
diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicSdncOperator.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicSdncOperator.java
new file mode 100644 (file)
index 0000000..d8c707c
--- /dev/null
@@ -0,0 +1,148 @@
+/*-
+ * ============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.actor.sdnc;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.BiFunction;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.resources.ResourceUtils;
+import org.onap.policy.controlloop.actor.test.BasicHttpOperation;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.onap.policy.sdnc.SdncRequest;
+import org.onap.policy.sdnc.SdncResponse;
+import org.onap.policy.sdnc.SdncResponseOutput;
+import org.powermock.reflect.Whitebox;
+
+/**
+ * Superclass for various operator tests.
+ */
+public abstract class BasicSdncOperator extends BasicHttpOperation<SdncRequest> {
+
+    protected SdncResponse response;
+
+    /**
+     * Constructs the object using a default actor and operation name.
+     */
+    public BasicSdncOperator() {
+        super();
+    }
+
+    /**
+     * Constructs the object.
+     *
+     * @param actor actor name
+     * @param operation operation name
+     */
+    public BasicSdncOperator(String actor, String operation) {
+        super(actor, operation);
+    }
+
+    /**
+     * Initializes mocks and sets up.
+     */
+    public void setUp() throws Exception {
+        super.setUp();
+
+        response = new SdncResponse();
+
+        SdncResponseOutput output = new SdncResponseOutput();
+        response.setResponseOutput(output);
+        output.setResponseCode("200");
+
+        when(rawResponse.readEntity(String.class)).thenReturn(new StandardCoder().encode(response));
+    }
+
+    /**
+     * Runs the operation and verifies that the response is successful.
+     *
+     * @param operation operation to run
+     * @return the request that was posted
+     */
+    protected SdncRequest verifyOperation(SdncOperation operation)
+                    throws InterruptedException, ExecutionException, TimeoutException {
+
+        CompletableFuture<OperationOutcome> future2 = operation.startOperationAsync(1, outcome);
+        assertFalse(future2.isDone());
+
+        verify(client).post(callbackCaptor.capture(), any(), requestCaptor.capture(), any());
+        callbackCaptor.getValue().completed(rawResponse);
+
+        assertEquals(PolicyResult.SUCCESS, future2.get(5, TimeUnit.SECONDS).getResult());
+
+        return requestCaptor.getValue().getEntity();
+    }
+
+    /**
+     * Pretty-prints a request and verifies that the result matches the expected JSON.
+     *
+     * @param <T> request type
+     * @param expectedJsonFile name of the file containing the expected JSON
+     * @param request request to verify
+     * @throws CoderException if the request cannot be pretty-printed
+     */
+    protected <T> void verifyRequest(String expectedJsonFile, T request) throws CoderException {
+        String json = new StandardCoder().encode(request, true);
+        String expected = ResourceUtils.getResourceAsString(expectedJsonFile);
+
+        // strip request id, because it changes each time
+        final String stripper = "svc-request-id[^,]*";
+        json = json.replaceFirst(stripper, "").trim();
+        expected = expected.replaceFirst(stripper, "").trim();
+
+        assertEquals(expected, json);
+    }
+
+    /**
+     * Verifies that an exception is thrown if a field is missing from the enrichment
+     * data.
+     *
+     * @param fieldName name of the field to be removed from the enrichment data
+     * @param expectedText text expected in the exception message
+     */
+    protected void verifyMissing(String fieldName, String expectedText,
+                    BiFunction<ControlLoopOperationParams,HttpOperator,SdncOperation> maker) {
+
+        makeContext();
+        enrichment.remove(fieldName);
+
+        SdncOperation oper = maker.apply(params, operator);
+
+        assertThatIllegalArgumentException().isThrownBy(() -> Whitebox.invokeMethod(oper, "makeRequest", 1))
+                        .withMessageContaining("missing").withMessageContaining(expectedText);
+    }
+
+    protected abstract Map<String, String> makeEnrichment();
+}
index 0a7bcad..7fc5ee7 100644 (file)
@@ -26,45 +26,51 @@ import static org.junit.Assert.assertNotNull;
 import java.util.Map;
 import org.junit.Before;
 import org.junit.Test;
-import org.onap.policy.common.utils.coder.CoderException;
 import org.onap.policy.sdnc.SdncRequest;
 
-public class RerouteOperatorTest extends BasicOperator {
+public class RerouteOperatorTest extends BasicSdncOperator {
 
-    private RerouteOperator oper;
+    private RerouteOperation oper;
 
+    public RerouteOperatorTest() {
+        super(DEFAULT_ACTOR, RerouteOperation.NAME);
+    }
 
     /**
      * Set up.
      */
     @Before
-    public void setUp() {
-        makeContext();
-        oper = new RerouteOperator(ACTOR);
+    public void setUp() throws Exception {
+        super.setUp();
+        oper = new RerouteOperation(params, operator);
     }
 
     @Test
     public void testRerouteOperator() {
-        assertEquals(ACTOR, oper.getActorName());
-        assertEquals(RerouteOperator.NAME, oper.getName());
+        assertEquals(DEFAULT_ACTOR, oper.getActorName());
+        assertEquals(RerouteOperation.NAME, oper.getName());
     }
 
     @Test
-    public void testConstructRequest() throws CoderException {
-        SdncRequest request = oper.constructRequest(context);
+    public void testMakeRequest() throws Exception {
+        SdncRequest request = oper.makeRequest(1);
         assertEquals("my-service", request.getNsInstanceId());
         assertEquals(REQ_ID, request.getRequestId());
-        assertEquals(RerouteOperator.URI, request.getUrl());
+        assertEquals(RerouteOperation.URI, request.getUrl());
         assertNotNull(request.getHealRequest().getRequestHeaderInfo().getSvcRequestId());
 
         verifyRequest("reroute.json", request);
 
-        verifyMissing(oper, RerouteOperator.SERVICE_ID_KEY, "service");
-        verifyMissing(oper, RerouteOperator.NETWORK_ID_KEY, "network");
+        verifyMissing(RerouteOperation.SERVICE_ID_KEY, "service", RerouteOperation::new);
+        verifyMissing(RerouteOperation.NETWORK_ID_KEY, "network", RerouteOperation::new);
+
+        // perform the operation
+        makeContext();
+        verifyRequest("reroute.json", verifyOperation(oper));
     }
 
     @Override
     protected Map<String, String> makeEnrichment() {
-        return Map.of(RerouteOperator.SERVICE_ID_KEY, "my-service", RerouteOperator.NETWORK_ID_KEY, "my-network");
+        return Map.of(RerouteOperation.SERVICE_ID_KEY, "my-service", RerouteOperation.NETWORK_ID_KEY, "my-network");
     }
 }
index 08655c3..ac81d49 100644 (file)
@@ -41,7 +41,7 @@ import org.onap.policy.sdnc.SdncRequest;
 
 public class SdncActorServiceProviderTest {
 
-    private static final String REROUTE = RerouteOperator.NAME;
+    private static final String REROUTE = RerouteOperation.NAME;
 
     /**
      * Set up before test class.
@@ -63,7 +63,7 @@ public class SdncActorServiceProviderTest {
         final SdncActorServiceProvider prov = new SdncActorServiceProvider();
 
         // verify that it has the operators we expect
-        var expected = Arrays.asList(BandwidthOnDemandOperator.NAME, RerouteOperator.NAME).stream().sorted()
+        var expected = Arrays.asList(BandwidthOnDemandOperation.NAME, RerouteOperation.NAME).stream().sorted()
                         .collect(Collectors.toList());
         var actual = prov.getOperationNames().stream().sorted().collect(Collectors.toList());
 
index 25d383e..4bc514c 100644 (file)
 package org.onap.policy.controlloop.actor.sdnc;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.Status;
-import lombok.Setter;
-import org.junit.After;
-import org.junit.AfterClass;
+import java.util.TreeMap;
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.Test;
-import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams;
-import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams.TopicParamsBuilder;
-import org.onap.policy.common.endpoints.http.client.HttpClient;
-import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance;
-import org.onap.policy.common.endpoints.http.server.HttpServletServer;
-import org.onap.policy.common.endpoints.http.server.HttpServletServerFactoryInstance;
-import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties;
-import org.onap.policy.common.gson.GsonMessageBodyHandler;
-import org.onap.policy.common.utils.coder.CoderException;
-import org.onap.policy.common.utils.coder.StandardCoder;
-import org.onap.policy.common.utils.network.NetworkUtil;
-import org.onap.policy.controlloop.VirtualControlLoopEvent;
-import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
-import org.onap.policy.controlloop.actorserviceprovider.Util;
-import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
-import org.onap.policy.controlloop.policy.PolicyResult;
-import org.onap.policy.sdnc.SdncHealRequest;
 import org.onap.policy.sdnc.SdncRequest;
-import org.onap.policy.sdnc.SdncResponse;
-import org.onap.policy.sdnc.SdncResponseOutput;
 
-public class SdncOperatorTest {
-    public static final String MEDIA_TYPE_APPLICATION_JSON = "application/json";
-    private static final String EXPECTED_EXCEPTION = "expected exception";
-    public static final String HTTP_CLIENT = "my-http-client";
-    public static final String HTTP_NO_SERVER = "my-http-no-server-client";
-    private static final String ACTOR = "my-actor";
-    private static final String OPERATION = "my-operation";
-
-    /**
-     * Outcome to be added to the response.
-     */
-    @Setter
-    private static SdncResponseOutput output;
+public class SdncOperatorTest extends BasicSdncOperator {
 
-
-    private VirtualControlLoopEvent event;
-    private ControlLoopEventContext context;
-    private MyOper oper;
+    private SdncRequest request;
+    private SdncOperation oper;
 
     /**
-     * Starts the SDNC simulator.
-     */
-    @BeforeClass
-    public static void setUpBeforeClass() throws Exception {
-        // allocate a port
-        int port = NetworkUtil.allocPort();
-
-        /*
-         * Start the simulator. Must use "Properties" to configure it, otherwise the
-         * server will use the wrong serialization provider.
-         */
-        Properties svrprops = getServerProperties("my-server", port);
-        HttpServletServerFactoryInstance.getServerFactory().build(svrprops).forEach(HttpServletServer::start);
-
-        /*
-         * Start the clients, one to the server, and one to a non-existent server.
-         */
-        TopicParamsBuilder builder = BusTopicParams.builder().managed(true).hostname("localhost").basePath("sdnc")
-                        .serializationProvider(GsonMessageBodyHandler.class.getName());
-
-        HttpClientFactoryInstance.getClientFactory().build(builder.clientName(HTTP_CLIENT).port(port).build());
-
-        HttpClientFactoryInstance.getClientFactory()
-                        .build(builder.clientName(HTTP_NO_SERVER).port(NetworkUtil.allocPort()).build());
-    }
-
-    @AfterClass
-    public static void tearDownAfterClass() {
-        HttpClientFactoryInstance.getClientFactory().destroy();
-        HttpServletServerFactoryInstance.getServerFactory().destroy();
-    }
-
-    /**
-     * Initializes {@link #oper} and sets {@link #output} to a success code.
+     * Sets up.
      */
     @Before
-    public void setUp() {
-        event = new VirtualControlLoopEvent();
-        context = new ControlLoopEventContext(event);
-
-        initOper(HTTP_CLIENT);
+    public void setUp() throws Exception {
+        super.setUp();
 
-        output = new SdncResponseOutput();
-        output.setResponseCode("200");
-    }
-
-    @After
-    public void tearDown() {
-        oper.shutdown();
+        oper = new SdncOperation(params, operator) {
+            @Override
+            protected SdncRequest makeRequest(int attempt) {
+                return request;
+            }
+        };
     }
 
     @Test
     public void testSdncOperator() {
-        assertEquals(ACTOR, oper.getActorName());
-        assertEquals(OPERATION, oper.getName());
-        assertEquals(ACTOR + "." + OPERATION, oper.getFullName());
-    }
-
-    @Test
-    public void testGetClient() {
-        assertNotNull(oper.getTheClient());
-    }
-
-    @Test
-    public void testStartOperationAsync_testPostRequest() throws Exception {
-        OperationOutcome outcome = runOperation();
-        assertNotNull(outcome);
-        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
-    }
-
-    /**
-     * Tests postRequest() when decode() throws an exception.
-     */
-    @Test
-    public void testPostRequestDecodeException() throws Exception {
-
-        oper.setDecodeFailure(true);
-
-        OperationOutcome outcome = runOperation();
-        assertNotNull(outcome);
-        assertEquals(PolicyResult.FAILURE_EXCEPTION, outcome.getResult());
-    }
-
-    /**
-     * Tests postRequest() when there is no "output" field in the response.
-     */
-    @Test
-    public void testPostRequestNoOutput() throws Exception {
-
-        setOutput(null);
-
-        OperationOutcome outcome = runOperation();
-        assertNotNull(outcome);
-        assertEquals(PolicyResult.FAILURE, outcome.getResult());
+        assertEquals(DEFAULT_ACTOR, oper.getActorName());
+        assertEquals(DEFAULT_OPERATION, oper.getName());
     }
 
-    /**
-     * Tests postRequest() when the output is not a success.
-     */
     @Test
-    public void testPostRequestOutputFailure() throws Exception {
-
-        output.setResponseCode(null);
-
-        OperationOutcome outcome = runOperation();
-        assertNotNull(outcome);
-        assertEquals(PolicyResult.FAILURE, outcome.getResult());
+    public void testStartOperationAsync_testStartRequestAsync() throws Exception {
+        verifyOperation(oper);
     }
 
-    /**
-     * Tests postRequest() when the post() request throws an exception retrieving the
-     * response.
-     */
     @Test
-    public void testPostRequestException() throws Exception {
-
-        // reset "oper" to point to a non-existent server
-        oper.shutdown();
-        initOper(HTTP_NO_SERVER);
-
-        OperationOutcome outcome = runOperation();
-        assertNotNull(outcome);
-        assertEquals(PolicyResult.FAILURE_EXCEPTION, outcome.getResult());
-    }
-
-    private static Properties getServerProperties(String name, int port) {
-        final Properties props = new Properties();
-        props.setProperty(PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES, name);
-
-        final String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + name;
-
-        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_REST_CLASSES_SUFFIX, Server.class.getName());
-        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_HOST_SUFFIX, "localhost");
-        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PORT_SUFFIX, String.valueOf(port));
-        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_MANAGED_SUFFIX, "true");
-        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SWAGGER_SUFFIX, "false");
-
-        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERIALIZATION_PROVIDER,
-                        GsonMessageBodyHandler.class.getName());
-        return props;
-    }
-
-    /**
-     * Initializes {@link #oper}.
-     *
-     * @param clientName name of the client which it should use
-     */
-    private void initOper(String clientName) {
-        oper = new MyOper();
-
-        HttpParams params = HttpParams.builder().clientName(clientName).path("request").build();
-        Map<String, Object> mapParams = Util.translateToMap(OPERATION, params);
-        oper.configure(mapParams);
-        oper.start();
-    }
+    public void testIsSuccess() {
+        // success case
+        response.getResponseOutput().setResponseCode("200");
+        assertTrue(oper.isSuccess(null, response));
 
-    /**
-     * Runs the operation.
-     *
-     * @return the outcome of the operation, or {@code null} if it does not complete in
-     *         time
-     */
-    private OperationOutcome runOperation() throws InterruptedException, ExecutionException, TimeoutException {
-        ControlLoopOperationParams params =
-                        ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).context(context).build();
+        // failure code
+        response.getResponseOutput().setResponseCode("555");
+        assertFalse(oper.isSuccess(null, response));
 
-        CompletableFuture<OperationOutcome> future = oper.startOperationAsync(params, 1, params.makeOutcome());
+        // null code
+        response.getResponseOutput().setResponseCode(null);
+        assertFalse(oper.isSuccess(null, response));
 
-        return future.get(5, TimeUnit.SECONDS);
+        // null output
+        response.setResponseOutput(null);
+        assertFalse(oper.isSuccess(null, response));
     }
 
-
-    private class MyOper extends SdncOperator {
-
-        /**
-         * Set to {@code true} to cause the decoder to throw an exception.
-         */
-        @Setter
-        private boolean decodeFailure = false;
-
-        public MyOper() {
-            super(ACTOR, OPERATION);
-        }
-
-        protected HttpClient getTheClient() {
-            return getClient();
-        }
-
-        @Override
-        protected SdncRequest constructRequest(ControlLoopEventContext context) {
-            SdncRequest request = new SdncRequest();
-
-            SdncHealRequest heal = new SdncHealRequest();
-            request.setHealRequest(heal);
-
-            return request;
-        }
-
-        @Override
-        protected StandardCoder makeDecoder() {
-            if (decodeFailure) {
-                // return a coder that throws exceptions when decode() is invoked
-                return new StandardCoder() {
-                    @Override
-                    public <T> T decode(String json, Class<T> clazz) throws CoderException {
-                        throw new CoderException(EXPECTED_EXCEPTION);
-                    }
-                };
-
-            } else {
-                return super.makeDecoder();
-            }
-        }
-    }
-
-    /**
-     * SDNC Simulator.
-     */
-    @Path("/sdnc")
-    @Produces(MEDIA_TYPE_APPLICATION_JSON)
-    public static class Server {
-
-        /**
-         * Generates a response.
-         *
-         * @param request incoming request
-         * @return resulting response
-         */
-        @POST
-        @Path("/request")
-        @Consumes(value = {MEDIA_TYPE_APPLICATION_JSON})
-        public Response postRequest(SdncRequest request) {
-
-            SdncResponse response = new SdncResponse();
-            response.setResponseOutput(output);
-
-            return Response.status(Status.OK).entity(response).build();
-        }
+    @Override
+    protected Map<String, String> makeEnrichment() {
+        return new TreeMap<>();
     }
 }
diff --git a/models-interactions/model-actors/actor.test/pom.xml b/models-interactions/model-actors/actor.test/pom.xml
new file mode 100644 (file)
index 0000000..6b05807
--- /dev/null
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<!--
+  ============LICENSE_START=======================================================
+  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=========================================================
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+        <artifactId>model-actors</artifactId>
+        <version>2.2.1-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>actor.test</artifactId>
+    <description>Utilities for testing actors</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+            <artifactId>events</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+            <artifactId>actorServiceProvider</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.policy.common</groupId>
+            <artifactId>policy-endpoints</artifactId>
+            <version>${policy.common.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-api-mockito2</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>compile</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/models-interactions/model-actors/actor.test/src/main/java/org/onap/policy/controlloop/actor/test/BasicHttpOperation.java b/models-interactions/model-actors/actor.test/src/main/java/org/onap/policy/controlloop/actor/test/BasicHttpOperation.java
new file mode 100644 (file)
index 0000000..15e4848
--- /dev/null
@@ -0,0 +1,169 @@
+/*-
+ * ============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.actor.test;
+
+import static org.mockito.Mockito.when;
+
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.InvocationCallback;
+import javax.ws.rs.core.Response;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.endpoints.http.client.HttpClient;
+import org.onap.policy.common.endpoints.http.client.HttpClientFactory;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+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.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+
+/**
+ * Superclass for various operator tests.
+ *
+ * @param <Q> request type
+ */
+public class BasicHttpOperation<Q> {
+    protected static final UUID REQ_ID = UUID.randomUUID();
+    protected static final String DEFAULT_ACTOR = "default-actor";
+    protected static final String DEFAULT_OPERATION = "default-operation";
+    protected static final String MY_CLIENT = "my-client";
+    protected static final String BASE_URI = "/base-uri";
+    protected static final String PATH = "/my-path";
+    protected static final String TARGET_ENTITY = "my-target";
+
+    protected final String actorName;
+    protected final String operationName;
+
+    @Captor
+    protected ArgumentCaptor<InvocationCallback<Response>> callbackCaptor;
+
+    @Captor
+    protected ArgumentCaptor<Entity<Q>> requestCaptor;
+
+    @Captor
+    protected ArgumentCaptor<Map<String, Object>> headerCaptor;
+
+    @Mock
+    protected ActorService service;
+
+    @Mock
+    protected HttpClient client;
+
+    @Mock
+    protected HttpClientFactory factory;
+
+    @Mock
+    protected Response rawResponse;
+
+    @Mock
+    protected HttpOperator operator;
+
+    protected CompletableFuture<Response> future;
+    protected ControlLoopOperationParams params;
+    protected Map<String, String> enrichment;
+    protected VirtualControlLoopEvent event;
+    protected ControlLoopEventContext context;
+    protected OperationOutcome outcome;
+
+    /**
+     * Constructs the object using a default actor and operation name.
+     */
+    public BasicHttpOperation() {
+        this.actorName = DEFAULT_ACTOR;
+        this.operationName = DEFAULT_OPERATION;
+    }
+
+    /**
+     * Constructs the object.
+     *
+     * @param actor actor name
+     * @param operation operation name
+     */
+    public BasicHttpOperation(String actor, String operation) {
+        this.actorName = actor;
+        this.operationName = operation;
+    }
+
+    /**
+     * Initializes mocks and sets up.
+     */
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(factory.get(MY_CLIENT)).thenReturn(client);
+
+        when(rawResponse.getStatus()).thenReturn(200);
+
+        future = new CompletableFuture<>();
+        when(client.getBaseUrl()).thenReturn(BASE_URI);
+
+        makeContext();
+
+        outcome = params.makeOutcome();
+
+        initOperator();
+    }
+
+    /**
+     * Reinitializes {@link #enrichment}, {@link #event}, {@link #context}, and
+     * {@link #params}.
+     */
+    protected void makeContext() {
+        enrichment = new TreeMap<>(makeEnrichment());
+
+        event = new VirtualControlLoopEvent();
+        event.setRequestId(REQ_ID);
+        event.setAai(enrichment);
+
+        context = new ControlLoopEventContext(event);
+
+        params = ControlLoopOperationParams.builder().context(context).actorService(service).actor(actorName)
+                        .operation(operationName).targetEntity(TARGET_ENTITY).build();
+    }
+
+    /**
+     * Initializes an operator so that it is "alive" and has the given names.
+     */
+    protected void initOperator() {
+        when(operator.isAlive()).thenReturn(true);
+        when(operator.getFullName()).thenReturn(actorName + "." + operationName);
+        when(operator.getActorName()).thenReturn(actorName);
+        when(operator.getName()).thenReturn(operationName);
+        when(operator.getClient()).thenReturn(client);
+        when(operator.getPath()).thenReturn(PATH);
+    }
+
+    /**
+     * Makes enrichment data.
+     *
+     * @return enrichment data
+     */
+    protected Map<String, String> makeEnrichment() {
+        return new TreeMap<>();
+    }
+}
diff --git a/models-interactions/model-actors/actor.test/src/test/java/org/onap/policy/controlloop/actor/test/BasicHttpOperationTest.java b/models-interactions/model-actors/actor.test/src/test/java/org/onap/policy/controlloop/actor/test/BasicHttpOperationTest.java
new file mode 100644 (file)
index 0000000..c33483d
--- /dev/null
@@ -0,0 +1,104 @@
+/*-
+ * ============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.actor.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class BasicHttpOperationTest {
+    private static final String ACTOR = "my-actor";
+    private static final String OPERATION = "my-operation";
+
+    private BasicHttpOperation<String> oper;
+
+
+    @Before
+    public void setUp() throws Exception {
+        oper = new BasicHttpOperation<>(ACTOR, OPERATION);
+        oper.setUp();
+    }
+
+    @Test
+    public void testBasicHttpOperation() {
+        oper = new BasicHttpOperation<>();
+        assertEquals(BasicHttpOperation.DEFAULT_ACTOR, oper.actorName);
+        assertEquals(BasicHttpOperation.DEFAULT_OPERATION, oper.operationName);
+    }
+
+    @Test
+    public void testBasicHttpOperationStringString() {
+        assertEquals(ACTOR, oper.actorName);
+        assertEquals(OPERATION, oper.operationName);
+    }
+
+    @Test
+    public void testSetUp() {
+        assertNotNull(oper.client);
+        assertSame(oper.client, oper.factory.get(BasicHttpOperation.MY_CLIENT));
+        assertEquals(200, oper.rawResponse.getStatus());
+        assertNotNull(oper.future);
+        assertEquals(BasicHttpOperation.BASE_URI, oper.client.getBaseUrl());
+        assertNotNull(oper.context);
+        assertNotNull(oper.outcome);
+        assertTrue(oper.operator.isAlive());
+    }
+
+    @Test
+    public void testMakeContext() {
+        oper.makeContext();
+
+        assertTrue(oper.enrichment.isEmpty());
+
+        assertSame(BasicHttpOperation.REQ_ID, oper.event.getRequestId());
+        assertSame(oper.enrichment, oper.event.getAai());
+
+        assertSame(oper.event, oper.context.getEvent());
+
+        assertSame(oper.context, oper.params.getContext());
+        assertSame(oper.service, oper.params.getActorService());
+        assertEquals(ACTOR, oper.params.getActor());
+        assertEquals(OPERATION, oper.params.getOperation());
+        assertEquals(BasicHttpOperation.TARGET_ENTITY, oper.params.getTargetEntity());
+    }
+
+    @Test
+    public void testInitOperator() throws Exception {
+        oper.initOperator();
+
+        assertTrue(oper.operator.isAlive());
+        assertEquals(ACTOR + "." + OPERATION, oper.operator.getFullName());
+        assertEquals(ACTOR, oper.operator.getActorName());
+        assertEquals(OPERATION, oper.operator.getName());
+        assertSame(oper.client, oper.operator.getClient());
+        assertEquals(BasicHttpOperation.PATH, oper.operator.getPath());
+    }
+
+    @Test
+    public void testMakeEnrichment() {
+        assertTrue(oper.makeEnrichment().isEmpty());
+    }
+
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandler.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandler.java
deleted file mode 100644 (file)
index d784038..0000000
+++ /dev/null
@@ -1,119 +0,0 @@
-/*-
- * ============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.actorserviceprovider;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Future;
-import javax.ws.rs.client.InvocationCallback;
-import lombok.AccessLevel;
-import lombok.Getter;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
-import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Handler for a <i>single</i> asynchronous response.
- *
- * @param <T> response type
- */
-@Getter
-public abstract class AsyncResponseHandler<T> implements InvocationCallback<T> {
-
-    private static final Logger logger = LoggerFactory.getLogger(AsyncResponseHandler.class);
-
-    @Getter(AccessLevel.NONE)
-    private final PipelineControllerFuture<OperationOutcome> result = new PipelineControllerFuture<>();
-    private final ControlLoopOperationParams params;
-    private final OperationOutcome outcome;
-
-    /**
-     * Constructs the object.
-     *
-     * @param params operation parameters
-     * @param outcome outcome to be populated based on the response
-     */
-    public AsyncResponseHandler(ControlLoopOperationParams params, OperationOutcome outcome) {
-        this.params = params;
-        this.outcome = outcome;
-    }
-
-    /**
-     * Handles the given future, arranging to cancel it when the response is received.
-     *
-     * @param future future to be handled
-     * @return a future to be used to cancel or wait for the response
-     */
-    public CompletableFuture<OperationOutcome> handle(Future<T> future) {
-        result.add(future);
-        return result;
-    }
-
-    /**
-     * Invokes {@link #doComplete()} and then completes "this" with the returned value.
-     */
-    @Override
-    public void completed(T rawResponse) {
-        try {
-            logger.trace("{}.{}: response completed for {}", params.getActor(), params.getOperation(),
-                            params.getRequestId());
-            result.complete(doComplete(rawResponse));
-
-        } catch (RuntimeException e) {
-            logger.trace("{}.{}: response handler threw an exception for {}", params.getActor(), params.getOperation(),
-                            params.getRequestId());
-            result.completeExceptionally(e);
-        }
-    }
-
-    /**
-     * Invokes {@link #doFailed()} and then completes "this" with the returned value.
-     */
-    @Override
-    public void failed(Throwable throwable) {
-        try {
-            logger.trace("{}.{}: response failure for {}", params.getActor(), params.getOperation(),
-                            params.getRequestId());
-            result.complete(doFailed(throwable));
-
-        } catch (RuntimeException e) {
-            logger.trace("{}.{}: response failure handler threw an exception for {}", params.getActor(),
-                            params.getOperation(), params.getRequestId());
-            result.completeExceptionally(e);
-        }
-    }
-
-    /**
-     * Completes the processing of a response.
-     *
-     * @param rawResponse raw response that was received
-     * @return the outcome
-     */
-    protected abstract OperationOutcome doComplete(T rawResponse);
-
-    /**
-     * Handles a response exception.
-     *
-     * @param thrown exception that was thrown
-     * @return the outcome
-     */
-    protected abstract OperationOutcome doFailed(Throwable thrown);
-}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operation.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operation.java
new file mode 100644 (file)
index 0000000..39977fd
--- /dev/null
@@ -0,0 +1,52 @@
+/*-
+ * ============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.actorserviceprovider;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * This is the service interface for defining an Actor operation used in Control Loop
+ * Operational Policies for performing actions on runtime entities.
+ */
+public interface Operation {
+
+    /**
+     * Gets the name of the associated actor.
+     *
+     * @return the name of the associated actor
+     */
+    String getActorName();
+
+    /**
+     * Gets the name of the operation.
+     *
+     * @return the operation name
+     */
+    String getName();
+
+    /**
+     * Called by enforcement PDP engine to start the operation. As part of the operation,
+     * it invokes the "start" and "complete" call-backs found within the parameters.
+     *
+     * @return a future that can be used to cancel or await the result of the operation
+     */
+    CompletableFuture<OperationOutcome> start();
+}
index c09460e..24faafd 100644 (file)
@@ -21,7 +21,6 @@
 package org.onap.policy.controlloop.actorserviceprovider;
 
 import java.util.Map;
-import java.util.concurrent.CompletableFuture;
 import org.onap.policy.common.capabilities.Configurable;
 import org.onap.policy.common.capabilities.Startable;
 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
@@ -47,11 +46,10 @@ public interface Operator extends Startable, Configurable<Map<String, Object>> {
     String getName();
 
     /**
-     * Called by enforcement PDP engine to start the operation. As part of the operation,
-     * it invokes the "start" and "complete" call-backs found within the parameters.
+     * Called by enforcement PDP engine to build the operation.
      *
-     * @param params parameters needed to start the operation
-     * @return a future that can be used to cancel or await the result of the operation
+     * @param params parameters needed by the operation
+     * @return a new operation
      */
-    CompletableFuture<OperationOutcome> startOperation(ControlLoopOperationParams params);
+    Operation buildOperation(ControlLoopOperationParams params);
 }
index c3ddd17..b885b5c 100644 (file)
@@ -23,9 +23,6 @@ package org.onap.policy.controlloop.actorserviceprovider;
 import java.util.Arrays;
 import java.util.LinkedHashMap;
 import java.util.Map;
-import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
-import org.onap.policy.common.endpoints.utils.NetLoggerUtil;
-import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
 import org.onap.policy.common.utils.coder.Coder;
 import org.onap.policy.common.utils.coder.CoderException;
 import org.onap.policy.common.utils.coder.StandardCoder;
@@ -55,82 +52,6 @@ public class Util {
         return new DelayedIdentString(object);
     }
 
-    /**
-     * Logs a REST request. If the request is not of type, String, then it attempts to
-     * pretty-print it into JSON before logging.
-     *
-     * @param url request URL
-     * @param request request to be logged
-     */
-    public static <T> void logRestRequest(String url, T request) {
-        logRestRequest(new StandardCoder(), url, request);
-    }
-
-    /**
-     * Logs a REST request. If the request is not of type, String, then it attempts to
-     * pretty-print it into JSON before logging.
-     *
-     * @param coder coder to be used to pretty-print the request
-     * @param url request URL
-     * @param request request to be logged
-     */
-    protected static <T> void logRestRequest(Coder coder, String url, T request) {
-        String json;
-        try {
-            if (request instanceof String) {
-                json = request.toString();
-            } else {
-                json = coder.encode(request, true);
-            }
-
-        } catch (CoderException e) {
-            logger.warn("cannot pretty-print request", e);
-            json = request.toString();
-        }
-
-        NetLoggerUtil.log(EventType.OUT, CommInfrastructure.REST, url, json);
-        logger.info("[OUT|{}|{}|]{}{}", CommInfrastructure.REST, url, NetLoggerUtil.SYSTEM_LS, json);
-    }
-
-    /**
-     * Logs a REST response. If the response is not of type, String, then it attempts to
-     * pretty-print it into JSON before logging.
-     *
-     * @param url request URL
-     * @param response response to be logged
-     */
-    public static <T> void logRestResponse(String url, T response) {
-        logRestResponse(new StandardCoder(), url, response);
-    }
-
-    /**
-     * Logs a REST response. If the request is not of type, String, then it attempts to
-     * pretty-print it into JSON before logging.
-     *
-     * @param coder coder to be used to pretty-print the response
-     * @param url request URL
-     * @param response response to be logged
-     */
-    protected static <T> void logRestResponse(Coder coder, String url, T response) {
-        String json;
-        try {
-            if (response == null) {
-                json = null;
-            } else if (response instanceof String) {
-                json = response.toString();
-            } else {
-                json = coder.encode(response, true);
-            }
-
-        } catch (CoderException e) {
-            logger.warn("cannot pretty-print response", e);
-            json = response.toString();
-        }
-
-        NetLoggerUtil.log(EventType.IN, CommInfrastructure.REST, url, json);
-        logger.info("[IN|{}|{}|]{}{}", CommInfrastructure.REST, url, NetLoggerUtil.SYSTEM_LS, json);
-    }
-
     /**
      * Runs a function and logs a message if it throws an exception. Does <i>not</i>
      * re-throw the exception.
index cd4d257..1c37a8e 100644 (file)
@@ -23,12 +23,15 @@ package org.onap.policy.controlloop.actorserviceprovider.controlloop;
 import java.io.Serializable;
 import java.util.Map;
 import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
 import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.NonNull;
 import lombok.Setter;
 import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
 
 /**
  * Context associated with a control loop event.
@@ -47,10 +50,22 @@ public class ControlLoopEventContext implements Serializable {
      */
     private final Map<String, String> enrichment;
 
+    /**
+     * Set of properties that have been stored in the context.
+     */
     @Getter(AccessLevel.NONE)
     @Setter(AccessLevel.NONE)
     private Map<String, Serializable> properties = new ConcurrentHashMap<>();
 
+    /**
+     * When {@link #obtain(String, ControlLoopOperationParams)} is invoked and the
+     * specified property is not found in {@link #properties}, it is retrieved. This holds
+     * the futures for the operations retrieving the properties.
+     */
+    @Getter(AccessLevel.NONE)
+    @Setter(AccessLevel.NONE)
+    private transient Map<String, CompletableFuture<OperationOutcome>> retrievers = new ConcurrentHashMap<>();
+
     /**
      * Request ID extracted from the event, or a generated value if the event has no
      * request id; never {@code null}.
@@ -100,4 +115,34 @@ public class ControlLoopEventContext implements Serializable {
     public void setProperty(String name, Serializable value) {
         properties.put(name, value);
     }
+
+    /**
+     * Obtains the given property.
+     *
+     * @param name name of the desired property
+     * @param params parameters needed to perform the operation to retrieve the desired
+     *        property
+     * @return a future for retrieving the property, {@code null} if the property has
+     *         already been retrieved
+     */
+    public CompletableFuture<OperationOutcome> obtain(String name, ControlLoopOperationParams params) {
+        if (properties.containsKey(name)) {
+            return null;
+        }
+
+        CompletableFuture<OperationOutcome> future = retrievers.get(name);
+        if (future != null) {
+            return future;
+        }
+
+        future = params.start();
+
+        CompletableFuture<OperationOutcome> oldFuture = retrievers.putIfAbsent(name, future);
+        if (oldFuture != null) {
+            future.cancel(false);
+            return oldFuture;
+        }
+
+        return future;
+    }
 }
index d7f322e..0c88ebe 100644 (file)
@@ -91,7 +91,7 @@ public class ActorImpl extends StartConfigPartial<Map<String, Object>> implement
     public Operator getOperator(String name) {
         Operator operator = name2operator.get(name);
         if (operator == null) {
-            throw new IllegalArgumentException("unknown operation " + getName() + "." + name);
+            throw new IllegalArgumentException("unknown operator " + getName() + "." + name);
         }
 
         return operator;
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperation.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperation.java
new file mode 100644 (file)
index 0000000..c4bf5f4
--- /dev/null
@@ -0,0 +1,286 @@
+/*-
+ * ============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.actorserviceprovider.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.function.Function;
+import javax.ws.rs.client.InvocationCallback;
+import javax.ws.rs.core.Response;
+import lombok.Getter;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.http.client.HttpClient;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+import org.onap.policy.common.utils.coder.Coder;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
+import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Operator that uses HTTP. The operator's parameters must be an {@link HttpParams}.
+ *
+ * @param <T> response type
+ */
+@Getter
+public abstract class HttpOperation<T> extends OperationPartial {
+    private static final Logger logger = LoggerFactory.getLogger(HttpOperation.class);
+    private static final Coder coder = new StandardCoder();
+
+    /**
+     * Operator that created this operation.
+     */
+    protected final HttpOperator operator;
+
+    /**
+     * Response class.
+     */
+    private final Class<T> responseClass;
+
+
+    /**
+     * Constructs the object.
+     *
+     * @param params operation parameters
+     * @param operator operator that created this operation
+     * @param clazz response class
+     */
+    public HttpOperation(ControlLoopOperationParams params, HttpOperator operator, Class<T> clazz) {
+        super(params, operator);
+        this.operator = operator;
+        this.responseClass = clazz;
+    }
+
+    /**
+     * If no timeout is specified, then it returns the operator's configured timeout.
+     */
+    @Override
+    protected long getTimeoutMs(Integer timeoutSec) {
+        return (timeoutSec == null || timeoutSec == 0 ? operator.getTimeoutMs() : super.getTimeoutMs(timeoutSec));
+    }
+
+    /**
+     * Makes the request headers. This simply returns an empty map.
+     *
+     * @return request headers, a non-null, modifiable map
+     */
+    protected Map<String, Object> makeHeaders() {
+        return new HashMap<>();
+    }
+
+    /**
+     * Gets the path to be used when performing the request; this is typically appended to
+     * the base URL. This method simply invokes {@link #getPath()}.
+     *
+     * @return the path URI suffix
+     */
+    public String makePath() {
+        return operator.getPath();
+    }
+
+    /**
+     * Makes the URL to which the "get" request should be posted. This ir primarily used
+     * for logging purposes. This particular method returns the base URL appended with the
+     * return value from {@link #makePath()}.
+     *
+     * @return the URL to which from which to get
+     */
+    public String makeUrl() {
+        return (operator.getClient().getBaseUrl() + makePath());
+    }
+
+    /**
+     * Arranges to handle a response.
+     *
+     * @param outcome outcome to be populate
+     * @param url URL to which to request was sent
+     * @param requester function to initiate the request and invoke the given callback
+     *        when it completes
+     * @return a future for the response
+     */
+    protected CompletableFuture<OperationOutcome> handleResponse(OperationOutcome outcome, String url,
+                    Function<InvocationCallback<Response>, Future<Response>> requester) {
+
+        final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+        final CompletableFuture<Response> future = new CompletableFuture<>();
+        final Executor executor = params.getExecutor();
+
+        // arrange for the callback to complete "future"
+        InvocationCallback<Response> callback = new InvocationCallback<>() {
+            @Override
+            public void completed(Response response) {
+                future.complete(response);
+            }
+
+            @Override
+            public void failed(Throwable throwable) {
+                logger.warn("{}.{}: response failure for {}", params.getActor(), params.getOperation(),
+                                params.getRequestId());
+                future.completeExceptionally(throwable);
+            }
+        };
+
+        // start the request and arrange to cancel it if the controller is canceled
+        controller.add(requester.apply(callback));
+
+        // once "future" completes, process the response, and then complete the controller
+        future.thenApplyAsync(response -> processResponse(outcome, url, response), executor)
+                        .whenCompleteAsync(controller.delayedComplete(), executor);
+
+        return controller;
+    }
+
+    /**
+     * Processes a response. This method simply sets the outcome to SUCCESS.
+     *
+     * @param outcome outcome to be populate
+     * @param url URL to which to request was sent
+     * @param response raw response to process
+     * @return the outcome
+     */
+    protected OperationOutcome processResponse(OperationOutcome outcome, String url, Response rawResponse) {
+
+        logger.info("{}.{}: response received for {}", params.getActor(), params.getOperation(), params.getRequestId());
+
+        String strResponse = HttpClient.getBody(rawResponse, String.class);
+
+        logRestResponse(url, strResponse);
+
+        T response;
+        if (responseClass == String.class) {
+            response = responseClass.cast(strResponse);
+
+        } else {
+            try {
+                response = makeCoder().decode(strResponse, responseClass);
+            } catch (CoderException e) {
+                logger.warn("{}.{} cannot decode response with http error code {} for {}", params.getActor(),
+                                params.getOperation(), rawResponse.getStatus(), params.getRequestId(), e);
+                return setOutcome(outcome, PolicyResult.FAILURE_EXCEPTION);
+            }
+        }
+
+        if (!isSuccess(rawResponse, response)) {
+            logger.info("{}.{} request failed with http error code {} for {}", params.getActor(), params.getOperation(),
+                            rawResponse.getStatus(), params.getRequestId());
+            return setOutcome(outcome, PolicyResult.FAILURE);
+        }
+
+        logger.info("{}.{} request succeeded for {}", params.getActor(), params.getOperation(), params.getRequestId());
+        setOutcome(outcome, PolicyResult.SUCCESS);
+        postProcessResponse(outcome, url, rawResponse, response);
+
+        return outcome;
+    }
+
+    /**
+     * Processes a successful response.
+     *
+     * @param outcome outcome to be populate
+     * @param url URL to which to request was sent
+     * @param rawResponse raw response
+     * @param response decoded response
+     */
+    protected void postProcessResponse(OperationOutcome outcome, String url, Response rawResponse, T response) {
+        // do nothing
+    }
+
+    /**
+     * Determines if the response indicates success. This method simply checks the HTTP
+     * status code.
+     *
+     * @param rawResponse raw response
+     * @param response decoded response
+     * @return {@code true} if the response indicates success, {@code false} otherwise
+     */
+    protected boolean isSuccess(Response rawResponse, T response) {
+        return (rawResponse.getStatus() == 200);
+    }
+
+    /**
+     * Logs a REST request. If the request is not of type, String, then it attempts to
+     * pretty-print it into JSON before logging.
+     *
+     * @param url request URL
+     * @param request request to be logged
+     */
+    public <Q> void logRestRequest(String url, Q request) {
+        String json;
+        try {
+            if (request == null) {
+                json = null;
+            } else if (request instanceof String) {
+                json = request.toString();
+            } else {
+                json = makeCoder().encode(request, true);
+            }
+
+        } catch (CoderException e) {
+            logger.warn("cannot pretty-print request", e);
+            json = request.toString();
+        }
+
+        NetLoggerUtil.log(EventType.OUT, CommInfrastructure.REST, url, json);
+        logger.info("[OUT|{}|{}|]{}{}", CommInfrastructure.REST, url, NetLoggerUtil.SYSTEM_LS, json);
+    }
+
+    /**
+     * Logs a REST response. If the response is not of type, String, then it attempts to
+     * pretty-print it into JSON before logging.
+     *
+     * @param url request URL
+     * @param response response to be logged
+     */
+    public <S> void logRestResponse(String url, S response) {
+        String json;
+        try {
+            if (response == null) {
+                json = null;
+            } else if (response instanceof String) {
+                json = response.toString();
+            } else {
+                json = makeCoder().encode(response, true);
+            }
+
+        } catch (CoderException e) {
+            logger.warn("cannot pretty-print response", e);
+            json = response.toString();
+        }
+
+        NetLoggerUtil.log(EventType.IN, CommInfrastructure.REST, url, json);
+        logger.info("[IN|{}|{}|]{}{}", CommInfrastructure.REST, url, NetLoggerUtil.SYSTEM_LS, json);
+    }
+
+    // these may be overridden by junit tests
+
+    protected Coder makeCoder() {
+        return coder;
+    }
+}
index 5664929..add74aa 100644 (file)
 package org.onap.policy.controlloop.actorserviceprovider.impl;
 
 import java.util.Map;
-import lombok.AccessLevel;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiFunction;
 import lombok.Getter;
 import org.onap.policy.common.endpoints.http.client.HttpClient;
 import org.onap.policy.common.endpoints.http.client.HttpClientFactory;
 import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance;
 import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
 import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
 import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
 import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
 
 /**
- * Operator that uses HTTP. The operator's parameters must be a {@link HttpParams}.
+ * Operator that uses HTTP. The operator's parameters must be an {@link HttpParams}.
  */
-public class HttpOperator extends OperatorPartial {
+@Getter
+public abstract class HttpOperator extends OperatorPartial {
 
-    @Getter(AccessLevel.PROTECTED)
     private HttpClient client;
 
-    @Getter
-    private long timeoutSec;
+    /**
+     * Default timeout, in milliseconds, if none specified in the request.
+     */
+    private long timeoutMs;
 
     /**
-     * URI path for this particular operation.
+     * URI path for this particular operation. Includes a leading "/".
      */
-    @Getter
     private String path;
 
 
@@ -59,6 +63,26 @@ public class HttpOperator extends OperatorPartial {
         super(actorName, name);
     }
 
+    /**
+     * Makes an operator that will construct operations.
+     *
+     * @param <T> response type
+     * @param actorName actor name
+     * @param operation operation name
+     * @param operationMaker function to make an operation
+     * @return a new operator
+     */
+    public static <T> HttpOperator makeOperator(String actorName, String operation,
+                    BiFunction<ControlLoopOperationParams, HttpOperator, HttpOperation<T>> operationMaker) {
+
+        return new HttpOperator(actorName, operation) {
+            @Override
+            public Operation buildOperation(ControlLoopOperationParams params) {
+                return operationMaker.apply(params, this);
+            }
+        };
+    }
+
     /**
      * Translates the parameters to an {@link HttpParams} and then extracts the relevant
      * values.
@@ -73,10 +97,10 @@ public class HttpOperator extends OperatorPartial {
 
         client = getClientFactory().get(params.getClientName());
         path = params.getPath();
-        timeoutSec = params.getTimeoutSec();
+        timeoutMs = TimeUnit.MILLISECONDS.convert(params.getTimeoutSec(), TimeUnit.SECONDS);
     }
 
-    // these may be overridden by junits
+    // these may be overridden by junit tests
 
     protected HttpClientFactory getClientFactory() {
         return HttpClientFactoryInstance.getClientFactory();
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartial.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartial.java
new file mode 100644 (file)
index 0000000..d00b88b
--- /dev/null
@@ -0,0 +1,844 @@
+/*-
+ * ============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.actorserviceprovider.impl;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import org.onap.policy.controlloop.ControlLoopOperation;
+import org.onap.policy.controlloop.actorserviceprovider.CallbackManager;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Partial implementation of an operator. In general, it's preferable that subclasses
+ * would override {@link #startOperationAsync(int, OperationOutcome)
+ * startOperationAsync()}. However, if that proves to be too difficult, then they can
+ * simply override {@link #doOperation(int, OperationOutcome) doOperation()}. In addition,
+ * if the operation requires any preprocessor steps, the subclass may choose to override
+ * {@link #startPreprocessorAsync()}.
+ * <p/>
+ * The futures returned by the methods within this class can be canceled, and will
+ * propagate the cancellation to any subtasks. Thus it is also expected that any futures
+ * returned by overridden methods will do the same. Of course, if a class overrides
+ * {@link #doOperation(int, OperationOutcome) doOperation()}, then there's little that can
+ * be done to cancel that particular operation.
+ */
+public abstract class OperationPartial implements Operation {
+
+    private static final Logger logger = LoggerFactory.getLogger(OperationPartial.class);
+    public static final long DEFAULT_RETRY_WAIT_MS = 1000L;
+
+    // values extracted from the operator
+
+    private final OperatorPartial operator;
+
+    /**
+     * Operation parameters.
+     */
+    protected final ControlLoopOperationParams params;
+
+
+    /**
+     * Constructs the object.
+     *
+     * @param params operation parameters
+     * @param operator operator that created this operation
+     */
+    public OperationPartial(ControlLoopOperationParams params, OperatorPartial operator) {
+        this.params = params;
+        this.operator = operator;
+    }
+
+    public Executor getBlockingExecutor() {
+        return operator.getBlockingExecutor();
+    }
+
+    public String getFullName() {
+        return operator.getFullName();
+    }
+
+    public String getActorName() {
+        return operator.getActorName();
+    }
+
+    public String getName() {
+        return operator.getName();
+    }
+
+    @Override
+    public final CompletableFuture<OperationOutcome> start() {
+        if (!operator.isAlive()) {
+            throw new IllegalStateException("operation is not running: " + getFullName());
+        }
+
+        // allocate a controller for the entire operation
+        final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+
+        CompletableFuture<OperationOutcome> preproc = startPreprocessorAsync();
+        if (preproc == null) {
+            // no preprocessor required - just start the operation
+            return startOperationAttempt(controller, 1);
+        }
+
+        /*
+         * Do preprocessor first and then, if successful, start the operation. Note:
+         * operations create their own outcome, ignoring the outcome from any previous
+         * steps.
+         *
+         * Wrap the preprocessor to ensure "stop" is propagated to it.
+         */
+        // @formatter:off
+        controller.wrap(preproc)
+                        .exceptionally(fromException("preprocessor of operation"))
+                        .thenCompose(handlePreprocessorFailure(controller))
+                        .thenCompose(unusedOutcome -> startOperationAttempt(controller, 1))
+                        .whenCompleteAsync(controller.delayedComplete(), params.getExecutor());
+        // @formatter:on
+
+        return controller;
+    }
+
+    /**
+     * Handles a failure in the preprocessor pipeline. If a failure occurred, then it
+     * invokes the call-backs, marks the controller complete, and returns an incomplete
+     * future, effectively halting the pipeline. Otherwise, it returns the outcome that it
+     * received.
+     * <p/>
+     * Assumes that no callbacks have been invoked yet.
+     *
+     * @param controller pipeline controller
+     * @return a function that checks the outcome status and continues, if successful, or
+     *         indicates a failure otherwise
+     */
+    private Function<OperationOutcome, CompletableFuture<OperationOutcome>> handlePreprocessorFailure(
+                    PipelineControllerFuture<OperationOutcome> controller) {
+
+        return outcome -> {
+
+            if (outcome != null && isSuccess(outcome)) {
+                logger.info("{}: preprocessor succeeded for {}", getFullName(), params.getRequestId());
+                return CompletableFuture.completedFuture(outcome);
+            }
+
+            logger.warn("preprocessor failed, discontinuing operation {} for {}", getFullName(), params.getRequestId());
+
+            final Executor executor = params.getExecutor();
+            final CallbackManager callbacks = new CallbackManager();
+
+            // propagate "stop" to the callbacks
+            controller.add(callbacks);
+
+            final OperationOutcome outcome2 = params.makeOutcome();
+
+            // TODO need a FAILURE_MISSING_DATA (e.g., A&AI)
+
+            outcome2.setResult(PolicyResult.FAILURE_GUARD);
+            outcome2.setMessage(outcome != null ? outcome.getMessage() : null);
+
+            // @formatter:off
+            CompletableFuture.completedFuture(outcome2)
+                            .whenCompleteAsync(callbackStarted(callbacks), executor)
+                            .whenCompleteAsync(callbackCompleted(callbacks), executor)
+                            .whenCompleteAsync(controller.delayedComplete(), executor);
+            // @formatter:on
+
+            return new CompletableFuture<>();
+        };
+    }
+
+    /**
+     * Invokes the operation's preprocessor step(s) as a "future". This method simply
+     * invokes {@link #startGuardAsync()}.
+     * <p/>
+     * This method assumes the following:
+     * <ul>
+     * <li>the operator is alive</li>
+     * <li>exceptions generated within the pipeline will be handled by the invoker</li>
+     * </ul>
+     *
+     * @return a function that will start the preprocessor and returns its outcome, or
+     *         {@code null} if this operation needs no preprocessor
+     */
+    protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
+        return startGuardAsync();
+    }
+
+    /**
+     * Invokes the operation's guard step(s) as a "future". This method simply returns
+     * {@code null}.
+     * <p/>
+     * This method assumes the following:
+     * <ul>
+     * <li>the operator is alive</li>
+     * <li>exceptions generated within the pipeline will be handled by the invoker</li>
+     * </ul>
+     *
+     * @return a function that will start the guard checks and returns its outcome, or
+     *         {@code null} if this operation has no guard
+     */
+    protected CompletableFuture<OperationOutcome> startGuardAsync() {
+        return null;
+    }
+
+    /**
+     * Starts the operation attempt, with no preprocessor. When all retries complete, it
+     * will complete the controller.
+     *
+     * @param controller controller for all operation attempts
+     * @param attempt attempt number, typically starting with 1
+     * @return a future that will return the final result of all attempts
+     */
+    private CompletableFuture<OperationOutcome> startOperationAttempt(
+                    PipelineControllerFuture<OperationOutcome> controller, int attempt) {
+
+        // propagate "stop" to the operation attempt
+        controller.wrap(startAttemptWithoutRetries(attempt)).thenCompose(retryOnFailure(controller, attempt))
+                        .whenCompleteAsync(controller.delayedComplete(), params.getExecutor());
+
+        return controller;
+    }
+
+    /**
+     * Starts the operation attempt, without doing any retries.
+     *
+     * @param params operation parameters
+     * @param attempt attempt number, typically starting with 1
+     * @return a future that will return the result of a single operation attempt
+     */
+    private CompletableFuture<OperationOutcome> startAttemptWithoutRetries(int attempt) {
+
+        logger.info("{}: start operation attempt {} for {}", getFullName(), attempt, params.getRequestId());
+
+        final Executor executor = params.getExecutor();
+        final OperationOutcome outcome = params.makeOutcome();
+        final CallbackManager callbacks = new CallbackManager();
+
+        // this operation attempt gets its own controller
+        final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+
+        // propagate "stop" to the callbacks
+        controller.add(callbacks);
+
+        // @formatter:off
+        CompletableFuture<OperationOutcome> future = CompletableFuture.completedFuture(outcome)
+                        .whenCompleteAsync(callbackStarted(callbacks), executor)
+                        .thenCompose(controller.wrap(outcome2 -> startOperationAsync(attempt, outcome2)));
+        // @formatter:on
+
+        // handle timeouts, if specified
+        long timeoutMillis = getTimeoutMs(params.getTimeoutSec());
+        if (timeoutMillis > 0) {
+            logger.info("{}: set timeout to {}ms for {}", getFullName(), timeoutMillis, params.getRequestId());
+            future = future.orTimeout(timeoutMillis, TimeUnit.MILLISECONDS);
+        }
+
+        /*
+         * Note: we re-invoke callbackStarted() just to be sure the callback is invoked
+         * before callbackCompleted() is invoked.
+         *
+         * Note: no need to remove "callbacks" from the pipeline, as we're going to stop
+         * the pipeline as the last step anyway.
+         */
+
+        // @formatter:off
+        future.exceptionally(fromException("operation"))
+                    .thenApply(setRetryFlag(attempt))
+                    .whenCompleteAsync(callbackStarted(callbacks), executor)
+                    .whenCompleteAsync(callbackCompleted(callbacks), executor)
+                    .whenCompleteAsync(controller.delayedComplete(), executor);
+        // @formatter:on
+
+        return controller;
+    }
+
+    /**
+     * Determines if the outcome was successful.
+     *
+     * @param outcome outcome to examine
+     * @return {@code true} if the outcome was successful
+     */
+    protected boolean isSuccess(OperationOutcome outcome) {
+        return (outcome.getResult() == PolicyResult.SUCCESS);
+    }
+
+    /**
+     * Determines if the outcome was a failure for this operator.
+     *
+     * @param outcome outcome to examine, or {@code null}
+     * @return {@code true} if the outcome is not {@code null} and was a failure
+     *         <i>and</i> was associated with this operator, {@code false} otherwise
+     */
+    protected boolean isActorFailed(OperationOutcome outcome) {
+        return (isSameOperation(outcome) && outcome.getResult() == PolicyResult.FAILURE);
+    }
+
+    /**
+     * Determines if the given outcome is for this operation.
+     *
+     * @param outcome outcome to examine
+     * @return {@code true} if the outcome is for this operation, {@code false} otherwise
+     */
+    protected boolean isSameOperation(OperationOutcome outcome) {
+        return OperationOutcome.isFor(outcome, getActorName(), getName());
+    }
+
+    /**
+     * Invokes the operation as a "future". This method simply invokes
+     * {@link #doOperation()} using the {@link #blockingExecutor "blocking executor"},
+     * returning the result via a "future".
+     * <p/>
+     * Note: if the operation uses blocking I/O, then it should <i>not</i> be run using
+     * the executor in the "params", as that may bring the background thread pool to a
+     * grinding halt. The {@link #blockingExecutor "blocking executor"} should be used
+     * instead.
+     * <p/>
+     * This method assumes the following:
+     * <ul>
+     * <li>the operator is alive</li>
+     * <li>verifyRunning() has been invoked</li>
+     * <li>callbackStarted() has been invoked</li>
+     * <li>the invoker will perform appropriate timeout checks</li>
+     * <li>exceptions generated within the pipeline will be handled by the invoker</li>
+     * </ul>
+     *
+     * @param attempt attempt number, typically starting with 1
+     * @return a function that will start the operation and return its result when
+     *         complete
+     */
+    protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+        return CompletableFuture.supplyAsync(() -> doOperation(attempt, outcome), getBlockingExecutor());
+    }
+
+    /**
+     * Low-level method that performs the operation. This can make the same assumptions
+     * that are made by {@link #doOperationAsFuture()}. This particular method simply
+     * throws an {@link UnsupportedOperationException}.
+     *
+     * @param attempt attempt number, typically starting with 1
+     * @param operation the operation being performed
+     * @return the outcome of the operation
+     */
+    protected OperationOutcome doOperation(int attempt, OperationOutcome operation) {
+
+        throw new UnsupportedOperationException("start operation " + getFullName());
+    }
+
+    /**
+     * Sets the outcome status to FAILURE_RETRIES, if the current operation outcome is
+     * FAILURE, assuming the policy specifies retries and the retry count has been
+     * exhausted.
+     *
+     * @param attempt latest attempt number, starting with 1
+     * @return a function to get the next future to execute
+     */
+    private Function<OperationOutcome, OperationOutcome> setRetryFlag(int attempt) {
+
+        return operation -> {
+            if (operation != null && !isActorFailed(operation)) {
+                /*
+                 * wrong type or wrong operation - just leave it as is. No need to log
+                 * anything here, as retryOnFailure() will log a message
+                 */
+                return operation;
+            }
+
+            // get a non-null operation
+            OperationOutcome oper2;
+            if (operation != null) {
+                oper2 = operation;
+            } else {
+                oper2 = params.makeOutcome();
+                oper2.setResult(PolicyResult.FAILURE);
+            }
+
+            int retry = getRetry(params.getRetry());
+            if (retry > 0 && attempt > retry) {
+                /*
+                 * retries were specified and we've already tried them all - change to
+                 * FAILURE_RETRIES
+                 */
+                logger.info("operation {} retries exhausted for {}", getFullName(), params.getRequestId());
+                oper2.setResult(PolicyResult.FAILURE_RETRIES);
+            }
+
+            return oper2;
+        };
+    }
+
+    /**
+     * Restarts the operation if it was a FAILURE. Assumes that {@link #setRetryFlag(int)}
+     * was previously invoked, and thus that the "operation" is not {@code null}.
+     *
+     * @param controller controller for all of the retries
+     * @param attempt latest attempt number, starting with 1
+     * @return a function to get the next future to execute
+     */
+    private Function<OperationOutcome, CompletableFuture<OperationOutcome>> retryOnFailure(
+                    PipelineControllerFuture<OperationOutcome> controller, int attempt) {
+
+        return operation -> {
+            if (!isActorFailed(operation)) {
+                // wrong type or wrong operation - just leave it as is
+                logger.info("not retrying operation {} for {}", getFullName(), params.getRequestId());
+                controller.complete(operation);
+                return new CompletableFuture<>();
+            }
+
+            if (getRetry(params.getRetry()) <= 0) {
+                // no retries - already marked as FAILURE, so just return it
+                logger.info("operation {} no retries for {}", getFullName(), params.getRequestId());
+                controller.complete(operation);
+                return new CompletableFuture<>();
+            }
+
+            /*
+             * Retry the operation.
+             */
+            long waitMs = getRetryWaitMs();
+            logger.info("retry operation {} in {}ms for {}", getFullName(), waitMs, params.getRequestId());
+
+            return sleep(waitMs, TimeUnit.MILLISECONDS)
+                            .thenCompose(unused -> startOperationAttempt(controller, attempt + 1));
+        };
+    }
+
+    /**
+     * Convenience method that starts a sleep(), running via a future.
+     *
+     * @param sleepTime time to sleep
+     * @param unit time unit
+     * @return a future that will complete when the sleep completes
+     */
+    protected CompletableFuture<Void> sleep(long sleepTime, TimeUnit unit) {
+        if (sleepTime <= 0) {
+            return CompletableFuture.completedFuture(null);
+        }
+
+        return new CompletableFuture<Void>().completeOnTimeout(null, sleepTime, unit);
+    }
+
+    /**
+     * Converts an exception into an operation outcome, returning a copy of the outcome to
+     * prevent background jobs from changing it.
+     *
+     * @param type type of item throwing the exception
+     * @return a function that will convert an exception into an operation outcome
+     */
+    private Function<Throwable, OperationOutcome> fromException(String type) {
+
+        return thrown -> {
+            OperationOutcome outcome = params.makeOutcome();
+
+            logger.warn("exception throw by {} {}.{} for {}", type, outcome.getActor(), outcome.getOperation(),
+                            params.getRequestId(), thrown);
+
+            return setOutcome(outcome, thrown);
+        };
+    }
+
+    /**
+     * Similar to {@link CompletableFuture#anyOf(CompletableFuture...)}, but it cancels
+     * any outstanding futures when one completes.
+     *
+     * @param futures futures for which to wait
+     * @return a future to cancel or await an outcome. If this future is canceled, then
+     *         all of the futures will be canceled
+     */
+    protected CompletableFuture<OperationOutcome> anyOf(List<CompletableFuture<OperationOutcome>> futures) {
+
+        // convert list to an array
+        @SuppressWarnings("rawtypes")
+        CompletableFuture[] arrFutures = futures.toArray(new CompletableFuture[futures.size()]);
+
+        @SuppressWarnings("unchecked")
+        CompletableFuture<OperationOutcome> result = anyOf(arrFutures);
+        return result;
+    }
+
+    /**
+     * Same as {@link CompletableFuture#anyOf(CompletableFuture...)}, but it cancels any
+     * outstanding futures when one completes.
+     *
+     * @param futures futures for which to wait
+     * @return a future to cancel or await an outcome. If this future is canceled, then
+     *         all of the futures will be canceled
+     */
+    protected CompletableFuture<OperationOutcome> anyOf(
+                    @SuppressWarnings("unchecked") CompletableFuture<OperationOutcome>... futures) {
+
+        if (futures.length == 1) {
+            return futures[0];
+        }
+
+        final Executor executor = params.getExecutor();
+        final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+
+        attachFutures(controller, futures);
+
+        // @formatter:off
+        CompletableFuture.anyOf(futures)
+                            .thenApply(object -> (OperationOutcome) object)
+                            .whenCompleteAsync(controller.delayedComplete(), executor);
+        // @formatter:on
+
+        return controller;
+    }
+
+    /**
+     * Similar to {@link CompletableFuture#allOf(CompletableFuture...)}, but it cancels
+     * the futures if returned future is canceled. The future returns the "worst" outcome,
+     * based on priority (see {@link #detmPriority(OperationOutcome)}).
+     *
+     * @param futures futures for which to wait
+     * @return a future to cancel or await an outcome. If this future is canceled, then
+     *         all of the futures will be canceled
+     */
+    protected CompletableFuture<OperationOutcome> allOf(List<CompletableFuture<OperationOutcome>> futures) {
+
+        // convert list to an array
+        @SuppressWarnings("rawtypes")
+        CompletableFuture[] arrFutures = futures.toArray(new CompletableFuture[futures.size()]);
+
+        @SuppressWarnings("unchecked")
+        CompletableFuture<OperationOutcome> result = allOf(arrFutures);
+        return result;
+    }
+
+    /**
+     * Same as {@link CompletableFuture#allOf(CompletableFuture...)}, but it cancels the
+     * futures if returned future is canceled. The future returns the "worst" outcome,
+     * based on priority (see {@link #detmPriority(OperationOutcome)}).
+     *
+     * @param futures futures for which to wait
+     * @return a future to cancel or await an outcome. If this future is canceled, then
+     *         all of the futures will be canceled
+     */
+    protected CompletableFuture<OperationOutcome> allOf(
+                    @SuppressWarnings("unchecked") CompletableFuture<OperationOutcome>... futures) {
+
+        if (futures.length == 1) {
+            return futures[0];
+        }
+
+        final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+
+        attachFutures(controller, futures);
+
+        OperationOutcome[] outcomes = new OperationOutcome[futures.length];
+
+        @SuppressWarnings("rawtypes")
+        CompletableFuture[] futures2 = new CompletableFuture[futures.length];
+
+        // record the outcomes of each future when it completes
+        for (int count = 0; count < futures2.length; ++count) {
+            final int count2 = count;
+            futures2[count] = futures[count].whenComplete((outcome2, thrown) -> outcomes[count2] = outcome2);
+        }
+
+        // @formatter:off
+        CompletableFuture.allOf(futures2)
+                        .thenApply(unused -> combineOutcomes(outcomes))
+                        .whenCompleteAsync(controller.delayedComplete(), params.getExecutor());
+        // @formatter:on
+
+        return controller;
+    }
+
+    /**
+     * Attaches the given futures to the controller.
+     *
+     * @param controller master controller for all of the futures
+     * @param futures futures to be attached to the controller
+     */
+    private void attachFutures(PipelineControllerFuture<OperationOutcome> controller,
+                    @SuppressWarnings("unchecked") CompletableFuture<OperationOutcome>... futures) {
+
+        if (futures.length == 0) {
+            throw new IllegalArgumentException("empty list of futures");
+        }
+
+        // attach each task
+        for (CompletableFuture<OperationOutcome> future : futures) {
+            controller.add(future);
+        }
+    }
+
+    /**
+     * Combines the outcomes from a set of tasks.
+     *
+     * @param outcomes outcomes to be examined
+     * @return the combined outcome
+     */
+    private OperationOutcome combineOutcomes(OperationOutcome[] outcomes) {
+
+        // identify the outcome with the highest priority
+        OperationOutcome outcome = outcomes[0];
+        int priority = detmPriority(outcome);
+
+        // start with "1", as we've already dealt with "0"
+        for (int count = 1; count < outcomes.length; ++count) {
+            OperationOutcome outcome2 = outcomes[count];
+            int priority2 = detmPriority(outcome2);
+
+            if (priority2 > priority) {
+                outcome = outcome2;
+                priority = priority2;
+            }
+        }
+
+        logger.info("{}: combined outcome of tasks is {} for {}", getFullName(),
+                        (outcome == null ? null : outcome.getResult()), params.getRequestId());
+
+        return outcome;
+    }
+
+    /**
+     * Determines the priority of an outcome based on its result.
+     *
+     * @param outcome outcome to examine, or {@code null}
+     * @return the outcome's priority
+     */
+    protected int detmPriority(OperationOutcome outcome) {
+        if (outcome == null || outcome.getResult() == null) {
+            return 1;
+        }
+
+        switch (outcome.getResult()) {
+            case SUCCESS:
+                return 0;
+
+            case FAILURE_GUARD:
+                return 2;
+
+            case FAILURE_RETRIES:
+                return 3;
+
+            case FAILURE:
+                return 4;
+
+            case FAILURE_TIMEOUT:
+                return 5;
+
+            case FAILURE_EXCEPTION:
+            default:
+                return 6;
+        }
+    }
+
+    /**
+     * Performs a task, after verifying that the controller is still running. Also checks
+     * that the previous outcome was successful, if specified.
+     *
+     * @param controller overall pipeline controller
+     * @param checkSuccess {@code true} to check the previous outcome, {@code false}
+     *        otherwise
+     * @param outcome outcome of the previous task
+     * @param task task to be performed
+     * @return the task, if everything checks out. Otherwise, it returns an incomplete
+     *         future and completes the controller instead
+     */
+    // @formatter:off
+    protected CompletableFuture<OperationOutcome> doTask(
+                    PipelineControllerFuture<OperationOutcome> controller,
+                    boolean checkSuccess, OperationOutcome outcome,
+                    CompletableFuture<OperationOutcome> task) {
+        // @formatter:on
+
+        if (checkSuccess && !isSuccess(outcome)) {
+            /*
+             * must complete before canceling so that cancel() doesn't cause controller to
+             * complete
+             */
+            controller.complete(outcome);
+            task.cancel(false);
+            return new CompletableFuture<>();
+        }
+
+        return controller.wrap(task);
+    }
+
+    /**
+     * Performs a task, after verifying that the controller is still running. Also checks
+     * that the previous outcome was successful, if specified.
+     *
+     * @param controller overall pipeline controller
+     * @param checkSuccess {@code true} to check the previous outcome, {@code false}
+     *        otherwise
+     * @param task function to start the task to be performed
+     * @return a function to perform the task. If everything checks out, then it returns
+     *         the task. Otherwise, it returns an incomplete future and completes the
+     *         controller instead
+     */
+    // @formatter:off
+    protected Function<OperationOutcome, CompletableFuture<OperationOutcome>> doTask(
+                    PipelineControllerFuture<OperationOutcome> controller,
+                    boolean checkSuccess,
+                    Function<OperationOutcome, CompletableFuture<OperationOutcome>> task) {
+        // @formatter:on
+
+        return outcome -> {
+
+            if (!controller.isRunning()) {
+                return new CompletableFuture<>();
+            }
+
+            if (checkSuccess && !isSuccess(outcome)) {
+                controller.complete(outcome);
+                return new CompletableFuture<>();
+            }
+
+            return controller.wrap(task.apply(outcome));
+        };
+    }
+
+    /**
+     * Sets the start time of the operation and invokes the callback to indicate that the
+     * operation has started. Does nothing if the pipeline has been stopped.
+     * <p/>
+     * This assumes that the "outcome" is not {@code null}.
+     *
+     * @param callbacks used to determine if the start callback can be invoked
+     * @return a function that sets the start time and invokes the callback
+     */
+    private BiConsumer<OperationOutcome, Throwable> callbackStarted(CallbackManager callbacks) {
+
+        return (outcome, thrown) -> {
+
+            if (callbacks.canStart()) {
+                // haven't invoked "start" callback yet
+                outcome.setStart(callbacks.getStartTime());
+                outcome.setEnd(null);
+                params.callbackStarted(outcome);
+            }
+        };
+    }
+
+    /**
+     * Sets the end time of the operation and invokes the callback to indicate that the
+     * operation has completed. Does nothing if the pipeline has been stopped.
+     * <p/>
+     * This assumes that the "outcome" is not {@code null}.
+     * <p/>
+     * Note: the start time must be a reference rather than a plain value, because it's
+     * value must be gotten on-demand, when the returned function is executed at a later
+     * time.
+     *
+     * @param callbacks used to determine if the end callback can be invoked
+     * @return a function that sets the end time and invokes the callback
+     */
+    private BiConsumer<OperationOutcome, Throwable> callbackCompleted(CallbackManager callbacks) {
+
+        return (outcome, thrown) -> {
+
+            if (callbacks.canEnd()) {
+                outcome.setStart(callbacks.getStartTime());
+                outcome.setEnd(callbacks.getEndTime());
+                params.callbackCompleted(outcome);
+            }
+        };
+    }
+
+    /**
+     * Sets an operation's outcome and message, based on a throwable.
+     *
+     * @param operation operation to be updated
+     * @return the updated operation
+     */
+    protected OperationOutcome setOutcome(OperationOutcome operation, Throwable thrown) {
+        PolicyResult result = (isTimeout(thrown) ? PolicyResult.FAILURE_TIMEOUT : PolicyResult.FAILURE_EXCEPTION);
+        return setOutcome(operation, result);
+    }
+
+    /**
+     * Sets an operation's outcome and default message based on the result.
+     *
+     * @param operation operation to be updated
+     * @param result result of the operation
+     * @return the updated operation
+     */
+    public OperationOutcome setOutcome(OperationOutcome operation, PolicyResult result) {
+        logger.trace("{}: set outcome {} for {}", getFullName(), result, params.getRequestId());
+        operation.setResult(result);
+        operation.setMessage(result == PolicyResult.SUCCESS ? ControlLoopOperation.SUCCESS_MSG
+                        : ControlLoopOperation.FAILED_MSG);
+
+        return operation;
+    }
+
+    /**
+     * Determines if a throwable is due to a timeout.
+     *
+     * @param thrown throwable of interest
+     * @return {@code true} if the throwable is due to a timeout, {@code false} otherwise
+     */
+    protected boolean isTimeout(Throwable thrown) {
+        if (thrown instanceof CompletionException) {
+            thrown = thrown.getCause();
+        }
+
+        return (thrown instanceof TimeoutException);
+    }
+
+    // these may be overridden by subclasses or junit tests
+
+    /**
+     * Gets the retry count.
+     *
+     * @param retry retry, extracted from the parameters, or {@code null}
+     * @return the number of retries, or {@code 0} if no retries were specified
+     */
+    protected int getRetry(Integer retry) {
+        return (retry == null ? 0 : retry);
+    }
+
+    /**
+     * Gets the retry wait, in milliseconds.
+     *
+     * @return the retry wait, in milliseconds
+     */
+    protected long getRetryWaitMs() {
+        return DEFAULT_RETRY_WAIT_MS;
+    }
+
+    /**
+     * Gets the operation timeout.
+     *
+     * @param timeoutSec timeout, in seconds, extracted from the parameters, or
+     *        {@code null}
+     * @return the operation timeout, in milliseconds, or {@code 0} if no timeout was
+     *         specified
+     */
+    protected long getTimeoutMs(Integer timeoutSec) {
+        return (timeoutSec == null ? 0 : TimeUnit.MILLISECONDS.convert(timeoutSec, TimeUnit.SECONDS));
+    }
+}
index df5258d..3e15c1b 100644 (file)
 
 package org.onap.policy.controlloop.actorserviceprovider.impl;
 
-import java.util.List;
 import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionException;
 import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.function.BiConsumer;
-import java.util.function.Function;
-import lombok.AccessLevel;
 import lombok.Getter;
-import lombok.Setter;
-import org.onap.policy.controlloop.ControlLoopOperation;
-import org.onap.policy.controlloop.actorserviceprovider.CallbackManager;
-import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
 import org.onap.policy.controlloop.actorserviceprovider.Operator;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
-import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture;
-import org.onap.policy.controlloop.policy.PolicyResult;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
- * Partial implementation of an operator. In general, it's preferable that subclasses
- * would override
- * {@link #startOperationAsync(ControlLoopOperationParams, int, OperationOutcome)
- * startOperationAsync()}. However, if that proves to be too difficult, then they can
- * simply override {@link #doOperation(ControlLoopOperationParams, int, OperationOutcome)
- * doOperation()}. In addition, if the operation requires any preprocessor steps, the
- * subclass may choose to override
- * {@link #startPreprocessorAsync(ControlLoopOperationParams) startPreprocessorAsync()}.
- * <p/>
- * The futures returned by the methods within this class can be canceled, and will
- * propagate the cancellation to any subtasks. Thus it is also expected that any futures
- * returned by overridden methods will do the same. Of course, if a class overrides
- * {@link #doOperation(ControlLoopOperationParams, int, OperationOutcome) doOperation()},
- * then there's little that can be done to cancel that particular operation.
+ * Partial implementation of an operator.
  */
 public abstract class OperatorPartial extends StartConfigPartial<Map<String, Object>> implements Operator {
 
-    private static final Logger logger = LoggerFactory.getLogger(OperatorPartial.class);
-
     /**
      * Executor to be used for tasks that may perform blocking I/O. The default executor
      * simply launches a new thread for each command that is submitted to it.
      * <p/>
-     * May be overridden by junit tests.
+     * The "get" method may be overridden by junit tests.
      */
-    @Getter(AccessLevel.PROTECTED)
-    @Setter(AccessLevel.PROTECTED)
-    private Executor blockingExecutor = command -> {
+    @Getter
+    private final Executor blockingExecutor = command -> {
         Thread thread = new Thread(command);
         thread.setDaemon(true);
         thread.start();
@@ -125,721 +92,4 @@ public abstract class OperatorPartial extends StartConfigPartial<Map<String, Obj
     protected void doShutdown() {
         // do nothing
     }
-
-    @Override
-    public final CompletableFuture<OperationOutcome> startOperation(ControlLoopOperationParams params) {
-        if (!isAlive()) {
-            throw new IllegalStateException("operation is not running: " + getFullName());
-        }
-
-        // allocate a controller for the entire operation
-        final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        CompletableFuture<OperationOutcome> preproc = startPreprocessorAsync(params);
-        if (preproc == null) {
-            // no preprocessor required - just start the operation
-            return startOperationAttempt(params, controller, 1);
-        }
-
-        /*
-         * Do preprocessor first and then, if successful, start the operation. Note:
-         * operations create their own outcome, ignoring the outcome from any previous
-         * steps.
-         *
-         * Wrap the preprocessor to ensure "stop" is propagated to it.
-         */
-        // @formatter:off
-        controller.wrap(preproc)
-                        .exceptionally(fromException(params, "preprocessor of operation"))
-                        .thenCompose(handlePreprocessorFailure(params, controller))
-                        .thenCompose(unusedOutcome -> startOperationAttempt(params, controller, 1));
-        // @formatter:on
-
-        return controller;
-    }
-
-    /**
-     * Handles a failure in the preprocessor pipeline. If a failure occurred, then it
-     * invokes the call-backs, marks the controller complete, and returns an incomplete
-     * future, effectively halting the pipeline. Otherwise, it returns the outcome that it
-     * received.
-     * <p/>
-     * Assumes that no callbacks have been invoked yet.
-     *
-     * @param params operation parameters
-     * @param controller pipeline controller
-     * @return a function that checks the outcome status and continues, if successful, or
-     *         indicates a failure otherwise
-     */
-    private Function<OperationOutcome, CompletableFuture<OperationOutcome>> handlePreprocessorFailure(
-                    ControlLoopOperationParams params, PipelineControllerFuture<OperationOutcome> controller) {
-
-        return outcome -> {
-
-            if (outcome != null && isSuccess(outcome)) {
-                logger.trace("{}: preprocessor succeeded for {}", getFullName(), params.getRequestId());
-                return CompletableFuture.completedFuture(outcome);
-            }
-
-            logger.warn("preprocessor failed, discontinuing operation {} for {}", getFullName(), params.getRequestId());
-
-            final Executor executor = params.getExecutor();
-            final CallbackManager callbacks = new CallbackManager();
-
-            // propagate "stop" to the callbacks
-            controller.add(callbacks);
-
-            final OperationOutcome outcome2 = params.makeOutcome();
-
-            // TODO need a FAILURE_MISSING_DATA (e.g., A&AI)
-
-            outcome2.setResult(PolicyResult.FAILURE_GUARD);
-            outcome2.setMessage(outcome != null ? outcome.getMessage() : null);
-
-            // @formatter:off
-            CompletableFuture.completedFuture(outcome2)
-                            .whenCompleteAsync(callbackStarted(params, callbacks), executor)
-                            .whenCompleteAsync(callbackCompleted(params, callbacks), executor)
-                            .whenCompleteAsync(controller.delayedComplete(), executor);
-            // @formatter:on
-
-            return new CompletableFuture<>();
-        };
-    }
-
-    /**
-     * Invokes the operation's preprocessor step(s) as a "future". This method simply
-     * returns {@code null}.
-     * <p/>
-     * This method assumes the following:
-     * <ul>
-     * <li>the operator is alive</li>
-     * <li>exceptions generated within the pipeline will be handled by the invoker</li>
-     * </ul>
-     *
-     * @param params operation parameters
-     * @return a function that will start the preprocessor and returns its outcome, or
-     *         {@code null} if this operation needs no preprocessor
-     */
-    protected CompletableFuture<OperationOutcome> startPreprocessorAsync(ControlLoopOperationParams params) {
-        return null;
-    }
-
-    /**
-     * Starts the operation attempt, with no preprocessor. When all retries complete, it
-     * will complete the controller.
-     *
-     * @param params operation parameters
-     * @param controller controller for all operation attempts
-     * @param attempt attempt number, typically starting with 1
-     * @return a future that will return the final result of all attempts
-     */
-    private CompletableFuture<OperationOutcome> startOperationAttempt(ControlLoopOperationParams params,
-                    PipelineControllerFuture<OperationOutcome> controller, int attempt) {
-
-        // propagate "stop" to the operation attempt
-        controller.wrap(startAttemptWithoutRetries(params, attempt))
-                        .thenCompose(retryOnFailure(params, controller, attempt));
-
-        return controller;
-    }
-
-    /**
-     * Starts the operation attempt, without doing any retries.
-     *
-     * @param params operation parameters
-     * @param attempt attempt number, typically starting with 1
-     * @return a future that will return the result of a single operation attempt
-     */
-    private CompletableFuture<OperationOutcome> startAttemptWithoutRetries(ControlLoopOperationParams params,
-                    int attempt) {
-
-        logger.info("{}: start operation attempt {} for {}", getFullName(), attempt, params.getRequestId());
-
-        final Executor executor = params.getExecutor();
-        final OperationOutcome outcome = params.makeOutcome();
-        final CallbackManager callbacks = new CallbackManager();
-
-        // this operation attempt gets its own controller
-        final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        // propagate "stop" to the callbacks
-        controller.add(callbacks);
-
-        // @formatter:off
-        CompletableFuture<OperationOutcome> future = CompletableFuture.completedFuture(outcome)
-                        .whenCompleteAsync(callbackStarted(params, callbacks), executor)
-                        .thenCompose(controller.wrap(outcome2 -> startOperationAsync(params, attempt, outcome2)));
-        // @formatter:on
-
-        // handle timeouts, if specified
-        long timeoutMillis = getTimeOutMillis(params.getTimeoutSec());
-        if (timeoutMillis > 0) {
-            logger.info("{}: set timeout to {}ms for {}", getFullName(), timeoutMillis, params.getRequestId());
-            future = future.orTimeout(timeoutMillis, TimeUnit.MILLISECONDS);
-        }
-
-        /*
-         * Note: we re-invoke callbackStarted() just to be sure the callback is invoked
-         * before callbackCompleted() is invoked.
-         *
-         * Note: no need to remove "callbacks" from the pipeline, as we're going to stop
-         * the pipeline as the last step anyway.
-         */
-
-        // @formatter:off
-        future.exceptionally(fromException(params, "operation"))
-                    .thenApply(setRetryFlag(params, attempt))
-                    .whenCompleteAsync(callbackStarted(params, callbacks), executor)
-                    .whenCompleteAsync(callbackCompleted(params, callbacks), executor)
-                    .whenCompleteAsync(controller.delayedComplete(), executor);
-        // @formatter:on
-
-        return controller;
-    }
-
-    /**
-     * Determines if the outcome was successful.
-     *
-     * @param outcome outcome to examine
-     * @return {@code true} if the outcome was successful
-     */
-    protected boolean isSuccess(OperationOutcome outcome) {
-        return (outcome.getResult() == PolicyResult.SUCCESS);
-    }
-
-    /**
-     * Determines if the outcome was a failure for this operator.
-     *
-     * @param outcome outcome to examine, or {@code null}
-     * @return {@code true} if the outcome is not {@code null} and was a failure
-     *         <i>and</i> was associated with this operator, {@code false} otherwise
-     */
-    protected boolean isActorFailed(OperationOutcome outcome) {
-        return (isSameOperation(outcome) && outcome.getResult() == PolicyResult.FAILURE);
-    }
-
-    /**
-     * Determines if the given outcome is for this operation.
-     *
-     * @param outcome outcome to examine
-     * @return {@code true} if the outcome is for this operation, {@code false} otherwise
-     */
-    protected boolean isSameOperation(OperationOutcome outcome) {
-        return OperationOutcome.isFor(outcome, getActorName(), getName());
-    }
-
-    /**
-     * Invokes the operation as a "future". This method simply invokes
-     * {@link #doOperation(ControlLoopOperationParams)} using the {@link #blockingExecutor
-     * "blocking executor"}, returning the result via a "future".
-     * <p/>
-     * Note: if the operation uses blocking I/O, then it should <i>not</i> be run using
-     * the executor in the "params", as that may bring the background thread pool to a
-     * grinding halt. The {@link #blockingExecutor "blocking executor"} should be used
-     * instead.
-     * <p/>
-     * This method assumes the following:
-     * <ul>
-     * <li>the operator is alive</li>
-     * <li>verifyRunning() has been invoked</li>
-     * <li>callbackStarted() has been invoked</li>
-     * <li>the invoker will perform appropriate timeout checks</li>
-     * <li>exceptions generated within the pipeline will be handled by the invoker</li>
-     * </ul>
-     *
-     * @param params operation parameters
-     * @param attempt attempt number, typically starting with 1
-     * @return a function that will start the operation and return its result when
-     *         complete
-     */
-    protected CompletableFuture<OperationOutcome> startOperationAsync(ControlLoopOperationParams params, int attempt,
-                    OperationOutcome outcome) {
-
-        return CompletableFuture.supplyAsync(() -> doOperation(params, attempt, outcome), getBlockingExecutor());
-    }
-
-    /**
-     * Low-level method that performs the operation. This can make the same assumptions
-     * that are made by {@link #doOperationAsFuture(ControlLoopOperationParams)}. This
-     * particular method simply throws an {@link UnsupportedOperationException}.
-     *
-     * @param params operation parameters
-     * @param attempt attempt number, typically starting with 1
-     * @param operation the operation being performed
-     * @return the outcome of the operation
-     */
-    protected OperationOutcome doOperation(ControlLoopOperationParams params, int attempt, OperationOutcome operation) {
-
-        throw new UnsupportedOperationException("start operation " + getFullName());
-    }
-
-    /**
-     * Sets the outcome status to FAILURE_RETRIES, if the current operation outcome is
-     * FAILURE, assuming the policy specifies retries and the retry count has been
-     * exhausted.
-     *
-     * @param params operation parameters
-     * @param attempt latest attempt number, starting with 1
-     * @return a function to get the next future to execute
-     */
-    private Function<OperationOutcome, OperationOutcome> setRetryFlag(ControlLoopOperationParams params, int attempt) {
-
-        return operation -> {
-            if (operation != null && !isActorFailed(operation)) {
-                /*
-                 * wrong type or wrong operation - just leave it as is. No need to log
-                 * anything here, as retryOnFailure() will log a message
-                 */
-                return operation;
-            }
-
-            // get a non-null operation
-            OperationOutcome oper2;
-            if (operation != null) {
-                oper2 = operation;
-            } else {
-                oper2 = params.makeOutcome();
-                oper2.setResult(PolicyResult.FAILURE);
-            }
-
-            Integer retry = params.getRetry();
-            if (retry != null && retry > 0 && attempt > retry) {
-                /*
-                 * retries were specified and we've already tried them all - change to
-                 * FAILURE_RETRIES
-                 */
-                logger.info("operation {} retries exhausted for {}", getFullName(), params.getRequestId());
-                oper2.setResult(PolicyResult.FAILURE_RETRIES);
-            }
-
-            return oper2;
-        };
-    }
-
-    /**
-     * Restarts the operation if it was a FAILURE. Assumes that
-     * {@link #setRetryFlag(ControlLoopOperationParams, int)} was previously invoked, and
-     * thus that the "operation" is not {@code null}.
-     *
-     * @param params operation parameters
-     * @param controller controller for all of the retries
-     * @param attempt latest attempt number, starting with 1
-     * @return a function to get the next future to execute
-     */
-    private Function<OperationOutcome, CompletableFuture<OperationOutcome>> retryOnFailure(
-                    ControlLoopOperationParams params, PipelineControllerFuture<OperationOutcome> controller,
-                    int attempt) {
-
-        return operation -> {
-            if (!isActorFailed(operation)) {
-                // wrong type or wrong operation - just leave it as is
-                logger.trace("not retrying operation {} for {}", getFullName(), params.getRequestId());
-                controller.complete(operation);
-                return new CompletableFuture<>();
-            }
-
-            Integer retry = params.getRetry();
-            if (retry == null || retry <= 0) {
-                // no retries - already marked as FAILURE, so just return it
-                logger.info("operation {} no retries for {}", getFullName(), params.getRequestId());
-                controller.complete(operation);
-                return new CompletableFuture<>();
-            }
-
-
-            /*
-             * Retry the operation.
-             */
-            logger.info("retry operation {} for {}", getFullName(), params.getRequestId());
-
-            return startOperationAttempt(params, controller, attempt + 1);
-        };
-    }
-
-    /**
-     * Converts an exception into an operation outcome, returning a copy of the outcome to
-     * prevent background jobs from changing it.
-     *
-     * @param params operation parameters
-     * @param type type of item throwing the exception
-     * @return a function that will convert an exception into an operation outcome
-     */
-    private Function<Throwable, OperationOutcome> fromException(ControlLoopOperationParams params, String type) {
-
-        return thrown -> {
-            OperationOutcome outcome = params.makeOutcome();
-
-            logger.warn("exception throw by {} {}.{} for {}", type, outcome.getActor(), outcome.getOperation(),
-                            params.getRequestId(), thrown);
-
-            return setOutcome(params, outcome, thrown);
-        };
-    }
-
-    /**
-     * Similar to {@link CompletableFuture#anyOf(CompletableFuture...)}, but it cancels
-     * any outstanding futures when one completes.
-     *
-     * @param params operation parameters
-     * @param futures futures for which to wait
-     * @return a future to cancel or await an outcome. If this future is canceled, then
-     *         all of the futures will be canceled
-     */
-    protected CompletableFuture<OperationOutcome> anyOf(ControlLoopOperationParams params,
-                    List<CompletableFuture<OperationOutcome>> futures) {
-
-        // convert list to an array
-        @SuppressWarnings("rawtypes")
-        CompletableFuture[] arrFutures = futures.toArray(new CompletableFuture[futures.size()]);
-
-        @SuppressWarnings("unchecked")
-        CompletableFuture<OperationOutcome> result = anyOf(params, arrFutures);
-        return result;
-    }
-
-    /**
-     * Same as {@link CompletableFuture#anyOf(CompletableFuture...)}, but it cancels any
-     * outstanding futures when one completes.
-     *
-     * @param params operation parameters
-     * @param futures futures for which to wait
-     * @return a future to cancel or await an outcome. If this future is canceled, then
-     *         all of the futures will be canceled
-     */
-    protected CompletableFuture<OperationOutcome> anyOf(ControlLoopOperationParams params,
-                    @SuppressWarnings("unchecked") CompletableFuture<OperationOutcome>... futures) {
-
-        final Executor executor = params.getExecutor();
-        final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        attachFutures(controller, futures);
-
-        // @formatter:off
-        CompletableFuture.anyOf(futures)
-                            .thenApply(object -> (OperationOutcome) object)
-                            .whenCompleteAsync(controller.delayedComplete(), executor);
-        // @formatter:on
-
-        return controller;
-    }
-
-    /**
-     * Similar to {@link CompletableFuture#allOf(CompletableFuture...)}, but it cancels
-     * the futures if returned future is canceled. The future returns the "worst" outcome,
-     * based on priority (see {@link #detmPriority(OperationOutcome)}).
-     *
-     * @param params operation parameters
-     * @param futures futures for which to wait
-     * @return a future to cancel or await an outcome. If this future is canceled, then
-     *         all of the futures will be canceled
-     */
-    protected CompletableFuture<OperationOutcome> allOf(ControlLoopOperationParams params,
-                    List<CompletableFuture<OperationOutcome>> futures) {
-
-        // convert list to an array
-        @SuppressWarnings("rawtypes")
-        CompletableFuture[] arrFutures = futures.toArray(new CompletableFuture[futures.size()]);
-
-        @SuppressWarnings("unchecked")
-        CompletableFuture<OperationOutcome> result = allOf(params, arrFutures);
-        return result;
-    }
-
-    /**
-     * Same as {@link CompletableFuture#allOf(CompletableFuture...)}, but it cancels the
-     * futures if returned future is canceled. The future returns the "worst" outcome,
-     * based on priority (see {@link #detmPriority(OperationOutcome)}).
-     *
-     * @param params operation parameters
-     * @param futures futures for which to wait
-     * @return a future to cancel or await an outcome. If this future is canceled, then
-     *         all of the futures will be canceled
-     */
-    protected CompletableFuture<OperationOutcome> allOf(ControlLoopOperationParams params,
-                    @SuppressWarnings("unchecked") CompletableFuture<OperationOutcome>... futures) {
-
-        final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        attachFutures(controller, futures);
-
-        OperationOutcome[] outcomes = new OperationOutcome[futures.length];
-
-        @SuppressWarnings("rawtypes")
-        CompletableFuture[] futures2 = new CompletableFuture[futures.length];
-
-        // record the outcomes of each future when it completes
-        for (int count = 0; count < futures2.length; ++count) {
-            final int count2 = count;
-            futures2[count] = futures[count].whenComplete((outcome2, thrown) -> outcomes[count2] = outcome2);
-        }
-
-        CompletableFuture.allOf(futures2).whenComplete(combineOutcomes(params, controller, outcomes));
-
-        return controller;
-    }
-
-    /**
-     * Attaches the given futures to the controller.
-     *
-     * @param controller master controller for all of the futures
-     * @param futures futures to be attached to the controller
-     */
-    private void attachFutures(PipelineControllerFuture<OperationOutcome> controller,
-                    @SuppressWarnings("unchecked") CompletableFuture<OperationOutcome>... futures) {
-
-        // attach each task
-        for (CompletableFuture<OperationOutcome> future : futures) {
-            controller.add(future);
-        }
-    }
-
-    /**
-     * Combines the outcomes from a set of tasks.
-     *
-     * @param params operation parameters
-     * @param future future to be completed with the combined result
-     * @param outcomes outcomes to be examined
-     */
-    private BiConsumer<Void, Throwable> combineOutcomes(ControlLoopOperationParams params,
-                    CompletableFuture<OperationOutcome> future, OperationOutcome[] outcomes) {
-
-        return (unused, thrown) -> {
-            if (thrown != null) {
-                future.completeExceptionally(thrown);
-                return;
-            }
-
-            // identify the outcome with the highest priority
-            OperationOutcome outcome = outcomes[0];
-            int priority = detmPriority(outcome);
-
-            // start with "1", as we've already dealt with "0"
-            for (int count = 1; count < outcomes.length; ++count) {
-                OperationOutcome outcome2 = outcomes[count];
-                int priority2 = detmPriority(outcome2);
-
-                if (priority2 > priority) {
-                    outcome = outcome2;
-                    priority = priority2;
-                }
-            }
-
-            logger.trace("{}: combined outcome of tasks is {} for {}", getFullName(),
-                            (outcome == null ? null : outcome.getResult()), params.getRequestId());
-
-            future.complete(outcome);
-        };
-    }
-
-    /**
-     * Determines the priority of an outcome based on its result.
-     *
-     * @param outcome outcome to examine, or {@code null}
-     * @return the outcome's priority
-     */
-    protected int detmPriority(OperationOutcome outcome) {
-        if (outcome == null) {
-            return 1;
-        }
-
-        switch (outcome.getResult()) {
-            case SUCCESS:
-                return 0;
-
-            case FAILURE_GUARD:
-                return 2;
-
-            case FAILURE_RETRIES:
-                return 3;
-
-            case FAILURE:
-                return 4;
-
-            case FAILURE_TIMEOUT:
-                return 5;
-
-            case FAILURE_EXCEPTION:
-            default:
-                return 6;
-        }
-    }
-
-    /**
-     * Performs a task, after verifying that the controller is still running. Also checks
-     * that the previous outcome was successful, if specified.
-     *
-     * @param params operation parameters
-     * @param controller overall pipeline controller
-     * @param checkSuccess {@code true} to check the previous outcome, {@code false}
-     *        otherwise
-     * @param outcome outcome of the previous task
-     * @param tasks tasks to be performed
-     * @return a function to perform the task. If everything checks out, then it returns
-     *         the task's future. Otherwise, it returns an incomplete future and completes
-     *         the controller instead.
-     */
-    // @formatter:off
-    protected CompletableFuture<OperationOutcome> doTask(ControlLoopOperationParams params,
-                    PipelineControllerFuture<OperationOutcome> controller,
-                    boolean checkSuccess, OperationOutcome outcome,
-                    CompletableFuture<OperationOutcome> task) {
-        // @formatter:on
-
-        if (checkSuccess && !isSuccess(outcome)) {
-            /*
-             * must complete before canceling so that cancel() doesn't cause controller to
-             * complete
-             */
-            controller.complete(outcome);
-            task.cancel(false);
-            return new CompletableFuture<>();
-        }
-
-        return controller.wrap(task);
-    }
-
-    /**
-     * Performs a task, after verifying that the controller is still running. Also checks
-     * that the previous outcome was successful, if specified.
-     *
-     * @param params operation parameters
-     * @param controller overall pipeline controller
-     * @param checkSuccess {@code true} to check the previous outcome, {@code false}
-     *        otherwise
-     * @param tasks tasks to be performed
-     * @return a function to perform the task. If everything checks out, then it returns
-     *         the task's future. Otherwise, it returns an incomplete future and completes
-     *         the controller instead.
-     */
-    // @formatter:off
-    protected Function<OperationOutcome, CompletableFuture<OperationOutcome>> doTask(ControlLoopOperationParams params,
-                    PipelineControllerFuture<OperationOutcome> controller,
-                    boolean checkSuccess,
-                    Function<OperationOutcome, CompletableFuture<OperationOutcome>> task) {
-        // @formatter:on
-
-        return outcome -> {
-
-            if (!controller.isRunning()) {
-                return new CompletableFuture<>();
-            }
-
-            if (checkSuccess && !isSuccess(outcome)) {
-                controller.complete(outcome);
-                return new CompletableFuture<>();
-            }
-
-            return controller.wrap(task.apply(outcome));
-        };
-    }
-
-    /**
-     * Sets the start time of the operation and invokes the callback to indicate that the
-     * operation has started. Does nothing if the pipeline has been stopped.
-     * <p/>
-     * This assumes that the "outcome" is not {@code null}.
-     *
-     * @param params operation parameters
-     * @param callbacks used to determine if the start callback can be invoked
-     * @return a function that sets the start time and invokes the callback
-     */
-    private BiConsumer<OperationOutcome, Throwable> callbackStarted(ControlLoopOperationParams params,
-                    CallbackManager callbacks) {
-
-        return (outcome, thrown) -> {
-
-            if (callbacks.canStart()) {
-                // haven't invoked "start" callback yet
-                outcome.setStart(callbacks.getStartTime());
-                outcome.setEnd(null);
-                params.callbackStarted(outcome);
-            }
-        };
-    }
-
-    /**
-     * Sets the end time of the operation and invokes the callback to indicate that the
-     * operation has completed. Does nothing if the pipeline has been stopped.
-     * <p/>
-     * This assumes that the "outcome" is not {@code null}.
-     * <p/>
-     * Note: the start time must be a reference rather than a plain value, because it's
-     * value must be gotten on-demand, when the returned function is executed at a later
-     * time.
-     *
-     * @param params operation parameters
-     * @param callbacks used to determine if the end callback can be invoked
-     * @return a function that sets the end time and invokes the callback
-     */
-    private BiConsumer<OperationOutcome, Throwable> callbackCompleted(ControlLoopOperationParams params,
-                    CallbackManager callbacks) {
-
-        return (outcome, thrown) -> {
-
-            if (callbacks.canEnd()) {
-                outcome.setStart(callbacks.getStartTime());
-                outcome.setEnd(callbacks.getEndTime());
-                params.callbackCompleted(outcome);
-            }
-        };
-    }
-
-    /**
-     * Sets an operation's outcome and message, based on a throwable.
-     *
-     * @param params operation parameters
-     * @param operation operation to be updated
-     * @return the updated operation
-     */
-    protected OperationOutcome setOutcome(ControlLoopOperationParams params, OperationOutcome operation,
-                    Throwable thrown) {
-        PolicyResult result = (isTimeout(thrown) ? PolicyResult.FAILURE_TIMEOUT : PolicyResult.FAILURE_EXCEPTION);
-        return setOutcome(params, operation, result);
-    }
-
-    /**
-     * Sets an operation's outcome and default message based on the result.
-     *
-     * @param params operation parameters
-     * @param operation operation to be updated
-     * @param result result of the operation
-     * @return the updated operation
-     */
-    protected OperationOutcome setOutcome(ControlLoopOperationParams params, OperationOutcome operation,
-                    PolicyResult result) {
-        logger.trace("{}: set outcome {} for {}", getFullName(), result, params.getRequestId());
-        operation.setResult(result);
-        operation.setMessage(result == PolicyResult.SUCCESS ? ControlLoopOperation.SUCCESS_MSG
-                        : ControlLoopOperation.FAILED_MSG);
-
-        return operation;
-    }
-
-    /**
-     * Determines if a throwable is due to a timeout.
-     *
-     * @param thrown throwable of interest
-     * @return {@code true} if the throwable is due to a timeout, {@code false} otherwise
-     */
-    protected boolean isTimeout(Throwable thrown) {
-        if (thrown instanceof CompletionException) {
-            thrown = thrown.getCause();
-        }
-
-        return (thrown instanceof TimeoutException);
-    }
-
-    // these may be overridden by junit tests
-
-    /**
-     * Gets the operation timeout. Subclasses may override this method to obtain the
-     * timeout in some other way (e.g., through configuration properties).
-     *
-     * @param timeoutSec timeout, in seconds, or {@code null}
-     * @return the operation timeout, in milliseconds
-     */
-    protected long getTimeOutMillis(Integer timeoutSec) {
-        return (timeoutSec == null ? 0 : TimeUnit.MILLISECONDS.convert(timeoutSec, TimeUnit.SECONDS));
-    }
 }
index 57fce40..9259160 100644 (file)
@@ -148,7 +148,8 @@ public class ControlLoopOperationParams {
         return actorService
                     .getActor(getActor())
                     .getOperator(getOperation())
-                    .startOperation(this);
+                    .buildOperation(this)
+                    .start();
         // @formatter:on
     }
 
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandlerTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandlerTest.java
deleted file mode 100644 (file)
index 31c6d20..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-/*-
- * ============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.actorserviceprovider;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-import java.util.UUID;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.atomic.AtomicReference;
-import org.junit.Before;
-import org.junit.Test;
-import org.onap.policy.controlloop.VirtualControlLoopEvent;
-import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
-import org.onap.policy.controlloop.policy.PolicyResult;
-
-public class AsyncResponseHandlerTest {
-
-    private static final String ACTOR = "my-actor";
-    private static final String OPERATION = "my-operation";
-    private static final UUID REQ_ID = UUID.randomUUID();
-    private static final String TEXT = "some text";
-
-    private VirtualControlLoopEvent event;
-    private ControlLoopEventContext context;
-    private ControlLoopOperationParams params;
-    private OperationOutcome outcome;
-    private MyHandler handler;
-
-    /**
-     * Initializes all fields, including {@link #handler}.
-     */
-    @Before
-    public void setUp() {
-        event = new VirtualControlLoopEvent();
-        event.setRequestId(REQ_ID);
-
-        context = new ControlLoopEventContext(event);
-        params = ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).context(context).build();
-        outcome = params.makeOutcome();
-
-        handler = new MyHandler(params, outcome);
-    }
-
-    @Test
-    public void testAsyncResponseHandler_testGetParams_testGetOutcome() {
-        assertSame(params, handler.getParams());
-        assertSame(outcome, handler.getOutcome());
-    }
-
-    @Test
-    public void testHandle() {
-        CompletableFuture<String> future = new CompletableFuture<>();
-        handler.handle(future).complete(outcome);
-
-        assertTrue(future.isCancelled());
-    }
-
-    @Test
-    public void testCompleted() throws Exception {
-        CompletableFuture<OperationOutcome> result = handler.handle(new CompletableFuture<>());
-        handler.completed(TEXT);
-        assertTrue(result.isDone());
-        assertSame(outcome, result.get());
-        assertEquals(PolicyResult.FAILURE_RETRIES, outcome.getResult());
-        assertEquals(TEXT, outcome.getMessage());
-    }
-
-    /**
-     * Tests completed() when doCompleted() throws an exception.
-     */
-    @Test
-    public void testCompletedException() throws Exception {
-        IllegalStateException except = new IllegalStateException();
-
-        outcome = params.makeOutcome();
-        handler = new MyHandler(params, outcome) {
-            @Override
-            protected OperationOutcome doComplete(String rawResponse) {
-                throw except;
-            }
-        };
-
-        CompletableFuture<OperationOutcome> result = handler.handle(new CompletableFuture<>());
-        handler.completed(TEXT);
-        assertTrue(result.isCompletedExceptionally());
-
-        AtomicReference<Throwable> thrown = new AtomicReference<>();
-        result.whenComplete((unused, thrown2) -> thrown.set(thrown2));
-
-        assertSame(except, thrown.get());
-    }
-
-    @Test
-    public void testFailed() throws Exception {
-        IllegalStateException except = new IllegalStateException();
-
-        CompletableFuture<OperationOutcome> result = handler.handle(new CompletableFuture<>());
-        handler.failed(except);
-
-        assertTrue(result.isDone());
-        assertSame(outcome, result.get());
-        assertEquals(PolicyResult.FAILURE_GUARD, outcome.getResult());
-    }
-
-    /**
-     * Tests failed() when doFailed() throws an exception.
-     */
-    @Test
-    public void testFailedException() throws Exception {
-        IllegalStateException except = new IllegalStateException();
-
-        outcome = params.makeOutcome();
-        handler = new MyHandler(params, outcome) {
-            @Override
-            protected OperationOutcome doFailed(Throwable thrown) {
-                throw except;
-            }
-        };
-
-        CompletableFuture<OperationOutcome> result = handler.handle(new CompletableFuture<>());
-        handler.failed(except);
-        assertTrue(result.isCompletedExceptionally());
-
-        AtomicReference<Throwable> thrown = new AtomicReference<>();
-        result.whenComplete((unused, thrown2) -> thrown.set(thrown2));
-
-        assertSame(except, thrown.get());
-    }
-
-    private class MyHandler extends AsyncResponseHandler<String> {
-
-        public MyHandler(ControlLoopOperationParams params, OperationOutcome outcome) {
-            super(params, outcome);
-        }
-
-        @Override
-        protected OperationOutcome doComplete(String rawResponse) {
-            OperationOutcome outcome = getOutcome();
-            outcome.setResult(PolicyResult.FAILURE_RETRIES);
-            outcome.setMessage(rawResponse);
-            return outcome;
-        }
-
-        @Override
-        protected OperationOutcome doFailed(Throwable thrown) {
-            OperationOutcome outcome = getOutcome();
-            outcome.setResult(PolicyResult.FAILURE_GUARD);
-            return outcome;
-        }
-    }
-}
index 4a3f321..0a2a5a9 100644 (file)
@@ -39,16 +39,10 @@ import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
-import org.onap.policy.common.utils.coder.CoderException;
-import org.onap.policy.common.utils.coder.StandardCoder;
 import org.onap.policy.common.utils.test.log.logback.ExtractAppender;
 import org.slf4j.LoggerFactory;
 
 public class UtilTest {
-    private static final String MY_REQUEST = "my-request";
-    private static final String URL = "my-url";
-    private static final String OUT_URL = "OUT|REST|my-url";
-    private static final String IN_URL = "IN|REST|my-url";
     protected static final String EXPECTED_EXCEPTION = "expected exception";
 
     /**
@@ -88,82 +82,6 @@ public class UtilTest {
         assertTrue(result.length() > 1);
     }
 
-    @Test
-    public void testLogRestRequest() throws CoderException {
-        // log structured data
-        appender.clearExtractions();
-        Util.logRestRequest(URL, new Abc(10, null, null));
-        List<String> output = appender.getExtracted();
-        assertEquals(1, output.size());
-
-        assertThat(output.get(0)).contains(OUT_URL).contains("{\n  \"intValue\": 10\n}");
-
-        // log a plain string
-        appender.clearExtractions();
-        Util.logRestRequest(URL, MY_REQUEST);
-        output = appender.getExtracted();
-        assertEquals(1, output.size());
-
-        assertThat(output.get(0)).contains(OUT_URL).contains(MY_REQUEST);
-
-        // exception from coder
-        StandardCoder coder = new StandardCoder() {
-            @Override
-            public String encode(Object object, boolean pretty) throws CoderException {
-                throw new CoderException(EXPECTED_EXCEPTION);
-            }
-        };
-
-        appender.clearExtractions();
-        Util.logRestRequest(coder, URL, new Abc(11, null, null));
-        output = appender.getExtracted();
-        assertEquals(2, output.size());
-        assertThat(output.get(0)).contains("cannot pretty-print request");
-        assertThat(output.get(1)).contains(OUT_URL);
-    }
-
-    @Test
-    public void testLogRestResponse() throws CoderException {
-        // log structured data
-        appender.clearExtractions();
-        Util.logRestResponse(URL, new Abc(10, null, null));
-        List<String> output = appender.getExtracted();
-        assertEquals(1, output.size());
-
-        assertThat(output.get(0)).contains(IN_URL).contains("{\n  \"intValue\": 10\n}");
-
-        // log null response
-        appender.clearExtractions();
-        Util.logRestResponse(URL, null);
-        output = appender.getExtracted();
-        assertEquals(1, output.size());
-
-        assertThat(output.get(0)).contains(IN_URL).contains("null");
-
-        // log a plain string
-        appender.clearExtractions();
-        Util.logRestResponse(URL, MY_REQUEST);
-        output = appender.getExtracted();
-        assertEquals(1, output.size());
-
-        assertThat(output.get(0)).contains(IN_URL).contains(MY_REQUEST);
-
-        // exception from coder
-        StandardCoder coder = new StandardCoder() {
-            @Override
-            public String encode(Object object, boolean pretty) throws CoderException {
-                throw new CoderException(EXPECTED_EXCEPTION);
-            }
-        };
-
-        appender.clearExtractions();
-        Util.logRestResponse(coder, URL, new Abc(11, null, null));
-        output = appender.getExtracted();
-        assertEquals(2, output.size());
-        assertThat(output.get(0)).contains("cannot pretty-print response");
-        assertThat(output.get(1)).contains(IN_URL);
-    }
-
     @Test
     public void testRunFunction() {
         // no exception, no log
index 0d917ad..b462043 100644 (file)
@@ -24,13 +24,21 @@ 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.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import java.util.Map;
 import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
 import org.junit.Before;
 import org.junit.Test;
 import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
 
 public class ControlLoopEventContextTest {
     private static final UUID REQ_ID = UUID.randomUUID();
@@ -84,4 +92,39 @@ public class ControlLoopEventContextTest {
         int intValue = context.getProperty("def");
         assertEquals(100, intValue);
     }
+
+    @Test
+    public void testObtain() {
+        final ControlLoopOperationParams params = mock(ControlLoopOperationParams.class);
+
+        // property is already loaded
+        context.setProperty("obtain-A", "value-A");
+        assertNull(context.obtain("obtain-A", params));
+
+        // new property - should retrieve
+        CompletableFuture<OperationOutcome> future = new CompletableFuture<>();
+        when(params.start()).thenReturn(future);
+        assertSame(future, context.obtain("obtain-B", params));
+
+        // repeat - should get the same future, without invoking start() again
+        assertSame(future, context.obtain("obtain-B", params));
+        verify(params).start();
+
+        // arrange for another invoker to start while this one is starting
+        CompletableFuture<OperationOutcome> future2 = new CompletableFuture<>();
+
+        when(params.start()).thenAnswer(args -> {
+
+            ControlLoopOperationParams params2 = mock(ControlLoopOperationParams.class);
+            when(params2.start()).thenReturn(future2);
+
+            assertSame(future2, context.obtain("obtain-C", params2));
+            return future;
+        });
+
+        assertSame(future2, context.obtain("obtain-C", params));
+
+        // should have canceled the interrupted future
+        assertTrue(future.isCancelled());
+    }
 }
index a209fb0..92cbbe7 100644 (file)
@@ -42,7 +42,9 @@ import org.junit.Before;
 import org.junit.Test;
 import org.onap.policy.common.parameters.ObjectValidationResult;
 import org.onap.policy.common.parameters.ValidationStatus;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
 import org.onap.policy.controlloop.actorserviceprovider.Operator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
 import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
 
 public class ActorImplTest {
@@ -375,10 +377,15 @@ public class ActorImplTest {
         return actor;
     }
 
-    private static class MyOper extends OperatorPartial implements Operator {
+    private static class MyOper extends OperatorPartial {
 
         public MyOper(String name) {
             super(ACTOR_NAME, name);
         }
+
+        @Override
+        public Operation buildOperation(ControlLoopOperationParams params) {
+            return null;
+        }
     }
 }
index 2da7899..8ce3b32 100644 (file)
@@ -38,7 +38,7 @@ public class HttpActorTest {
     private static final String ACTOR = "my-actor";
     private static final String UNKNOWN = "unknown";
     private static final String CLIENT = "my-client";
-    private static final long TIMEOUT = 10L;
+    private static final int TIMEOUT = 10;
 
     private HttpActor actor;
 
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperationTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperationTest.java
new file mode 100644 (file)
index 0000000..19f781d
--- /dev/null
@@ -0,0 +1,781 @@
+/*-
+ * ============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.actorserviceprovider.impl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+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.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import ch.qos.logback.classic.Logger;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.UUID;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.InvocationCallback;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import lombok.Getter;
+import lombok.Setter;
+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.common.endpoints.event.comm.bus.internal.BusTopicParams;
+import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams.TopicParamsBuilder;
+import org.onap.policy.common.endpoints.http.client.HttpClient;
+import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance;
+import org.onap.policy.common.endpoints.http.server.HttpServletServer;
+import org.onap.policy.common.endpoints.http.server.HttpServletServerFactoryInstance;
+import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties;
+import org.onap.policy.common.gson.GsonMessageBodyHandler;
+import org.onap.policy.common.utils.coder.Coder;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.network.NetworkUtil;
+import org.onap.policy.common.utils.test.log.logback.ExtractAppender;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.slf4j.LoggerFactory;
+
+public class HttpOperationTest {
+
+    private static final IllegalStateException EXPECTED_EXCEPTION = new IllegalStateException("expected exception");
+    private static final String ACTOR = "my-actor";
+    private static final String OPERATION = "my-name";
+    private static final String HTTP_CLIENT = "my-client";
+    private static final String HTTP_NO_SERVER = "my-http-no-server-client";
+    private static final String MEDIA_TYPE_APPLICATION_JSON = "application/json";
+    private static final String MY_REQUEST = "my-request";
+    private static final String BASE_URI = "oper";
+    private static final String PATH = "/my-path";
+    private static final String TEXT = "my-text";
+    private static final UUID REQ_ID = UUID.randomUUID();
+
+    /**
+     * Used to attach an appender to the class' logger.
+     */
+    private static final Logger logger = (Logger) LoggerFactory.getLogger(HttpOperation.class);
+    private static final ExtractAppender appender = new ExtractAppender();
+
+    /**
+     * {@code True} if the server should reject the request, {@code false} otherwise.
+     */
+    private static boolean rejectRequest;
+
+    // call counts of each method type in the server
+    private static int nget;
+    private static int npost;
+    private static int nput;
+    private static int ndelete;
+
+    @Mock
+    private HttpClient client;
+
+    @Mock
+    private Response response;
+
+    private VirtualControlLoopEvent event;
+    private ControlLoopEventContext context;
+    private ControlLoopOperationParams params;
+    private OperationOutcome outcome;
+    private AtomicReference<InvocationCallback<Response>> callback;
+    private Future<Response> future;
+    private HttpOperator operator;
+    private MyGetOperation<String> oper;
+
+    /**
+     * Starts the simulator.
+     */
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+        // allocate a port
+        int port = NetworkUtil.allocPort();
+
+        /*
+         * Start the simulator. Must use "Properties" to configure it, otherwise the
+         * server will use the wrong serialization provider.
+         */
+        Properties svrprops = getServerProperties("my-server", port);
+        HttpServletServerFactoryInstance.getServerFactory().build(svrprops).forEach(HttpServletServer::start);
+
+        if (!NetworkUtil.isTcpPortOpen("localhost", port, 100, 100)) {
+            HttpServletServerFactoryInstance.getServerFactory().destroy();
+            throw new IllegalStateException("server is not running");
+        }
+
+        /*
+         * Start the clients, one to the server, and one to a non-existent server.
+         */
+        TopicParamsBuilder builder = BusTopicParams.builder().managed(true).hostname("localhost").basePath(BASE_URI)
+                        .serializationProvider(GsonMessageBodyHandler.class.getName());
+
+        HttpClientFactoryInstance.getClientFactory().build(builder.clientName(HTTP_CLIENT).port(port).build());
+
+        HttpClientFactoryInstance.getClientFactory()
+                        .build(builder.clientName(HTTP_NO_SERVER).port(NetworkUtil.allocPort()).build());
+
+        /**
+         * Attach appender to the logger.
+         */
+        appender.setContext(logger.getLoggerContext());
+        appender.start();
+
+        logger.addAppender(appender);
+    }
+
+    /**
+     * Destroys the Http factories and stops the appender.
+     */
+    @AfterClass
+    public static void tearDownAfterClass() {
+        appender.stop();
+
+        HttpClientFactoryInstance.getClientFactory().destroy();
+        HttpServletServerFactoryInstance.getServerFactory().destroy();
+    }
+
+    /**
+     * Initializes fields, including {@link #oper}, and resets the static fields used by
+     * the REST server.
+     */
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        appender.clearExtractions();
+
+        rejectRequest = false;
+        nget = 0;
+        npost = 0;
+        nput = 0;
+        ndelete = 0;
+
+        when(response.readEntity(String.class)).thenReturn(TEXT);
+        when(response.getStatus()).thenReturn(200);
+
+        event = new VirtualControlLoopEvent();
+        event.setRequestId(REQ_ID);
+
+        context = new ControlLoopEventContext(event);
+        params = ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).context(context).build();
+
+        outcome = params.makeOutcome();
+
+        callback = new AtomicReference<>();
+        future = new CompletableFuture<>();
+
+        operator = new HttpOperator(ACTOR, OPERATION) {
+            @Override
+            public Operation buildOperation(ControlLoopOperationParams params) {
+                return null;
+            }
+
+            @Override
+            public HttpClient getClient() {
+                return client;
+            }
+        };
+
+        initOper(operator, HTTP_CLIENT);
+
+        oper = new MyGetOperation<>(String.class);
+    }
+
+    @Test
+    public void testHttpOperator() {
+        assertEquals(ACTOR, oper.getActorName());
+        assertEquals(OPERATION, oper.getName());
+        assertEquals(ACTOR + "." + OPERATION, oper.getFullName());
+    }
+
+    @Test
+    public void testMakeHeaders() {
+        assertEquals(Collections.emptyMap(), oper.makeHeaders());
+    }
+
+    @Test
+    public void testMakePath() {
+        assertEquals(PATH, oper.makePath());
+    }
+
+    @Test
+    public void testMakeUrl() {
+        // use a real client
+        client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
+
+        assertThat(oper.makeUrl()).endsWith("/" + BASE_URI + PATH);
+    }
+
+    @Test
+    public void testDoConfigureMapOfStringObject_testGetClient_testGetPath_testGetTimeoutMs() {
+
+        // no default yet
+        assertEquals(0L, oper.getTimeoutMs(null));
+        assertEquals(0L, oper.getTimeoutMs(0));
+
+        // should use given value
+        assertEquals(20 * 1000L, oper.getTimeoutMs(20));
+
+        // indicate we have a timeout value
+        operator = spy(operator);
+        when(operator.getTimeoutMs()).thenReturn(30L);
+
+        oper = new MyGetOperation<String>(String.class);
+
+        // should use default
+        assertEquals(30L, oper.getTimeoutMs(null));
+        assertEquals(30L, oper.getTimeoutMs(0));
+
+        // should use given value
+        assertEquals(40 * 1000L, oper.getTimeoutMs(40));
+    }
+
+    /**
+     * Tests handleResponse() when it completes.
+     */
+    @Test
+    public void testHandleResponseComplete() throws Exception {
+        CompletableFuture<OperationOutcome> future2 = oper.handleResponse(outcome, PATH, cb -> {
+            callback.set(cb);
+            return future;
+        });
+
+        assertFalse(future2.isDone());
+        assertNotNull(callback.get());
+        callback.get().completed(response);
+
+        assertSame(outcome, future2.get(5, TimeUnit.SECONDS));
+
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    /**
+     * Tests handleResponse() when it fails.
+     */
+    @Test
+    public void testHandleResponseFailed() throws Exception {
+        CompletableFuture<OperationOutcome> future2 = oper.handleResponse(outcome, PATH, cb -> {
+            callback.set(cb);
+            return future;
+        });
+
+        assertFalse(future2.isDone());
+        assertNotNull(callback.get());
+        callback.get().failed(EXPECTED_EXCEPTION);
+
+        assertThatThrownBy(() -> future2.get(5, TimeUnit.SECONDS)).hasCause(EXPECTED_EXCEPTION);
+
+        // future and future2 may be completed in parallel so we must wait again
+        assertThatThrownBy(() -> future.get(5, TimeUnit.SECONDS)).isInstanceOf(CancellationException.class);
+        assertTrue(future.isCancelled());
+    }
+
+    /**
+     * Tests processResponse() when it's a success and the response type is a String.
+     */
+    @Test
+    public void testProcessResponseSuccessString() {
+        assertSame(outcome, oper.processResponse(outcome, PATH, response));
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    /**
+     * Tests processResponse() when it's a failure.
+     */
+    @Test
+    public void testProcessResponseFailure() {
+        when(response.getStatus()).thenReturn(555);
+        assertSame(outcome, oper.processResponse(outcome, PATH, response));
+        assertEquals(PolicyResult.FAILURE, outcome.getResult());
+    }
+
+    /**
+     * Tests processResponse() when the decoder succeeds.
+     */
+    @Test
+    public void testProcessResponseDecodeOk() throws CoderException {
+        when(response.readEntity(String.class)).thenReturn("10");
+
+        MyGetOperation<Integer> oper2 = new MyGetOperation<>(Integer.class);
+
+        assertSame(outcome, oper2.processResponse(outcome, PATH, response));
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    /**
+     * Tests processResponse() when the decoder throws an exception.
+     */
+    @Test
+    public void testProcessResponseDecodeExcept() throws CoderException {
+        MyGetOperation<Integer> oper2 = new MyGetOperation<>(Integer.class);
+
+        assertSame(outcome, oper2.processResponse(outcome, PATH, response));
+        assertEquals(PolicyResult.FAILURE_EXCEPTION, outcome.getResult());
+    }
+
+    @Test
+    public void testPostProcessResponse() {
+        assertThatCode(() -> oper.postProcessResponse(outcome, PATH, null, null)).doesNotThrowAnyException();
+    }
+
+    @Test
+    public void testIsSuccess() {
+        when(response.getStatus()).thenReturn(200);
+        assertTrue(oper.isSuccess(response, null));
+
+        when(response.getStatus()).thenReturn(555);
+        assertFalse(oper.isSuccess(response, null));
+    }
+
+    /**
+     * Tests a GET.
+     */
+    @Test
+    public void testGet() throws Exception {
+        // use a real client
+        client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
+
+        MyGetOperation<MyResponse> oper2 = new MyGetOperation<>(MyResponse.class);
+
+        OperationOutcome outcome = runOperation(oper2);
+        assertNotNull(outcome);
+        assertEquals(1, nget);
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    /**
+     * Tests a DELETE.
+     */
+    @Test
+    public void testDelete() throws Exception {
+        // use a real client
+        client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
+
+        MyDeleteOperation oper2 = new MyDeleteOperation();
+
+        OperationOutcome outcome = runOperation(oper2);
+        assertNotNull(outcome);
+        assertEquals(1, ndelete);
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    /**
+     * Tests a POST.
+     */
+    @Test
+    public void testPost() throws Exception {
+        // use a real client
+        client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
+
+        MyPostOperation oper2 = new MyPostOperation();
+
+        OperationOutcome outcome = runOperation(oper2);
+        assertNotNull(outcome);
+        assertEquals(1, npost);
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    /**
+     * Tests a PUT.
+     */
+    @Test
+    public void testPut() throws Exception {
+        // use a real client
+        client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
+
+        MyPutOperation oper2 = new MyPutOperation();
+
+        OperationOutcome outcome = runOperation(oper2);
+        assertNotNull(outcome);
+        assertEquals(1, nput);
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    @Test
+    public void testLogRestRequest() throws CoderException {
+        // log structured data
+        appender.clearExtractions();
+        oper.logRestRequest(PATH, new MyRequest());
+        List<String> output = appender.getExtracted();
+        assertEquals(1, output.size());
+
+        assertThat(output.get(0)).contains(PATH).contains("{\n  \"input\": \"some input\"\n}");
+
+        // log a plain string
+        appender.clearExtractions();
+        oper.logRestRequest(PATH, MY_REQUEST);
+        output = appender.getExtracted();
+        assertEquals(1, output.size());
+
+        assertThat(output.get(0)).contains(PATH).contains(MY_REQUEST);
+
+        // log a null request
+        appender.clearExtractions();
+        oper.logRestRequest(PATH, null);
+        output = appender.getExtracted();
+        assertEquals(1, output.size());
+
+        // exception from coder
+        oper = new MyGetOperation<>(String.class) {
+            @Override
+            protected Coder makeCoder() {
+                return new StandardCoder() {
+                    @Override
+                    public String encode(Object object, boolean pretty) throws CoderException {
+                        throw new CoderException(EXPECTED_EXCEPTION);
+                    }
+                };
+            }
+        };
+
+        appender.clearExtractions();
+        oper.logRestRequest(PATH, new MyRequest());
+        output = appender.getExtracted();
+        assertEquals(2, output.size());
+        assertThat(output.get(0)).contains("cannot pretty-print request");
+        assertThat(output.get(1)).contains(PATH);
+    }
+
+    @Test
+    public void testLogRestResponse() throws CoderException {
+        // log structured data
+        appender.clearExtractions();
+        oper.logRestResponse(PATH, new MyResponse());
+        List<String> output = appender.getExtracted();
+        assertEquals(1, output.size());
+
+        assertThat(output.get(0)).contains(PATH).contains("{\n  \"output\": \"some output\"\n}");
+
+        // log a plain string
+        appender.clearExtractions();
+        oper.logRestResponse(PATH, MY_REQUEST);
+        output = appender.getExtracted();
+        assertEquals(1, output.size());
+
+        // log a null response
+        appender.clearExtractions();
+        oper.logRestResponse(PATH, null);
+        output = appender.getExtracted();
+        assertEquals(1, output.size());
+
+        assertThat(output.get(0)).contains(PATH).contains("null");
+
+        // exception from coder
+        oper = new MyGetOperation<>(String.class) {
+            @Override
+            protected Coder makeCoder() {
+                return new StandardCoder() {
+                    @Override
+                    public String encode(Object object, boolean pretty) throws CoderException {
+                        throw new CoderException(EXPECTED_EXCEPTION);
+                    }
+                };
+            }
+        };
+
+        appender.clearExtractions();
+        oper.logRestResponse(PATH, new MyResponse());
+        output = appender.getExtracted();
+        assertEquals(2, output.size());
+        assertThat(output.get(0)).contains("cannot pretty-print response");
+        assertThat(output.get(1)).contains(PATH);
+    }
+
+    @Test
+    public void testMakeDecoder() {
+        assertNotNull(oper.makeCoder());
+    }
+
+    /**
+     * Gets server properties.
+     *
+     * @param name server name
+     * @param port server port
+     * @return server properties
+     */
+    private static Properties getServerProperties(String name, int port) {
+        final Properties props = new Properties();
+        props.setProperty(PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES, name);
+
+        final String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + name;
+
+        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_REST_CLASSES_SUFFIX, Server.class.getName());
+        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_HOST_SUFFIX, "localhost");
+        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PORT_SUFFIX, String.valueOf(port));
+        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_MANAGED_SUFFIX, "true");
+        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SWAGGER_SUFFIX, "false");
+
+        props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERIALIZATION_PROVIDER,
+                        GsonMessageBodyHandler.class.getName());
+        return props;
+    }
+
+    /**
+     * Initializes the given operator.
+     *
+     * @param operator operator to be initialized
+     * @param clientName name of the client which it should use
+     */
+    private void initOper(HttpOperator operator, String clientName) {
+        operator.stop();
+
+        HttpParams params = HttpParams.builder().clientName(clientName).path(PATH).build();
+        Map<String, Object> mapParams = Util.translateToMap(OPERATION, params);
+        operator.configure(mapParams);
+        operator.start();
+    }
+
+    /**
+     * Runs the operation.
+     *
+     * @param operator operator on which to start the operation
+     * @return the outcome of the operation, or {@code null} if it does not complete in
+     *         time
+     */
+    private <T> OperationOutcome runOperation(HttpOperation<T> operator)
+                    throws InterruptedException, ExecutionException, TimeoutException {
+
+        CompletableFuture<OperationOutcome> future = operator.start();
+
+        return future.get(5, TimeUnit.SECONDS);
+    }
+
+    @Getter
+    @Setter
+    public static class MyRequest {
+        private String input = "some input";
+    }
+
+    @Getter
+    @Setter
+    public static class MyResponse {
+        private String output = "some output";
+    }
+
+    private class MyGetOperation<T> extends HttpOperation<T> {
+        public MyGetOperation(Class<T> responseClass) {
+            super(HttpOperationTest.this.params, HttpOperationTest.this.operator, responseClass);
+        }
+
+        @Override
+        protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+            Map<String, Object> headers = makeHeaders();
+
+            headers.put("Accept", MediaType.APPLICATION_JSON);
+            String url = makeUrl();
+
+            logRestRequest(url, null);
+
+            // @formatter:off
+            return handleResponse(outcome, url,
+                callback -> operator.getClient().get(callback, makePath(), headers));
+            // @formatter:on
+        }
+    }
+
+    private class MyPostOperation extends HttpOperation<MyResponse> {
+        public MyPostOperation() {
+            super(HttpOperationTest.this.params, HttpOperationTest.this.operator, MyResponse.class);
+        }
+
+        @Override
+        protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+            MyRequest request = new MyRequest();
+
+            Entity<MyRequest> entity = Entity.entity(request, MediaType.APPLICATION_JSON);
+
+            Map<String, Object> headers = makeHeaders();
+
+            headers.put("Accept", MediaType.APPLICATION_JSON);
+            String url = makeUrl();
+
+            logRestRequest(url, request);
+
+            // @formatter:off
+            return handleResponse(outcome, url,
+                callback -> operator.getClient().post(callback, makePath(), entity, headers));
+            // @formatter:on
+        }
+    }
+
+    private class MyPutOperation extends HttpOperation<MyResponse> {
+        public MyPutOperation() {
+            super(HttpOperationTest.this.params, HttpOperationTest.this.operator, MyResponse.class);
+        }
+
+        @Override
+        protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+            MyRequest request = new MyRequest();
+
+            Entity<MyRequest> entity = Entity.entity(request, MediaType.APPLICATION_JSON);
+
+            Map<String, Object> headers = makeHeaders();
+
+            headers.put("Accept", MediaType.APPLICATION_JSON);
+            String url = makeUrl();
+
+            logRestRequest(url, request);
+
+            // @formatter:off
+            return handleResponse(outcome, url,
+                callback -> operator.getClient().put(callback, makePath(), entity, headers));
+            // @formatter:on
+        }
+    }
+
+    private class MyDeleteOperation extends HttpOperation<String> {
+        public MyDeleteOperation() {
+            super(HttpOperationTest.this.params, HttpOperationTest.this.operator, String.class);
+        }
+
+        @Override
+        protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+            Map<String, Object> headers = makeHeaders();
+
+            headers.put("Accept", MediaType.APPLICATION_JSON);
+            String url = makeUrl();
+
+            logRestRequest(url, null);
+
+            // @formatter:off
+            return handleResponse(outcome, url,
+                callback -> operator.getClient().delete(callback, makePath(), headers));
+            // @formatter:on
+        }
+    }
+
+    /**
+     * Simulator.
+     */
+    @Path("/" + BASE_URI)
+    @Produces(MEDIA_TYPE_APPLICATION_JSON)
+    @Consumes(value = {MEDIA_TYPE_APPLICATION_JSON})
+    public static class Server {
+
+        /**
+         * Generates a response to a GET.
+         *
+         * @return resulting response
+         */
+        @GET
+        @Path(PATH)
+        public Response getRequest() {
+            ++nget;
+
+            if (rejectRequest) {
+                return Response.status(Status.BAD_REQUEST).build();
+
+            } else {
+                return Response.status(Status.OK).entity(new MyResponse()).build();
+            }
+        }
+
+        /**
+         * Generates a response to a POST.
+         *
+         * @param request incoming request
+         * @return resulting response
+         */
+        @POST
+        @Path(PATH)
+        public Response postRequest(MyRequest request) {
+            ++npost;
+
+            if (rejectRequest) {
+                return Response.status(Status.BAD_REQUEST).build();
+
+            } else {
+                return Response.status(Status.OK).entity(new MyResponse()).build();
+            }
+        }
+
+        /**
+         * Generates a response to a PUT.
+         *
+         * @param request incoming request
+         * @return resulting response
+         */
+        @PUT
+        @Path(PATH)
+        public Response putRequest(MyRequest request) {
+            ++nput;
+
+            if (rejectRequest) {
+                return Response.status(Status.BAD_REQUEST).build();
+
+            } else {
+                return Response.status(Status.OK).entity(new MyResponse()).build();
+            }
+        }
+
+        /**
+         * Generates a response to a DELETE.
+         *
+         * @return resulting response
+         */
+        @DELETE
+        @Path(PATH)
+        public Response deleteRequest() {
+            ++ndelete;
+
+            if (rejectRequest) {
+                return Response.status(Status.BAD_REQUEST).build();
+
+            } else {
+                return Response.status(Status.OK).entity(new MyResponse()).build();
+            }
+        }
+    }
+}
index c006cf3..081bb34 100644 (file)
@@ -23,19 +23,25 @@ package org.onap.policy.controlloop.actorserviceprovider.impl;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import java.util.Map;
+import java.util.concurrent.CompletableFuture;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.onap.policy.common.endpoints.http.client.HttpClient;
 import org.onap.policy.common.endpoints.http.client.HttpClientFactory;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
 import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
 import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
 import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
 
@@ -43,62 +49,116 @@ public class HttpOperatorTest {
 
     private static final String ACTOR = "my-actor";
     private static final String OPERATION = "my-name";
-    private static final String CLIENT = "my-client";
-    private static final String PATH = "my-path";
-    private static final long TIMEOUT = 100;
+    private static final String HTTP_CLIENT = "my-client";
+    private static final String PATH = "/my-path";
+    private static final int TIMEOUT = 100;
 
     @Mock
     private HttpClient client;
 
-    private HttpOperator oper;
+    @Mock
+    private HttpClientFactory factory;
+
+    private MyOperator oper;
 
     /**
-     * Initializes fields, including {@link #oper}.
+     * Initializes fields, including {@link #oper}, and resets the static fields used by
+     * the REST server.
      */
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        oper = new HttpOperator(ACTOR, OPERATION);
+        when(factory.get(HTTP_CLIENT)).thenReturn(client);
+
+        oper = new MyOperator();
+
+        HttpParams params = HttpParams.builder().clientName(HTTP_CLIENT).path(PATH).timeoutSec(TIMEOUT).build();
+        Map<String, Object> paramMap = Util.translateToMap(OPERATION, params);
+        oper.configure(paramMap);
     }
 
     @Test
-    public void testDoConfigureMapOfStringObject_testGetClient_testGetPath_testGetTimeoutSec() {
+    public void testHttpOperator() {
+        assertEquals(ACTOR, oper.getActorName());
+        assertEquals(OPERATION, oper.getName());
+        assertEquals(ACTOR + "." + OPERATION, oper.getFullName());
+    }
+
+    @Test
+    public void testGetClient() {
+        assertNotNull(oper.getClient());
+    }
+
+    @Test
+    public void testMakeOperator() {
+        HttpOperator oper2 = HttpOperator.makeOperator(ACTOR, OPERATION, MyOperation::new);
+        assertNotNull(oper2);
+
+        VirtualControlLoopEvent event = new VirtualControlLoopEvent();
+        ControlLoopEventContext context = new ControlLoopEventContext(event);
+        ControlLoopOperationParams params =
+                        ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).context(context).build();
+
+        Operation operation1 = oper2.buildOperation(params);
+        assertNotNull(operation1);
+
+        Operation operation2 = oper2.buildOperation(params);
+        assertNotNull(operation2);
+        assertNotSame(operation1, operation2);
+    }
+
+    @Test
+    public void testDoConfigureMapOfStringObject_testGetClient_testGetPath_testGetTimeoutMs() {
+        // start with an UNCONFIGURED operator
+        oper.shutdown();
+        oper = new MyOperator();
+
         assertNull(oper.getClient());
         assertNull(oper.getPath());
-        assertEquals(0L, oper.getTimeoutSec());
-
-        oper = new HttpOperator(ACTOR, OPERATION) {
-            @Override
-            protected HttpClientFactory getClientFactory() {
-                HttpClientFactory factory = mock(HttpClientFactory.class);
-                when(factory.get(CLIENT)).thenReturn(client);
-                return factory;
-            }
-        };
-
-        HttpParams params = HttpParams.builder().clientName(CLIENT).path(PATH).timeoutSec(TIMEOUT).build();
+
+        // no timeout yet
+        assertEquals(0L, oper.getTimeoutMs());
+
+        HttpParams params = HttpParams.builder().clientName(HTTP_CLIENT).path(PATH).timeoutSec(TIMEOUT).build();
         Map<String, Object> paramMap = Util.translateToMap(OPERATION, params);
         oper.configure(paramMap);
 
         assertSame(client, oper.getClient());
         assertEquals(PATH, oper.getPath());
-        assertEquals(TIMEOUT, oper.getTimeoutSec());
+
+        // should use given value
+        assertEquals(TIMEOUT * 1000, oper.getTimeoutMs());
 
         // test invalid parameters
         paramMap.remove("path");
         assertThatThrownBy(() -> oper.configure(paramMap)).isInstanceOf(ParameterValidationRuntimeException.class);
     }
 
-    @Test
-    public void testHttpOperator() {
-        assertEquals(ACTOR, oper.getActorName());
-        assertEquals(OPERATION, oper.getName());
-        assertEquals(ACTOR + "." + OPERATION, oper.getFullName());
+    private class MyOperator extends HttpOperator {
+        public MyOperator() {
+            super(ACTOR, OPERATION);
+        }
+
+        @Override
+        public Operation buildOperation(ControlLoopOperationParams params) {
+            return null;
+        }
+
+        @Override
+        protected HttpClientFactory getClientFactory() {
+            return factory;
+        }
     }
 
-    @Test
-    public void testGetClient() {
-        assertNotNull(oper.getClientFactory());
+    private class MyOperation extends HttpOperation<String> {
+        public MyOperation(ControlLoopOperationParams params, HttpOperator operator) {
+            super(params, operator, String.class);
+        }
+
+        @Override
+        protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+            return null;
+        }
     }
 }
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartialTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartialTest.java
new file mode 100644 (file)
index 0000000..0d5cb24
--- /dev/null
@@ -0,0 +1,1302 @@
+/*-
+ * ============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.actorserviceprovider.impl;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+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 java.time.Instant;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Queue;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import lombok.Getter;
+import lombok.Setter;
+import org.junit.Before;
+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.ControlLoopOperation;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture;
+import org.onap.policy.controlloop.policy.PolicyResult;
+
+public class OperationPartialTest {
+    private static final int MAX_PARALLEL_REQUESTS = 10;
+    private static final String EXPECTED_EXCEPTION = "expected exception";
+    private static final String ACTOR = "my-actor";
+    private static final String OPERATION = "my-operation";
+    private static final String TARGET = "my-target";
+    private static final int TIMEOUT = 1000;
+    private static final UUID REQ_ID = UUID.randomUUID();
+
+    private static final List<PolicyResult> FAILURE_RESULTS = Arrays.asList(PolicyResult.values()).stream()
+                    .filter(result -> result != PolicyResult.SUCCESS).collect(Collectors.toList());
+
+    private VirtualControlLoopEvent event;
+    private ControlLoopEventContext context;
+    private MyExec executor;
+    private ControlLoopOperationParams params;
+
+    private MyOper oper;
+
+    private int numStart;
+    private int numEnd;
+
+    private Instant tstart;
+
+    private OperationOutcome opstart;
+    private OperationOutcome opend;
+
+    private OperatorPartial operator;
+
+    /**
+     * Initializes the fields, including {@link #oper}.
+     */
+    @Before
+    public void setUp() {
+        event = new VirtualControlLoopEvent();
+        event.setRequestId(REQ_ID);
+
+        context = new ControlLoopEventContext(event);
+        executor = new MyExec();
+
+        params = ControlLoopOperationParams.builder().completeCallback(this::completer).context(context)
+                        .executor(executor).actor(ACTOR).operation(OPERATION).timeoutSec(TIMEOUT)
+                        .startCallback(this::starter).targetEntity(TARGET).build();
+
+        operator = new OperatorPartial(ACTOR, OPERATION) {
+            @Override
+            public Executor getBlockingExecutor() {
+                return executor;
+            }
+
+            @Override
+            public Operation buildOperation(ControlLoopOperationParams params) {
+                return null;
+            }
+        };
+
+        operator.configure(null);
+        operator.start();
+
+        oper = new MyOper();
+
+        tstart = null;
+
+        opstart = null;
+        opend = null;
+    }
+
+    @Test
+    public void testOperatorPartial_testGetActorName_testGetName() {
+        assertEquals(ACTOR, oper.getActorName());
+        assertEquals(OPERATION, oper.getName());
+        assertEquals(ACTOR + "." + OPERATION, oper.getFullName());
+    }
+
+    @Test
+    public void testGetBlockingThread() throws Exception {
+        CompletableFuture<Void> future = new CompletableFuture<>();
+
+        // use the real executor
+        OperatorPartial oper2 = new OperatorPartial(ACTOR, OPERATION) {
+            @Override
+            public Operation buildOperation(ControlLoopOperationParams params) {
+                return null;
+            }
+        };
+
+        oper2.getBlockingExecutor().execute(() -> future.complete(null));
+
+        assertNull(future.get(5, TimeUnit.SECONDS));
+    }
+
+    /**
+     * Exercises the doXxx() methods.
+     */
+    @Test
+    public void testDoXxx() {
+        assertThatCode(() -> operator.doConfigure(null)).doesNotThrowAnyException();
+        assertThatCode(() -> operator.doStart()).doesNotThrowAnyException();
+        assertThatCode(() -> operator.doStop()).doesNotThrowAnyException();
+        assertThatCode(() -> operator.doShutdown()).doesNotThrowAnyException();
+
+    }
+
+    @Test
+    public void testStart() {
+        verifyRun("testStart", 1, 1, PolicyResult.SUCCESS);
+    }
+
+    /**
+     * Tests startOperation() when the operator is not running.
+     */
+    @Test
+    public void testStartNotRunning() {
+        // stop the operator
+        operator.stop();
+
+        assertThatIllegalStateException().isThrownBy(() -> oper.start());
+    }
+
+    /**
+     * Tests startOperation() when the operation has a preprocessor.
+     */
+    @Test
+    public void testStartWithPreprocessor() {
+        AtomicInteger count = new AtomicInteger();
+
+        CompletableFuture<OperationOutcome> preproc = CompletableFuture.supplyAsync(() -> {
+            count.incrementAndGet();
+            return makeSuccess();
+        }, executor);
+
+        oper.setGuard(preproc);
+
+        verifyRun("testStartWithPreprocessor_testStartPreprocessor", 1, 1, PolicyResult.SUCCESS);
+
+        assertEquals(1, count.get());
+    }
+
+    /**
+     * Tests start() with multiple running requests.
+     */
+    @Test
+    public void testStartMultiple() {
+        for (int count = 0; count < MAX_PARALLEL_REQUESTS; ++count) {
+            oper.start();
+        }
+
+        assertTrue(executor.runAll());
+
+        assertNotNull(opstart);
+        assertNotNull(opend);
+        assertEquals(PolicyResult.SUCCESS, opend.getResult());
+
+        assertEquals(MAX_PARALLEL_REQUESTS, numStart);
+        assertEquals(MAX_PARALLEL_REQUESTS, oper.getCount());
+        assertEquals(MAX_PARALLEL_REQUESTS, numEnd);
+    }
+
+    /**
+     * Tests startPreprocessor() when the preprocessor returns a failure.
+     */
+    @Test
+    public void testStartPreprocessorFailure() {
+        oper.setGuard(CompletableFuture.completedFuture(makeFailure()));
+
+        verifyRun("testStartPreprocessorFailure", 1, 0, PolicyResult.FAILURE_GUARD);
+    }
+
+    /**
+     * Tests startPreprocessor() when the preprocessor throws an exception.
+     */
+    @Test
+    public void testStartPreprocessorException() {
+        // arrange for the preprocessor to throw an exception
+        oper.setGuard(CompletableFuture.failedFuture(new IllegalStateException(EXPECTED_EXCEPTION)));
+
+        verifyRun("testStartPreprocessorException", 1, 0, PolicyResult.FAILURE_GUARD);
+    }
+
+    /**
+     * Tests startPreprocessor() when the pipeline is not running.
+     */
+    @Test
+    public void testStartPreprocessorNotRunning() {
+        // arrange for the preprocessor to return success, which will be ignored
+        oper.setGuard(CompletableFuture.completedFuture(makeSuccess()));
+
+        oper.start().cancel(false);
+        assertTrue(executor.runAll());
+
+        assertNull(opstart);
+        assertNull(opend);
+
+        assertEquals(0, numStart);
+        assertEquals(0, oper.getCount());
+        assertEquals(0, numEnd);
+    }
+
+    /**
+     * Tests startPreprocessor() when the preprocessor <b>builder</b> throws an exception.
+     */
+    @Test
+    public void testStartPreprocessorBuilderException() {
+        oper = new MyOper() {
+            @Override
+            protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
+                throw new IllegalStateException(EXPECTED_EXCEPTION);
+            }
+        };
+
+        assertThatIllegalStateException().isThrownBy(() -> oper.start());
+
+        // should be nothing in the queue
+        assertEquals(0, executor.getQueueLength());
+    }
+
+    @Test
+    public void testStartPreprocessorAsync() {
+        assertNull(oper.startPreprocessorAsync());
+    }
+
+    @Test
+    public void testStartGuardAsync() {
+        assertNull(oper.startGuardAsync());
+    }
+
+    @Test
+    public void testStartOperationAsync() {
+        oper.start();
+        assertTrue(executor.runAll());
+
+        assertEquals(1, oper.getCount());
+    }
+
+    @Test
+    public void testIsSuccess() {
+        OperationOutcome outcome = new OperationOutcome();
+
+        outcome.setResult(PolicyResult.SUCCESS);
+        assertTrue(oper.isSuccess(outcome));
+
+        for (PolicyResult failure : FAILURE_RESULTS) {
+            outcome.setResult(failure);
+            assertFalse("testIsSuccess-" + failure, oper.isSuccess(outcome));
+        }
+    }
+
+    @Test
+    public void testIsActorFailed() {
+        assertFalse(oper.isActorFailed(null));
+
+        OperationOutcome outcome = params.makeOutcome();
+
+        // incorrect outcome
+        outcome.setResult(PolicyResult.SUCCESS);
+        assertFalse(oper.isActorFailed(outcome));
+
+        outcome.setResult(PolicyResult.FAILURE_RETRIES);
+        assertFalse(oper.isActorFailed(outcome));
+
+        // correct outcome
+        outcome.setResult(PolicyResult.FAILURE);
+
+        // incorrect actor
+        outcome.setActor(TARGET);
+        assertFalse(oper.isActorFailed(outcome));
+        outcome.setActor(null);
+        assertFalse(oper.isActorFailed(outcome));
+        outcome.setActor(ACTOR);
+
+        // incorrect operation
+        outcome.setOperation(TARGET);
+        assertFalse(oper.isActorFailed(outcome));
+        outcome.setOperation(null);
+        assertFalse(oper.isActorFailed(outcome));
+        outcome.setOperation(OPERATION);
+
+        // correct values
+        assertTrue(oper.isActorFailed(outcome));
+    }
+
+    @Test
+    public void testDoOperation() {
+        /*
+         * Use an operation that doesn't override doOperation().
+         */
+        OperationPartial oper2 = new OperationPartial(params, operator) {};
+
+        oper2.start();
+        assertTrue(executor.runAll());
+
+        assertNotNull(opend);
+        assertEquals(PolicyResult.FAILURE_EXCEPTION, opend.getResult());
+    }
+
+    @Test
+    public void testTimeout() throws Exception {
+
+        // use a real executor
+        params = params.toBuilder().executor(ForkJoinPool.commonPool()).build();
+
+        // trigger timeout very quickly
+        oper = new MyOper() {
+            @Override
+            protected long getTimeoutMs(Integer timeoutSec) {
+                return 1;
+            }
+
+            @Override
+            protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+                OperationOutcome outcome2 = params.makeOutcome();
+                outcome2.setResult(PolicyResult.SUCCESS);
+
+                /*
+                 * Create an incomplete future that will timeout after the operation's
+                 * timeout. If it fires before the other timer, then it will return a
+                 * SUCCESS outcome.
+                 */
+                CompletableFuture<OperationOutcome> future = new CompletableFuture<>();
+                future = future.orTimeout(1, TimeUnit.SECONDS).handleAsync((unused1, unused2) -> outcome,
+                                params.getExecutor());
+
+                return future;
+            }
+        };
+
+        assertEquals(PolicyResult.FAILURE_TIMEOUT, oper.start().get().getResult());
+    }
+
+    /**
+     * Tests retry functions, when the count is set to zero and retries are exhausted.
+     */
+    @Test
+    public void testSetRetryFlag_testRetryOnFailure_ZeroRetries_testStartOperationAttempt() {
+        params = params.toBuilder().retry(0).build();
+
+        // new params, thus need a new operation
+        oper = new MyOper();
+
+        oper.setMaxFailures(10);
+
+        verifyRun("testSetRetryFlag_testRetryOnFailure_ZeroRetries", 1, 1, PolicyResult.FAILURE);
+    }
+
+    /**
+     * Tests retry functions, when the count is null and retries are exhausted.
+     */
+    @Test
+    public void testSetRetryFlag_testRetryOnFailure_NullRetries() {
+        params = params.toBuilder().retry(null).build();
+
+        // new params, thus need a new operation
+        oper = new MyOper();
+
+        oper.setMaxFailures(10);
+
+        verifyRun("testSetRetryFlag_testRetryOnFailure_NullRetries", 1, 1, PolicyResult.FAILURE);
+    }
+
+    /**
+     * Tests retry functions, when retries are exhausted.
+     */
+    @Test
+    public void testSetRetryFlag_testRetryOnFailure_RetriesExhausted() {
+        final int maxRetries = 3;
+        params = params.toBuilder().retry(maxRetries).build();
+
+        // new params, thus need a new operation
+        oper = new MyOper();
+
+        oper.setMaxFailures(10);
+
+        verifyRun("testSetRetryFlag_testRetryOnFailure_RetriesExhausted", maxRetries + 1, maxRetries + 1,
+                        PolicyResult.FAILURE_RETRIES);
+    }
+
+    /**
+     * Tests retry functions, when a success follows some retries.
+     */
+    @Test
+    public void testSetRetryFlag_testRetryOnFailure_SuccessAfterRetries() {
+        params = params.toBuilder().retry(10).build();
+
+        // new params, thus need a new operation
+        oper = new MyOper();
+
+        final int maxFailures = 3;
+        oper.setMaxFailures(maxFailures);
+
+        verifyRun("testSetRetryFlag_testRetryOnFailure_SuccessAfterRetries", maxFailures + 1, maxFailures + 1,
+                        PolicyResult.SUCCESS);
+    }
+
+    /**
+     * Tests retry functions, when the outcome is {@code null}.
+     */
+    @Test
+    public void testSetRetryFlag_testRetryOnFailure_NullOutcome() {
+
+        // arrange to return null from doOperation()
+        oper = new MyOper() {
+            @Override
+            protected OperationOutcome doOperation(int attempt, OperationOutcome operation) {
+
+                // update counters
+                super.doOperation(attempt, operation);
+                return null;
+            }
+        };
+
+        verifyRun("testSetRetryFlag_testRetryOnFailure_NullOutcome", 1, 1, PolicyResult.FAILURE, null, noop());
+    }
+
+    @Test
+    public void testSleep() throws Exception {
+        CompletableFuture<Void> future = oper.sleep(-1, TimeUnit.SECONDS);
+        assertTrue(future.isDone());
+        assertNull(future.get());
+
+        // edge case
+        future = oper.sleep(0, TimeUnit.SECONDS);
+        assertTrue(future.isDone());
+        assertNull(future.get());
+
+        /*
+         * Start a second sleep we can use to check the first while it's running.
+         */
+        tstart = Instant.now();
+        future = oper.sleep(100, TimeUnit.MILLISECONDS);
+
+        CompletableFuture<Void> future2 = oper.sleep(10, TimeUnit.MILLISECONDS);
+
+        // wait for second to complete and verify that the first has not completed
+        future2.get();
+        assertFalse(future.isDone());
+
+        // wait for second to complete
+        future.get();
+
+        long diff = Instant.now().toEpochMilli() - tstart.toEpochMilli();
+        assertTrue(diff >= 99);
+    }
+
+    @Test
+    public void testIsSameOperation() {
+        assertFalse(oper.isSameOperation(null));
+
+        OperationOutcome outcome = params.makeOutcome();
+
+        // wrong actor - should be false
+        outcome.setActor(null);
+        assertFalse(oper.isSameOperation(outcome));
+        outcome.setActor(TARGET);
+        assertFalse(oper.isSameOperation(outcome));
+        outcome.setActor(ACTOR);
+
+        // wrong operation - should be null
+        outcome.setOperation(null);
+        assertFalse(oper.isSameOperation(outcome));
+        outcome.setOperation(TARGET);
+        assertFalse(oper.isSameOperation(outcome));
+        outcome.setOperation(OPERATION);
+
+        assertTrue(oper.isSameOperation(outcome));
+    }
+
+    /**
+     * Tests handleFailure() when the outcome is a success.
+     */
+    @Test
+    public void testHandlePreprocessorFailureTrue() {
+        oper.setGuard(CompletableFuture.completedFuture(makeSuccess()));
+        verifyRun("testHandlePreprocessorFailureTrue", 1, 1, PolicyResult.SUCCESS);
+    }
+
+    /**
+     * Tests handleFailure() when the outcome is <i>not</i> a success.
+     */
+    @Test
+    public void testHandlePreprocessorFailureFalse() throws Exception {
+        oper.setGuard(CompletableFuture.completedFuture(makeFailure()));
+        verifyRun("testHandlePreprocessorFailureFalse", 1, 0, PolicyResult.FAILURE_GUARD);
+    }
+
+    /**
+     * Tests handleFailure() when the outcome is {@code null}.
+     */
+    @Test
+    public void testHandlePreprocessorFailureNull() throws Exception {
+        // arrange to return null from the preprocessor
+        oper.setGuard(CompletableFuture.completedFuture(null));
+
+        verifyRun("testHandlePreprocessorFailureNull", 1, 0, PolicyResult.FAILURE_GUARD);
+    }
+
+    @Test
+    public void testFromException() {
+        // arrange to generate an exception when operation runs
+        oper.setGenException(true);
+
+        verifyRun("testFromException", 1, 1, PolicyResult.FAILURE_EXCEPTION);
+    }
+
+    /**
+     * Tests fromException() when there is no exception.
+     */
+    @Test
+    public void testFromExceptionNoExcept() {
+        verifyRun("testFromExceptionNoExcept", 1, 1, PolicyResult.SUCCESS);
+    }
+
+    /**
+     * Tests both flavors of anyOf(), because one invokes the other.
+     */
+    @Test
+    public void testAnyOf() throws Exception {
+        // first task completes, others do not
+        List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
+
+        final OperationOutcome outcome = params.makeOutcome();
+
+        tasks.add(CompletableFuture.completedFuture(outcome));
+        tasks.add(new CompletableFuture<>());
+        tasks.add(new CompletableFuture<>());
+
+        CompletableFuture<OperationOutcome> result = oper.anyOf(tasks);
+        assertTrue(executor.runAll());
+
+        assertTrue(result.isDone());
+        assertSame(outcome, result.get());
+
+        // second task completes, others do not
+        tasks = new LinkedList<>();
+
+        tasks.add(new CompletableFuture<>());
+        tasks.add(CompletableFuture.completedFuture(outcome));
+        tasks.add(new CompletableFuture<>());
+
+        result = oper.anyOf(tasks);
+        assertTrue(executor.runAll());
+
+        assertTrue(result.isDone());
+        assertSame(outcome, result.get());
+
+        // third task completes, others do not
+        tasks = new LinkedList<>();
+
+        tasks.add(new CompletableFuture<>());
+        tasks.add(new CompletableFuture<>());
+        tasks.add(CompletableFuture.completedFuture(outcome));
+
+        result = oper.anyOf(tasks);
+        assertTrue(executor.runAll());
+
+        assertTrue(result.isDone());
+        assertSame(outcome, result.get());
+    }
+
+    /**
+     * Tests both flavors of anyOf(), for edge cases: zero items, and one item.
+     */
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testAnyOfEdge() throws Exception {
+        List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
+
+        // zero items: check both using a list and using an array
+        assertThatIllegalArgumentException().isThrownBy(() -> oper.anyOf(tasks));
+        assertThatIllegalArgumentException().isThrownBy(() -> oper.anyOf());
+
+        // one item: : check both using a list and using an array
+        CompletableFuture<OperationOutcome> future1 = new CompletableFuture<>();
+        tasks.add(future1);
+
+        assertSame(future1, oper.anyOf(tasks));
+        assertSame(future1, oper.anyOf(future1));
+    }
+
+    /**
+     * Tests both flavors of allOf(), because one invokes the other.
+     */
+    @Test
+    public void testAllOf() throws Exception {
+        List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
+
+        final OperationOutcome outcome = params.makeOutcome();
+
+        CompletableFuture<OperationOutcome> future1 = new CompletableFuture<>();
+        CompletableFuture<OperationOutcome> future2 = new CompletableFuture<>();
+        CompletableFuture<OperationOutcome> future3 = new CompletableFuture<>();
+
+        tasks.add(future1);
+        tasks.add(future2);
+        tasks.add(future3);
+
+        CompletableFuture<OperationOutcome> result = oper.allOf(tasks);
+
+        assertTrue(executor.runAll());
+        assertFalse(result.isDone());
+        future1.complete(outcome);
+
+        // complete 3 before 2
+        assertTrue(executor.runAll());
+        assertFalse(result.isDone());
+        future3.complete(outcome);
+
+        assertTrue(executor.runAll());
+        assertFalse(result.isDone());
+        future2.complete(outcome);
+
+        // all of them are now done
+        assertTrue(executor.runAll());
+        assertTrue(result.isDone());
+        assertSame(outcome, result.get());
+    }
+
+    /**
+     * Tests both flavors of allOf(), for edge cases: zero items, and one item.
+     */
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testAllOfEdge() throws Exception {
+        List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
+
+        // zero items: check both using a list and using an array
+        assertThatIllegalArgumentException().isThrownBy(() -> oper.allOf(tasks));
+        assertThatIllegalArgumentException().isThrownBy(() -> oper.allOf());
+
+        // one item: : check both using a list and using an array
+        CompletableFuture<OperationOutcome> future1 = new CompletableFuture<>();
+        tasks.add(future1);
+
+        assertSame(future1, oper.allOf(tasks));
+        assertSame(future1, oper.allOf(future1));
+    }
+
+    @Test
+    public void testCombineOutcomes() throws Exception {
+        // only one outcome
+        verifyOutcomes(0, PolicyResult.SUCCESS);
+        verifyOutcomes(0, PolicyResult.FAILURE_EXCEPTION);
+
+        // maximum is in different positions
+        verifyOutcomes(0, PolicyResult.FAILURE, PolicyResult.SUCCESS, PolicyResult.FAILURE_GUARD);
+        verifyOutcomes(1, PolicyResult.SUCCESS, PolicyResult.FAILURE, PolicyResult.FAILURE_GUARD);
+        verifyOutcomes(2, PolicyResult.SUCCESS, PolicyResult.FAILURE_GUARD, PolicyResult.FAILURE);
+
+        // null outcome
+        final List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
+        tasks.add(CompletableFuture.completedFuture(null));
+        CompletableFuture<OperationOutcome> result = oper.allOf(tasks);
+
+        assertTrue(executor.runAll());
+        assertTrue(result.isDone());
+        assertNull(result.get());
+
+        // one throws an exception during execution
+        IllegalStateException except = new IllegalStateException(EXPECTED_EXCEPTION);
+
+        tasks.clear();
+        tasks.add(CompletableFuture.completedFuture(params.makeOutcome()));
+        tasks.add(CompletableFuture.failedFuture(except));
+        tasks.add(CompletableFuture.completedFuture(params.makeOutcome()));
+        result = oper.allOf(tasks);
+
+        assertTrue(executor.runAll());
+        assertTrue(result.isCompletedExceptionally());
+        result.whenComplete((unused, thrown) -> assertSame(except, thrown));
+    }
+
+    private void verifyOutcomes(int expected, PolicyResult... results) throws Exception {
+        List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
+
+
+        OperationOutcome expectedOutcome = null;
+
+        for (int count = 0; count < results.length; ++count) {
+            OperationOutcome outcome = params.makeOutcome();
+            outcome.setResult(results[count]);
+            tasks.add(CompletableFuture.completedFuture(outcome));
+
+            if (count == expected) {
+                expectedOutcome = outcome;
+            }
+        }
+
+        CompletableFuture<OperationOutcome> result = oper.allOf(tasks);
+
+        assertTrue(executor.runAll());
+        assertTrue(result.isDone());
+        assertSame(expectedOutcome, result.get());
+    }
+
+    private Function<OperationOutcome, CompletableFuture<OperationOutcome>> makeTask(
+                    final OperationOutcome taskOutcome) {
+
+        return outcome -> CompletableFuture.completedFuture(taskOutcome);
+    }
+
+    @Test
+    public void testDetmPriority() throws CoderException {
+        assertEquals(1, oper.detmPriority(null));
+
+        OperationOutcome outcome = params.makeOutcome();
+
+        Map<PolicyResult, Integer> map = Map.of(PolicyResult.SUCCESS, 0, PolicyResult.FAILURE_GUARD, 2,
+                        PolicyResult.FAILURE_RETRIES, 3, PolicyResult.FAILURE, 4, PolicyResult.FAILURE_TIMEOUT, 5,
+                        PolicyResult.FAILURE_EXCEPTION, 6);
+
+        for (Entry<PolicyResult, Integer> ent : map.entrySet()) {
+            outcome.setResult(ent.getKey());
+            assertEquals(ent.getKey().toString(), ent.getValue().intValue(), oper.detmPriority(outcome));
+        }
+
+        /*
+         * Test null result. We can't actually set it to null, because the set() method
+         * won't allow it. Instead, we decode it from a structure.
+         */
+        outcome = new StandardCoder().decode("{\"result\":null}", OperationOutcome.class);
+        assertEquals(1, oper.detmPriority(outcome));
+    }
+
+    /**
+     * Tests doTask(Future) when the controller is not running.
+     */
+    @Test
+    public void testDoTaskFutureNotRunning() throws Exception {
+        CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>();
+
+        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+        controller.complete(params.makeOutcome());
+
+        CompletableFuture<OperationOutcome> future = oper.doTask(controller, false, params.makeOutcome(), taskFuture);
+        assertFalse(future.isDone());
+        assertTrue(executor.runAll());
+
+        // should not have run the task
+        assertFalse(future.isDone());
+
+        // should have canceled the task future
+        assertTrue(taskFuture.isCancelled());
+    }
+
+    /**
+     * Tests doTask(Future) when the previous outcome was successful.
+     */
+    @Test
+    public void testDoTaskFutureSuccess() throws Exception {
+        CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>();
+        final OperationOutcome taskOutcome = params.makeOutcome();
+
+        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+
+        CompletableFuture<OperationOutcome> future = oper.doTask(controller, true, params.makeOutcome(), taskFuture);
+
+        taskFuture.complete(taskOutcome);
+        assertTrue(executor.runAll());
+
+        assertTrue(future.isDone());
+        assertSame(taskOutcome, future.get());
+
+        // controller should not be done yet
+        assertFalse(controller.isDone());
+    }
+
+    /**
+     * Tests doTask(Future) when the previous outcome was failed.
+     */
+    @Test
+    public void testDoTaskFutureFailure() throws Exception {
+        CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>();
+        final OperationOutcome failedOutcome = params.makeOutcome();
+        failedOutcome.setResult(PolicyResult.FAILURE);
+
+        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+
+        CompletableFuture<OperationOutcome> future = oper.doTask(controller, true, failedOutcome, taskFuture);
+        assertFalse(future.isDone());
+        assertTrue(executor.runAll());
+
+        // should not have run the task
+        assertFalse(future.isDone());
+
+        // should have canceled the task future
+        assertTrue(taskFuture.isCancelled());
+
+        // controller SHOULD be done now
+        assertTrue(controller.isDone());
+        assertSame(failedOutcome, controller.get());
+    }
+
+    /**
+     * Tests doTask(Future) when the previous outcome was failed, but not checking
+     * success.
+     */
+    @Test
+    public void testDoTaskFutureUncheckedFailure() throws Exception {
+        CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>();
+        final OperationOutcome failedOutcome = params.makeOutcome();
+        failedOutcome.setResult(PolicyResult.FAILURE);
+
+        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+
+        CompletableFuture<OperationOutcome> future = oper.doTask(controller, false, failedOutcome, taskFuture);
+        assertFalse(future.isDone());
+
+        // complete the task
+        OperationOutcome taskOutcome = params.makeOutcome();
+        taskFuture.complete(taskOutcome);
+
+        assertTrue(executor.runAll());
+
+        // should have run the task
+        assertTrue(future.isDone());
+
+        assertTrue(future.isDone());
+        assertSame(taskOutcome, future.get());
+
+        // controller should not be done yet
+        assertFalse(controller.isDone());
+    }
+
+    /**
+     * Tests doTask(Function) when the controller is not running.
+     */
+    @Test
+    public void testDoTaskFunctionNotRunning() throws Exception {
+        AtomicBoolean invoked = new AtomicBoolean();
+
+        Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = outcome -> {
+            invoked.set(true);
+            return CompletableFuture.completedFuture(params.makeOutcome());
+        };
+
+        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+        controller.complete(params.makeOutcome());
+
+        CompletableFuture<OperationOutcome> future = oper.doTask(controller, false, task).apply(params.makeOutcome());
+        assertFalse(future.isDone());
+        assertTrue(executor.runAll());
+
+        // should not have run the task
+        assertFalse(future.isDone());
+
+        // should not have even invoked the task
+        assertFalse(invoked.get());
+    }
+
+    /**
+     * Tests doTask(Function) when the previous outcome was successful.
+     */
+    @Test
+    public void testDoTaskFunctionSuccess() throws Exception {
+        final OperationOutcome taskOutcome = params.makeOutcome();
+
+        final OperationOutcome failedOutcome = params.makeOutcome();
+
+        Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = makeTask(taskOutcome);
+
+        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+
+        CompletableFuture<OperationOutcome> future = oper.doTask(controller, true, task).apply(failedOutcome);
+
+        assertTrue(future.isDone());
+        assertSame(taskOutcome, future.get());
+
+        // controller should not be done yet
+        assertFalse(controller.isDone());
+    }
+
+    /**
+     * Tests doTask(Function) when the previous outcome was failed.
+     */
+    @Test
+    public void testDoTaskFunctionFailure() throws Exception {
+        final OperationOutcome failedOutcome = params.makeOutcome();
+        failedOutcome.setResult(PolicyResult.FAILURE);
+
+        AtomicBoolean invoked = new AtomicBoolean();
+
+        Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = outcome -> {
+            invoked.set(true);
+            return CompletableFuture.completedFuture(params.makeOutcome());
+        };
+
+        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+
+        CompletableFuture<OperationOutcome> future = oper.doTask(controller, true, task).apply(failedOutcome);
+        assertFalse(future.isDone());
+        assertTrue(executor.runAll());
+
+        // should not have run the task
+        assertFalse(future.isDone());
+
+        // should not have even invoked the task
+        assertFalse(invoked.get());
+
+        // controller should have the failed task
+        assertTrue(controller.isDone());
+        assertSame(failedOutcome, controller.get());
+    }
+
+    /**
+     * Tests doTask(Function) when the previous outcome was failed, but not checking
+     * success.
+     */
+    @Test
+    public void testDoTaskFunctionUncheckedFailure() throws Exception {
+        final OperationOutcome taskOutcome = params.makeOutcome();
+
+        final OperationOutcome failedOutcome = params.makeOutcome();
+        failedOutcome.setResult(PolicyResult.FAILURE);
+
+        Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = makeTask(taskOutcome);
+
+        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+
+        CompletableFuture<OperationOutcome> future = oper.doTask(controller, false, task).apply(failedOutcome);
+
+        assertTrue(future.isDone());
+        assertSame(taskOutcome, future.get());
+
+        // controller should not be done yet
+        assertFalse(controller.isDone());
+    }
+
+    /**
+     * Tests callbackStarted() when the pipeline has already been stopped.
+     */
+    @Test
+    public void testCallbackStartedNotRunning() {
+        AtomicReference<Future<OperationOutcome>> future = new AtomicReference<>();
+
+        /*
+         * arrange to stop the controller when the start-callback is invoked, but capture
+         * the outcome
+         */
+        params = params.toBuilder().startCallback(oper -> {
+            starter(oper);
+            future.get().cancel(false);
+        }).build();
+
+        // new params, thus need a new operation
+        oper = new MyOper();
+
+        future.set(oper.start());
+        assertTrue(executor.runAll());
+
+        // should have only run once
+        assertEquals(1, numStart);
+    }
+
+    /**
+     * Tests callbackCompleted() when the pipeline has already been stopped.
+     */
+    @Test
+    public void testCallbackCompletedNotRunning() {
+        AtomicReference<Future<OperationOutcome>> future = new AtomicReference<>();
+
+        // arrange to stop the controller when the start-callback is invoked
+        params = params.toBuilder().startCallback(oper -> {
+            future.get().cancel(false);
+        }).build();
+
+        // new params, thus need a new operation
+        oper = new MyOper();
+
+        future.set(oper.start());
+        assertTrue(executor.runAll());
+
+        // should not have been set
+        assertNull(opend);
+        assertEquals(0, numEnd);
+    }
+
+    @Test
+    public void testSetOutcomeControlLoopOperationOutcomeThrowable() {
+        final CompletionException timex = new CompletionException(new TimeoutException(EXPECTED_EXCEPTION));
+
+        OperationOutcome outcome;
+
+        outcome = new OperationOutcome();
+        oper.setOutcome(outcome, timex);
+        assertEquals(ControlLoopOperation.FAILED_MSG, outcome.getMessage());
+        assertEquals(PolicyResult.FAILURE_TIMEOUT, outcome.getResult());
+
+        outcome = new OperationOutcome();
+        oper.setOutcome(outcome, new IllegalStateException(EXPECTED_EXCEPTION));
+        assertEquals(ControlLoopOperation.FAILED_MSG, outcome.getMessage());
+        assertEquals(PolicyResult.FAILURE_EXCEPTION, outcome.getResult());
+    }
+
+    @Test
+    public void testSetOutcomeControlLoopOperationOutcomePolicyResult() {
+        OperationOutcome outcome;
+
+        outcome = new OperationOutcome();
+        oper.setOutcome(outcome, PolicyResult.SUCCESS);
+        assertEquals(ControlLoopOperation.SUCCESS_MSG, outcome.getMessage());
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+
+        for (PolicyResult result : FAILURE_RESULTS) {
+            outcome = new OperationOutcome();
+            oper.setOutcome(outcome, result);
+            assertEquals(result.toString(), ControlLoopOperation.FAILED_MSG, outcome.getMessage());
+            assertEquals(result.toString(), result, outcome.getResult());
+        }
+    }
+
+    @Test
+    public void testIsTimeout() {
+        final TimeoutException timex = new TimeoutException(EXPECTED_EXCEPTION);
+
+        assertFalse(oper.isTimeout(new IllegalStateException(EXPECTED_EXCEPTION)));
+        assertFalse(oper.isTimeout(new IllegalStateException(timex)));
+        assertFalse(oper.isTimeout(new CompletionException(new IllegalStateException(timex))));
+        assertFalse(oper.isTimeout(new CompletionException(null)));
+        assertFalse(oper.isTimeout(new CompletionException(new CompletionException(timex))));
+
+        assertTrue(oper.isTimeout(timex));
+        assertTrue(oper.isTimeout(new CompletionException(timex)));
+    }
+
+    @Test
+    public void testGetRetry() {
+        assertEquals(0, oper.getRetry(null));
+        assertEquals(10, oper.getRetry(10));
+    }
+
+    @Test
+    public void testGetRetryWait() {
+        // need an operator that doesn't override the retry time
+        OperationPartial oper2 = new OperationPartial(params, operator) {};
+        assertEquals(OperationPartial.DEFAULT_RETRY_WAIT_MS, oper2.getRetryWaitMs());
+    }
+
+    @Test
+    public void testGetTimeOutMs() {
+        assertEquals(TIMEOUT * 1000, oper.getTimeoutMs(params.getTimeoutSec()));
+
+        params = params.toBuilder().timeoutSec(null).build();
+
+        // new params, thus need a new operation
+        oper = new MyOper();
+
+        assertEquals(0, oper.getTimeoutMs(params.getTimeoutSec()));
+    }
+
+    private void starter(OperationOutcome oper) {
+        ++numStart;
+        tstart = oper.getStart();
+        opstart = oper;
+    }
+
+    private void completer(OperationOutcome oper) {
+        ++numEnd;
+        opend = oper;
+    }
+
+    /**
+     * Gets a function that does nothing.
+     *
+     * @param <T> type of input parameter expected by the function
+     * @return a function that does nothing
+     */
+    private <T> Consumer<T> noop() {
+        return unused -> {
+        };
+    }
+
+    private OperationOutcome makeSuccess() {
+        OperationOutcome outcome = params.makeOutcome();
+        outcome.setResult(PolicyResult.SUCCESS);
+
+        return outcome;
+    }
+
+    private OperationOutcome makeFailure() {
+        OperationOutcome outcome = params.makeOutcome();
+        outcome.setResult(PolicyResult.FAILURE);
+
+        return outcome;
+    }
+
+    /**
+     * Verifies a run.
+     *
+     * @param testName test name
+     * @param expectedCallbacks number of callbacks expected
+     * @param expectedOperations number of operation invocations expected
+     * @param expectedResult expected outcome
+     */
+    private void verifyRun(String testName, int expectedCallbacks, int expectedOperations,
+                    PolicyResult expectedResult) {
+
+        String expectedSubRequestId =
+                        (expectedResult == PolicyResult.FAILURE_EXCEPTION ? null : String.valueOf(expectedOperations));
+
+        verifyRun(testName, expectedCallbacks, expectedOperations, expectedResult, expectedSubRequestId, noop());
+    }
+
+    /**
+     * Verifies a run.
+     *
+     * @param testName test name
+     * @param expectedCallbacks number of callbacks expected
+     * @param expectedOperations number of operation invocations expected
+     * @param expectedResult expected outcome
+     * @param expectedSubRequestId expected sub request ID
+     * @param manipulator function to modify the future returned by
+     *        {@link OperationPartial#start(ControlLoopOperationParams)} before the tasks
+     *        in the executor are run
+     */
+    private void verifyRun(String testName, int expectedCallbacks, int expectedOperations, PolicyResult expectedResult,
+                    String expectedSubRequestId, Consumer<CompletableFuture<OperationOutcome>> manipulator) {
+
+        CompletableFuture<OperationOutcome> future = oper.start();
+
+        manipulator.accept(future);
+
+        assertTrue(testName, executor.runAll());
+
+        assertEquals(testName, expectedCallbacks, numStart);
+        assertEquals(testName, expectedCallbacks, numEnd);
+
+        if (expectedCallbacks > 0) {
+            assertNotNull(testName, opstart);
+            assertNotNull(testName, opend);
+            assertEquals(testName, expectedResult, opend.getResult());
+
+            assertSame(testName, tstart, opstart.getStart());
+            assertSame(testName, tstart, opend.getStart());
+
+            try {
+                assertTrue(future.isDone());
+                assertSame(testName, opend, future.get());
+
+            } catch (InterruptedException | ExecutionException e) {
+                throw new IllegalStateException(e);
+            }
+
+            if (expectedOperations > 0) {
+                assertEquals(testName, expectedSubRequestId, opend.getSubRequestId());
+            }
+        }
+
+        assertEquals(testName, expectedOperations, oper.getCount());
+    }
+
+    private class MyOper extends OperationPartial {
+        @Getter
+        private int count = 0;
+
+        @Setter
+        private boolean genException;
+
+        @Setter
+        private int maxFailures = 0;
+
+        @Setter
+        private CompletableFuture<OperationOutcome> guard;
+
+
+        public MyOper() {
+            super(OperationPartialTest.this.params, operator);
+        }
+
+        @Override
+        protected OperationOutcome doOperation(int attempt, OperationOutcome operation) {
+            ++count;
+            if (genException) {
+                throw new IllegalStateException(EXPECTED_EXCEPTION);
+            }
+
+            operation.setSubRequestId(String.valueOf(attempt));
+
+            if (count > maxFailures) {
+                operation.setResult(PolicyResult.SUCCESS);
+            } else {
+                operation.setResult(PolicyResult.FAILURE);
+            }
+
+            return operation;
+        }
+
+        @Override
+        protected CompletableFuture<OperationOutcome> startGuardAsync() {
+            return (guard != null ? guard : super.startGuardAsync());
+        }
+
+        @Override
+        protected long getRetryWaitMs() {
+            /*
+             * Sleep timers run in the background, but we want to control things via the
+             * "executor", thus we avoid sleep timers altogether by simply returning 0.
+             */
+            return 0L;
+        }
+    }
+
+    /**
+     * Executor that will run tasks until the queue is empty or a maximum number of tasks
+     * have been executed. Doesn't actually run anything until {@link #runAll()} is
+     * invoked.
+     */
+    private static class MyExec implements Executor {
+        private static final int MAX_TASKS = MAX_PARALLEL_REQUESTS * 100;
+
+        private Queue<Runnable> commands = new LinkedList<>();
+
+        public MyExec() {
+            // do nothing
+        }
+
+        public int getQueueLength() {
+            return commands.size();
+        }
+
+        @Override
+        public void execute(Runnable command) {
+            commands.add(command);
+        }
+
+        public boolean runAll() {
+            for (int count = 0; count < MAX_TASKS && !commands.isEmpty(); ++count) {
+                commands.remove().run();
+            }
+
+            return commands.isEmpty();
+        }
+    }
+}
index 21bc656..370426f 100644 (file)
 
 package org.onap.policy.controlloop.actorserviceprovider.impl;
 
-import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
 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.Mockito.never;
 import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import java.time.Instant;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Queue;
 import java.util.TreeMap;
-import java.util.UUID;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ForkJoinPool;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import lombok.Getter;
-import lombok.Setter;
 import org.junit.Before;
 import org.junit.Test;
-import org.onap.policy.controlloop.ControlLoopOperation;
-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.Operation;
 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
-import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture;
-import org.onap.policy.controlloop.policy.PolicyResult;
 
 public class OperatorPartialTest {
-    private static final int MAX_PARALLEL_REQUESTS = 10;
-    private static final String EXPECTED_EXCEPTION = "expected exception";
     private static final String ACTOR = "my-actor";
-    private static final String OPERATOR = "my-operator";
-    private static final String TARGET = "my-target";
-    private static final int TIMEOUT = 1000;
-    private static final UUID REQ_ID = UUID.randomUUID();
+    private static final String OPERATION = "my-name";
 
-    private static final List<PolicyResult> FAILURE_RESULTS = Arrays.asList(PolicyResult.values()).stream()
-                    .filter(result -> result != PolicyResult.SUCCESS).collect(Collectors.toList());
-
-    private VirtualControlLoopEvent event;
-    private Map<String, Object> config;
-    private ControlLoopEventContext context;
-    private MyExec executor;
-    private ControlLoopOperationParams params;
-
-    private MyOper oper;
-
-    private int numStart;
-    private int numEnd;
-
-    private Instant tstart;
-
-    private OperationOutcome opstart;
-    private OperationOutcome opend;
+    private OperatorPartial operator;
 
     /**
-     * Initializes the fields, including {@link #oper}.
+     * Initializes {@link #operator}.
      */
     @Before
     public void setUp() {
-        event = new VirtualControlLoopEvent();
-        event.setRequestId(REQ_ID);
-
-        config = new TreeMap<>();
-        context = new ControlLoopEventContext(event);
-        executor = new MyExec();
-
-        params = ControlLoopOperationParams.builder().completeCallback(this::completer).context(context)
-                        .executor(executor).actor(ACTOR).operation(OPERATOR).timeoutSec(TIMEOUT)
-                        .startCallback(this::starter).targetEntity(TARGET).build();
-
-        oper = new MyOper();
-        oper.configure(new TreeMap<>());
-        oper.start();
-
-        tstart = null;
-
-        opstart = null;
-        opend = null;
-    }
-
-    @Test
-    public void testOperatorPartial_testGetActorName_testGetName() {
-        assertEquals(ACTOR, oper.getActorName());
-        assertEquals(OPERATOR, oper.getName());
-        assertEquals(ACTOR + "." + OPERATOR, oper.getFullName());
-    }
-
-    @Test
-    public void testGetBlockingExecutor() throws InterruptedException {
-        CountDownLatch latch = new CountDownLatch(1);
-
-        /*
-         * Use an operator that doesn't override getBlockingExecutor().
-         */
-        OperatorPartial oper2 = new OperatorPartial(ACTOR, OPERATOR) {};
-        oper2.getBlockingExecutor().execute(() -> latch.countDown());
-
-        assertTrue(latch.await(5, TimeUnit.SECONDS));
-    }
-
-    @Test
-    public void testDoConfigure() {
-        oper = spy(new MyOper());
-
-        oper.configure(config);
-        verify(oper).configure(config);
-
-        // repeat - SHOULD be run again
-        oper.configure(config);
-        verify(oper, times(2)).configure(config);
-    }
-
-    @Test
-    public void testDoStart() {
-        oper = spy(new MyOper());
-
-        oper.configure(config);
-        oper.start();
-
-        verify(oper).doStart();
-
-        // others should not have been invoked
-        verify(oper, never()).doStop();
-        verify(oper, never()).doShutdown();
-    }
-
-    @Test
-    public void testDoStop() {
-        oper = spy(new MyOper());
-
-        oper.configure(config);
-        oper.start();
-        oper.stop();
-
-        verify(oper).doStop();
-
-        // should not have been re-invoked
-        verify(oper).doStart();
-
-        // others should not have been invoked
-        verify(oper, never()).doShutdown();
-    }
-
-    @Test
-    public void testDoShutdown() {
-        oper = spy(new MyOper());
-
-        oper.configure(config);
-        oper.start();
-        oper.shutdown();
-
-        verify(oper).doShutdown();
-
-        // should not have been re-invoked
-        verify(oper).doStart();
-
-        // others should not have been invoked
-        verify(oper, never()).doStop();
-    }
-
-    @Test
-    public void testStartOperation() {
-        verifyRun("testStartOperation", 1, 1, PolicyResult.SUCCESS);
-    }
-
-    /**
-     * Tests startOperation() when the operator is not running.
-     */
-    @Test
-    public void testStartOperationNotRunning() {
-        // use a new operator, one that hasn't been started yet
-        oper = new MyOper();
-        oper.configure(new TreeMap<>());
-
-        assertThatIllegalStateException().isThrownBy(() -> oper.startOperation(params));
-    }
-
-    /**
-     * Tests startOperation() when the operation has a preprocessor.
-     */
-    @Test
-    public void testStartOperationWithPreprocessor() {
-        AtomicInteger count = new AtomicInteger();
-
-        CompletableFuture<OperationOutcome> preproc = CompletableFuture.supplyAsync(() -> {
-            count.incrementAndGet();
-            return makeSuccess();
-        }, executor);
-
-        oper.setPreProcessor(preproc);
-
-        verifyRun("testStartOperationWithPreprocessor_testStartPreprocessor", 1, 1, PolicyResult.SUCCESS);
-
-        assertEquals(1, count.get());
-    }
-
-    /**
-     * Tests startOperation() with multiple running requests.
-     */
-    @Test
-    public void testStartOperationMultiple() {
-        for (int count = 0; count < MAX_PARALLEL_REQUESTS; ++count) {
-            oper.startOperation(params);
-        }
-
-        assertTrue(executor.runAll());
-
-        assertNotNull(opstart);
-        assertNotNull(opend);
-        assertEquals(PolicyResult.SUCCESS, opend.getResult());
-
-        assertEquals(MAX_PARALLEL_REQUESTS, numStart);
-        assertEquals(MAX_PARALLEL_REQUESTS, oper.getCount());
-        assertEquals(MAX_PARALLEL_REQUESTS, numEnd);
-    }
-
-    /**
-     * Tests startPreprocessor() when the preprocessor returns a failure.
-     */
-    @Test
-    public void testStartPreprocessorFailure() {
-        oper.setPreProcessor(CompletableFuture.completedFuture(makeFailure()));
-
-        verifyRun("testStartPreprocessorFailure", 1, 0, PolicyResult.FAILURE_GUARD);
-    }
-
-    /**
-     * Tests startPreprocessor() when the preprocessor throws an exception.
-     */
-    @Test
-    public void testStartPreprocessorException() {
-        // arrange for the preprocessor to throw an exception
-        oper.setPreProcessor(CompletableFuture.failedFuture(new IllegalStateException(EXPECTED_EXCEPTION)));
-
-        verifyRun("testStartPreprocessorException", 1, 0, PolicyResult.FAILURE_GUARD);
-    }
-
-    /**
-     * Tests startPreprocessor() when the pipeline is not running.
-     */
-    @Test
-    public void testStartPreprocessorNotRunning() {
-        // arrange for the preprocessor to return success, which will be ignored
-        oper.setPreProcessor(CompletableFuture.completedFuture(makeSuccess()));
-
-        oper.startOperation(params).cancel(false);
-        assertTrue(executor.runAll());
-
-        assertNull(opstart);
-        assertNull(opend);
-
-        assertEquals(0, numStart);
-        assertEquals(0, oper.getCount());
-        assertEquals(0, numEnd);
-    }
-
-    /**
-     * Tests startPreprocessor() when the preprocessor <b>builder</b> throws an exception.
-     */
-    @Test
-    public void testStartPreprocessorBuilderException() {
-        oper = new MyOper() {
-            @Override
-            protected CompletableFuture<OperationOutcome> startPreprocessorAsync(ControlLoopOperationParams params) {
-                throw new IllegalStateException(EXPECTED_EXCEPTION);
-            }
-        };
-
-        oper.configure(new TreeMap<>());
-        oper.start();
-
-        assertThatIllegalStateException().isThrownBy(() -> oper.startOperation(params));
-
-        // should be nothing in the queue
-        assertEquals(0, executor.getQueueLength());
-    }
-
-    @Test
-    public void testStartPreprocessorAsync() {
-        assertNull(oper.startPreprocessorAsync(params));
-    }
-
-    @Test
-    public void testStartOperationAsync() {
-        oper.startOperation(params);
-        assertTrue(executor.runAll());
-
-        assertEquals(1, oper.getCount());
-    }
-
-    @Test
-    public void testIsSuccess() {
-        OperationOutcome outcome = new OperationOutcome();
-
-        outcome.setResult(PolicyResult.SUCCESS);
-        assertTrue(oper.isSuccess(outcome));
-
-        for (PolicyResult failure : FAILURE_RESULTS) {
-            outcome.setResult(failure);
-            assertFalse("testIsSuccess-" + failure, oper.isSuccess(outcome));
-        }
-    }
-
-    @Test
-    public void testIsActorFailed() {
-        assertFalse(oper.isActorFailed(null));
-
-        OperationOutcome outcome = params.makeOutcome();
-
-        // incorrect outcome
-        outcome.setResult(PolicyResult.SUCCESS);
-        assertFalse(oper.isActorFailed(outcome));
-
-        outcome.setResult(PolicyResult.FAILURE_RETRIES);
-        assertFalse(oper.isActorFailed(outcome));
-
-        // correct outcome
-        outcome.setResult(PolicyResult.FAILURE);
-
-        // incorrect actor
-        outcome.setActor(TARGET);
-        assertFalse(oper.isActorFailed(outcome));
-        outcome.setActor(null);
-        assertFalse(oper.isActorFailed(outcome));
-        outcome.setActor(ACTOR);
-
-        // incorrect operation
-        outcome.setOperation(TARGET);
-        assertFalse(oper.isActorFailed(outcome));
-        outcome.setOperation(null);
-        assertFalse(oper.isActorFailed(outcome));
-        outcome.setOperation(OPERATOR);
-
-        // correct values
-        assertTrue(oper.isActorFailed(outcome));
-    }
-
-    @Test
-    public void testDoOperation() {
-        /*
-         * Use an operator that doesn't override doOperation().
-         */
-        OperatorPartial oper2 = new OperatorPartial(ACTOR, OPERATOR) {
-            @Override
-            protected Executor getBlockingExecutor() {
-                return executor;
-            }
-        };
-
-        oper2.configure(new TreeMap<>());
-        oper2.start();
-
-        oper2.startOperation(params);
-        assertTrue(executor.runAll());
-
-        assertNotNull(opend);
-        assertEquals(PolicyResult.FAILURE_EXCEPTION, opend.getResult());
-    }
-
-    @Test
-    public void testTimeout() throws Exception {
-
-        // use a real executor
-        params = params.toBuilder().executor(ForkJoinPool.commonPool()).build();
-
-        // trigger timeout very quickly
-        oper = new MyOper() {
-            @Override
-            protected long getTimeOutMillis(Integer timeoutSec) {
-                return 1;
-            }
-
-            @Override
-            protected CompletableFuture<OperationOutcome> startOperationAsync(ControlLoopOperationParams params,
-                            int attempt, OperationOutcome outcome) {
-
-                OperationOutcome outcome2 = params.makeOutcome();
-                outcome2.setResult(PolicyResult.SUCCESS);
-
-                /*
-                 * Create an incomplete future that will timeout after the operation's
-                 * timeout. If it fires before the other timer, then it will return a
-                 * SUCCESS outcome.
-                 */
-                CompletableFuture<OperationOutcome> future = new CompletableFuture<>();
-                future = future.orTimeout(1, TimeUnit.SECONDS).handleAsync((unused1, unused2) -> outcome,
-                                params.getExecutor());
-
-                return future;
-            }
-        };
-
-        oper.configure(new TreeMap<>());
-        oper.start();
-
-        assertEquals(PolicyResult.FAILURE_TIMEOUT, oper.startOperation(params).get().getResult());
-    }
-
-    /**
-     * Verifies that the timer doesn't encompass the preprocessor and doesn't stop the
-     * operation once the preprocessor completes.
-     */
-    @Test
-    public void testTimeoutInPreprocessor() throws Exception {
-
-        // use a real executor
-        params = params.toBuilder().executor(ForkJoinPool.commonPool()).build();
-
-        // trigger timeout very quickly
-        oper = new MyOper() {
+        operator = new OperatorPartial(ACTOR, OPERATION) {
             @Override
-            protected long getTimeOutMillis(Integer timeoutSec) {
-                return 10;
-            }
-
-            @Override
-            protected Executor getBlockingExecutor() {
-                return command -> {
-                    Thread thread = new Thread(command);
-                    thread.start();
-                };
-            }
-
-            @Override
-            protected CompletableFuture<OperationOutcome> startPreprocessorAsync(ControlLoopOperationParams params) {
-
-                OperationOutcome outcome = makeSuccess();
-
-                /*
-                 * Create an incomplete future that will timeout after the operation's
-                 * timeout. If it fires before the other timer, then it will return a
-                 * SUCCESS outcome.
-                 */
-                CompletableFuture<OperationOutcome> future = new CompletableFuture<>();
-                future = future.orTimeout(200, TimeUnit.MILLISECONDS).handleAsync((unused1, unused2) -> outcome,
-                                params.getExecutor());
-
-                return future;
-            }
-        };
-
-        oper.configure(new TreeMap<>());
-        oper.start();
-
-        OperationOutcome result = oper.startOperation(params).get();
-        assertEquals(PolicyResult.SUCCESS, result.getResult());
-
-        assertNotNull(opstart);
-        assertNotNull(opend);
-        assertEquals(PolicyResult.SUCCESS, opend.getResult());
-
-        assertEquals(1, numStart);
-        assertEquals(1, oper.getCount());
-        assertEquals(1, numEnd);
-    }
-
-    /**
-     * Tests retry functions, when the count is set to zero and retries are exhausted.
-     */
-    @Test
-    public void testSetRetryFlag_testRetryOnFailure_ZeroRetries_testStartOperationAttempt() {
-        params = params.toBuilder().retry(0).build();
-        oper.setMaxFailures(10);
-
-        verifyRun("testSetRetryFlag_testRetryOnFailure_ZeroRetries", 1, 1, PolicyResult.FAILURE);
-    }
-
-    /**
-     * Tests retry functions, when the count is null and retries are exhausted.
-     */
-    @Test
-    public void testSetRetryFlag_testRetryOnFailure_NullRetries() {
-        params = params.toBuilder().retry(null).build();
-        oper.setMaxFailures(10);
-
-        verifyRun("testSetRetryFlag_testRetryOnFailure_NullRetries", 1, 1, PolicyResult.FAILURE);
-    }
-
-    /**
-     * Tests retry functions, when retries are exhausted.
-     */
-    @Test
-    public void testSetRetryFlag_testRetryOnFailure_RetriesExhausted() {
-        final int maxRetries = 3;
-        params = params.toBuilder().retry(maxRetries).build();
-        oper.setMaxFailures(10);
-
-        verifyRun("testSetRetryFlag_testRetryOnFailure_RetriesExhausted", maxRetries + 1, maxRetries + 1,
-                        PolicyResult.FAILURE_RETRIES);
-    }
-
-    /**
-     * Tests retry functions, when a success follows some retries.
-     */
-    @Test
-    public void testSetRetryFlag_testRetryOnFailure_SuccessAfterRetries() {
-        params = params.toBuilder().retry(10).build();
-
-        final int maxFailures = 3;
-        oper.setMaxFailures(maxFailures);
-
-        verifyRun("testSetRetryFlag_testRetryOnFailure_SuccessAfterRetries", maxFailures + 1, maxFailures + 1,
-                        PolicyResult.SUCCESS);
-    }
-
-    /**
-     * Tests retry functions, when the outcome is {@code null}.
-     */
-    @Test
-    public void testSetRetryFlag_testRetryOnFailure_NullOutcome() {
-
-        // arrange to return null from doOperation()
-        oper = new MyOper() {
-            @Override
-            protected OperationOutcome doOperation(ControlLoopOperationParams params, int attempt,
-                            OperationOutcome operation) {
-
-                // update counters
-                super.doOperation(params, attempt, operation);
+            public Operation buildOperation(ControlLoopOperationParams params) {
                 return null;
             }
         };
-
-        oper.configure(new TreeMap<>());
-        oper.start();
-
-        verifyRun("testSetRetryFlag_testRetryOnFailure_NullOutcome", 1, 1, PolicyResult.FAILURE, null, noop());
-    }
-
-    @Test
-    public void testIsSameOperation() {
-        assertFalse(oper.isSameOperation(null));
-
-        OperationOutcome outcome = params.makeOutcome();
-
-        // wrong actor - should be false
-        outcome.setActor(null);
-        assertFalse(oper.isSameOperation(outcome));
-        outcome.setActor(TARGET);
-        assertFalse(oper.isSameOperation(outcome));
-        outcome.setActor(ACTOR);
-
-        // wrong operation - should be null
-        outcome.setOperation(null);
-        assertFalse(oper.isSameOperation(outcome));
-        outcome.setOperation(TARGET);
-        assertFalse(oper.isSameOperation(outcome));
-        outcome.setOperation(OPERATOR);
-
-        assertTrue(oper.isSameOperation(outcome));
-    }
-
-    /**
-     * Tests handleFailure() when the outcome is a success.
-     */
-    @Test
-    public void testHandlePreprocessorFailureTrue() {
-        oper.setPreProcessor(CompletableFuture.completedFuture(makeSuccess()));
-        verifyRun("testHandlePreprocessorFailureTrue", 1, 1, PolicyResult.SUCCESS);
-    }
-
-    /**
-     * Tests handleFailure() when the outcome is <i>not</i> a success.
-     */
-    @Test
-    public void testHandlePreprocessorFailureFalse() throws Exception {
-        oper.setPreProcessor(CompletableFuture.completedFuture(makeFailure()));
-        verifyRun("testHandlePreprocessorFailureFalse", 1, 0, PolicyResult.FAILURE_GUARD);
-    }
-
-    /**
-     * Tests handleFailure() when the outcome is {@code null}.
-     */
-    @Test
-    public void testHandlePreprocessorFailureNull() throws Exception {
-        // arrange to return null from the preprocessor
-        oper.setPreProcessor(CompletableFuture.completedFuture(null));
-
-        verifyRun("testHandlePreprocessorFailureNull", 1, 0, PolicyResult.FAILURE_GUARD);
-    }
-
-    @Test
-    public void testFromException() {
-        // arrange to generate an exception when operation runs
-        oper.setGenException(true);
-
-        verifyRun("testFromException", 1, 1, PolicyResult.FAILURE_EXCEPTION);
-    }
-
-    /**
-     * Tests fromException() when there is no exception.
-     */
-    @Test
-    public void testFromExceptionNoExcept() {
-        verifyRun("testFromExceptionNoExcept", 1, 1, PolicyResult.SUCCESS);
-    }
-
-    /**
-     * Tests both flavors of anyOf(), because one invokes the other.
-     */
-    @Test
-    public void testAnyOf() throws Exception {
-        // first task completes, others do not
-        List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
-
-        final OperationOutcome outcome = params.makeOutcome();
-
-        tasks.add(CompletableFuture.completedFuture(outcome));
-        tasks.add(new CompletableFuture<>());
-        tasks.add(new CompletableFuture<>());
-
-        CompletableFuture<OperationOutcome> result = oper.anyOf(params, tasks);
-        assertTrue(executor.runAll());
-
-        assertTrue(result.isDone());
-        assertSame(outcome, result.get());
-
-        // second task completes, others do not
-        tasks = new LinkedList<>();
-
-        tasks.add(new CompletableFuture<>());
-        tasks.add(CompletableFuture.completedFuture(outcome));
-        tasks.add(new CompletableFuture<>());
-
-        result = oper.anyOf(params, tasks);
-        assertTrue(executor.runAll());
-
-        assertTrue(result.isDone());
-        assertSame(outcome, result.get());
-
-        // third task completes, others do not
-        tasks = new LinkedList<>();
-
-        tasks.add(new CompletableFuture<>());
-        tasks.add(new CompletableFuture<>());
-        tasks.add(CompletableFuture.completedFuture(outcome));
-
-        result = oper.anyOf(params, tasks);
-        assertTrue(executor.runAll());
-
-        assertTrue(result.isDone());
-        assertSame(outcome, result.get());
-    }
-
-    /**
-     * Tests both flavors of allOf(), because one invokes the other.
-     */
-    @Test
-    public void testAllOf() throws Exception {
-        List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
-
-        final OperationOutcome outcome = params.makeOutcome();
-
-        CompletableFuture<OperationOutcome> future1 = new CompletableFuture<>();
-        CompletableFuture<OperationOutcome> future2 = new CompletableFuture<>();
-        CompletableFuture<OperationOutcome> future3 = new CompletableFuture<>();
-
-        tasks.add(future1);
-        tasks.add(future2);
-        tasks.add(future3);
-
-        CompletableFuture<OperationOutcome> result = oper.allOf(params, tasks);
-
-        assertTrue(executor.runAll());
-        assertFalse(result.isDone());
-        future1.complete(outcome);
-
-        // complete 3 before 2
-        assertTrue(executor.runAll());
-        assertFalse(result.isDone());
-        future3.complete(outcome);
-
-        assertTrue(executor.runAll());
-        assertFalse(result.isDone());
-        future2.complete(outcome);
-
-        // all of them are now done
-        assertTrue(executor.runAll());
-        assertTrue(result.isDone());
-        assertSame(outcome, result.get());
-    }
-
-    @Test
-    public void testCombineOutcomes() throws Exception {
-        // only one outcome
-        verifyOutcomes(0, PolicyResult.SUCCESS);
-        verifyOutcomes(0, PolicyResult.FAILURE_EXCEPTION);
-
-        // maximum is in different positions
-        verifyOutcomes(0, PolicyResult.FAILURE, PolicyResult.SUCCESS, PolicyResult.FAILURE_GUARD);
-        verifyOutcomes(1, PolicyResult.SUCCESS, PolicyResult.FAILURE, PolicyResult.FAILURE_GUARD);
-        verifyOutcomes(2, PolicyResult.SUCCESS, PolicyResult.FAILURE_GUARD, PolicyResult.FAILURE);
-
-        // null outcome
-        final List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
-        tasks.add(CompletableFuture.completedFuture(null));
-        CompletableFuture<OperationOutcome> result = oper.allOf(params, tasks);
-
-        assertTrue(executor.runAll());
-        assertTrue(result.isDone());
-        assertNull(result.get());
-
-        // one throws an exception during execution
-        IllegalStateException except = new IllegalStateException(EXPECTED_EXCEPTION);
-
-        tasks.clear();
-        tasks.add(CompletableFuture.completedFuture(params.makeOutcome()));
-        tasks.add(CompletableFuture.failedFuture(except));
-        tasks.add(CompletableFuture.completedFuture(params.makeOutcome()));
-        result = oper.allOf(params, tasks);
-
-        assertTrue(executor.runAll());
-        assertTrue(result.isCompletedExceptionally());
-        result.whenComplete((unused, thrown) -> assertSame(except, thrown));
-    }
-
-    private void verifyOutcomes(int expected, PolicyResult... results) throws Exception {
-        List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
-
-
-        OperationOutcome expectedOutcome = null;
-
-        for (int count = 0; count < results.length; ++count) {
-            OperationOutcome outcome = params.makeOutcome();
-            outcome.setResult(results[count]);
-            tasks.add(CompletableFuture.completedFuture(outcome));
-
-            if (count == expected) {
-                expectedOutcome = outcome;
-            }
-        }
-
-        CompletableFuture<OperationOutcome> result = oper.allOf(params, tasks);
-
-        assertTrue(executor.runAll());
-        assertTrue(result.isDone());
-        assertSame(expectedOutcome, result.get());
-    }
-
-    private Function<OperationOutcome, CompletableFuture<OperationOutcome>> makeTask(
-                    final OperationOutcome taskOutcome) {
-
-        return outcome -> CompletableFuture.completedFuture(taskOutcome);
-    }
-
-    @Test
-    public void testDetmPriority() {
-        assertEquals(1, oper.detmPriority(null));
-
-        OperationOutcome outcome = params.makeOutcome();
-
-        Map<PolicyResult, Integer> map = Map.of(PolicyResult.SUCCESS, 0, PolicyResult.FAILURE_GUARD, 2,
-                        PolicyResult.FAILURE_RETRIES, 3, PolicyResult.FAILURE, 4, PolicyResult.FAILURE_TIMEOUT, 5,
-                        PolicyResult.FAILURE_EXCEPTION, 6);
-
-        for (Entry<PolicyResult, Integer> ent : map.entrySet()) {
-            outcome.setResult(ent.getKey());
-            assertEquals(ent.getKey().toString(), ent.getValue().intValue(), oper.detmPriority(outcome));
-        }
-    }
-
-    /**
-     * Tests doTask(Future) when the controller is not running.
-     */
-    @Test
-    public void testDoTaskFutureNotRunning() throws Exception {
-        CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>();
-
-        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-        controller.complete(params.makeOutcome());
-
-        CompletableFuture<OperationOutcome> future =
-                        oper.doTask(params, controller, false, params.makeOutcome(), taskFuture);
-        assertFalse(future.isDone());
-        assertTrue(executor.runAll());
-
-        // should not have run the task
-        assertFalse(future.isDone());
-
-        // should have canceled the task future
-        assertTrue(taskFuture.isCancelled());
-    }
-
-    /**
-     * Tests doTask(Future) when the previous outcome was successful.
-     */
-    @Test
-    public void testDoTaskFutureSuccess() throws Exception {
-        CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>();
-        final OperationOutcome taskOutcome = params.makeOutcome();
-
-        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        CompletableFuture<OperationOutcome> future =
-                        oper.doTask(params, controller, true, params.makeOutcome(), taskFuture);
-
-        taskFuture.complete(taskOutcome);
-        assertTrue(executor.runAll());
-
-        assertTrue(future.isDone());
-        assertSame(taskOutcome, future.get());
-
-        // controller should not be done yet
-        assertFalse(controller.isDone());
-    }
-
-    /**
-     * Tests doTask(Future) when the previous outcome was failed.
-     */
-    @Test
-    public void testDoTaskFutureFailure() throws Exception {
-        CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>();
-        final OperationOutcome failedOutcome = params.makeOutcome();
-        failedOutcome.setResult(PolicyResult.FAILURE);
-
-        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        CompletableFuture<OperationOutcome> future = oper.doTask(params, controller, true, failedOutcome, taskFuture);
-        assertFalse(future.isDone());
-        assertTrue(executor.runAll());
-
-        // should not have run the task
-        assertFalse(future.isDone());
-
-        // should have canceled the task future
-        assertTrue(taskFuture.isCancelled());
-
-        // controller SHOULD be done now
-        assertTrue(controller.isDone());
-        assertSame(failedOutcome, controller.get());
-    }
-
-    /**
-     * Tests doTask(Future) when the previous outcome was failed, but not checking
-     * success.
-     */
-    @Test
-    public void testDoTaskFutureUncheckedFailure() throws Exception {
-        CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>();
-        final OperationOutcome failedOutcome = params.makeOutcome();
-        failedOutcome.setResult(PolicyResult.FAILURE);
-
-        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        CompletableFuture<OperationOutcome> future = oper.doTask(params, controller, false, failedOutcome, taskFuture);
-        assertFalse(future.isDone());
-
-        // complete the task
-        OperationOutcome taskOutcome = params.makeOutcome();
-        taskFuture.complete(taskOutcome);
-
-        assertTrue(executor.runAll());
-
-        // should have run the task
-        assertTrue(future.isDone());
-
-        assertTrue(future.isDone());
-        assertSame(taskOutcome, future.get());
-
-        // controller should not be done yet
-        assertFalse(controller.isDone());
-    }
-
-    /**
-     * Tests doTask(Function) when the controller is not running.
-     */
-    @Test
-    public void testDoTaskFunctionNotRunning() throws Exception {
-        AtomicBoolean invoked = new AtomicBoolean();
-
-        Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = outcome -> {
-            invoked.set(true);
-            return CompletableFuture.completedFuture(params.makeOutcome());
-        };
-
-        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-        controller.complete(params.makeOutcome());
-
-        CompletableFuture<OperationOutcome> future =
-                        oper.doTask(params, controller, false, task).apply(params.makeOutcome());
-        assertFalse(future.isDone());
-        assertTrue(executor.runAll());
-
-        // should not have run the task
-        assertFalse(future.isDone());
-
-        // should not have even invoked the task
-        assertFalse(invoked.get());
-    }
-
-    /**
-     * Tests doTask(Function) when the previous outcome was successful.
-     */
-    @Test
-    public void testDoTaskFunctionSuccess() throws Exception {
-        final OperationOutcome taskOutcome = params.makeOutcome();
-
-        final OperationOutcome failedOutcome = params.makeOutcome();
-
-        Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = makeTask(taskOutcome);
-
-        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        CompletableFuture<OperationOutcome> future = oper.doTask(params, controller, true, task).apply(failedOutcome);
-
-        assertTrue(future.isDone());
-        assertSame(taskOutcome, future.get());
-
-        // controller should not be done yet
-        assertFalse(controller.isDone());
-    }
-
-    /**
-     * Tests doTask(Function) when the previous outcome was failed.
-     */
-    @Test
-    public void testDoTaskFunctionFailure() throws Exception {
-        final OperationOutcome failedOutcome = params.makeOutcome();
-        failedOutcome.setResult(PolicyResult.FAILURE);
-
-        AtomicBoolean invoked = new AtomicBoolean();
-
-        Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = outcome -> {
-            invoked.set(true);
-            return CompletableFuture.completedFuture(params.makeOutcome());
-        };
-
-        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        CompletableFuture<OperationOutcome> future = oper.doTask(params, controller, true, task).apply(failedOutcome);
-        assertFalse(future.isDone());
-        assertTrue(executor.runAll());
-
-        // should not have run the task
-        assertFalse(future.isDone());
-
-        // should not have even invoked the task
-        assertFalse(invoked.get());
-
-        // controller should have the failed task
-        assertTrue(controller.isDone());
-        assertSame(failedOutcome, controller.get());
-    }
-
-    /**
-     * Tests doTask(Function) when the previous outcome was failed, but not checking
-     * success.
-     */
-    @Test
-    public void testDoTaskFunctionUncheckedFailure() throws Exception {
-        final OperationOutcome taskOutcome = params.makeOutcome();
-
-        final OperationOutcome failedOutcome = params.makeOutcome();
-        failedOutcome.setResult(PolicyResult.FAILURE);
-
-        Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = makeTask(taskOutcome);
-
-        PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
-        CompletableFuture<OperationOutcome> future = oper.doTask(params, controller, false, task).apply(failedOutcome);
-
-        assertTrue(future.isDone());
-        assertSame(taskOutcome, future.get());
-
-        // controller should not be done yet
-        assertFalse(controller.isDone());
     }
 
-    /**
-     * Tests callbackStarted() when the pipeline has already been stopped.
-     */
     @Test
-    public void testCallbackStartedNotRunning() {
-        AtomicReference<Future<OperationOutcome>> future = new AtomicReference<>();
-
-        /*
-         * arrange to stop the controller when the start-callback is invoked, but capture
-         * the outcome
-         */
-        params = params.toBuilder().startCallback(oper -> {
-            starter(oper);
-            future.get().cancel(false);
-        }).build();
-
-        future.set(oper.startOperation(params));
-        assertTrue(executor.runAll());
-
-        // should have only run once
-        assertEquals(1, numStart);
+    public void testOperatorPartial_testGetActorName_testGetName() {
+        assertEquals(ACTOR, operator.getActorName());
+        assertEquals(OPERATION, operator.getName());
+        assertEquals(ACTOR + "." + OPERATION, operator.getFullName());
     }
 
-    /**
-     * Tests callbackCompleted() when the pipeline has already been stopped.
-     */
     @Test
-    public void testCallbackCompletedNotRunning() {
-        AtomicReference<Future<OperationOutcome>> future = new AtomicReference<>();
-
-        // arrange to stop the controller when the start-callback is invoked
-        params = params.toBuilder().startCallback(oper -> {
-            future.get().cancel(false);
-        }).build();
+    public void testDoStart() {
+        operator.configure(null);
 
-        future.set(oper.startOperation(params));
-        assertTrue(executor.runAll());
+        operator = spy(operator);
+        operator.start();
 
-        // should not have been set
-        assertNull(opend);
-        assertEquals(0, numEnd);
+        verify(operator).doStart();
     }
 
     @Test
-    public void testSetOutcomeControlLoopOperationOutcomeThrowable() {
-        final CompletionException timex = new CompletionException(new TimeoutException(EXPECTED_EXCEPTION));
-
-        OperationOutcome outcome;
+    public void testDoStop() {
+        operator.configure(null);
+        operator.start();
 
-        outcome = new OperationOutcome();
-        oper.setOutcome(params, outcome, timex);
-        assertEquals(ControlLoopOperation.FAILED_MSG, outcome.getMessage());
-        assertEquals(PolicyResult.FAILURE_TIMEOUT, outcome.getResult());
+        operator = spy(operator);
+        operator.stop();
 
-        outcome = new OperationOutcome();
-        oper.setOutcome(params, outcome, new IllegalStateException(EXPECTED_EXCEPTION));
-        assertEquals(ControlLoopOperation.FAILED_MSG, outcome.getMessage());
-        assertEquals(PolicyResult.FAILURE_EXCEPTION, outcome.getResult());
+        verify(operator).doStop();
     }
 
     @Test
-    public void testSetOutcomeControlLoopOperationOutcomePolicyResult() {
-        OperationOutcome outcome;
+    public void testDoShutdown() {
+        operator.configure(null);
+        operator.start();
 
-        outcome = new OperationOutcome();
-        oper.setOutcome(params, outcome, PolicyResult.SUCCESS);
-        assertEquals(ControlLoopOperation.SUCCESS_MSG, outcome.getMessage());
-        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+        operator = spy(operator);
+        operator.shutdown();
 
-        for (PolicyResult result : FAILURE_RESULTS) {
-            outcome = new OperationOutcome();
-            oper.setOutcome(params, outcome, result);
-            assertEquals(result.toString(), ControlLoopOperation.FAILED_MSG, outcome.getMessage());
-            assertEquals(result.toString(), result, outcome.getResult());
-        }
+        verify(operator).doShutdown();
     }
 
     @Test
-    public void testIsTimeout() {
-        final TimeoutException timex = new TimeoutException(EXPECTED_EXCEPTION);
+    public void testDoConfigureMapOfStringObject() {
+        operator = spy(operator);
 
-        assertFalse(oper.isTimeout(new IllegalStateException(EXPECTED_EXCEPTION)));
-        assertFalse(oper.isTimeout(new IllegalStateException(timex)));
-        assertFalse(oper.isTimeout(new CompletionException(new IllegalStateException(timex))));
-        assertFalse(oper.isTimeout(new CompletionException(null)));
-        assertFalse(oper.isTimeout(new CompletionException(new CompletionException(timex))));
+        Map<String, Object> params = new TreeMap<>();
+        operator.configure(params);
 
-        assertTrue(oper.isTimeout(timex));
-        assertTrue(oper.isTimeout(new CompletionException(timex)));
+        verify(operator).doConfigure(params);
     }
 
     @Test
-    public void testGetTimeOutMillis() {
-        assertEquals(TIMEOUT * 1000, oper.getTimeOutMillis(params.getTimeoutSec()));
-
-        params = params.toBuilder().timeoutSec(null).build();
-        assertEquals(0, oper.getTimeOutMillis(params.getTimeoutSec()));
-    }
-
-    private void starter(OperationOutcome oper) {
-        ++numStart;
-        tstart = oper.getStart();
-        opstart = oper;
-    }
-
-    private void completer(OperationOutcome oper) {
-        ++numEnd;
-        opend = oper;
-    }
-
-    /**
-     * Gets a function that does nothing.
-     *
-     * @param <T> type of input parameter expected by the function
-     * @return a function that does nothing
-     */
-    private <T> Consumer<T> noop() {
-        return unused -> {
-        };
-    }
-
-    private OperationOutcome makeSuccess() {
-        OperationOutcome outcome = params.makeOutcome();
-        outcome.setResult(PolicyResult.SUCCESS);
-
-        return outcome;
-    }
-
-    private OperationOutcome makeFailure() {
-        OperationOutcome outcome = params.makeOutcome();
-        outcome.setResult(PolicyResult.FAILURE);
-
-        return outcome;
-    }
-
-    /**
-     * Verifies a run.
-     *
-     * @param testName test name
-     * @param expectedCallbacks number of callbacks expected
-     * @param expectedOperations number of operation invocations expected
-     * @param expectedResult expected outcome
-     */
-    private void verifyRun(String testName, int expectedCallbacks, int expectedOperations,
-                    PolicyResult expectedResult) {
-
-        String expectedSubRequestId =
-                        (expectedResult == PolicyResult.FAILURE_EXCEPTION ? null : String.valueOf(expectedOperations));
-
-        verifyRun(testName, expectedCallbacks, expectedOperations, expectedResult, expectedSubRequestId, noop());
-    }
-
-    /**
-     * Verifies a run.
-     *
-     * @param testName test name
-     * @param expectedCallbacks number of callbacks expected
-     * @param expectedOperations number of operation invocations expected
-     * @param expectedResult expected outcome
-     * @param expectedSubRequestId expected sub request ID
-     * @param manipulator function to modify the future returned by
-     *        {@link OperatorPartial#startOperation(ControlLoopOperationParams)} before
-     *        the tasks in the executor are run
-     */
-    private void verifyRun(String testName, int expectedCallbacks, int expectedOperations, PolicyResult expectedResult,
-                    String expectedSubRequestId, Consumer<CompletableFuture<OperationOutcome>> manipulator) {
-
-        CompletableFuture<OperationOutcome> future = oper.startOperation(params);
-
-        manipulator.accept(future);
-
-        assertTrue(testName, executor.runAll());
-
-        assertEquals(testName, expectedCallbacks, numStart);
-        assertEquals(testName, expectedCallbacks, numEnd);
-
-        if (expectedCallbacks > 0) {
-            assertNotNull(testName, opstart);
-            assertNotNull(testName, opend);
-            assertEquals(testName, expectedResult, opend.getResult());
-
-            assertSame(testName, tstart, opstart.getStart());
-            assertSame(testName, tstart, opend.getStart());
-
-            try {
-                assertTrue(future.isDone());
-                assertSame(testName, opend, future.get());
-
-            } catch (InterruptedException | ExecutionException e) {
-                throw new IllegalStateException(e);
-            }
-
-            if (expectedOperations > 0) {
-                assertEquals(testName, expectedSubRequestId, opend.getSubRequestId());
-            }
-        }
-
-        assertEquals(testName, expectedOperations, oper.getCount());
-    }
-
-    private class MyOper extends OperatorPartial {
-        @Getter
-        private int count = 0;
-
-        @Setter
-        private boolean genException;
-
-        @Setter
-        private int maxFailures = 0;
-
-        @Setter
-        private CompletableFuture<OperationOutcome> preProcessor;
-
-        public MyOper() {
-            super(ACTOR, OPERATOR);
-        }
-
-        @Override
-        protected OperationOutcome doOperation(ControlLoopOperationParams params, int attempt,
-                        OperationOutcome operation) {
-            ++count;
-            if (genException) {
-                throw new IllegalStateException(EXPECTED_EXCEPTION);
-            }
-
-            operation.setSubRequestId(String.valueOf(attempt));
-
-            if (count > maxFailures) {
-                operation.setResult(PolicyResult.SUCCESS);
-            } else {
-                operation.setResult(PolicyResult.FAILURE);
-            }
-
-            return operation;
-        }
-
-        @Override
-        protected CompletableFuture<OperationOutcome> startPreprocessorAsync(ControlLoopOperationParams params) {
-            return (preProcessor != null ? preProcessor : super.startPreprocessorAsync(params));
-        }
-
-        @Override
-        protected Executor getBlockingExecutor() {
-            return executor;
-        }
-    }
-
-    /**
-     * Executor that will run tasks until the queue is empty or a maximum number of tasks
-     * have been executed.
-     */
-    private static class MyExec implements Executor {
-        private static final int MAX_TASKS = MAX_PARALLEL_REQUESTS * 100;
-
-        private Queue<Runnable> commands = new LinkedList<>();
-
-        public MyExec() {
-            // do nothing
-        }
-
-        public int getQueueLength() {
-            return commands.size();
-        }
-
-        @Override
-        public void execute(Runnable command) {
-            commands.add(command);
-        }
-
-        public boolean runAll() {
-            for (int count = 0; count < MAX_TASKS && !commands.isEmpty(); ++count) {
-                commands.remove().run();
-            }
-
-            return commands.isEmpty();
-        }
+    public void testGetBlockingExecutor() {
+        assertNotNull(operator.getBlockingExecutor());
     }
 }
index 9dd19d5..a5215a4 100644 (file)
@@ -51,6 +51,7 @@ import org.mockito.MockitoAnnotations;
 import org.onap.policy.common.parameters.BeanValidationResult;
 import org.onap.policy.controlloop.VirtualControlLoopEvent;
 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;
@@ -87,11 +88,14 @@ public class ControlLoopOperationParamsTest {
     private Executor executor;
 
     @Mock
-    private CompletableFuture<OperationOutcome> operation;
+    private CompletableFuture<OperationOutcome> operFuture;
 
     @Mock
     private Operator operator;
 
+    @Mock
+    private Operation operation;
+
     @Mock
     private Consumer<OperationOutcome> starter;
 
@@ -110,7 +114,8 @@ public class ControlLoopOperationParamsTest {
 
         when(actorService.getActor(ACTOR)).thenReturn(actor);
         when(actor.getOperator(OPERATION)).thenReturn(operator);
-        when(operator.startOperation(any())).thenReturn(operation);
+        when(operator.buildOperation(any())).thenReturn(operation);
+        when(operation.start()).thenReturn(operFuture);
 
         when(event.getRequestId()).thenReturn(REQ_ID);
 
@@ -128,7 +133,7 @@ public class ControlLoopOperationParamsTest {
 
     @Test
     public void testStart() {
-        assertSame(operation, params.start());
+        assertSame(operFuture, params.start());
 
         assertThatIllegalArgumentException().isThrownBy(() -> params.toBuilder().context(null).build().start());
     }
index 6c1f538..daa0aff 100644 (file)
@@ -41,7 +41,7 @@ public class HttpActorParamsTest {
 
     private static final String CONTAINER = "my-container";
     private static final String CLIENT = "my-client";
-    private static final long TIMEOUT = 10;
+    private static final int TIMEOUT = 10;
 
     private static final String PATH1 = "path #1";
     private static final String PATH2 = "path #2";
index 6cf7328..ae4a79f 100644 (file)
@@ -36,7 +36,7 @@ public class HttpParamsTest {
     private static final String CONTAINER = "my-container";
     private static final String CLIENT = "my-client";
     private static final String PATH = "my-path";
-    private static final long TIMEOUT = 10;
+    private static final int TIMEOUT = 10;
 
     private HttpParams params;
 
index a6b11ef..4a00c06 100644 (file)
@@ -424,7 +424,7 @@ public class PipelineControllerFutureTest {
     }
 
     /**
-     * Tests add(Function) when the controller is canceled after the future is added.
+     * Tests wrap(Function) when the controller is canceled after the future is added.
      */
     @Test
     public void testWrapFunctionCancel() throws Exception {
@@ -442,7 +442,7 @@ public class PipelineControllerFutureTest {
     }
 
     /**
-     * Tests add(Function) when the controller is not running.
+     * Tests wrap(Function) when the controller is not running.
      */
     @Test
     public void testWrapFunctionNotRunning() {
index c7fe46e..8604688 100644 (file)
@@ -39,4 +39,9 @@
     <logger name="org.onap.policy.controlloop.actorserviceprovider.Util" level="info" additivity="false">
         <appender-ref ref="STDOUT" />
     </logger>
+
+    <!-- this is required for HttpOperationTest -->
+    <logger name="org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperation" level="info" additivity="false">
+        <appender-ref ref="STDOUT" />
+    </logger>
 </configuration>
index 8765eb4..029ac7f 100644 (file)
@@ -36,6 +36,7 @@
 
   <modules>
     <module>actorServiceProvider</module>
+    <module>actor.test</module>
     <module>actor.appc</module>
     <module>actor.vfc</module>
     <module>actor.sdnc</module>
diff --git a/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/AaiConstants.java b/models-interactions/model-impl/aai/src/main/java/org/onap/policy/aai/AaiConstants.java
new file mode 100644 (file)
index 0000000..084e4a5
--- /dev/null
@@ -0,0 +1,34 @@
+/*-
+ * ============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.aai;
+
+/**
+ * Constants used with A&AI classes.
+ */
+public class AaiConstants {
+
+    public static final String ACTOR_NAME = "AAI";
+    public static final String CONTEXT_PREFIX = ACTOR_NAME + ".";
+
+    private AaiConstants() {
+        // do nothing
+    }
+}
index 2010a5d..6fb42db 100644 (file)
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  *
  * ================================================================================
- * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2019-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.
@@ -50,6 +50,7 @@ import org.slf4j.LoggerFactory;
 
 public class AaiCqResponse implements Serializable {
     private static final long serialVersionUID = 1L;
+    public static final String CONTEXT_KEY = AaiConstants.CONTEXT_PREFIX + "AaiCqResponse";
     private static final String GENERIC_VNF = "generic-vnf";
     private static final String VF_MODULE = "vf-module";
     private static final Logger LOGGER = LoggerFactory.getLogger(AaiCqResponse.class);
index 923b8d3..41f4d3b 100644 (file)
@@ -2,7 +2,7 @@
  * ============LICENSE_START=======================================================
  * aai
  * ================================================================================
- * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved.
+ * Copyright (C) 2017-2020 AT&T Intellectual Property. All rights reserved.
  * Modifications Copyright (C) 2019 Nordix Foundation.
  * Modifications Copyright (C) 2019 Samsung Electronics Co., Ltd.
  * ================================================================================
@@ -47,6 +47,8 @@ import org.slf4j.LoggerFactory;
  */
 public final class AaiManager {
 
+    // TODO remove this class
+
     /** The Constant logger. */
     private static final Logger logger = LoggerFactory.getLogger(AaiManager.class);
 
index 1fe23cf..83923f1 100644 (file)
@@ -23,6 +23,8 @@ package org.onap.policy.aai.util;
 
 public class AaiException extends Exception {
 
+    // TODO remove this class
+
     private static final long serialVersionUID = 9220983727706207465L;
 
     public AaiException() {
index e42325f..326294a 100644 (file)
@@ -26,6 +26,8 @@ import com.google.gson.GsonBuilder;
 
 public final class Serialization {
 
+    // TODO remove this class
+
     public static final Gson gsonPretty = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create();
 
     private Serialization() {