Merge "Add/Update test cases for PdpStatistics entity"
authorPamela Dragosh <pdragosh@research.att.com>
Mon, 17 Feb 2020 16:47:37 +0000 (16:47 +0000)
committerGerrit Code Review <gerrit@onap.org>
Mon, 17 Feb 2020 16:47:37 +0000 (16:47 +0000)
68 files changed:
models-examples/src/main/resources/policies/vCPE.policy.operational.legacy.input.json [moved from models-examples/src/main/resources/policies/vCPE.policy.operational.input.json with 100% similarity]
models-examples/src/main/resources/policies/vCPE.policy.operational.legacy.output.json [moved from models-examples/src/main/resources/policies/vCPE.policy.operational.output.json with 100% similarity]
models-examples/src/main/resources/policies/vCPE.policy.operational.output.tosca.yaml
models-examples/src/main/resources/policies/vDNS.policy.operational.legacy.input.json [moved from models-examples/src/main/resources/policies/vDNS.policy.operational.input.json with 100% similarity]
models-examples/src/main/resources/policies/vDNS.policy.operational.legacy.output.json [moved from models-examples/src/main/resources/policies/vDNS.policy.operational.output.json with 100% similarity]
models-examples/src/main/resources/policies/vFirewall.policy.operational.input.tosca.json
models-examples/src/main/resources/policies/vFirewall.policy.operational.legacy.input.json [moved from models-examples/src/main/resources/policies/vFirewall.policy.operational.input.json with 100% similarity]
models-examples/src/main/resources/policies/vFirewall.policy.operational.legacy.output.json [moved from models-examples/src/main/resources/policies/vFirewall.policy.operational.output.json with 100% similarity]
models-examples/src/main/resources/policytypes/onap.policies.controlloop.Operational.yaml
models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.Common.yaml
models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.common.Apex.yaml
models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.common.Drools.yaml [new file with mode: 0644]
models-examples/src/main/resources/policytypes/onap.policies.monitoring.cdap.tca.hi.lo.app.yaml
models-examples/src/main/resources/policytypes/onap.policies.monitoring.dcaegen2.collectors.datafile.datafile-app-server.yaml
models-interactions/model-actors/actor.aai/pom.xml [new file with mode: 0644]
models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiActorServiceProvider.java [new file with mode: 0644]
models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiCustomQueryOperation.java [new file with mode: 0644]
models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiGetOperation.java [new file with mode: 0644]
models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiUtil.java [new file with mode: 0644]
models-interactions/model-actors/actor.aai/src/main/resources/META-INF/services/org.onap.policy.controlloop.actorServiceProvider.spi.Actor [new file with mode: 0644]
models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiActorServiceProviderTest.java [new file with mode: 0644]
models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiCustomQueryOperationTest.java [new file with mode: 0644]
models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiGetOperatorTest.java [new file with mode: 0644]
models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiUtilTest.java [new file with mode: 0644]
models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/BasicAaiOperator.java [new file with mode: 0644]
models-interactions/model-actors/actor.test/src/main/java/org/onap/policy/controlloop/actor/test/BasicHttpOperation.java
models-interactions/model-actors/actor.test/src/test/java/org/onap/policy/controlloop/actor/test/BasicHttpOperationTest.java
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperation.java
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/TopicPairActor.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/TopicPairOperation.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/TopicPairOperator.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicPairActorParams.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicPairParams.java [moved from models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParams.java with 85% similarity]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/Forwarder.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/SelectorKey.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicListenerImpl.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicPair.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicPairManager.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperationTest.java
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/MyExec.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartialTest.java
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/TopicPairOperationTest.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/TopicPairOperatorTest.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicPairActorParamsTest.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicPairParamsTest.java [moved from models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParamsTest.java with 82% similarity]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/ForwarderTest.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/SelectorKeyTest.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicListenerImplTest.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicPairTest.java [new file with mode: 0644]
models-interactions/model-actors/actorServiceProvider/src/test/resources/logback-test.xml
models-interactions/model-actors/pom.xml
models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyLegacyOperationalPersistenceTest.java
models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyToscaPersistenceTest.java
models-tosca/src/main/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProvider.java
models-tosca/src/main/java/org/onap/policy/models/tosca/legacy/provider/LegacyProvider.java
models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicy.java
models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicyType.java
models-tosca/src/main/java/org/onap/policy/models/tosca/simple/provider/SimpleToscaProvider.java
models-tosca/src/main/java/org/onap/policy/models/tosca/utils/ToscaServiceTemplateUtils.java
models-tosca/src/main/java/org/onap/policy/models/tosca/utils/ToscaUtils.java
models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/concepts/ToscaPolicyFilterTest.java
models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/concepts/ToscaPolicyTypeFilterTest.java
models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProviderPolicyTypeTest.java
models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/mapping/LegacyOperationalPolicyMapperTest.java
models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/provider/LegacyProvider4LegacyOperationalTest.java
models-tosca/src/test/java/org/onap/policy/models/tosca/simple/serialization/MonitoringPolicyTypeSerializationTest.java
models-tosca/src/test/java/org/onap/policy/models/tosca/utils/ToscaServiceTemplateUtilsTest.java
models-tosca/src/test/resources/onap.policies.NoVersion.yaml

index e4a0694..2c7981a 100644 (file)
@@ -23,7 +23,7 @@ topology_template:
                actor: APPC
                recipe: Restart
                target:
-                 type: VM
+                 targetType: VM
                retry: 3
                timeout: 1200
                success: final_success
index 63c0d8b..f6f15fe 100644 (file)
@@ -4,32 +4,32 @@
         "policies": [
             {
                 "operational.modifyconfig": {
-                    "type": "onap.policies.controlloop.Operational",
-                    "version": "1.0.0",
+                    "type": "onap.policies.controlloop.operational.common.Drools",
+                    "type_version": "1.0.0",
                     "metadata": {
                         "policy-id": "operational.modifyconfig"
                     },
                     "properties": {
-                        "controlLoop": {
-                            "version": "2.0.0",
-                            "controlLoopName": "ControlLoop-vFirewall-d0a1dfc6-94f5-4fd4-a5b5-4630b438850a",
-                            "trigger_policy": "unique-policy-id-1-modifyConfig",
-                            "timeout": 1200,
-                            "abatement": false
-                        },
-                        "policies": [
+                        "id": "ControlLoop-vFirewall-d0a1dfc6-94f5-4fd4-a5b5-4630b438850a",
+                        "timeout": 1200,
+                        "abatement": false,
+                        "trigger": "unique-policy-id-1-modifyConfig",
+                        "operations": [
                             {
                                 "id": "unique-policy-id-1-modifyConfig",
-                                "name": "modify packet gen config",
-                                "description": null,
-                                "actor": "APPC",
-                                "recipe": "ModifyConfig",
-                                "target": {
-                                    "resourceID": "Eace933104d443b496b8.nodes.heat.vpg",
-                                    "type": "VNF"
+                                "description": "Modify the packet generator",
+                                "operation": {
+                                    "actor": "APPC",
+                                    "operation": "ModifyConfig",
+                                    "target": {
+                                        "targetType": "VNF",
+                                        "entityId": {
+                                            "resourceID": "bbb3cefd-01c8-413c-9bdd-2b92f9ca3d38"
+                                        }
+                                    }
                                 },
-                                "retry": 0,
                                 "timeout": 300,
+                                "retries": 0,
                                 "success": "final_success",
                                 "failure": "final_failure",
                                 "failure_timeout": "final_failure_timeout",
@@ -37,7 +37,8 @@
                                 "failure_exception": "final_failure_exception",
                                 "failure_guard": "final_failure_guard"
                             }
-                        ]
+                        ],
+                        "controllerName": "usecases"
                     }
                 }
             }
index f21fd5a..773e0c9 100644 (file)
@@ -3,4 +3,4 @@ policy_types:
    onap.policies.controlloop.Operational:
       derived_from: tosca.policies.Root
       version: 1.0.0
-      description: Operational Policy for Control Loops
\ No newline at end of file
+      description: Operational Policy for Control Loops Supporting Legacy YAML Policy Definition.
\ No newline at end of file
index 0dbe7e4..4a918be 100644 (file)
@@ -3,7 +3,9 @@ policy_types:
     onap.policies.controlloop.operational.Common:
         derived_from: tosca.policies.Root
         version: 1.0.0
-        description: Operational Policy for Control Loop execution
+        description: |
+            Operational Policy for Control Loop execution. Originated in Frankfurt to support TOSCA Compliant
+            Policy Types. This does NOT support the legacy Policy YAML policy type.
         properties:
             id:
                 type: string
@@ -31,36 +33,7 @@ policy_types:
                 entry_schema:
                     type: onap.datatype.controlloop.Operation
 
-    onap.policies.controlloop.operational.common.Drools:
-        derived_from: onap.policies.controlloop.operational.Common
-        type_version: 1.0.0
-        version: 1.0.0
-        description: Operational policies for Drools PDP
-        properties:
-            controllerName:
-                type: string
-                description: Drools controller properties
-                required: false
-
 data_types:
-    # TBD if this is needed
-    onap.datatype.controlloop.operation.Failure:
-        derived_from: tosca.datatypes.Root
-        description: Captures information of an operational failure performed for control loop
-        properties:
-            messages:
-                type: string
-                description: error message
-                required: true
-            category:
-                type: string
-                description: |
-                    The category the error occurred in. Whether this is a general error from the actor, or the operation
-                    timed out, retries were exhausted in trying to execute the operation, a guard policy prevented the
-                    operation from occuring, or an exception in the system caused the failure.
-                constraints:
-                - valid_values: [error, timeout, retries, guard, exception]
-
     onap.datatype.controlloop.Target:
         derived_from: tosca.datatypes.Root
         description: Definition for a entity in A&AI to perform a control loop operation on
@@ -77,6 +50,8 @@ data_types:
                     Map of values that identify the resource. If none are provided, it is assumed that the
                     entity that generated the ONSET event will be the target.
                 required: false
+                entry_schema:
+                    type: string
 
     onap.datatype.controlloop.Actor:
         derived_from: tosca.datatypes.Root
@@ -91,7 +66,7 @@ data_types:
                 description: The operation the actor is performing.
                 required: true
             target:
-                type: string
+                type: onap.datatype.controlloop.Target
                 description: The resource the operation should be performed on.
                 required: true
                 metadata:
index e1555e8..9c6c612 100644 (file)
@@ -2,6 +2,7 @@ tosca_definitions_version: tosca_simple_yaml_1_0_0
 policy_types:
     onap.policies.controlloop.operational.common.Apex:
         derived_from: onap.policies.controlloop.operational.Common
+        type_version: 1.0.0
         version: 1.0.0
         description: Operational policies for Apex PDP
         properties:
diff --git a/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.common.Drools.yaml b/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.common.Drools.yaml
new file mode 100644 (file)
index 0000000..2d793cc
--- /dev/null
@@ -0,0 +1,13 @@
+tosca_definitions_version: tosca_simple_yaml_1_0_0
+policy_types:
+    onap.policies.controlloop.operational.common.Drools:
+        derived_from: onap.policies.controlloop.operational.Common
+        type_version: 1.0.0
+        version: 1.0.0
+        description: Operational policies for Drools PDP
+        properties:
+            controllerName:
+                type: string
+                description: Drools controller properties
+                required: false
+
index 2f5abdd..5fa4308 100644 (file)
@@ -2,7 +2,8 @@ tosca_definitions_version: tosca_simple_yaml_1_0_0
 policy_types:
    onap.policies.Monitoring:
       derived_from: tosca.policies.Root
-      description: a base policy type for all policies that governs monitoring provisioning
+      version: 1.0.0
+      description: a base policy type for all policies that govern monitoring provisioning
    onap.policies.monitoring.cdap.tca.hi.lo.app:
       derived_from: onap.policies.Monitoring
       version: 1.0.0
index cf70e9b..8419b09 100644 (file)
@@ -2,10 +2,10 @@ tosca_definitions_version: tosca_simple_yaml_1_0_0
 policy_types:
    onap.policies.Monitoring:
       derived_from: tosca.policies.Root
-      description: a base policy type for all policies that govern monitoring provision
+      description: a base policy type for all policies that govern monitoring provisioning
       version: 1.0.0
    onap.policies.monitoring.dcaegen2.collectors.datafile.datafile-app-server:
-      derived_from: policy.nodes.Root
+      derived_from: onap.policies.Monitoring
       version: 1.0.0
       properties:
          buscontroller_feed_publishing_endpoint:
diff --git a/models-interactions/model-actors/actor.aai/pom.xml b/models-interactions/model-actors/actor.aai/pom.xml
new file mode 100644 (file)
index 0000000..deb0181
--- /dev/null
@@ -0,0 +1,89 @@
+<?xml version="1.0"?>
+<!--
+  ============LICENSE_START=======================================================
+  Copyright (C) 2018 Huawei Intellectual Property. All rights reserved.
+  Modifications Copyright (C) 2019-2020 Nordix Foundation.
+  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.aai</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>aai</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>
diff --git a/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiActorServiceProvider.java b/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiActorServiceProvider.java
new file mode 100644 (file)
index 0000000..df427c3
--- /dev/null
@@ -0,0 +1,47 @@
+/*-
+ * ============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.aai;
+
+import org.onap.policy.aai.AaiConstants;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpActor;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+
+/**
+ * A&AI Actor.
+ */
+public class AaiActorServiceProvider extends HttpActor {
+    public static final String NAME = AaiConstants.ACTOR_NAME;
+
+    /**
+     * Constructs the object.
+     */
+    public AaiActorServiceProvider() {
+        super(NAME);
+
+        addOperator(HttpOperator.makeOperator(NAME, AaiCustomQueryOperation.NAME,
+                        AaiCustomQueryOperation::new));
+
+        // add all "get" operators
+        for (String operation : AaiGetOperation.OPERATIONS) {
+            addOperator(HttpOperator.makeOperator(NAME, operation, AaiGetOperation::new));
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiCustomQueryOperation.java b/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiCustomQueryOperation.java
new file mode 100644 (file)
index 0000000..5f43270
--- /dev/null
@@ -0,0 +1,127 @@
+/*-
+ * ============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.aai;
+
+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.aai.AaiConstants;
+import org.onap.policy.aai.AaiCqResponse;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A&AI Custom Query. Stores the {@link AaiCqResponse} in the context. In addition, if the
+ * context does not contain the "tenant" data for the vserver, then it will request that,
+ * as well.
+ */
+public class AaiCustomQueryOperation extends HttpOperation<String> {
+    private static final Logger logger = LoggerFactory.getLogger(AaiCustomQueryOperation.class);
+
+    public static final String NAME = "CustomQuery";
+
+    public static final String RESOURCE_LINK = "resource-link";
+    public static final String RESULT_DATA = "result-data";
+
+    private static final String PREFIX = "/aai/v16";
+
+    /**
+     * Constructs the object.
+     *
+     * @param params operation parameters
+     * @param operator operator that created this operation
+     */
+    public AaiCustomQueryOperation(ControlLoopOperationParams params, HttpOperator operator) {
+        super(params, operator, String.class);
+    }
+
+    /**
+     * Queries the vserver, if necessary.
+     */
+    @Override
+    protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
+        String vserver = params.getTargetEntity();
+
+        ControlLoopOperationParams tenantParams = params.toBuilder().actor(AaiConstants.ACTOR_NAME)
+                        .operation(AaiGetOperation.TENANT).payload(null).retry(null).timeoutSec(null).build();
+
+        return params.getContext().obtain(AaiGetOperation.getTenantKey(vserver), tenantParams);
+    }
+
+    @Override
+    protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+        Map<String, String> request = makeRequest();
+
+        Entity<Map<String, String>> 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
+    }
+
+    /**
+     * Constructs the custom query using the previously retrieved tenant data.
+     */
+    private Map<String, String> makeRequest() {
+        String vserver = params.getTargetEntity();
+        StandardCoderObject tenant = params.getContext().getProperty(AaiGetOperation.getTenantKey(vserver));
+
+        String resourceLink = tenant.getString(RESULT_DATA, 0, RESOURCE_LINK);
+        if (resourceLink == null) {
+            throw new IllegalArgumentException("cannot perform custom query - no resource-link");
+        }
+
+        resourceLink = resourceLink.replace(PREFIX, "");
+
+        return Map.of("start", resourceLink, "query", "query/closed-loop");
+    }
+
+    @Override
+    protected Map<String, Object> makeHeaders() {
+        return AaiUtil.makeHeaders(params);
+    }
+
+    /**
+     * Injects the response into the context.
+     */
+    @Override
+    protected void postProcessResponse(OperationOutcome outcome, String url, Response rawResponse, String response) {
+
+        logger.info("{}: caching response for {}", getFullName(), params.getRequestId());
+        params.getContext().setProperty(AaiCqResponse.CONTEXT_KEY, new AaiCqResponse(response));
+    }
+}
diff --git a/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiGetOperation.java b/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiGetOperation.java
new file mode 100644 (file)
index 0000000..3bc359a
--- /dev/null
@@ -0,0 +1,133 @@
+/*-
+ * ============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.aai;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.onap.policy.aai.AaiConstants;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Superclass of A&AI operators that use "get" to perform their request and store their
+ * response within the context as a {@link StandardCoderObject}. The property name under
+ * which they are stored is ${actor}.${operation}.${targetEntity}.
+ */
+public class AaiGetOperation extends HttpOperation<StandardCoderObject> {
+    private static final Logger logger = LoggerFactory.getLogger(AaiGetOperation.class);
+
+    public static final int DEFAULT_RETRY = 3;
+
+    // operation names
+    public static final String TENANT = "Tenant";
+
+    // property prefixes
+    private static final String TENANT_KEY_PREFIX = AaiConstants.CONTEXT_PREFIX + TENANT + ".";
+
+    /**
+     * Operation names supported by this operator.
+     */
+    public static final Set<String> OPERATIONS = Set.of(TENANT);
+
+
+    /**
+     * Responses that are retrieved from A&AI are placed in the operation context under
+     * the name "${propertyPrefix}.${targetEntity}".
+     */
+    private final String propertyPrefix;
+
+    /**
+     * Constructs the object.
+     *
+     * @param params operation parameters
+     * @param operator operator that created this operation
+     */
+    public AaiGetOperation(ControlLoopOperationParams params, HttpOperator operator) {
+        super(params, operator, StandardCoderObject.class);
+        this.propertyPrefix = operator.getFullName() + ".";
+    }
+
+    /**
+     * Gets the "context key" for the tenant query response associated with the given
+     * target entity.
+     *
+     * @param targetEntity target entity
+     * @return the "context key" for the response associated with the given target
+     */
+    public static String getTenantKey(String targetEntity) {
+        return (TENANT_KEY_PREFIX + targetEntity);
+    }
+
+    @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
+    }
+
+    @Override
+    protected Map<String, Object> makeHeaders() {
+        return AaiUtil.makeHeaders(params);
+    }
+
+    @Override
+    public String makePath() {
+        return (operator.getPath() + "/" + params.getTargetEntity());
+    }
+
+    /**
+     * Injects the response into the context.
+     */
+    @Override
+    protected void postProcessResponse(OperationOutcome outcome, String url, Response rawResponse,
+                    StandardCoderObject response) {
+        String entity = params.getTargetEntity();
+
+        logger.info("{}: caching response of {} for {}", getFullName(), entity, params.getRequestId());
+
+        params.getContext().setProperty(propertyPrefix + entity, response);
+    }
+
+    /**
+     * Provides a default retry value, if none specified.
+     */
+    @Override
+    protected int getRetry(Integer retry) {
+        return (retry == null ? DEFAULT_RETRY : retry);
+    }
+}
diff --git a/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiUtil.java b/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiUtil.java
new file mode 100644 (file)
index 0000000..14edc3a
--- /dev/null
@@ -0,0 +1,50 @@
+/*-
+ * ============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.aai;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+
+/**
+ * Utilities used by A&AI classes.
+ */
+public class AaiUtil {
+
+    private AaiUtil() {
+        // do nothing
+    }
+
+    /**
+     * Makes standard request headers for A&AI requests.
+     *
+     * @param params operation parameters
+     * @return new request headers
+     */
+    public static Map<String, Object> makeHeaders(ControlLoopOperationParams params) {
+        Map<String, Object> headers = new HashMap<>();
+
+        headers.put("X-FromAppId", "POLICY");
+        headers.put("X-TransactionId", params.getRequestId().toString());
+
+        return headers;
+    }
+}
diff --git a/models-interactions/model-actors/actor.aai/src/main/resources/META-INF/services/org.onap.policy.controlloop.actorServiceProvider.spi.Actor b/models-interactions/model-actors/actor.aai/src/main/resources/META-INF/services/org.onap.policy.controlloop.actorServiceProvider.spi.Actor
new file mode 100644 (file)
index 0000000..6a52e3f
--- /dev/null
@@ -0,0 +1 @@
+org.onap.policy.controlloop.actor.aai.AaiActorServiceProvider
diff --git a/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiActorServiceProviderTest.java b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiActorServiceProviderTest.java
new file mode 100644 (file)
index 0000000..513f339
--- /dev/null
@@ -0,0 +1,48 @@
+/*-
+ * ============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.aai;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+
+public class AaiActorServiceProviderTest {
+
+    @Test
+    public void testAaiActorServiceProvider() {
+        final AaiActorServiceProvider prov = new AaiActorServiceProvider();
+
+        // verify that it has the operators we expect
+        List<String> expected = new LinkedList<>();
+        expected.add(AaiCustomQueryOperation.NAME);
+        expected.addAll(AaiGetOperation.OPERATIONS);
+
+        Collections.sort(expected);
+
+        var actual = prov.getOperationNames().stream().sorted().collect(Collectors.toList());
+
+        assertEquals(expected.toString(), actual.toString());
+    }
+}
diff --git a/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiCustomQueryOperationTest.java b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiCustomQueryOperationTest.java
new file mode 100644 (file)
index 0000000..eca5062
--- /dev/null
@@ -0,0 +1,189 @@
+/*-
+ * ============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.aai;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.onap.policy.aai.AaiConstants;
+import org.onap.policy.aai.AaiCqResponse;
+import org.onap.policy.common.endpoints.http.client.HttpClientFactory;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+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.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
+import org.onap.policy.controlloop.actorserviceprovider.spi.Actor;
+import org.onap.policy.controlloop.policy.PolicyResult;
+
+public class AaiCustomQueryOperationTest extends BasicAaiOperator<Map<String, String>> {
+    private static final StandardCoder coder = new StandardCoder();
+
+    private static final String MY_LINK = "my-link";
+
+    @Mock
+    private Actor tenantActor;
+
+    private AaiCustomQueryOperation oper;
+
+    public AaiCustomQueryOperationTest() {
+        super(AaiConstants.ACTOR_NAME, AaiCustomQueryOperation.NAME);
+    }
+
+    /**
+     * Sets up.
+     */
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        MyTenantOperator tenantOperator = new MyTenantOperator();
+
+        when(service.getActor(AaiConstants.ACTOR_NAME)).thenReturn(tenantActor);
+        when(tenantActor.getOperator(AaiGetOperation.TENANT)).thenReturn(tenantOperator);
+
+        oper = new AaiCustomQueryOperation(params, operator);
+    }
+
+    @Test
+    public void testAaiCustomQueryOperation() {
+        assertEquals(AaiConstants.ACTOR_NAME, oper.getActorName());
+        assertEquals(AaiCustomQueryOperation.NAME, oper.getName());
+    }
+
+    @Test
+    public void testStartOperationAsync_testStartPreprocessorAsync_testMakeRequest_testPostProcess() throws Exception {
+        // need two responses
+        when(rawResponse.readEntity(String.class)).thenReturn(makeTenantReply()).thenReturn(makeCqReply());
+        when(client.get(any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+        when(client.put(any(), any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+
+        CompletableFuture<OperationOutcome> future2 = oper.start();
+
+        assertEquals(PolicyResult.SUCCESS, future2.get(5, TimeUnit.SECONDS).getResult());
+
+        // tenant response should have been cached within the context
+        assertNotNull(context.getProperty(AaiGetOperation.getTenantKey(TARGET_ENTITY)));
+
+        // custom query response should have been cached within the context
+        AaiCqResponse cqData = context.getProperty(AaiCqResponse.CONTEXT_KEY);
+        assertNotNull(cqData);
+    }
+
+    /**
+     * Tests when preprocessor step is not needed.
+     */
+    @Test
+    public void testStartOperationAsync_testStartPreprocessorAsyncNotNeeded() throws Exception {
+        // pre-load the tenant data
+        final StandardCoderObject data = preloadTenantData();
+
+        // only need one response
+        when(rawResponse.readEntity(String.class)).thenReturn(makeCqReply());
+        when(client.put(any(), any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+
+        CompletableFuture<OperationOutcome> future2 = oper.start();
+
+        assertEquals(PolicyResult.SUCCESS, future2.get(5, TimeUnit.SECONDS).getResult());
+
+        // should not have replaced tenant response
+        assertSame(data, context.getProperty(AaiGetOperation.getTenantKey(TARGET_ENTITY)));
+
+        // custom query response should have been cached within the context
+        AaiCqResponse cqData = context.getProperty(AaiCqResponse.CONTEXT_KEY);
+        assertNotNull(cqData);
+    }
+
+    @Test
+    public void testMakeHeaders() {
+        verifyHeaders(oper.makeHeaders());
+    }
+
+    @Test
+    public void testMakeRequestNoResourceLink() throws Exception {
+        // pre-load EMPTY tenant data
+        preloadTenantData(new StandardCoderObject());
+
+        when(rawResponse.readEntity(String.class)).thenReturn(makeCqReply());
+        when(client.put(any(), any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+
+        CompletableFuture<OperationOutcome> future2 = oper.start();
+
+        assertEquals(PolicyResult.FAILURE_EXCEPTION, future2.get(5, TimeUnit.SECONDS).getResult());
+    }
+
+    private String makeTenantReply() throws Exception {
+        Map<String, String> links = Map.of(AaiCustomQueryOperation.RESOURCE_LINK, MY_LINK);
+        List<Map<String, String>> data = Arrays.asList(links);
+
+        Map<String, Object> reply = Map.of(AaiCustomQueryOperation.RESULT_DATA, data);
+        return coder.encode(reply);
+    }
+
+    private String makeCqReply() {
+        return "{}";
+    }
+
+    private StandardCoderObject preloadTenantData() throws Exception {
+        StandardCoderObject data = coder.decode(makeTenantReply(), StandardCoderObject.class);
+        preloadTenantData(data);
+        return data;
+    }
+
+    private void preloadTenantData(StandardCoderObject data) {
+        context.setProperty(AaiGetOperation.getTenantKey(TARGET_ENTITY), data);
+    }
+
+    protected class MyTenantOperator extends HttpOperator {
+        public MyTenantOperator() {
+            super(AaiConstants.ACTOR_NAME, AaiGetOperation.TENANT);
+
+            HttpParams http = HttpParams.builder().clientName(MY_CLIENT).path(PATH).build();
+
+            configure(Util.translateToMap(AaiGetOperation.TENANT, http));
+            start();
+        }
+
+        @Override
+        public Operation buildOperation(ControlLoopOperationParams params) {
+            return new AaiGetOperation(params, this);
+        }
+
+        @Override
+        protected HttpClientFactory getClientFactory() {
+            return factory;
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiGetOperatorTest.java b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiGetOperatorTest.java
new file mode 100644 (file)
index 0000000..ca90ce4
--- /dev/null
@@ -0,0 +1,131 @@
+/*-
+ * ============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.aai;
+
+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.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.aai.AaiConstants;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.policy.PolicyResult;
+
+public class AaiGetOperatorTest extends BasicAaiOperator<Void> {
+
+    private static final String INPUT_FIELD = "input";
+    private static final String TEXT = "my-text";
+
+    private AaiGetOperation oper;
+
+    public AaiGetOperatorTest() {
+        super(AaiConstants.ACTOR_NAME, AaiGetOperation.TENANT);
+    }
+
+    /**
+     * Sets up.
+     */
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        oper = new AaiGetOperation(params, operator);
+    }
+
+    @Test
+    public void testGetRetry() {
+        // use default if null retry
+        assertEquals(AaiGetOperation.DEFAULT_RETRY, oper.getRetry(null));
+
+        // otherwise, use specified value
+        assertEquals(0, oper.getRetry(0));
+        assertEquals(10, oper.getRetry(10));
+    }
+
+    @Test
+    public void testStartOperationAsync_testStartQueryAsync_testPostProcessResponse() throws Exception {
+
+        // return a map in the reply
+        Map<String, String> reply = Map.of(INPUT_FIELD, TEXT);
+        when(rawResponse.readEntity(String.class)).thenReturn(new StandardCoder().encode(reply));
+
+        when(client.get(any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+
+        CompletableFuture<OperationOutcome> future2 = oper.startOperationAsync(1, outcome);
+        assertFalse(future2.isDone());
+
+        assertEquals(PolicyResult.SUCCESS, future2.get(5, TimeUnit.SECONDS).getResult());
+
+        // data should have been cached within the context
+        StandardCoderObject data = context.getProperty(AaiGetOperation.getTenantKey(TARGET_ENTITY));
+        assertNotNull(data);
+        assertEquals(TEXT, data.getString(INPUT_FIELD));
+    }
+
+    /**
+     * Tests startOperationAsync() when there's a failure.
+     */
+    @Test
+    public void testStartOperationAsyncFailure() throws Exception {
+
+        when(rawResponse.getStatus()).thenReturn(500);
+        when(rawResponse.readEntity(String.class)).thenReturn("");
+
+        when(client.get(any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+
+        CompletableFuture<OperationOutcome> future2 = oper.startOperationAsync(1, outcome);
+        assertFalse(future2.isDone());
+
+        assertEquals(PolicyResult.FAILURE, future2.get(5, TimeUnit.SECONDS).getResult());
+
+        // data should NOT have been cached within the context
+        assertNull(context.getProperty(AaiGetOperation.getTenantKey(TARGET_ENTITY)));
+    }
+
+    @Test
+    public void testMakeHeaders() {
+        verifyHeaders(oper.makeHeaders());
+    }
+
+    @Test
+    public void testMakePath() {
+        assertEquals(PATH + "/" + TARGET_ENTITY, oper.makePath());
+    }
+
+    @Test
+    public void testAaiGetOperator() {
+        assertEquals(AaiConstants.ACTOR_NAME, oper.getActorName());
+        assertEquals(AaiGetOperation.TENANT, oper.getName());
+    }
+
+    @Test
+    public void testGetTenantKey() {
+        assertEquals("AAI.Tenant." + TARGET_ENTITY, AaiGetOperation.getTenantKey(TARGET_ENTITY));
+    }
+}
diff --git a/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiUtilTest.java b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiUtilTest.java
new file mode 100644 (file)
index 0000000..39ed6fe
--- /dev/null
@@ -0,0 +1,36 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.aai;
+
+import java.util.Map;
+import org.junit.Test;
+
+public class AaiUtilTest extends BasicAaiOperator<Void> {
+
+    @Test
+    public void testMakeHeaders() {
+        makeContext();
+
+        Map<String, Object> headers = AaiUtil.makeHeaders(params);
+
+        verifyHeaders(headers);
+    }
+}
diff --git a/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/BasicAaiOperator.java b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/BasicAaiOperator.java
new file mode 100644 (file)
index 0000000..50b562a
--- /dev/null
@@ -0,0 +1,54 @@
+/*-
+ * ============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.aai;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Map;
+import org.onap.policy.controlloop.actor.test.BasicHttpOperation;
+
+/**
+ * Superclass for various operator tests.
+ */
+public abstract class BasicAaiOperator<Q> extends BasicHttpOperation<Q> {
+
+    /**
+     * Constructs the object using a default actor and operation name.
+     */
+    public BasicAaiOperator() {
+        super();
+    }
+
+    /**
+     * Constructs the object.
+     *
+     * @param actor actor name
+     * @param operation operation name
+     */
+    public BasicAaiOperator(String actor, String operation) {
+        super(actor, operation);
+    }
+
+    protected void verifyHeaders(Map<String, Object> headers) {
+        assertEquals("POLICY", headers.get("X-FromAppId").toString());
+        assertEquals(params.getRequestId().toString(), headers.get("X-TransactionId"));
+    }
+}
index 15e4848..cd9681c 100644 (file)
@@ -33,6 +33,7 @@ import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
 import org.onap.policy.common.endpoints.http.client.HttpClient;
 import org.onap.policy.common.endpoints.http.client.HttpClientFactory;
 import org.onap.policy.controlloop.VirtualControlLoopEvent;
@@ -166,4 +167,18 @@ public class BasicHttpOperation<Q> {
     protected Map<String, String> makeEnrichment() {
         return new TreeMap<>();
     }
+
+    /**
+     * Provides a response to an asynchronous HttpClient call.
+     *
+     * @param response response to be provided to the call
+     * @return a function that provides the response to the call
+     */
+    protected Answer<CompletableFuture<Response>> provideResponse(Response response) {
+        return args -> {
+            InvocationCallback<Response> cb = args.getArgument(0);
+            cb.completed(response);
+            return CompletableFuture.completedFuture(response);
+        };
+    }
 }
index c33483d..1de2f92 100644 (file)
@@ -24,7 +24,11 @@ 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 static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
 
+import javax.ws.rs.client.InvocationCallback;
+import javax.ws.rs.core.Response;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -55,7 +59,7 @@ public class BasicHttpOperationTest {
     }
 
     @Test
-    public void testSetUp() {
+    public void testSetUp() throws Exception {
         assertNotNull(oper.client);
         assertSame(oper.client, oper.factory.get(BasicHttpOperation.MY_CLIENT));
         assertEquals(200, oper.rawResponse.getStatus());
@@ -101,4 +105,23 @@ public class BasicHttpOperationTest {
         assertTrue(oper.makeEnrichment().isEmpty());
     }
 
+    @Test
+    public void testProvideResponse() throws Exception {
+        InvocationCallback<Response> cb = new InvocationCallback<>() {
+            @Override
+            public void completed(Response response) {
+                // do nothing
+            }
+
+            @Override
+            public void failed(Throwable throwable) {
+                // do nothing
+            }
+        };
+
+
+        when(oper.client.get(any(), any(), any())).thenAnswer(oper.provideResponse(oper.rawResponse));
+
+        assertSame(oper.rawResponse, oper.client.get(cb, null, null).get());
+    }
 }
index c4bf5f4..ba75f0b 100644 (file)
@@ -181,9 +181,9 @@ public abstract class HttpOperation<T> extends OperationPartial {
             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);
+                logger.warn("{}.{} cannot decode response for {}", params.getActor(), params.getOperation(),
+                                params.getRequestId(), e);
+                throw new IllegalArgumentException("cannot decode response");
             }
         }
 
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/TopicPairActor.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/TopicPairActor.java
new file mode 100644 (file)
index 0000000..c3e1e5c
--- /dev/null
@@ -0,0 +1,112 @@
+/*-
+ * ============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.Map;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import org.apache.commons.lang3.tuple.Pair;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.TopicPairActorParams;
+import org.onap.policy.controlloop.actorserviceprovider.topic.TopicPair;
+import org.onap.policy.controlloop.actorserviceprovider.topic.TopicPairManager;
+
+/**
+ * Actor that uses a topic pair. The actor's parameters must be a
+ * {@link TopicPairActorParams}.
+ */
+public class TopicPairActor extends ActorImpl implements TopicPairManager {
+
+    /**
+     * Maps a topic source and target name to its topic pair.
+     */
+    private final Map<Pair<String, String>, TopicPair> params2topic = new ConcurrentHashMap<>();
+
+
+    /**
+     * Constructs the object.
+     *
+     * @param name actor's name
+     */
+    public TopicPairActor(String name) {
+        super(name);
+    }
+
+    @Override
+    protected void doStart() {
+        params2topic.values().forEach(TopicPair::start);
+        super.doStart();
+    }
+
+    @Override
+    protected void doStop() {
+        params2topic.values().forEach(TopicPair::stop);
+        super.doStop();
+    }
+
+    @Override
+    protected void doShutdown() {
+        params2topic.values().forEach(TopicPair::shutdown);
+        params2topic.clear();
+        super.doShutdown();
+    }
+
+    @Override
+    public TopicPair getTopicPair(String source, String target) {
+        Pair<String, String> key = Pair.of(source, target);
+        return params2topic.computeIfAbsent(key, pair -> new TopicPair(source, target));
+    }
+
+    /**
+     * Translates the parameters to a {@link TopicPairActorParams} and then creates a
+     * function that will extract operator-specific parameters.
+     */
+    @Override
+    protected Function<String, Map<String, Object>> makeOperatorParameters(Map<String, Object> actorParameters) {
+        String actorName = getName();
+
+        TopicPairActorParams params = Util.translate(actorName, actorParameters, TopicPairActorParams.class);
+        ValidationResult result = params.validate(getName());
+        if (!result.isValid()) {
+            throw new ParameterValidationRuntimeException("invalid parameters", result);
+        }
+
+        // create a map of the default parameters
+        Map<String, Object> defaultParams = Util.translateToMap(getName(), params.getDefaults());
+        Map<String, Map<String, Object>> operations = params.getOperation();
+
+        return operationName -> {
+            Map<String, Object> specificParams = operations.get(operationName);
+            if (specificParams == null) {
+                return null;
+            }
+
+            // start with a copy of defaults and overlay with specific
+            Map<String, Object> subparams = new TreeMap<>(defaultParams);
+            subparams.putAll(specificParams);
+
+            return Util.translateToMap(getName() + "." + operationName, subparams);
+        };
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/TopicPairOperation.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/TopicPairOperation.java
new file mode 100644 (file)
index 0000000..6b584d7
--- /dev/null
@@ -0,0 +1,316 @@
+/*-
+ * ============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.Executor;
+import java.util.concurrent.TimeUnit;
+import lombok.Getter;
+import org.apache.commons.lang3.tuple.Triple;
+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.PropertyUtils.TriConsumer;
+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.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.TopicPairParams;
+import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture;
+import org.onap.policy.controlloop.actorserviceprovider.topic.Forwarder;
+import org.onap.policy.controlloop.actorserviceprovider.topic.TopicPair;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Operation that uses a Topic pair.
+ *
+ * @param <S> response type
+ */
+@Getter
+public abstract class TopicPairOperation<Q, S> extends OperationPartial {
+    private static final Logger logger = LoggerFactory.getLogger(TopicPairOperation.class);
+    private static final Coder coder = new StandardCoder();
+
+    // fields extracted from the operator
+
+    private final TopicPair topicPair;
+    private final Forwarder forwarder;
+    private final TopicPairParams pairParams;
+    private final long timeoutMs;
+
+    /**
+     * Response class.
+     */
+    private final Class<S> responseClass;
+
+
+    /**
+     * Constructs the object.
+     *
+     * @param params operation parameters
+     * @param operator operator that created this operation
+     * @param clazz response class
+     */
+    public TopicPairOperation(ControlLoopOperationParams params, TopicPairOperator operator, Class<S> clazz) {
+        super(params, operator);
+        this.topicPair = operator.getTopicPair();
+        this.forwarder = operator.getForwarder();
+        this.pairParams = operator.getParams();
+        this.responseClass = clazz;
+        this.timeoutMs = TimeUnit.MILLISECONDS.convert(pairParams.getTimeoutSec(), TimeUnit.SECONDS);
+    }
+
+    /**
+     * If no timeout is specified, then it returns the default timeout.
+     */
+    @Override
+    protected long getTimeoutMs(Integer timeoutSec) {
+        // TODO move this method to the superclass
+        return (timeoutSec == null || timeoutSec == 0 ? this.timeoutMs : super.getTimeoutMs(timeoutSec));
+    }
+
+    /**
+     * Publishes the request and arranges to receive the response.
+     */
+    @Override
+    protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+        final Q request = makeRequest(attempt);
+        final List<String> expectedKeyValues = getExpectedKeyValues(attempt, request);
+
+        final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+        final CompletableFuture<Triple<CommInfrastructure, String, StandardCoderObject>> future =
+                        new CompletableFuture<>();
+        final Executor executor = params.getExecutor();
+
+        // register a listener BEFORE publishing
+
+        // @formatter:off
+        TriConsumer<CommInfrastructure, String, StandardCoderObject> listener =
+            (infra, rawResponse, scoResponse) -> future.complete(Triple.of(infra, rawResponse, scoResponse));
+        // @formatter:on
+
+        // TODO this currently only allows a single matching response
+
+        forwarder.register(expectedKeyValues, listener);
+
+        // ensure listener is unregistered if the controller is canceled
+        controller.add(() -> forwarder.unregister(expectedKeyValues, listener));
+
+        // publish the request
+        try {
+            publishRequest(request);
+        } catch (RuntimeException e) {
+            logger.warn("{}: failed to publish request for {}", getFullName(), params.getRequestId());
+            forwarder.unregister(expectedKeyValues, listener);
+            throw e;
+        }
+
+
+        // once "future" completes, process the response, and then complete the controller
+
+        // @formatter:off
+        future.thenApplyAsync(
+            triple -> processResponse(triple.getLeft(), outcome, triple.getMiddle(), triple.getRight()),
+                            executor)
+                        .whenCompleteAsync(controller.delayedComplete(), executor);
+        // @formatter:on
+
+        return controller;
+    }
+
+    /**
+     * Makes the request.
+     *
+     * @param attempt operation attempt
+     * @return a new request
+     */
+    protected abstract Q makeRequest(int attempt);
+
+    /**
+     * Gets values, expected in the response, that should match the selector keys.
+     *
+     * @param attempt operation attempt
+     * @param request request to be published
+     * @return a list of the values to be matched by the selector keys
+     */
+    protected abstract List<String> getExpectedKeyValues(int attempt, Q request);
+
+    /**
+     * Publishes the request. Encodes the request, if it is not already a String.
+     *
+     * @param request request to be published
+     */
+    protected void publishRequest(Q request) {
+        String json;
+        try {
+            if (request instanceof String) {
+                json = request.toString();
+            } else {
+                json = makeCoder().encode(request);
+            }
+        } catch (CoderException e) {
+            throw new IllegalArgumentException("cannot encode request", e);
+        }
+
+        List<CommInfrastructure> list = topicPair.publish(json);
+        if (list.isEmpty()) {
+            throw new IllegalStateException("nothing published");
+        }
+
+        logTopicRequest(list, request);
+    }
+
+    /**
+     * Processes a response.
+     *
+     * @param infra communication infrastructure on which the response was received
+     * @param outcome outcome to be populated
+     * @param response raw response to process
+     * @param scoResponse response, as a {@link StandardCoderObject}
+     * @return the outcome
+     */
+    protected OperationOutcome processResponse(CommInfrastructure infra, OperationOutcome outcome, String rawResponse,
+                    StandardCoderObject scoResponse) {
+
+        logger.info("{}.{}: response received for {}", params.getActor(), params.getOperation(), params.getRequestId());
+
+        logTopicResponse(infra, rawResponse);
+
+        S response;
+        if (responseClass == String.class) {
+            response = responseClass.cast(rawResponse);
+
+        } else if (responseClass == StandardCoderObject.class) {
+            response = responseClass.cast(scoResponse);
+
+        } else {
+            try {
+                response = makeCoder().decode(rawResponse, responseClass);
+            } catch (CoderException e) {
+                logger.warn("{}.{} cannot decode response for {}", params.getActor(), params.getOperation(),
+                                params.getRequestId());
+                throw new IllegalArgumentException("cannot decode response", e);
+            }
+        }
+
+        if (!isSuccess(rawResponse, response)) {
+            logger.info("{}.{} request failed  for {}", params.getActor(), params.getOperation(),
+                            params.getRequestId());
+            return setOutcome(outcome, PolicyResult.FAILURE);
+        }
+
+        logger.info("{}.{} request succeeded for {}", params.getActor(), params.getOperation(), params.getRequestId());
+        setOutcome(outcome, PolicyResult.SUCCESS);
+        postProcessResponse(outcome, rawResponse, response);
+
+        return outcome;
+    }
+
+    /**
+     * Processes a successful response.
+     *
+     * @param outcome outcome to be populated
+     * @param rawResponse raw response
+     * @param response decoded response
+     */
+    protected void postProcessResponse(OperationOutcome outcome, String rawResponse, S response) {
+        // do nothing
+    }
+
+    /**
+     * Determines if the response indicates success.
+     *
+     * @param rawResponse raw response
+     * @param response decoded response
+     * @return {@code true} if the response indicates success, {@code false} otherwise
+     */
+    protected abstract boolean isSuccess(String rawResponse, S response);
+
+    /**
+     * Logs a TOPIC request. If the request is not of type, String, then it attempts to
+     * pretty-print it into JSON before logging.
+     *
+     * @param infrastructures list of communication infrastructures on which it was
+     *        published
+     * @param request request to be logged
+     */
+    protected void logTopicRequest(List<CommInfrastructure> infrastructures, Q request) {
+        if (infrastructures.isEmpty()) {
+            return;
+        }
+
+        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();
+        }
+
+        for (CommInfrastructure infra : infrastructures) {
+            logger.info("[OUT|{}|{}|]{}{}", infra, pairParams.getTarget(), NetLoggerUtil.SYSTEM_LS, json);
+        }
+    }
+
+    /**
+     * Logs a TOPIC response. If the response is not of type, String, then it attempts to
+     * pretty-print it into JSON before logging.
+     *
+     * @param infra communication infrastructure on which the response was received
+     * @param response response to be logged
+     */
+    protected <T> void logTopicResponse(CommInfrastructure infra, T 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();
+        }
+
+        logger.info("[IN|{}|{}|]{}{}", infra, pairParams.getSource(), NetLoggerUtil.SYSTEM_LS, json);
+    }
+
+    // these may be overridden by junit tests
+
+    protected Coder makeCoder() {
+        return coder;
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/TopicPairOperator.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/TopicPairOperator.java
new file mode 100644 (file)
index 0000000..8ce0133
--- /dev/null
@@ -0,0 +1,156 @@
+/*-
+ * ============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.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import lombok.Getter;
+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.ParameterValidationRuntimeException;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.TopicPairParams;
+import org.onap.policy.controlloop.actorserviceprovider.topic.Forwarder;
+import org.onap.policy.controlloop.actorserviceprovider.topic.SelectorKey;
+import org.onap.policy.controlloop.actorserviceprovider.topic.TopicPair;
+import org.onap.policy.controlloop.actorserviceprovider.topic.TopicPairManager;
+
+/**
+ * Operator that uses a pair of topics, one for publishing the request, and another for
+ * receiving the response. Topic operators may share a {@link TopicPair}.
+ */
+public abstract class TopicPairOperator extends OperatorPartial {
+
+    /**
+     * Manager from which to get the topic pair.
+     */
+    private final TopicPairManager pairManager;
+
+    /**
+     * Keys used to extract the fields used to select responses for this operator.
+     */
+    private final List<SelectorKey> selectorKeys;
+
+    /*
+     * The remaining fields are initialized when configure() is invoked, thus they may
+     * change.
+     */
+
+    /**
+     * Current parameters. While {@link params} may change, the values contained within it
+     * will not, thus operations may copy it.
+     */
+    @Getter
+    private TopicPairParams params;
+
+    /**
+     * Topic pair associated with the parameters.
+     */
+    @Getter
+    private TopicPair topicPair;
+
+    /**
+     * Forwarder associated with the parameters.
+     */
+    @Getter
+    private Forwarder forwarder;
+
+
+    /**
+     * Constructs the object.
+     *
+     * @param actorName name of the actor with which this operator is associated
+     * @param name operation name
+     * @param pairManager manager from which to get the topic pair
+     * @param selectorKeys keys used to extract the fields used to select responses for
+     *        this operator
+     */
+    public TopicPairOperator(String actorName, String name, TopicPairManager pairManager,
+                    List<SelectorKey> selectorKeys) {
+        super(actorName, name);
+        this.pairManager = pairManager;
+        this.selectorKeys = selectorKeys;
+    }
+
+    @Override
+    protected void doConfigure(Map<String, Object> parameters) {
+        params = Util.translate(getFullName(), parameters, TopicPairParams.class);
+        ValidationResult result = params.validate(getFullName());
+        if (!result.isValid()) {
+            throw new ParameterValidationRuntimeException("invalid parameters", result);
+        }
+
+        topicPair = pairManager.getTopicPair(params.getSource(), params.getTarget());
+        forwarder = topicPair.addForwarder(selectorKeys);
+    }
+
+    /**
+     * Makes an operator that will construct operations.
+     *
+     * @param <Q> request type
+     * @param <S> response type
+     * @param actorName actor name
+     * @param operation operation name
+     * @param pairManager manager from which to get the topic pair
+     * @param operationMaker function to make an operation
+     * @param keys keys used to extract the fields used to select responses for this
+     *        operator
+     * @return a new operator
+     */
+    // @formatter:off
+    public static <Q,S> TopicPairOperator makeOperator(String actorName, String operation, TopicPairManager pairManager,
+                    BiFunction<ControlLoopOperationParams, TopicPairOperator, TopicPairOperation<Q,S>> operationMaker,
+                    SelectorKey... keys) {
+        // @formatter:off
+
+        return makeOperator(actorName, operation, pairManager, Arrays.asList(keys), operationMaker);
+    }
+
+    /**
+     * Makes an operator that will construct operations.
+     *
+     * @param <Q> request type
+     * @param <S> response type
+     * @param actorName actor name
+     * @param operation operation name
+     * @param pairManager manager from which to get the topic pair
+     * @param keys keys used to extract the fields used to select responses for
+     *        this operator
+     * @param operationMaker function to make an operation
+     * @return a new operator
+     */
+    // @formatter:off
+    public static <Q,S> TopicPairOperator makeOperator(String actorName, String operation, TopicPairManager pairManager,
+                    List<SelectorKey> keys,
+                    BiFunction<ControlLoopOperationParams, TopicPairOperator, TopicPairOperation<Q,S>> operationMaker) {
+        // @formatter:on
+
+        return new TopicPairOperator(actorName, operation, pairManager, keys) {
+            @Override
+            public synchronized Operation buildOperation(ControlLoopOperationParams params) {
+                return operationMaker.apply(params, this);
+            }
+        };
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicPairActorParams.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicPairActorParams.java
new file mode 100644 (file)
index 0000000..42a44ee
--- /dev/null
@@ -0,0 +1,93 @@
+/*-
+ * ============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.parameters;
+
+import java.util.Map;
+import lombok.Builder;
+import lombok.Data;
+import org.onap.policy.common.parameters.BeanValidationResult;
+import org.onap.policy.common.parameters.BeanValidator;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.common.parameters.annotations.NotBlank;
+import org.onap.policy.common.parameters.annotations.NotNull;
+
+/**
+ * Parameters used by Actors whose Operators use a pair of Topics, one to publish requests
+ * and the other to receive responses.
+ */
+@NotNull
+@NotBlank
+@Data
+@Builder
+public class TopicPairActorParams {
+
+    /**
+     * This contains the default parameters that are used when an operation doesn't
+     * specify them. Note: each operation to be used must still have an entry in
+     * {@link #operation}, even if it's empty. Otherwise, the given operation will not be
+     * started.
+     */
+    private TopicPairParams defaults;
+
+    /**
+     * Maps an operation name to its individual parameters.
+     */
+    private Map<String, Map<String, Object>> operation;
+
+
+    /**
+     * Validates the parameters.
+     *
+     * @param name name of the object containing these parameters
+     * @return "this"
+     * @throws IllegalArgumentException if the parameters are invalid
+     */
+    public TopicPairActorParams doValidation(String name) {
+        ValidationResult result = validate(name);
+        if (!result.isValid()) {
+            throw new ParameterValidationRuntimeException("invalid parameters", result);
+        }
+
+        return this;
+    }
+
+    /**
+     * Validates the parameters.
+     *
+     * @param resultName name of the result
+     *
+     * @return the validation result
+     */
+    public ValidationResult validate(String resultName) {
+        BeanValidationResult result = new BeanValidator().validateTop(resultName, this);
+
+        if (defaults != null) {
+            result.addResult(defaults.validate("defaults"));
+        }
+
+        // @formatter:off
+        result.validateMap("operation", operation,
+            (result2, entry) -> result2.validateNotNull(entry.getKey(), entry.getValue()));
+        // @formatter:on
+
+        return result;
+    }
+}
@@ -29,34 +29,34 @@ import org.onap.policy.common.parameters.annotations.NotBlank;
 import org.onap.policy.common.parameters.annotations.NotNull;
 
 /**
- * Parameters used by Operators that connect to a server via DMaaP.
+ * Parameters used by Operators that use a pair of Topics, one to publish requests and the
+ * other to receive responses.
  */
 @NotNull
 @NotBlank
 @Data
 @Builder(toBuilder = true)
-public class TopicParams {
+public class TopicPairParams {
 
     /**
-     * Name of the target topic end point to which requests should be published.
+     * Source topic end point, from which to read responses.
      */
-    private String target;
+    private String source;
 
     /**
-     * Source topic end point, from which to read responses.
+     * Name of the target topic end point to which requests should be published.
      */
-    private String source;
+    private String target;
 
     /**
-     * Amount of time, in seconds to wait for the response, where zero indicates that it
-     * should wait forever. The default is zero.
+     * Amount of time, in seconds to wait for the response. The default is five minutes.
      */
-    @Min(0)
+    @Min(1)
     @Builder.Default
-    private long timeoutSec = 0;
+    private int timeoutSec = 300;
 
     /**
-     * Validates both the publisher and the subscriber parameters.
+     * Validates the parameters.
      *
      * @param resultName name of the result
      *
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/Forwarder.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/Forwarder.java
new file mode 100644 (file)
index 0000000..8e9109c
--- /dev/null
@@ -0,0 +1,141 @@
+/*-
+ * ============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.topic;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.PropertyUtils.TriConsumer;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Forwarder that selectively forwards message to listeners based on the content of the
+ * message. Each forwarder is associated with a single set of selector keys. Listeners are
+ * then registered with that forwarder for a particular set of values for the given keys.
+ */
+public class Forwarder {
+    private static final Logger logger = LoggerFactory.getLogger(Forwarder.class);
+
+    /**
+     * Maps a set of field values to one or more listeners.
+     */
+    // @formatter:off
+    private final Map<List<String>, Map<TriConsumer<CommInfrastructure, String, StandardCoderObject>, String>>
+                values2listeners = new ConcurrentHashMap<>();
+    // @formatter:on
+
+    /**
+     * Keys used to extract the field values from the {@link StandardCoderObject}.
+     */
+    private final List<SelectorKey> keys;
+
+    /**
+     * Constructs the object.
+     *
+     * @param keys keys used to extract the field's value from the
+     *        {@link StandardCoderObject}
+     */
+    public Forwarder(List<SelectorKey> keys) {
+        this.keys = keys;
+    }
+
+    /**
+     * Registers a listener for messages containing the given field values.
+     *
+     * @param values field values of interest, in one-to-one correspondence with the keys
+     * @param listener listener to register
+     */
+    public void register(List<String> values, TriConsumer<CommInfrastructure, String, StandardCoderObject> listener) {
+        if (keys.size() != values.size()) {
+            throw new IllegalArgumentException("key/value mismatch");
+        }
+
+        values2listeners.compute(values, (key, listeners) -> {
+            Map<TriConsumer<CommInfrastructure, String, StandardCoderObject>, String> map = listeners;
+            if (map == null) {
+                map = new ConcurrentHashMap<>();
+            }
+
+            map.put(listener, "");
+            return map;
+        });
+    }
+
+    /**
+     * Unregisters a listener for messages containing the given field values.
+     *
+     * @param values field values of interest, in one-to-one correspondence with the keys
+     * @param listener listener to unregister
+     */
+    public void unregister(List<String> values, TriConsumer<CommInfrastructure, String, StandardCoderObject> listener) {
+        values2listeners.computeIfPresent(values, (key, listeners) -> {
+            listeners.remove(listener);
+            return (listeners.isEmpty() ? null : listeners);
+        });
+    }
+
+    /**
+     * Processes a message, forwarding it to the appropriate listeners, if any.
+     *
+     * @param infra communication infrastructure on which the response was received
+     * @param textMessage original text message that was received
+     * @param scoMessage decoded text message
+     */
+    public void onMessage(CommInfrastructure infra, String textMessage, StandardCoderObject scoMessage) {
+        // extract the key values from the message
+        List<String> values = new ArrayList<>(keys.size());
+        for (SelectorKey key : keys) {
+            String value = key.extractField(scoMessage);
+            if (value == null) {
+                /*
+                 * No value for this field, so this message is not relevant to this
+                 * forwarder.
+                 */
+                return;
+            }
+
+            values.add(value);
+        }
+
+        // get the listeners for this set of values
+        Map<TriConsumer<CommInfrastructure, String, StandardCoderObject>, String> listeners =
+                        values2listeners.get(values);
+        if (listeners == null) {
+            // no listeners for this particular list of values
+            return;
+        }
+
+
+        // forward the message to each listener
+        for (TriConsumer<CommInfrastructure, String, StandardCoderObject> listener : listeners.keySet()) {
+            try {
+                listener.accept(infra, textMessage, scoMessage);
+            } catch (RuntimeException e) {
+                logger.warn("exception thrown by listener {}", Util.ident(listener), e);
+            }
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/SelectorKey.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/SelectorKey.java
new file mode 100644 (file)
index 0000000..fc57273
--- /dev/null
@@ -0,0 +1,57 @@
+/*-
+ * ============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.topic;
+
+import lombok.EqualsAndHashCode;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+
+/**
+ * Selector key, which contains a hierarchical list of Strings and Integers that are used
+ * to extract the content of a field, typically from a {@link StandardCoderObject}.
+ */
+@EqualsAndHashCode
+public class SelectorKey {
+
+    /**
+     * Names and indices used to extract the field's value.
+     */
+    private final Object[] fieldIdentifiers;
+
+    /**
+     * Constructs the object.
+     *
+     * @param fieldIdentifiers names and indices used to extract the field's value
+     */
+    public SelectorKey(Object... fieldIdentifiers) {
+        this.fieldIdentifiers = fieldIdentifiers;
+    }
+
+    /**
+     * Extracts the given field from an object.
+     *
+     * @param object object from which to extract the field
+     * @return the extracted value, or {@code null} if the object does not contain the
+     *         field
+     */
+    public String extractField(StandardCoderObject object) {
+        return object.getString(fieldIdentifiers);
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicListenerImpl.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicListenerImpl.java
new file mode 100644 (file)
index 0000000..eb805ca
--- /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.actorserviceprovider.topic;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.event.comm.TopicListener;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A topic listener. When a message arrives on a topic, it is forwarded to listeners based
+ * on the content of fields found within the message. However, depending on the message
+ * type, the relevant fields might be found in different places within the message's
+ * object hierarchy. For each different list of keys, this class maintains a
+ * {@link Forwarder}, which is used to forward the message to all relevant listeners.
+ * <p/>
+ * Once a selector has been added, it is not removed until {@link #shutdown()} is invoked.
+ * As selectors are typically only added by Operators, and not by individual Operations,
+ * this should not pose a problem.
+ */
+public class TopicListenerImpl implements TopicListener {
+    private static final Logger logger = LoggerFactory.getLogger(TopicListenerImpl.class);
+    private static StandardCoder coder = new StandardCoder();
+
+    /**
+     * Maps selector to a forwarder.
+     */
+    private final Map<List<SelectorKey>, Forwarder> selector2forwarder = new ConcurrentHashMap<>();
+
+
+    /**
+     * Removes all forwarders.
+     */
+    public void shutdown() {
+        selector2forwarder.clear();
+    }
+
+    /**
+     * Adds a forwarder, if it doesn't already exist.
+     *
+     * @param keys the selector keys
+     * @return the forwarder associated with the given selector keys
+     */
+    public Forwarder addForwarder(SelectorKey... keys) {
+        return addForwarder(Arrays.asList(keys));
+    }
+
+    /**
+     * Adds a forwarder, if it doesn't already exist.
+     *
+     * @param keys the selector keys
+     * @return the forwarder associated with the given selector keys
+     */
+    public Forwarder addForwarder(List<SelectorKey> keys) {
+        return selector2forwarder.computeIfAbsent(keys, key -> new Forwarder(keys));
+    }
+
+    /**
+     * Decodes the message and then forwards it to each forwarder for processing.
+     */
+    @Override
+    public void onTopicEvent(CommInfrastructure infra, String topic, String message) {
+        StandardCoderObject object;
+        try {
+            object = coder.decode(message, StandardCoderObject.class);
+        } catch (CoderException e) {
+            logger.warn("cannot decode message", e);
+            return;
+        }
+
+        /*
+         * We don't know which selector is appropriate for the message, so we just let
+         * them all take a crack at it.
+         */
+        for (Forwarder forwarder : selector2forwarder.values()) {
+            forwarder.onMessage(infra, message, object);
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicPair.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicPair.java
new file mode 100644 (file)
index 0000000..c0cfe25
--- /dev/null
@@ -0,0 +1,122 @@
+/*-
+ * ============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.topic;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import lombok.Getter;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.event.comm.TopicEndpoint;
+import org.onap.policy.common.endpoints.event.comm.TopicEndpointManager;
+import org.onap.policy.common.endpoints.event.comm.TopicSink;
+import org.onap.policy.common.endpoints.event.comm.TopicSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A pair of topics, one of which is used to publish requests and the other to receive
+ * responses.
+ */
+public class TopicPair extends TopicListenerImpl {
+    private static final Logger logger = LoggerFactory.getLogger(TopicPair.class);
+
+    @Getter
+    private final String source;
+
+    @Getter
+    private final String target;
+
+    private final List<TopicSink> publishers;
+    private final List<TopicSource> subscribers;
+
+    /**
+     * Constructs the object.
+     *
+     * @param source source topic name
+     * @param target target topic name
+     */
+    public TopicPair(String source, String target) {
+        this.source = source;
+        this.target = target;
+
+        publishers = getTopicEndpointManager().getTopicSinks(target);
+        if (publishers.isEmpty()) {
+            throw new IllegalArgumentException("no sinks for topic: " + target);
+        }
+
+        subscribers = getTopicEndpointManager().getTopicSources(Arrays.asList(source));
+        if (subscribers.isEmpty()) {
+            throw new IllegalArgumentException("no sources for topic: " + source);
+        }
+    }
+
+    /**
+     * Starts listening on the source topic(s).
+     */
+    public void start() {
+        subscribers.forEach(topic -> topic.register(this));
+    }
+
+    /**
+     * Stops listening on the source topic(s).
+     */
+    public void stop() {
+        subscribers.forEach(topic -> topic.unregister(this));
+    }
+
+    /**
+     * Stops listening on the source topic(s) and clears all of the forwarders.
+     */
+    @Override
+    public void shutdown() {
+        stop();
+        super.shutdown();
+    }
+
+    /**
+     * Publishes a message to the target topic.
+     *
+     * @param message message to be published
+     * @return a list of the infrastructures on which it was published
+     */
+    public List<CommInfrastructure> publish(String message) {
+        List<CommInfrastructure> infrastructures = new ArrayList<>(publishers.size());
+
+        for (TopicSink topic : publishers) {
+            try {
+                topic.send(message);
+                infrastructures.add(topic.getTopicCommInfrastructure());
+
+            } catch (RuntimeException e) {
+                logger.warn("cannot publish to {}:{}", topic.getTopicCommInfrastructure(), target, e);
+            }
+        }
+
+        return infrastructures;
+    }
+
+    // these may be overridden by junit tests
+
+    protected TopicEndpoint getTopicEndpointManager() {
+        return TopicEndpointManager.getManager();
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicPairManager.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicPairManager.java
new file mode 100644 (file)
index 0000000..c351f95
--- /dev/null
@@ -0,0 +1,37 @@
+/*-
+ * ============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.topic;
+
+/**
+ * Manages topic pairs.
+ */
+@FunctionalInterface
+public interface TopicPairManager {
+
+    /**
+     * Gets the topic pair for the given parameters, creating one if it does not exist.
+     *
+     * @param source source topic name
+     * @param target target topic name
+     * @return the topic pair associated with the given source and target topics
+     */
+    TopicPair getTopicPair(String source, String target);
+}
index 19f781d..39d6fd4 100644 (file)
@@ -22,6 +22,7 @@ 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.assertThatIllegalArgumentException;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -359,8 +360,7 @@ public class HttpOperationTest {
     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());
+        assertThatIllegalArgumentException().isThrownBy(() -> oper2.processResponse(outcome, PATH, response));
     }
 
     @Test
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/MyExec.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/MyExec.java
new file mode 100644 (file)
index 0000000..6515eb3
--- /dev/null
@@ -0,0 +1,65 @@
+/*-
+ * ============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.LinkedList;
+import java.util.Queue;
+import java.util.concurrent.Executor;
+
+/**
+ * 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.
+ */
+public class MyExec implements Executor {
+
+    // TODO move this to policy-common/utils-test
+
+    private final int maxTasks;
+    private final Queue<Runnable> commands = new LinkedList<>();
+
+    public MyExec(int maxTasks) {
+        this.maxTasks = maxTasks;
+    }
+
+    public int getQueueLength() {
+        return commands.size();
+    }
+
+    @Override
+    public void execute(Runnable command) {
+        commands.add(command);
+    }
+
+    /**
+     * Runs all tasks until the queue is empty or the maximum number of tasks have been
+     * reached.
+     *
+     * @return {@code true} if the queue is empty, {@code false} if the maximum number of
+     *         tasks have been reached before the queue was completed
+     */
+    public boolean runAll() {
+        for (int count = 0; count < maxTasks && !commands.isEmpty(); ++count) {
+            commands.remove().run();
+        }
+
+        return commands.isEmpty();
+    }
+}
index 0d5cb24..f28c1f6 100644 (file)
@@ -36,7 +36,6 @@ 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;
@@ -105,7 +104,7 @@ public class OperationPartialTest {
         event.setRequestId(REQ_ID);
 
         context = new ControlLoopEventContext(event);
-        executor = new MyExec();
+        executor = new MyExec(100 * MAX_PARALLEL_REQUESTS);
 
         params = ControlLoopOperationParams.builder().completeCallback(this::completer).context(context)
                         .executor(executor).actor(ACTOR).operation(OPERATION).timeoutSec(TIMEOUT)
@@ -1267,36 +1266,4 @@ public class OperationPartialTest {
             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();
-        }
-    }
 }
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/TopicPairOperationTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/TopicPairOperationTest.java
new file mode 100644 (file)
index 0000000..4e45b1a
--- /dev/null
@@ -0,0 +1,503 @@
+/*-
+ * ============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.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.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import ch.qos.logback.classic.Logger;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+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.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.PropertyUtils.TriConsumer;
+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.coder.StandardCoderObject;
+import org.onap.policy.common.utils.test.log.logback.ExtractAppender;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.TopicPairParams;
+import org.onap.policy.controlloop.actorserviceprovider.topic.Forwarder;
+import org.onap.policy.controlloop.actorserviceprovider.topic.TopicPair;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.slf4j.LoggerFactory;
+
+public class TopicPairOperationTest {
+    private static final List<CommInfrastructure> INFRA_LIST =
+                    Arrays.asList(CommInfrastructure.NOOP, CommInfrastructure.UEB);
+    private static final IllegalStateException EXPECTED_EXCEPTION = new IllegalStateException("expected exception");
+    private static final String ACTOR = "my-actor";
+    private static final String OPERATION = "my-operation";
+    private static final String REQ_ID = "my-request-id";
+    private static final String MY_SOURCE = "my-source";
+    private static final String MY_TARGET = "my-target";
+    private static final String TEXT = "some text";
+    private static final int TIMEOUT_SEC = 10;
+    private static final long TIMEOUT_MS = 1000 * TIMEOUT_SEC;
+
+    private static final StandardCoder coder = new StandardCoder();
+
+    /**
+     * Used to attach an appender to the class' logger.
+     */
+    private static final Logger logger = (Logger) LoggerFactory.getLogger(TopicPairOperation.class);
+    private static final ExtractAppender appender = new ExtractAppender();
+
+    @Mock
+    private TopicPairOperator operator;
+    @Mock
+    private TopicPair pair;
+    @Mock
+    private Forwarder forwarder;
+
+    @Captor
+    private ArgumentCaptor<TriConsumer<CommInfrastructure, String, StandardCoderObject>> listenerCaptor;
+
+    private ControlLoopOperationParams params;
+    private TopicPairParams topicParams;
+    private OperationOutcome outcome;
+    private StandardCoderObject stdResponse;
+    private String responseText;
+    private MyExec executor;
+    private TopicPairOperation<MyRequest, MyResponse> oper;
+
+    /**
+     * Attaches the appender to the logger.
+     */
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+        /**
+         * Attach appender to the logger.
+         */
+        appender.setContext(logger.getLoggerContext());
+        appender.start();
+
+        logger.addAppender(appender);
+    }
+
+    /**
+     * Stops the appender.
+     */
+    @AfterClass
+    public static void tearDownAfterClass() {
+        appender.stop();
+    }
+
+    /**
+     * Sets up.
+     */
+    @Before
+    public void setUp() throws CoderException {
+        MockitoAnnotations.initMocks(this);
+
+        appender.clearExtractions();
+
+        topicParams = TopicPairParams.builder().source(MY_SOURCE).target(MY_TARGET).timeoutSec(TIMEOUT_SEC).build();
+
+        when(operator.getActorName()).thenReturn(ACTOR);
+        when(operator.getName()).thenReturn(OPERATION);
+        when(operator.getTopicPair()).thenReturn(pair);
+        when(operator.getForwarder()).thenReturn(forwarder);
+        when(operator.getParams()).thenReturn(topicParams);
+        when(operator.isAlive()).thenReturn(true);
+
+        when(pair.publish(any())).thenReturn(INFRA_LIST);
+
+        executor = new MyExec(100);
+
+        params = ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).executor(executor).build();
+        outcome = params.makeOutcome();
+
+        responseText = coder.encode(new MyResponse());
+        stdResponse = coder.decode(responseText, StandardCoderObject.class);
+
+        oper = new MyOperation();
+    }
+
+    @Test
+    public void testTopicPairOperation_testGetTopicPair_testGetForwarder_testGetPairParams() {
+        assertEquals(ACTOR, oper.getActorName());
+        assertEquals(OPERATION, oper.getName());
+        assertSame(pair, oper.getTopicPair());
+        assertSame(forwarder, oper.getForwarder());
+        assertSame(topicParams, oper.getPairParams());
+        assertEquals(TIMEOUT_MS, oper.getTimeoutMs());
+        assertSame(MyResponse.class, oper.getResponseClass());
+    }
+
+    @Test
+    public void testStartOperationAsync() throws Exception {
+        CompletableFuture<OperationOutcome> future = oper.startOperationAsync(1, outcome);
+        assertFalse(future.isDone());
+
+        verify(forwarder).register(eq(Arrays.asList(REQ_ID)), listenerCaptor.capture());
+
+        verify(forwarder, never()).unregister(any(), any());
+
+        verify(pair).publish(any());
+
+        // provide the response
+        listenerCaptor.getValue().accept(CommInfrastructure.NOOP, responseText, stdResponse);
+
+        // run the tasks
+        assertTrue(executor.runAll());
+
+        assertTrue(future.isDone());
+
+        assertSame(outcome, future.get(5, TimeUnit.SECONDS));
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+
+        verify(forwarder).unregister(eq(Arrays.asList(REQ_ID)), eq(listenerCaptor.getValue()));
+    }
+
+    /**
+     * Tests startOperationAsync() when the publisher throws an exception.
+     */
+    @Test
+    public void testStartOperationAsyncException() throws Exception {
+        // indicate that nothing was published
+        when(pair.publish(any())).thenReturn(Arrays.asList());
+
+        assertThatIllegalStateException().isThrownBy(() -> oper.startOperationAsync(1, outcome));
+
+        verify(forwarder).register(eq(Arrays.asList(REQ_ID)), listenerCaptor.capture());
+
+        // must still unregister
+        verify(forwarder).unregister(eq(Arrays.asList(REQ_ID)), eq(listenerCaptor.getValue()));
+    }
+
+    @Test
+    public void testGetTimeoutMsInteger() {
+        // use default
+        assertEquals(TIMEOUT_MS, oper.getTimeoutMs(null));
+        assertEquals(TIMEOUT_MS, oper.getTimeoutMs(0));
+
+        // use provided value
+        assertEquals(5000, oper.getTimeoutMs(5));
+    }
+
+    @Test
+    public void testPublishRequest() {
+        oper.publishRequest(new MyRequest());
+        assertEquals(INFRA_LIST.size(), appender.getExtracted().size());
+    }
+
+    /**
+     * Tests publishRequest() when nothing is published.
+     */
+    @Test
+    public void testPublishRequestUnpublished() {
+        when(pair.publish(any())).thenReturn(Arrays.asList());
+        assertThatIllegalStateException().isThrownBy(() -> oper.publishRequest(new MyRequest()));
+    }
+
+    /**
+     * Tests publishRequest() when the request type is a String.
+     */
+    @Test
+    public void testPublishRequestString() {
+        MyStringOperation oper2 = new MyStringOperation();
+        oper2.publishRequest(TEXT);
+        assertEquals(INFRA_LIST.size(), appender.getExtracted().size());
+    }
+
+    /**
+     * Tests publishRequest() when the coder throws an exception.
+     */
+    @Test
+    public void testPublishRequestException() {
+        setOperCoderException();
+        assertThatIllegalArgumentException().isThrownBy(() -> oper.publishRequest(new MyRequest()));
+    }
+
+    /**
+     * Tests processResponse() when it's a success and the response type is a String.
+     */
+    @Test
+    public void testProcessResponseSuccessString() {
+        MyStringOperation oper2 = new MyStringOperation();
+
+        assertSame(outcome, oper2.processResponse(CommInfrastructure.NOOP, outcome, TEXT, null));
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    /**
+     * Tests processResponse() when it's a success and the response type is a
+     * StandardCoderObject.
+     */
+    @Test
+    public void testProcessResponseSuccessSco() {
+        MyScoOperation oper2 = new MyScoOperation();
+
+        assertSame(outcome, oper2.processResponse(CommInfrastructure.NOOP, outcome, responseText, stdResponse));
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    /**
+     * Tests processResponse() when it's a failure.
+     */
+    @Test
+    public void testProcessResponseFailure() throws CoderException {
+        // indicate error in the response
+        MyResponse resp = new MyResponse();
+        resp.setOutput("error");
+
+        responseText = coder.encode(resp);
+        stdResponse = coder.decode(responseText, StandardCoderObject.class);
+
+        assertSame(outcome, oper.processResponse(CommInfrastructure.NOOP, outcome, responseText, stdResponse));
+        assertEquals(PolicyResult.FAILURE, outcome.getResult());
+    }
+
+    /**
+     * Tests processResponse() when the decoder succeeds.
+     */
+    @Test
+    public void testProcessResponseDecodeOk() throws CoderException {
+        assertSame(outcome, oper.processResponse(CommInfrastructure.NOOP, outcome, responseText, stdResponse));
+        assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+    }
+
+    /**
+     * Tests processResponse() when the decoder throws an exception.
+     */
+    @Test
+    public void testProcessResponseDecodeExcept() throws CoderException {
+        // @formatter:off
+        assertThatIllegalArgumentException().isThrownBy(
+            () -> oper.processResponse(CommInfrastructure.NOOP, outcome, "{invalid json", stdResponse));
+        // @formatter:on
+    }
+
+    @Test
+    public void testPostProcessResponse() {
+        assertThatCode(() -> oper.postProcessResponse(outcome, null, null)).doesNotThrowAnyException();
+    }
+
+    @Test
+    public void testLogTopicRequest() {
+        // nothing to log
+        appender.clearExtractions();
+        oper.logTopicRequest(Arrays.asList(), new MyRequest());
+        assertEquals(0, appender.getExtracted().size());
+
+        // log structured data
+        appender.clearExtractions();
+        oper.logTopicRequest(INFRA_LIST, new MyRequest());
+        List<String> output = appender.getExtracted();
+        assertEquals(2, output.size());
+
+        assertThat(output.get(0)).contains(CommInfrastructure.NOOP.toString())
+                        .contains("{\n  \"theRequestId\": \"my-request-id\"\n}");
+
+        assertThat(output.get(1)).contains(CommInfrastructure.UEB.toString())
+                        .contains("{\n  \"theRequestId\": \"my-request-id\"\n}");
+
+        // log a plain string
+        appender.clearExtractions();
+        new MyStringOperation().logTopicRequest(Arrays.asList(CommInfrastructure.NOOP), TEXT);
+        output = appender.getExtracted();
+        assertEquals(1, output.size());
+        assertThat(output.get(0)).contains(CommInfrastructure.NOOP.toString()).contains(TEXT);
+
+        // log a null request
+        appender.clearExtractions();
+        oper.logTopicRequest(Arrays.asList(CommInfrastructure.NOOP), null);
+        output = appender.getExtracted();
+        assertEquals(1, output.size());
+
+        assertThat(output.get(0)).contains(CommInfrastructure.NOOP.toString()).contains("null");
+
+        // exception from coder
+        setOperCoderException();
+
+        appender.clearExtractions();
+        oper.logTopicRequest(Arrays.asList(CommInfrastructure.NOOP), new MyRequest());
+        output = appender.getExtracted();
+        assertEquals(2, output.size());
+        assertThat(output.get(0)).contains("cannot pretty-print request");
+        assertThat(output.get(1)).contains(CommInfrastructure.NOOP.toString());
+    }
+
+    @Test
+    public void testLogTopicResponse() {
+        // log structured data
+        appender.clearExtractions();
+        oper.logTopicResponse(CommInfrastructure.NOOP, new MyResponse());
+        List<String> output = appender.getExtracted();
+        assertEquals(1, output.size());
+
+        assertThat(output.get(0)).contains(CommInfrastructure.NOOP.toString())
+                        .contains("{\n  \"requestId\": \"my-request-id\"\n}");
+
+        // log a plain string
+        appender.clearExtractions();
+        new MyStringOperation().logTopicResponse(CommInfrastructure.NOOP, TEXT);
+        output = appender.getExtracted();
+        assertEquals(1, output.size());
+        assertThat(output.get(0)).contains(CommInfrastructure.NOOP.toString()).contains(TEXT);
+
+        // log a null response
+        appender.clearExtractions();
+        oper.logTopicResponse(CommInfrastructure.NOOP, null);
+        output = appender.getExtracted();
+        assertEquals(1, output.size());
+
+        assertThat(output.get(0)).contains(CommInfrastructure.NOOP.toString()).contains("null");
+
+        // exception from coder
+        setOperCoderException();
+
+        appender.clearExtractions();
+        oper.logTopicResponse(CommInfrastructure.NOOP, new MyResponse());
+        output = appender.getExtracted();
+        assertEquals(2, output.size());
+        assertThat(output.get(0)).contains("cannot pretty-print response");
+        assertThat(output.get(1)).contains(CommInfrastructure.NOOP.toString());
+    }
+
+    @Test
+    public void testMakeCoder() {
+        assertNotNull(oper.makeCoder());
+    }
+
+    /**
+     * Creates a new {@link #oper} whose coder will throw an exception.
+     */
+    private void setOperCoderException() {
+        oper = new MyOperation() {
+            @Override
+            protected Coder makeCoder() {
+                return new StandardCoder() {
+                    @Override
+                    public String encode(Object object, boolean pretty) throws CoderException {
+                        throw new CoderException(EXPECTED_EXCEPTION);
+                    }
+                };
+            }
+        };
+    }
+
+    @Getter
+    @Setter
+    public static class MyRequest {
+        private String theRequestId = REQ_ID;
+        private String input;
+    }
+
+    @Getter
+    @Setter
+    public static class MyResponse {
+        private String requestId = REQ_ID;
+        private String output;
+    }
+
+
+    private class MyStringOperation extends TopicPairOperation<String, String> {
+        public MyStringOperation() {
+            super(TopicPairOperationTest.this.params, operator, String.class);
+        }
+
+        @Override
+        protected String makeRequest(int attempt) {
+            return TEXT;
+        }
+
+        @Override
+        protected List<String> getExpectedKeyValues(int attempt, String request) {
+            return Arrays.asList(REQ_ID);
+        }
+
+        @Override
+        protected boolean isSuccess(String rawResponse, String response) {
+            return (response != null);
+        }
+    }
+
+
+    private class MyScoOperation extends TopicPairOperation<MyRequest, StandardCoderObject> {
+        public MyScoOperation() {
+            super(TopicPairOperationTest.this.params, operator, StandardCoderObject.class);
+        }
+
+        @Override
+        protected MyRequest makeRequest(int attempt) {
+            return new MyRequest();
+        }
+
+        @Override
+        protected List<String> getExpectedKeyValues(int attempt, MyRequest request) {
+            return Arrays.asList(REQ_ID);
+        }
+
+        @Override
+        protected boolean isSuccess(String rawResponse, StandardCoderObject response) {
+            return (response.getString("output") == null);
+        }
+    }
+
+
+    private class MyOperation extends TopicPairOperation<MyRequest, MyResponse> {
+        public MyOperation() {
+            super(TopicPairOperationTest.this.params, operator, MyResponse.class);
+        }
+
+        @Override
+        protected MyRequest makeRequest(int attempt) {
+            return new MyRequest();
+        }
+
+        @Override
+        protected List<String> getExpectedKeyValues(int attempt, MyRequest request) {
+            return Arrays.asList(REQ_ID);
+        }
+
+        @Override
+        protected boolean isSuccess(String rawResponse, MyResponse response) {
+            return (response.getOutput() == null);
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/TopicPairOperatorTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/TopicPairOperatorTest.java
new file mode 100644 (file)
index 0000000..dd25902
--- /dev/null
@@ -0,0 +1,140 @@
+/*-
+ * ============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.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiFunction;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+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.ParameterValidationRuntimeException;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.TopicPairParams;
+import org.onap.policy.controlloop.actorserviceprovider.topic.Forwarder;
+import org.onap.policy.controlloop.actorserviceprovider.topic.SelectorKey;
+import org.onap.policy.controlloop.actorserviceprovider.topic.TopicPair;
+import org.onap.policy.controlloop.actorserviceprovider.topic.TopicPairManager;
+
+public class TopicPairOperatorTest {
+    private static final String ACTOR = "my-actor";
+    private static final String OPERATION = "my-operation";
+    private static final String MY_SOURCE = "my-source";
+    private static final String MY_TARGET = "my-target";
+    private static final int TIMEOUT_SEC = 10;
+
+    @Mock
+    private TopicPairManager mgr;
+    @Mock
+    private TopicPair pair;
+    @Mock
+    private Forwarder forwarder;
+    @Mock
+    private TopicPairOperation<String, Integer> operation;
+
+    private List<SelectorKey> keys;
+    private TopicPairParams params;
+    private MyOperator oper;
+
+    /**
+     * Sets up.
+     */
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        keys = List.of(new SelectorKey(""));
+
+        when(mgr.getTopicPair(MY_SOURCE, MY_TARGET)).thenReturn(pair);
+        when(pair.addForwarder(keys)).thenReturn(forwarder);
+
+        oper = new MyOperator(keys);
+
+        params = TopicPairParams.builder().source(MY_SOURCE).target(MY_TARGET).timeoutSec(TIMEOUT_SEC).build();
+        oper.configure(Util.translateToMap(OPERATION, params));
+        oper.start();
+    }
+
+    @Test
+    public void testTopicPairOperator_testGetParams_testGetTopicPair_testGetForwarder() {
+        assertEquals(ACTOR, oper.getActorName());
+        assertEquals(OPERATION, oper.getName());
+        assertEquals(params, oper.getParams());
+        assertSame(pair, oper.getTopicPair());
+        assertSame(forwarder, oper.getForwarder());
+    }
+
+    @Test
+    public void testDoConfigure() {
+        oper.stop();
+
+        // invalid parameters
+        params.setSource(null);
+        assertThatThrownBy(() -> oper.configure(Util.translateToMap(OPERATION, params)))
+                        .isInstanceOf(ParameterValidationRuntimeException.class);
+    }
+
+    @Test
+    public void testMakeOperator() {
+        AtomicReference<ControlLoopOperationParams> paramsRef = new AtomicReference<>();
+        AtomicReference<TopicPairOperator> operRef = new AtomicReference<>();
+
+        // @formatter:off
+        BiFunction<ControlLoopOperationParams, TopicPairOperator, TopicPairOperation<String, Integer>> maker =
+            (params, operator) -> {
+                paramsRef.set(params);
+                operRef.set(operator);
+                return operation;
+            };
+        // @formatter:on
+
+        TopicPairOperator oper2 = TopicPairOperator.makeOperator(ACTOR, OPERATION, mgr, maker, new SelectorKey(""));
+
+        assertEquals(ACTOR, oper2.getActorName());
+        assertEquals(OPERATION, oper2.getName());
+
+        ControlLoopOperationParams params2 = ControlLoopOperationParams.builder().build();
+
+        assertSame(operation, oper2.buildOperation(params2));
+        assertSame(params2, paramsRef.get());
+        assertSame(oper2, operRef.get());
+    }
+
+
+    private class MyOperator extends TopicPairOperator {
+        public MyOperator(List<SelectorKey> selectorKeys) {
+            super(ACTOR, OPERATION, mgr, selectorKeys);
+        }
+
+        @Override
+        public Operation buildOperation(ControlLoopOperationParams params) {
+            return null;
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicPairActorParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicPairActorParamsTest.java
new file mode 100644 (file)
index 0000000..4322c5f
--- /dev/null
@@ -0,0 +1,132 @@
+/*-
+ * ============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.parameters;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+
+public class TopicPairActorParamsTest {
+    private static final String MY_NAME = "my-name";
+
+    private static final String DFLT_SOURCE = "default-source";
+    private static final String DFLT_TARGET = "default-target";
+    private static final int DFLT_TIMEOUT = 10;
+
+    private static final String OPER1_NAME = "oper A";
+    private static final String OPER1_SOURCE = "source A";
+    private static final String OPER1_TARGET = "target A";
+    private static final int OPER1_TIMEOUT = 20;
+
+    // oper2 uses some default values
+    private static final String OPER2_NAME = "oper B";
+    private static final String OPER2_SOURCE = "source B";
+
+    // oper3 uses default values for everything
+    private static final String OPER3_NAME = "oper C";
+
+    private TopicPairParams defaults;
+    private Map<String, Map<String, Object>> operMap;
+    private TopicPairActorParams params;
+
+
+    /**
+     * Sets up.
+     */
+    @Before
+    public void setUp() {
+        defaults = TopicPairParams.builder().source(DFLT_SOURCE).target(DFLT_TARGET).timeoutSec(DFLT_TIMEOUT).build();
+
+        TopicPairParams oper1 = TopicPairParams.builder().source(OPER1_SOURCE).target(OPER1_TARGET)
+                        .timeoutSec(OPER1_TIMEOUT).build();
+
+        Map<String, Object> oper1Map = Util.translateToMap(OPER1_NAME, oper1);
+        Map<String, Object> oper2Map = Map.of("source", OPER2_SOURCE);
+        Map<String, Object> oper3Map = Collections.emptyMap();
+        operMap = Map.of(OPER1_NAME, oper1Map, OPER2_NAME, oper2Map, OPER3_NAME, oper3Map);
+
+        params = TopicPairActorParams.builder().defaults(defaults).operation(operMap).build();
+
+    }
+
+    @Test
+    public void testTopicPairActorParams() {
+        assertSame(defaults, params.getDefaults());
+        assertSame(operMap, params.getOperation());
+    }
+
+    @Test
+    public void testDoValidation() {
+        assertSame(params, params.doValidation(MY_NAME));
+
+        // test with invalid parameters
+        defaults.setTimeoutSec(-1);
+        assertThatThrownBy(() -> params.doValidation(MY_NAME)).isInstanceOf(ParameterValidationRuntimeException.class);
+    }
+
+    @Test
+    public void testValidate() {
+        ValidationResult result;
+
+        // null defaults
+        params.setDefaults(null);
+        result = params.validate(MY_NAME);
+        assertFalse(result.isValid());
+        assertThat(result.getResult()).contains("defaults").contains("null");
+        params.setDefaults(defaults);
+
+        // invalid value in defaults
+        defaults.setTimeoutSec(-1);
+        result = params.validate(MY_NAME);
+        assertFalse(result.isValid());
+        assertThat(result.getResult()).contains("defaults").contains("timeoutSec");
+        defaults.setTimeoutSec(DFLT_TIMEOUT);
+
+        // null map
+        params.setOperation(null);
+        result = params.validate(MY_NAME);
+        assertFalse(result.isValid());
+        assertThat(result.getResult()).contains("operation");
+        params.setOperation(operMap);
+
+        // null entry in the map
+        Map<String, Map<String, Object>> map2 = new TreeMap<>(operMap);
+        map2.put(OPER2_NAME, null);
+        params.setOperation(map2);
+        result = params.validate(MY_NAME);
+        assertFalse(result.isValid());
+        assertThat(result.getResult()).contains("operation").contains("null");
+        params.setOperation(operMap);
+
+        // test success case
+        assertTrue(params.validate(MY_NAME).isValid());
+    }
+}
@@ -29,20 +29,20 @@ import java.util.function.Function;
 import org.junit.Before;
 import org.junit.Test;
 import org.onap.policy.common.parameters.ValidationResult;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.TopicParams.TopicParamsBuilder;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.TopicPairParams.TopicPairParamsBuilder;
 
-public class TopicParamsTest {
+public class TopicPairParamsTest {
 
     private static final String CONTAINER = "my-container";
     private static final String TARGET = "my-target";
     private static final String SOURCE = "my-source";
-    private static final long TIMEOUT = 10;
+    private static final int TIMEOUT = 10;
 
-    private TopicParams params;
+    private TopicPairParams params;
 
     @Before
     public void setUp() {
-        params = TopicParams.builder().target(TARGET).source(SOURCE).timeoutSec(TIMEOUT).build();
+        params = TopicPairParams.builder().target(TARGET).source(SOURCE).timeoutSec(TIMEOUT).build();
     }
 
     @Test
@@ -52,8 +52,11 @@ public class TopicParamsTest {
         testValidateField("timeoutSec", "minimum", bldr -> bldr.timeoutSec(-1));
 
         // check edge cases
-        assertTrue(params.toBuilder().timeoutSec(0).build().validate(CONTAINER).isValid());
+        assertFalse(params.toBuilder().timeoutSec(0).build().validate(CONTAINER).isValid());
         assertTrue(params.toBuilder().timeoutSec(1).build().validate(CONTAINER).isValid());
+
+        // some default values should be valid
+        assertTrue(TopicPairParams.builder().target(TARGET).source(SOURCE).build().validate(CONTAINER).isValid());
     }
 
     @Test
@@ -66,7 +69,7 @@ public class TopicParamsTest {
     }
 
     private void testValidateField(String fieldName, String expected,
-                    Function<TopicParamsBuilder, TopicParamsBuilder> makeInvalid) {
+                    Function<TopicPairParamsBuilder, TopicPairParamsBuilder> makeInvalid) {
 
         // original params should be valid
         ValidationResult result = params.validate(CONTAINER);
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/ForwarderTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/ForwarderTest.java
new file mode 100644 (file)
index 0000000..24f8b70
--- /dev/null
@@ -0,0 +1,201 @@
+/*-
+ * ============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.topic;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.util.Arrays;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.PropertyUtils.TriConsumer;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+
+public class ForwarderTest {
+    private static final CommInfrastructure INFRA = CommInfrastructure.NOOP;
+    private static final String TEXT = "some text";
+
+    private static final String KEY1 = "requestId";
+    private static final String KEY2 = "container";
+    private static final String SUBKEY = "subRequestId";
+
+    private static final String VALUEA_REQID = "hello";
+    private static final String VALUEA_SUBREQID = "world";
+
+    // request id is shared with value A
+    private static final String VALUEB_REQID = "hello";
+    private static final String VALUEB_SUBREQID = "another world";
+
+    // unique values
+    private static final String VALUEC_REQID = "bye";
+    private static final String VALUEC_SUBREQID = "bye-bye";
+
+    @Mock
+    private TriConsumer<CommInfrastructure, String, StandardCoderObject> listener1;
+
+    @Mock
+    private TriConsumer<CommInfrastructure, String, StandardCoderObject> listener1b;
+
+    @Mock
+    private TriConsumer<CommInfrastructure, String, StandardCoderObject> listener2;
+
+    @Mock
+    private TriConsumer<CommInfrastructure, String, StandardCoderObject> listener3;
+
+    private Forwarder forwarder;
+
+
+    /**
+     * Sets up.
+     */
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        forwarder = new Forwarder(Arrays.asList(new SelectorKey(KEY1), new SelectorKey(KEY2, SUBKEY)));
+
+        forwarder.register(Arrays.asList(VALUEA_REQID, VALUEA_SUBREQID), listener1);
+        forwarder.register(Arrays.asList(VALUEA_REQID, VALUEA_SUBREQID), listener1b);
+        forwarder.register(Arrays.asList(VALUEB_REQID, VALUEB_SUBREQID), listener2);
+        forwarder.register(Arrays.asList(VALUEC_REQID, VALUEC_SUBREQID), listener3);
+    }
+
+    @Test
+    public void testRegister() {
+        // key size mismatches
+        assertThatIllegalArgumentException().isThrownBy(() -> forwarder.register(Arrays.asList(), listener1))
+                        .withMessage("key/value mismatch");
+        assertThatIllegalArgumentException()
+                        .isThrownBy(() -> forwarder.register(Arrays.asList(VALUEA_REQID), listener1))
+                        .withMessage("key/value mismatch");
+    }
+
+    @Test
+    public void testUnregister() {
+        // remove listener1b
+        forwarder.unregister(Arrays.asList(VALUEA_REQID, VALUEA_SUBREQID), listener1b);
+
+        StandardCoderObject sco = makeMessage(Map.of(KEY1, VALUEA_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+        forwarder.onMessage(INFRA, TEXT, sco);
+
+        verify(listener1).accept(INFRA, TEXT, sco);
+        verify(listener1b, never()).accept(any(), any(), any());
+
+        // remove listener1
+        forwarder.unregister(Arrays.asList(VALUEA_REQID, VALUEA_SUBREQID), listener1);
+        forwarder.onMessage(INFRA, TEXT, sco);
+
+        // route a message to listener2
+        sco = makeMessage(Map.of(KEY1, VALUEB_REQID, KEY2, Map.of(SUBKEY, VALUEB_SUBREQID)));
+        forwarder.onMessage(INFRA, TEXT, sco);
+        verify(listener2).accept(INFRA, TEXT, sco);
+
+        // no more messages to listener1 or 1b
+        verify(listener1).accept(any(), any(), any());
+        verify(listener1b, never()).accept(any(), any(), any());
+    }
+
+    @Test
+    public void testOnMessage() {
+        StandardCoderObject sco = makeMessage(Map.of(KEY1, VALUEA_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+        forwarder.onMessage(INFRA, TEXT, sco);
+
+        verify(listener1).accept(INFRA, TEXT, sco);
+        verify(listener1b).accept(INFRA, TEXT, sco);
+
+        // repeat - counts should increment
+        forwarder.onMessage(INFRA, TEXT, sco);
+
+        verify(listener1, times(2)).accept(INFRA, TEXT, sco);
+        verify(listener1b, times(2)).accept(INFRA, TEXT, sco);
+
+        // should not have been invoked
+        verify(listener2, never()).accept(any(), any(), any());
+        verify(listener3, never()).accept(any(), any(), any());
+
+        // try other listeners now
+        sco = makeMessage(Map.of(KEY1, VALUEB_REQID, KEY2, Map.of(SUBKEY, VALUEB_SUBREQID)));
+        forwarder.onMessage(INFRA, TEXT, sco);
+        verify(listener2).accept(INFRA, TEXT, sco);
+
+        sco = makeMessage(Map.of(KEY1, VALUEC_REQID, KEY2, Map.of(SUBKEY, VALUEC_SUBREQID)));
+        forwarder.onMessage(INFRA, TEXT, sco);
+        verify(listener3).accept(INFRA, TEXT, sco);
+
+        // message has no listeners
+        sco = makeMessage(Map.of(KEY1, "xyzzy", KEY2, Map.of(SUBKEY, VALUEB_SUBREQID)));
+        forwarder.onMessage(INFRA, TEXT, sco);
+
+        // message doesn't have both keys
+        sco = makeMessage(Map.of(KEY1, VALUEA_REQID));
+        forwarder.onMessage(INFRA, TEXT, sco);
+
+        // counts should not have incremented
+        verify(listener1, times(2)).accept(any(), any(), any());
+        verify(listener1b, times(2)).accept(any(), any(), any());
+        verify(listener2).accept(any(), any(), any());
+        verify(listener3).accept(any(), any(), any());
+
+        // listener throws an exception
+        doThrow(new IllegalStateException("expected exception")).when(listener1).accept(any(), any(), any());
+    }
+
+    /*
+     * Tests onMessage() when listener1 throws an exception.
+     */
+    @Test
+    public void testOnMessageListenerException1() {
+        doThrow(new IllegalStateException("expected exception")).when(listener1).accept(any(), any(), any());
+
+        StandardCoderObject sco = makeMessage(Map.of(KEY1, VALUEA_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+        forwarder.onMessage(INFRA, TEXT, sco);
+
+        verify(listener1b).accept(INFRA, TEXT, sco);
+    }
+
+    /*
+     * Tests onMessage() when listener1b throws an exception.
+     */
+    @Test
+    public void testOnMessageListenerException1b() {
+        doThrow(new IllegalStateException("expected exception")).when(listener1b).accept(any(), any(), any());
+
+        StandardCoderObject sco = makeMessage(Map.of(KEY1, VALUEA_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+        forwarder.onMessage(INFRA, TEXT, sco);
+
+        verify(listener1).accept(INFRA, TEXT, sco);
+    }
+
+    /**
+     * Makes a message from a map.
+     */
+    private StandardCoderObject makeMessage(Map<String, Object> map) {
+        return Util.translate("", map, StandardCoderObject.class);
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/SelectorKeyTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/SelectorKeyTest.java
new file mode 100644 (file)
index 0000000..19df9c2
--- /dev/null
@@ -0,0 +1,93 @@
+/*-
+ * ============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.topic;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.util.Map;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+
+public class SelectorKeyTest {
+    private static final String FIELD1 = "map";
+    private static final String FIELD2 = "abc";
+    private static final String FIELDX = "abd";
+
+    private SelectorKey key;
+
+    @Before
+    public void setUp() {
+        key = new SelectorKey(FIELD1, FIELD2);
+    }
+
+    @Test
+    public void testHashCode_testEquals() {
+        SelectorKey key2 = new SelectorKey(FIELD1, FIELD2);
+        assertEquals(key, key2);
+        assertEquals(key.hashCode(), key2.hashCode());
+
+        key2 = new SelectorKey(FIELD1, FIELDX);
+        assertNotEquals(key, key2);
+        assertNotEquals(key.hashCode(), key2.hashCode());
+
+        // test empty key
+        key = new SelectorKey();
+        key2 = new SelectorKey();
+        assertEquals(key, key2);
+        assertEquals(key.hashCode(), key2.hashCode());
+    }
+
+    @Test
+    public void testExtractField() {
+        Map<String, Object> map = Map.of("hello", "world", FIELD1, Map.of("another", "", FIELD2, "value B"));
+        StandardCoderObject sco = Util.translate("", map, StandardCoderObject.class);
+
+        String result = key.extractField(sco);
+        assertNotNull(result);
+        assertEquals("value B", result);
+
+        // shorter key
+        assertEquals("world", new SelectorKey("hello").extractField(sco));
+        assertNull(new SelectorKey("bye").extractField(sco));
+
+        // not found
+        assertNull(new SelectorKey(FIELD1, "not field 2").extractField(sco));
+
+        // test with empty key
+        assertNull(new SelectorKey().extractField(sco));
+    }
+
+    @Getter
+    @Setter
+    @Builder
+    protected static class Data {
+        private String text;
+        private Map<String, String> map;
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicListenerImplTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicListenerImplTest.java
new file mode 100644 (file)
index 0000000..a370857
--- /dev/null
@@ -0,0 +1,154 @@
+/*-
+ * ============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.topic;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import java.util.Arrays;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.PropertyUtils.TriConsumer;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+
+public class TopicListenerImplTest {
+    private static final StandardCoder coder = new StandardCoder();
+    private static final CommInfrastructure INFRA = CommInfrastructure.NOOP;
+    private static final String MY_TOPIC = "my-topic";
+    private static final String KEY1 = "requestId";
+    private static final String KEY2 = "container";
+    private static final String SUBKEY = "subRequestId";
+
+    private static final String VALUEA_REQID = "hello";
+    private static final String VALUEA_SUBREQID = "world";
+
+    private static final String VALUEB_REQID = "bye";
+
+    private Forwarder forwarder1;
+    private Forwarder forwarder2;
+    private TopicListenerImpl topic;
+
+    @Mock
+    private TriConsumer<CommInfrastructure, String, StandardCoderObject> listener1;
+
+    @Mock
+    private TriConsumer<CommInfrastructure, String, StandardCoderObject> listener1b;
+
+    @Mock
+    private TriConsumer<CommInfrastructure, String, StandardCoderObject> listener2;
+
+
+    /**
+     * Sets up.
+     */
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        topic = new TopicListenerImpl();
+
+        forwarder1 = topic.addForwarder(new SelectorKey(KEY1));
+        forwarder2 = topic.addForwarder(new SelectorKey(KEY1), new SelectorKey(KEY2, SUBKEY));
+
+        assertNotNull(forwarder1);
+        assertNotNull(forwarder2);
+        assertNotSame(forwarder1, forwarder2);
+
+        forwarder1.register(Arrays.asList(VALUEA_REQID), listener1);
+        forwarder1.register(Arrays.asList(VALUEB_REQID), listener1b);
+        forwarder2.register(Arrays.asList(VALUEA_REQID, VALUEA_SUBREQID), listener2);
+    }
+
+    @Test
+    public void testShutdown() {
+        // shut it down, which should clear all forwarders
+        topic.shutdown();
+
+        // should get a new forwarder now
+        Forwarder forwarder = topic.addForwarder(new SelectorKey(KEY1));
+        assertNotSame(forwarder1, forwarder);
+        assertNotSame(forwarder2, forwarder);
+
+        // new forwarder should be unchanged
+        assertSame(forwarder, topic.addForwarder(new SelectorKey(KEY1)));
+    }
+
+    @Test
+    public void testAddForwarder() {
+        assertSame(forwarder1, topic.addForwarder(new SelectorKey(KEY1)));
+        assertSame(forwarder2, topic.addForwarder(new SelectorKey(KEY1), new SelectorKey(KEY2, SUBKEY)));
+    }
+
+    @Test
+    public void testOnTopicEvent() {
+        /*
+         * send a message that should go to listener1 on forwarder1 and listener2 on
+         * forwarder2
+         */
+        String msg = makeMessage(Map.of(KEY1, VALUEA_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+        topic.onTopicEvent(INFRA, MY_TOPIC, msg);
+
+        verify(listener1).accept(eq(INFRA), eq(msg), any());
+        verify(listener2).accept(eq(INFRA), eq(msg), any());
+
+        // not to listener1b
+        verify(listener1b, never()).accept(any(), any(), any());
+
+        /*
+         * now send a message that should only go to listener1b on forwarder1
+         */
+        msg = makeMessage(Map.of(KEY1, VALUEB_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+        topic.onTopicEvent(INFRA, MY_TOPIC, msg);
+
+        // should route to listener1 on forwarder1 and listener2 on forwarder2
+        verify(listener1b).accept(eq(INFRA), eq(msg), any());
+
+        // try one where the coder throws an exception
+        topic.onTopicEvent(INFRA, MY_TOPIC, "{invalid-json");
+
+        // no extra invocations
+        verify(listener1).accept(any(), any(), any());
+        verify(listener1b).accept(any(), any(), any());
+        verify(listener2).accept(any(), any(), any());
+    }
+
+    /**
+     * Makes a message from a map.
+     */
+    private String makeMessage(Map<String, Object> map) {
+        try {
+            return coder.encode(map);
+        } catch (CoderException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicPairTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicPairTest.java
new file mode 100644 (file)
index 0000000..c6557d0
--- /dev/null
@@ -0,0 +1,158 @@
+/*-
+ * ============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.topic;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.event.comm.TopicEndpoint;
+import org.onap.policy.common.endpoints.event.comm.TopicSink;
+import org.onap.policy.common.endpoints.event.comm.TopicSource;
+
+public class TopicPairTest {
+    private static final String UNKNOWN = "unknown";
+    private static final String MY_SOURCE = "pair-source";
+    private static final String MY_TARGET = "pair-target";
+    private static final String TEXT = "some text";
+
+    @Mock
+    private TopicSink publisher1;
+
+    @Mock
+    private TopicSink publisher2;
+
+    @Mock
+    private TopicSource subscriber1;
+
+    @Mock
+    private TopicSource subscriber2;
+
+    @Mock
+    private TopicEndpoint mgr;
+
+    private TopicPair pair;
+
+
+    /**
+     * Sets up.
+     */
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mgr.getTopicSinks(MY_TARGET)).thenReturn(Arrays.asList(publisher1, publisher2));
+        when(mgr.getTopicSources(eq(Arrays.asList(MY_SOURCE)))).thenReturn(Arrays.asList(subscriber1, subscriber2));
+
+        when(publisher1.getTopicCommInfrastructure()).thenReturn(CommInfrastructure.NOOP);
+        when(publisher2.getTopicCommInfrastructure()).thenReturn(CommInfrastructure.UEB);
+
+        pair = new MyTopicPair(MY_SOURCE, MY_TARGET);
+
+        pair.start();
+    }
+
+    @Test
+    public void testTopicPair_testGetSource_testGetTarget() {
+        assertEquals(MY_SOURCE, pair.getSource());
+        assertEquals(MY_TARGET, pair.getTarget());
+
+        verify(mgr).getTopicSinks(anyString());
+        verify(mgr).getTopicSources(any());
+
+        // source not found
+        assertThatIllegalArgumentException().isThrownBy(() -> new MyTopicPair(UNKNOWN, MY_TARGET))
+                        .withMessageContaining("sources").withMessageContaining(UNKNOWN);
+
+        // target not found
+        assertThatIllegalArgumentException().isThrownBy(() -> new MyTopicPair(MY_SOURCE, UNKNOWN))
+                        .withMessageContaining("sinks").withMessageContaining(UNKNOWN);
+    }
+
+    @Test
+    public void testShutdown() {
+        pair.shutdown();
+        verify(subscriber1).unregister(pair);
+        verify(subscriber2).unregister(pair);
+    }
+
+    @Test
+    public void testStart() {
+        verify(subscriber1).register(pair);
+        verify(subscriber2).register(pair);
+    }
+
+    @Test
+    public void testStop() {
+        pair.stop();
+        verify(subscriber1).unregister(pair);
+        verify(subscriber2).unregister(pair);
+    }
+
+    @Test
+    public void testPublish() {
+        List<CommInfrastructure> infrastructures = pair.publish(TEXT);
+        assertEquals(Arrays.asList(CommInfrastructure.NOOP, CommInfrastructure.UEB), infrastructures);
+
+        verify(publisher1).send(TEXT);
+        verify(publisher2).send(TEXT);
+
+        // first one throws an exception - should have only published to the second
+        when(publisher1.send(any())).thenThrow(new IllegalStateException("expected exception"));
+
+        infrastructures = pair.publish(TEXT);
+        assertEquals(Arrays.asList(CommInfrastructure.UEB), infrastructures);
+
+        verify(publisher2, times(2)).send(TEXT);
+    }
+
+    @Test
+    public void testGetTopicEndpointManager() {
+        // setting "mgr" to null should cause it to use the superclass' method
+        mgr = null;
+        assertNotNull(pair.getTopicEndpointManager());
+    }
+
+
+    private class MyTopicPair extends TopicPair {
+        public MyTopicPair(String source, String target) {
+            super(source, target);
+        }
+
+        @Override
+        protected TopicEndpoint getTopicEndpointManager() {
+            return (mgr != null ? mgr : super.getTopicEndpointManager());
+        }
+    }
+}
index 8604688..b11983b 100644 (file)
     <logger name="org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperation" level="info" additivity="false">
         <appender-ref ref="STDOUT" />
     </logger>
+
+    <!-- this is required for TopicPairOperationTest -->
+    <logger
+            name="org.onap.policy.controlloop.actorserviceprovider.impl.TopicPairOperation"
+            level="info" additivity="false">
+        <appender-ref ref="STDOUT" />
+    </logger>
 </configuration>
index 029ac7f..3089114 100644 (file)
@@ -37,6 +37,7 @@
   <modules>
     <module>actorServiceProvider</module>
     <module>actor.test</module>
+    <module>actor.aai</module>
     <module>actor.appc</module>
     <module>actor.vfc</module>
     <module>actor.sdnc</module>
index 0eee4b2..30428b0 100644 (file)
@@ -1,6 +1,7 @@
 /*-
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2019-2020 Nordix Foundation.
+ *  Modifications Copyright (C) 2020 AT&T.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -55,15 +56,15 @@ public class PolicyLegacyOperationalPersistenceTest {
 
     // @formatter:off
     private String[] policyInputResourceNames = {
-        "policies/vCPE.policy.operational.input.json",
-        "policies/vDNS.policy.operational.input.json",
-        "policies/vFirewall.policy.operational.input.json"
+        "policies/vCPE.policy.operational.legacy.input.json",
+        "policies/vDNS.policy.operational.legacy.input.json",
+        "policies/vFirewall.policy.operational.legacy.input.json"
     };
 
     private String[] policyOutputResourceNames = {
-        "policies/vCPE.policy.operational.output.json",
-        "policies/vDNS.policy.operational.output.json",
-        "policies/vFirewall.policy.operational.output.json"
+        "policies/vCPE.policy.operational.legacy.output.json",
+        "policies/vDNS.policy.operational.legacy.output.json",
+        "policies/vFirewall.policy.operational.legacy.output.json"
     };
     // @formatter:on
 
@@ -148,8 +149,8 @@ public class PolicyLegacyOperationalPersistenceTest {
     private void createPolicyTypes() throws CoderException, PfModelException, URISyntaxException {
         Set<String> policyTypeResources = ResourceUtils.getDirectoryContents("policytypes");
 
-        for (String policyTyoeResource : policyTypeResources) {
-            Object yamlObject = new Yaml().load(ResourceUtils.getResourceAsString(policyTyoeResource));
+        for (String policyTypeResource : policyTypeResources) {
+            Object yamlObject = new Yaml().load(ResourceUtils.getResourceAsString(policyTypeResource));
             String yamlAsJsonString = new StandardCoder().encode(yamlObject);
 
             ToscaServiceTemplate toscaServiceTemplatePolicyType =
index 0cdc2ad..3116868 100644 (file)
@@ -36,6 +36,7 @@ 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.common.utils.coder.YamlJsonTranslator;
 import org.onap.policy.common.utils.resources.ResourceUtils;
 import org.onap.policy.models.base.PfModelException;
 import org.onap.policy.models.provider.PolicyModelsProvider;
@@ -44,7 +45,8 @@ import org.onap.policy.models.provider.PolicyModelsProviderParameters;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyFilter;
 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
-import org.yaml.snakeyaml.Yaml;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Test persistence of monitoring policies to and from the database.
@@ -52,7 +54,10 @@ import org.yaml.snakeyaml.Yaml;
  * @author Liam Fallon (liam.fallon@est.tech)
  */
 public class PolicyToscaPersistenceTest {
-    private StandardCoder standardCoder;
+    private static final Logger LOGGER = LoggerFactory.getLogger(PolicyToscaPersistenceTest.class);
+
+    private YamlJsonTranslator yamlJsonTranslator = new YamlJsonTranslator();
+    private StandardCoder standardCoder = new StandardCoder();
 
     private PolicyModelsProvider databaseProvider;
 
@@ -78,14 +83,6 @@ public class PolicyToscaPersistenceTest {
         createPolicyTypes();
     }
 
-    /**
-     * Set up standard coder.
-     */
-    @Before
-    public void setupStandardCoder() {
-        standardCoder = new StandardCoder();
-    }
-
     @After
     public void teardown() throws Exception {
         databaseProvider.close();
@@ -103,29 +100,44 @@ public class PolicyToscaPersistenceTest {
             String policyString = ResourceUtils.getResourceAsString(policyResource);
 
             if (policyResource.endsWith("yaml")) {
-                testYamlStringPolicyPersistence(policyString);
+                testPolicyPersistence(yamlJsonTranslator.fromYaml(policyString, ToscaServiceTemplate.class));
             } else {
-                testJsonStringPolicyPersistence(policyString);
+                testPolicyPersistence(standardCoder.decode(policyString, ToscaServiceTemplate.class));
             }
         }
     }
 
-    private void testYamlStringPolicyPersistence(final String policyString) throws Exception {
-        Object yamlObject = new Yaml().load(policyString);
-        String yamlAsJsonString = new StandardCoder().encode(yamlObject);
+    @Test
+    public void testNamingPolicyGet() throws PfModelException {
+        String policyYamlString = ResourceUtils.getResourceAsString("policies/sdnc.policy.naming.input.tosca.yaml");
+        ToscaServiceTemplate serviceTemplate =
+                yamlJsonTranslator.fromYaml(policyYamlString, ToscaServiceTemplate.class);
 
-        testJsonStringPolicyPersistence(yamlAsJsonString);
+        long createStartTime = System.currentTimeMillis();
+        databaseProvider.createPolicies(serviceTemplate);
+        LOGGER.trace("Naming policy create time (ms): {}", System.currentTimeMillis() - createStartTime);
+
+        long getStartTime = System.currentTimeMillis();
+        ToscaServiceTemplate namingServiceTemplate =
+                databaseProvider.getPolicies("SDNC_Policy.ONAP_VNF_NAMING_TIMESTAMP", "1.0.0");
+        LOGGER.trace("Naming policy get time (ms): {}", System.currentTimeMillis() - getStartTime);
+
+        assertEquals(1, namingServiceTemplate.getToscaTopologyTemplate().getPoliciesAsMap().size());
+        assertEquals(1, namingServiceTemplate.getPolicyTypesAsMap().size());
+        assertEquals(3, namingServiceTemplate.getDataTypesAsMap().size());
+
+        long deleteStartTime = System.currentTimeMillis();
+        databaseProvider.deletePolicy("SDNC_Policy.ONAP_VNF_NAMING_TIMESTAMP", "1.0.0");
+        LOGGER.trace("Naming policy delete time (ms): {}", System.currentTimeMillis() - deleteStartTime);
     }
 
     /**
      * Check persistence of a policy.
      *
-     * @param policyString the policy as a string
+     * @param serviceTemplate the service template containing the policy
      * @throws Exception any exception thrown
      */
-    public void testJsonStringPolicyPersistence(@NonNull final String policyString) throws Exception {
-        ToscaServiceTemplate serviceTemplate = standardCoder.decode(policyString, ToscaServiceTemplate.class);
-
+    public void testPolicyPersistence(@NonNull final ToscaServiceTemplate serviceTemplate) throws Exception {
         assertNotNull(serviceTemplate);
 
         databaseProvider.createPolicies(serviceTemplate);
@@ -170,11 +182,9 @@ public class PolicyToscaPersistenceTest {
         Set<String> policyTypeResources = ResourceUtils.getDirectoryContents("policytypes");
 
         for (String policyTypeResource : policyTypeResources) {
-            Object yamlObject = new Yaml().load(ResourceUtils.getResourceAsString(policyTypeResource));
-            String yamlAsJsonString = new StandardCoder().encode(yamlObject);
-
+            String policyTypeYamlString = ResourceUtils.getResourceAsString(policyTypeResource);
             ToscaServiceTemplate toscaServiceTemplatePolicyType =
-                    standardCoder.decode(yamlAsJsonString, ToscaServiceTemplate.class);
+                    yamlJsonTranslator.fromYaml(policyTypeYamlString, ToscaServiceTemplate.class);
 
             assertNotNull(toscaServiceTemplatePolicyType);
             databaseProvider.createPolicyTypes(toscaServiceTemplatePolicyType);
index 6e60303..a65cdbe 100644 (file)
@@ -423,6 +423,7 @@ public class AuthorativeToscaProvider {
      */
     private <T extends ToscaEntity> List<T> handlePfModelRuntimeException(final PfModelRuntimeException pfme) {
         if (Status.NOT_FOUND.equals(pfme.getErrorResponse().getResponseCode())) {
+            LOGGER.trace("request did not find any results", pfme);
             return Collections.emptyList();
         } else {
             throw pfme;
index f5335fe..09cc6c0 100644 (file)
@@ -93,13 +93,13 @@ public class LegacyProvider {
 
         LOGGER.debug("->createOperationalPolicy: legacyOperationalPolicy={}", legacyOperationalPolicy);
 
-        JpaToscaServiceTemplate incomingServiceTemplate =
+        JpaToscaServiceTemplate legacyOperationalServiceTemplate =
                 new LegacyOperationalPolicyMapper().toToscaServiceTemplate(legacyOperationalPolicy);
-        JpaToscaServiceTemplate outgoingingServiceTemplate =
-                new SimpleToscaProvider().createPolicies(dao, incomingServiceTemplate);
+
+        new SimpleToscaProvider().createPolicies(dao, legacyOperationalServiceTemplate);
 
         LegacyOperationalPolicy createdLegacyOperationalPolicy =
-                new LegacyOperationalPolicyMapper().fromToscaServiceTemplate(outgoingingServiceTemplate);
+                new LegacyOperationalPolicyMapper().fromToscaServiceTemplate(legacyOperationalServiceTemplate);
 
         LOGGER.debug("<-createOperationalPolicy: createdLegacyOperationalPolicy={}", createdLegacyOperationalPolicy);
         return createdLegacyOperationalPolicy;
@@ -223,7 +223,6 @@ public class LegacyProvider {
         return updatedLegacyGuardPolicyMap;
     }
 
-
     /**
      * Delete legacy guard policy.
      *
index 2816df0..b500a8b 100644 (file)
@@ -261,6 +261,11 @@ public class JpaToscaPolicy extends JpaToscaEntityType<ToscaPolicy> implements P
     public PfValidationResult validate(@NonNull final PfValidationResult resultIn) {
         PfValidationResult result = super.validate(resultIn);
 
+        if (PfKey.NULL_KEY_VERSION.equals(getKey().getVersion())) {
+            result.addValidationMessage(new PfValidationMessage(getKey(), this.getClass(), ValidationResult.INVALID,
+                    "key version is a null version"));
+        }
+
         if (type == null || type.isNullKey()) {
             result.addValidationMessage(new PfValidationMessage(getKey(), this.getClass(), ValidationResult.INVALID,
                     "type is null or a null key"));
index b068bea..423620d 100644 (file)
@@ -203,6 +203,11 @@ public class JpaToscaPolicyType extends JpaToscaEntityType<ToscaPolicyType> impl
     public PfValidationResult validate(@NonNull final PfValidationResult resultIn) {
         PfValidationResult result = super.validate(resultIn);
 
+        if (PfKey.NULL_KEY_VERSION.equals(getKey().getVersion())) {
+            result.addValidationMessage(new PfValidationMessage(getKey(), this.getClass(), ValidationResult.INVALID,
+                    "key version is a null version"));
+        }
+
         if (properties != null) {
             result = validateProperties(result);
         }
index 9c7d6d3..c537bbc 100644 (file)
@@ -106,7 +106,7 @@ public class SimpleToscaProvider {
 
         PfValidationResult result = serviceTemplateToWrite.validate(new PfValidationResult());
         if (!result.isValid()) {
-            throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, result.toString());
+            throw new PfModelRuntimeException(Response.Status.NOT_ACCEPTABLE, result.toString());
         }
 
         new SimpleToscaServiceTemplateProvider().write(dao, serviceTemplateToWrite);
@@ -264,6 +264,9 @@ public class SimpleToscaProvider {
                     "policy types for " + name + ":" + version + DO_NOT_EXIST);
         }
 
+        JpaToscaServiceTemplate dataTypeServiceTemplate = new JpaToscaServiceTemplate(serviceTemplate);
+        dataTypeServiceTemplate.setPolicyTypes(null);
+
         for (JpaToscaPolicyType policyType : serviceTemplate.getPolicyTypes().getConceptMap().values()) {
             Collection<PfConceptKey> referencedDataTypeKeys = policyType.getReferencedDataTypes();
 
@@ -271,11 +274,13 @@ public class SimpleToscaProvider {
                 JpaToscaServiceTemplate dataTypeEntityTreeServiceTemplate =
                         getDataTypes(dao, referencedDataTypeKey.getName(), referencedDataTypeKey.getVersion());
 
-                serviceTemplate =
-                        ToscaServiceTemplateUtils.addFragment(serviceTemplate, dataTypeEntityTreeServiceTemplate);
+                dataTypeServiceTemplate = ToscaServiceTemplateUtils.addFragment(dataTypeServiceTemplate,
+                        dataTypeEntityTreeServiceTemplate);
             }
         }
 
+        serviceTemplate = ToscaServiceTemplateUtils.addFragment(serviceTemplate, dataTypeServiceTemplate);
+
         LOGGER.debug("<-getPolicyTypes: name={}, version={}, serviceTemplate={}", name, version, serviceTemplate);
         return serviceTemplate;
     }
@@ -372,7 +377,11 @@ public class SimpleToscaProvider {
             throws PfModelException {
         LOGGER.debug("->getPolicies: name={}, version={}", name, version);
 
-        JpaToscaServiceTemplate serviceTemplate = getServiceTemplate(dao);
+        JpaToscaServiceTemplate dbServiceTemplate = getServiceTemplate(dao);
+
+        JpaToscaServiceTemplate serviceTemplate = new JpaToscaServiceTemplate(dbServiceTemplate);
+        serviceTemplate.setDataTypes(new JpaToscaDataTypes());
+        serviceTemplate.setPolicyTypes(new JpaToscaPolicyTypes());
 
         if (!ToscaUtils.doPoliciesExist(serviceTemplate)) {
             throw new PfModelRuntimeException(Response.Status.NOT_FOUND,
@@ -386,18 +395,20 @@ public class SimpleToscaProvider {
                     "policies for " + name + ":" + version + DO_NOT_EXIST);
         }
 
+        JpaToscaServiceTemplate returnServiceTemplate = new JpaToscaServiceTemplate(serviceTemplate);
+        returnServiceTemplate.getTopologyTemplate().setPolicies(new JpaToscaPolicies());
+
         for (JpaToscaPolicy policy : serviceTemplate.getTopologyTemplate().getPolicies().getConceptMap().values()) {
-            if (policy.getDerivedFrom() != null) {
-                JpaToscaServiceTemplate referencedEntitiesServiceTemplate =
-                        getPolicyTypes(dao, policy.getDerivedFrom().getName(), policy.getDerivedFrom().getVersion());
+            JpaToscaServiceTemplate referencedEntitiesServiceTemplate =
+                    getPolicyTypes(dao, policy.getType().getName(), policy.getType().getVersion());
 
-                serviceTemplate =
-                        ToscaServiceTemplateUtils.addFragment(serviceTemplate, referencedEntitiesServiceTemplate);
-            }
+            returnServiceTemplate.getTopologyTemplate().getPolicies().getConceptMap().put(policy.getKey(), policy);
+            returnServiceTemplate =
+                    ToscaServiceTemplateUtils.addFragment(returnServiceTemplate, referencedEntitiesServiceTemplate);
         }
 
-        LOGGER.debug("<-getPolicies: name={}, version={}, serviceTemplate={}", name, version, serviceTemplate);
-        return serviceTemplate;
+        LOGGER.debug("<-getPolicies: name={}, version={}, serviceTemplate={}", name, version, returnServiceTemplate);
+        return returnServiceTemplate;
     }
 
     /**
@@ -497,7 +508,7 @@ public class SimpleToscaProvider {
         if (policyType == null) {
             String errorMessage =
                     "policy type " + policyTypeKey.getId() + " for policy " + policy.getId() + " does not exist";
-            throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, errorMessage);
+            throw new PfModelRuntimeException(Response.Status.NOT_ACCEPTABLE, errorMessage);
         }
     }
 
@@ -526,7 +537,7 @@ public class SimpleToscaProvider {
         // We should have one and only one returned entry
         if (filterdPolicyTypeList.size() != 1) {
             String errorMessage = "search for latest policy type " + policyTypeName + " returned more than one entry";
-            throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, errorMessage);
+            throw new PfModelRuntimeException(Response.Status.CONFLICT, errorMessage);
         }
 
         return (JpaToscaPolicyType) filterdPolicyTypeList.get(0);
index 87b499b..6f21c19 100644 (file)
@@ -74,7 +74,7 @@ public class ToscaServiceTemplateUtils {
         compositeTemplate.setPolicyTypes(
                 addFragmentEntitites(compositeTemplate.getPolicyTypes(), fragmentTemplate.getPolicyTypes(), result));
 
-        if (originalTemplate.getTopologyTemplate() != null) {
+        if (originalTemplate.getTopologyTemplate() != null && fragmentTemplate.getTopologyTemplate() != null) {
             if (originalTemplate.getTopologyTemplate()
                     .compareToWithoutEntities(fragmentTemplate.getTopologyTemplate()) == 0) {
                 compositeTemplate.getTopologyTemplate()
@@ -95,7 +95,7 @@ public class ToscaServiceTemplateUtils {
 
         if (!result.isValid()) {
             String message = result.toString();
-            throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, message);
+            throw new PfModelRuntimeException(Response.Status.NOT_ACCEPTABLE, message);
         }
 
         return compositeTemplate;
index cc04319..b75273e 100644 (file)
@@ -147,7 +147,7 @@ public final class ToscaUtils {
             final Function<JpaToscaServiceTemplate, String> checkerFunction) {
         String message = checkerFunction.apply(serviceTemplate);
         if (message != null) {
-            throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, message);
+            throw new PfModelRuntimeException(Response.Status.NOT_FOUND, message);
         }
     }
 
@@ -263,7 +263,7 @@ public final class ToscaUtils {
         }
 
         if (!result.isValid()) {
-            throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, result.toString());
+            throw new PfModelRuntimeException(Response.Status.NOT_ACCEPTABLE, result.toString());
         }
 
         entityTypes.getConceptMap().entrySet()
index 0bf3710..858ac09 100644 (file)
@@ -87,6 +87,7 @@ public class ToscaPolicyFilterTest {
         for (String policyResourceName : policyResourceNames) {
             String policyString = ResourceUtils.getResourceAsString(policyResourceName);
             if (policyResourceName.endsWith("yaml")) {
+                LOGGER.info("loading {}", policyResourceName);
                 Object yamlObject = new Yaml().load(policyString);
                 policyString = new GsonBuilder().setPrettyPrinting().create().toJson(yamlObject);
             }
@@ -150,7 +151,7 @@ public class ToscaPolicyFilterTest {
         assertEquals(VERSION_100, filteredList.get(7).getVersion());
         assertEquals(VERSION_100, filteredList.get(12).getVersion());
 
-        assertEquals(24, policyList.size());
+        assertEquals(23, policyList.size());
         assertEquals(22, filteredList.size());
 
         policyList.get(10).setVersion("2.0.0");
@@ -172,7 +173,7 @@ public class ToscaPolicyFilterTest {
     public void testFilterNameVersion() {
         ToscaPolicyFilter filter = ToscaPolicyFilter.builder().name("operational.modifyconfig").build();
         List<ToscaPolicy> filteredList = filter.filter(policyList);
-        assertEquals(2, filteredList.size());
+        assertEquals(1, filteredList.size());
 
         filter = ToscaPolicyFilter.builder().name("guard.frequency.scaleout").build();
         filteredList = filter.filter(policyList);
@@ -184,7 +185,7 @@ public class ToscaPolicyFilterTest {
 
         filter = ToscaPolicyFilter.builder().version(VERSION_100).build();
         filteredList = filter.filter(policyList);
-        assertEquals(22, filteredList.size());
+        assertEquals(21, filteredList.size());
 
         filter = ToscaPolicyFilter.builder().name("OSDF_CASABLANCA.SubscriberPolicy_v1").version(VERSION_100).build();
         filteredList = filter.filter(policyList);
@@ -192,7 +193,7 @@ public class ToscaPolicyFilterTest {
 
         filter = ToscaPolicyFilter.builder().name("operational.modifyconfig").version(VERSION_100).build();
         filteredList = filter.filter(policyList);
-        assertEquals(1, filteredList.size());
+        assertEquals(0, filteredList.size());
     }
 
     @Test
@@ -200,11 +201,11 @@ public class ToscaPolicyFilterTest {
         // null pattern
         ToscaPolicyFilter filter = ToscaPolicyFilter.builder().versionPrefix(null).build();
         List<ToscaPolicy> filteredList = filter.filter(policyList);
-        assertEquals(24, filteredList.size());
+        assertEquals(23, filteredList.size());
 
         filter = ToscaPolicyFilter.builder().versionPrefix("1.").build();
         filteredList = filter.filter(policyList);
-        assertEquals(22, filteredList.size());
+        assertEquals(21, filteredList.size());
 
         filter = ToscaPolicyFilter.builder().versionPrefix("100.").build();
         filteredList = filter.filter(policyList);
@@ -215,7 +216,11 @@ public class ToscaPolicyFilterTest {
     public void testFilterTypeVersion() {
         ToscaPolicyFilter filter = ToscaPolicyFilter.builder().type("onap.policies.controlloop.Operational").build();
         List<ToscaPolicy> filteredList = filter.filter(policyList);
-        assertEquals(1, filteredList.size());
+        assertEquals(0, filteredList.size());
+
+        filter = ToscaPolicyFilter.builder().type("onap.policies.controlloop.operational.common.Apex").build();
+        filteredList = filter.filter(policyList);
+        assertEquals(0, filteredList.size());
 
         filter = ToscaPolicyFilter.builder().type("onap.policies.controlloop.operational.common.Drools").build();
         filteredList = filter.filter(policyList);
@@ -231,7 +236,7 @@ public class ToscaPolicyFilterTest {
 
         filter = ToscaPolicyFilter.builder().typeVersion(VERSION_000).build();
         filteredList = filter.filter(policyList);
-        assertEquals(4, filteredList.size());
+        assertEquals(3, filteredList.size());
 
         filter = ToscaPolicyFilter.builder().type("onap.policies.optimization.resource.HpaPolicy")
                 .typeVersion(VERSION_100).build();
@@ -241,6 +246,6 @@ public class ToscaPolicyFilterTest {
         filter = ToscaPolicyFilter.builder().type("onap.policies.controlloop.Operational").typeVersion(VERSION_000)
                 .build();
         filteredList = filter.filter(policyList);
-        assertEquals(1, filteredList.size());
+        assertEquals(0, filteredList.size());
     }
 }
index 30696ce..0f038d3 100644 (file)
@@ -163,7 +163,7 @@ public class ToscaPolicyTypeFilterTest {
     public void testFilterNameVersion() {
         ToscaPolicyTypeFilter filter = ToscaPolicyTypeFilter.builder().name("onap.policies.Monitoring").build();
         List<ToscaPolicyType> filteredList = filter.filter(typeList);
-        assertEquals(2, filteredList.size());
+        assertEquals(1, filteredList.size());
 
         filter = ToscaPolicyTypeFilter.builder().name("onap.policies.monitoring.cdap.tca.hi.lo.app").build();
         filteredList = filter.filter(typeList);
@@ -173,9 +173,9 @@ public class ToscaPolicyTypeFilterTest {
         filteredList = filter.filter(typeList);
         assertEquals(0, filteredList.size());
 
-        filter = ToscaPolicyTypeFilter.builder().version(VERSION_000).build();
+        filter = ToscaPolicyTypeFilter.builder().version(VERSION_100).build();
         filteredList = filter.filter(typeList);
-        assertEquals(1, filteredList.size());
+        assertEquals(20, filteredList.size());
 
         filter = ToscaPolicyTypeFilter.builder().name("onap.policies.optimization.Vim_fit").version(VERSION_000)
                 .build();
index ae350bd..3f0d9e2 100644 (file)
@@ -58,11 +58,11 @@ import org.yaml.snakeyaml.Yaml;
  */
 public class AuthorativeToscaProviderPolicyTypeTest {
     private static final String VERSION = "version";
-    private static final String POLICY_NO_VERSION_VERSION0 = "onap.policies.NoVersion:0.0.0";
+    private static final String POLICY_NO_VERSION_VERSION1 = "onap.policies.NoVersion:0.0.1";
     private static final String POLICY_NO_VERSION = "onap.policies.NoVersion";
     private static final String MISSING_POLICY_TYPES = "no policy types specified on service template";
     private static final String DAO_IS_NULL = "^dao is marked .*on.*ull but is null$";
-    private static final String VERSION_000 = "0.0.0";
+    private static final String VERSION_001 = "0.0.1";
     private static String yamlAsJsonString;
     private PfDao pfDao;
     private StandardCoder standardCoder;
@@ -135,7 +135,7 @@ public class AuthorativeToscaProviderPolicyTypeTest {
         ToscaServiceTemplate createdServiceTemplate =
                 new AuthorativeToscaProvider().createPolicyTypes(pfDao, toscaServiceTemplate);
 
-        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION0);
+        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION1);
 
         ToscaPolicyType beforePolicyType = toscaServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
         ToscaPolicyType createdPolicyType = createdServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
@@ -150,7 +150,7 @@ public class AuthorativeToscaProviderPolicyTypeTest {
         assertEquals(0, ObjectUtils.compare(beforePolicyType.getDescription(), createdPolicyType.getDescription()));
 
         List<ToscaPolicyType> gotPolicyTypeList =
-                new AuthorativeToscaProvider().getPolicyTypeList(pfDao, POLICY_NO_VERSION, VERSION_000);
+                new AuthorativeToscaProvider().getPolicyTypeList(pfDao, POLICY_NO_VERSION, VERSION_001);
         assertEquals(2, gotPolicyTypeList.size());
         assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName()));
 
@@ -162,14 +162,14 @@ public class AuthorativeToscaProviderPolicyTypeTest {
         assertEquals(2, gotPolicyTypeList.size());
         assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName()));
 
-        gotPolicyTypeList = new AuthorativeToscaProvider().getPolicyTypeList(pfDao, null, VERSION_000);
+        gotPolicyTypeList = new AuthorativeToscaProvider().getPolicyTypeList(pfDao, null, VERSION_001);
         assertEquals(2, gotPolicyTypeList.size());
         assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName()));
 
         assertThatThrownBy(() -> new AuthorativeToscaProvider().getPolicyTypeList(new DefaultPfDao(), POLICY_NO_VERSION,
-                VERSION_000)).hasMessageContaining("Policy Framework DAO has not been initialized");
+                VERSION_001)).hasMessageContaining("Policy Framework DAO has not been initialized");
 
-        assertTrue(new AuthorativeToscaProvider().getPolicyTypeList(pfDao, "i.dont.Exist", VERSION_000).isEmpty());
+        assertTrue(new AuthorativeToscaProvider().getPolicyTypeList(pfDao, "i.dont.Exist", VERSION_001).isEmpty());
     }
 
     @Test
@@ -212,7 +212,7 @@ public class AuthorativeToscaProviderPolicyTypeTest {
         ToscaServiceTemplate createdServiceTemplate =
                 new AuthorativeToscaProvider().createPolicyTypes(pfDao, toscaServiceTemplate);
 
-        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION0);
+        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION1);
 
         ToscaPolicyType beforePolicyType = toscaServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
         ToscaPolicyType createdPolicyType = createdServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
@@ -234,14 +234,14 @@ public class AuthorativeToscaProviderPolicyTypeTest {
         assertEquals(0, ObjectUtils.compare(beforePolicyType.getDescription(), gotPolicyType.getDescription()));
 
         gotServiceTemplate = new AuthorativeToscaProvider().getFilteredPolicyTypes(pfDao,
-                ToscaPolicyTypeFilter.builder().name(policyTypeKey.getName()).version(VERSION_000).build());
+                ToscaPolicyTypeFilter.builder().name(policyTypeKey.getName()).version(VERSION_001).build());
 
         gotPolicyType = gotServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
         assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName()));
         assertEquals(0, ObjectUtils.compare(beforePolicyType.getDescription(), gotPolicyType.getDescription()));
 
         List<ToscaPolicyType> gotPolicyTypeList =
-                new AuthorativeToscaProvider().getPolicyTypeList(pfDao, POLICY_NO_VERSION, VERSION_000);
+                new AuthorativeToscaProvider().getPolicyTypeList(pfDao, POLICY_NO_VERSION, VERSION_001);
         assertEquals(2, gotPolicyTypeList.size());
         assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName()));
 
@@ -256,7 +256,7 @@ public class AuthorativeToscaProviderPolicyTypeTest {
         assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName()));
 
         gotPolicyTypeList = new AuthorativeToscaProvider().getFilteredPolicyTypeList(pfDao,
-                ToscaPolicyTypeFilter.builder().name(policyTypeKey.getName()).version(VERSION_000).build());
+                ToscaPolicyTypeFilter.builder().name(policyTypeKey.getName()).version(VERSION_001).build());
         assertEquals(1, gotPolicyTypeList.size());
         assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName()));
 
@@ -296,7 +296,7 @@ public class AuthorativeToscaProviderPolicyTypeTest {
         ToscaServiceTemplate createdServiceTemplate =
                 new AuthorativeToscaProvider().createPolicyTypes(pfDao, toscaServiceTemplate);
 
-        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION0);
+        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION1);
 
         ToscaPolicyType beforePolicyType = toscaServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
         ToscaPolicyType createdPolicyType = createdServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
@@ -328,7 +328,7 @@ public class AuthorativeToscaProviderPolicyTypeTest {
         ToscaServiceTemplate createdServiceTemplate =
                 new AuthorativeToscaProvider().createPolicyTypes(pfDao, toscaServiceTemplate);
 
-        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION0);
+        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION1);
 
         ToscaPolicyType beforePolicyType = toscaServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
         ToscaPolicyType createdPolicyType = createdServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
@@ -379,7 +379,7 @@ public class AuthorativeToscaProviderPolicyTypeTest {
         ToscaServiceTemplate createdServiceTemplate =
                 new AuthorativeToscaProvider().createPolicyTypes(pfDao, toscaServiceTemplate);
 
-        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION0);
+        PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION1);
 
         ToscaPolicyType beforePolicyType = toscaServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
         ToscaPolicyType createdPolicyType = createdServiceTemplate.getPolicyTypes().get(policyTypeKey.getName());
@@ -395,7 +395,7 @@ public class AuthorativeToscaProviderPolicyTypeTest {
 
         assertThatThrownBy(() -> {
             new AuthorativeToscaProvider().getPolicyTypes(pfDao, policyTypeKey.getName(), policyTypeKey.getVersion());
-        }).hasMessage("policy types for onap.policies.NoVersion:0.0.0 do not exist");
+        }).hasMessage("policy types for onap.policies.NoVersion:0.0.1 do not exist");
     }
 
     @Test
index 0aa1da0..4dcfeaf 100644 (file)
@@ -1,7 +1,7 @@
 /*-
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2019-2020 Nordix Foundation.
- *  Modifications Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ *  Modifications 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.
@@ -71,7 +71,7 @@ public class LegacyOperationalPolicyMapperTest {
         JpaToscaServiceTemplate policyTypeServiceTemplate = new JpaToscaServiceTemplate();
         policyTypeServiceTemplate.fromAuthorative(policyTypes);
 
-        String vcpePolicyJson = ResourceUtils.getResourceAsString("policies/vCPE.policy.operational.input.json");
+        String vcpePolicyJson = ResourceUtils.getResourceAsString("policies/vCPE.policy.operational.legacy.input.json");
         LegacyOperationalPolicy legacyOperationalPolicy =
                 standardCoder.decode(vcpePolicyJson, LegacyOperationalPolicy.class);
 
index ec03122..4d0fd6f 100644 (file)
@@ -1,7 +1,7 @@
 /*-
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2019-2020 Nordix Foundation.
- *  Modifications Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ *  Modifications 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.
@@ -51,8 +51,8 @@ import org.yaml.snakeyaml.Yaml;
  */
 public class LegacyProvider4LegacyOperationalTest {
     private static final String POLICY_ID_IS_NULL = "^policyId is marked .*on.*ull but is null$";
-    private static final String VCPE_OUTPUT_JSON = "policies/vCPE.policy.operational.output.json";
-    private static final String VCPE_INPUT_JSON = "policies/vCPE.policy.operational.input.json";
+    private static final String VCPE_OUTPUT_JSON = "policies/vCPE.policy.operational.legacy.output.json";
+    private static final String VCPE_INPUT_JSON = "policies/vCPE.policy.operational.legacy.input.json";
     private static final String DAO_IS_NULL = "^dao is marked .*on.*ull but is null$";
     private PfDao pfDao;
     private StandardCoder standardCoder;
index 01a52f7..0a8283e 100644 (file)
@@ -1,7 +1,7 @@
 /*-
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2019-2020 Nordix Foundation.
- *  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.
@@ -155,9 +155,9 @@ public class MonitoringPolicyTypeSerializationTest {
 
         Entry<PfConceptKey, JpaToscaPolicyType> firstPolicyType = policyTypesIter.next();
         assertEquals(MONITORING, firstPolicyType.getKey().getName());
-        assertEquals(VERSION_000, firstPolicyType.getKey().getVersion());
+        assertEquals(VERSION_100, firstPolicyType.getKey().getVersion());
         assertEquals("tosca.policies.Root", firstPolicyType.getValue().getDerivedFrom().getName());
-        assertEquals("a base policy type for all policies that governs monitoring provisioning",
+        assertEquals("a base policy type for all policies that govern monitoring provisioning",
                 firstPolicyType.getValue().getDescription());
 
         Entry<PfConceptKey, JpaToscaPolicyType> secondPolicyType = policyTypesIter.next();
@@ -376,13 +376,13 @@ public class MonitoringPolicyTypeSerializationTest {
         assertEquals(MONITORING, firstPolicyType.getKey().getName());
         assertEquals(VERSION_100, firstPolicyType.getKey().getVersion());
         assertEquals("tosca.policies.Root", firstPolicyType.getValue().getDerivedFrom().getName());
-        assertEquals("a base policy type for all policies that govern monitoring provision",
+        assertEquals("a base policy type for all policies that govern monitoring provisioning",
                 firstPolicyType.getValue().getDescription());
 
         Entry<PfConceptKey, JpaToscaPolicyType> secondPolicyType = policyTypesIter.next();
         assertEquals(DCAE, secondPolicyType.getKey().getName());
         assertEquals(VERSION_100, secondPolicyType.getKey().getVersion());
-        assertEquals("policy.nodes.Root", secondPolicyType.getValue().getDerivedFrom().getName());
+        assertEquals("onap.policies.Monitoring", secondPolicyType.getValue().getDerivedFrom().getName());
         assertTrue(secondPolicyType.getValue().getProperties().size() == 2);
 
         Iterator<JpaToscaProperty> propertiesIter = secondPolicyType.getValue().getProperties().values().iterator();
index ca46de1..f9a0143 100644 (file)
@@ -201,5 +201,19 @@ public class ToscaServiceTemplateUtilsTest {
         assertEquals(dt1, dtIterator.next());
         assertEquals(pt0, compositeTemplate05.getPolicyTypes().getAll(null).iterator().next());
         assertEquals(p0, compositeTemplate05.getTopologyTemplate().getPolicies().getAll(null).iterator().next());
+
+        JpaToscaServiceTemplate fragmentTemplate09 = new JpaToscaServiceTemplate();
+
+        fragmentTemplate09.setDataTypes(new JpaToscaDataTypes());
+        fragmentTemplate09.getDataTypes().getConceptMap().put(dt1.getKey(), dt1);
+
+        fragmentTemplate09.setPolicyTypes(new JpaToscaPolicyTypes());
+        fragmentTemplate09.getPolicyTypes().getConceptMap().put(pt0.getKey(), pt0);
+
+        fragmentTemplate09.setTopologyTemplate(null);
+
+        JpaToscaServiceTemplate compositeTemplate06 =
+                ToscaServiceTemplateUtils.addFragment(compositeTemplate05, fragmentTemplate09);
+        assertEquals(compositeTemplate05.getTopologyTemplate(), compositeTemplate06.getTopologyTemplate());
     }
 }
index 5923eb2..2dda556 100644 (file)
@@ -6,6 +6,7 @@ policy_types:
       description: The base policy type for all policies that govern optimization
    onap.policies.NoVersion:
       derived_from: onap.policies.Optimization
+      version: 0.0.1
       properties:
          applicableResources:
             type: list