aboutsummaryrefslogtreecommitdiffstats
path: root/models-interactions/model-actors
diff options
context:
space:
mode:
Diffstat (limited to 'models-interactions/model-actors')
-rw-r--r--models-interactions/model-actors/actor.aai/pom.xml83
-rw-r--r--models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiActorServiceProvider.java47
-rw-r--r--models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiCustomQueryOperation.java132
-rw-r--r--models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiGetOperation.java137
-rw-r--r--models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiUtil.java50
-rw-r--r--models-interactions/model-actors/actor.aai/src/main/resources/META-INF/services/org.onap.policy.controlloop.actorServiceProvider.spi.Actor1
-rw-r--r--models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiActorServiceProviderTest.java48
-rw-r--r--models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiCustomQueryOperationTest.java200
-rw-r--r--models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiGetOperationTest.java137
-rw-r--r--models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiUtilTest.java36
-rw-r--r--models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/BasicAaiOperation.java54
-rw-r--r--models-interactions/model-actors/actor.appc/pom.xml125
-rw-r--r--models-interactions/model-actors/actor.appc/src/main/java/org/onap/policy/controlloop/actor/appc/AppcActorServiceProvider.java38
-rw-r--r--models-interactions/model-actors/actor.sdnc/pom.xml115
-rw-r--r--models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperation.java (renamed from models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperator.java)15
-rw-r--r--models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperation.java (renamed from models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperator.java)15
-rw-r--r--models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProvider.java8
-rw-r--r--models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperation.java95
-rw-r--r--models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperator.java148
-rw-r--r--models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperationTest.java (renamed from models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperatorTest.java)32
-rw-r--r--models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicOperator.java94
-rw-r--r--models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicSdncOperation.java152
-rw-r--r--models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperationTest.java (renamed from models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperatorTest.java)34
-rw-r--r--models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProviderTest.java4
-rw-r--r--models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncOperationTest.java87
-rw-r--r--models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncOperatorTest.java326
-rw-r--r--models-interactions/model-actors/actor.test/pom.xml74
-rw-r--r--models-interactions/model-actors/actor.test/src/main/java/org/onap/policy/controlloop/actor/test/BasicBidirectionalTopicOperation.java181
-rw-r--r--models-interactions/model-actors/actor.test/src/main/java/org/onap/policy/controlloop/actor/test/BasicHttpOperation.java190
-rw-r--r--models-interactions/model-actors/actor.test/src/test/java/org/onap/policy/controlloop/actor/test/BasicBidirectionalTopicOperationTest.java140
-rw-r--r--models-interactions/model-actors/actor.test/src/test/java/org/onap/policy/controlloop/actor/test/BasicHttpOperationTest.java129
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/ActorService.java6
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandler.java119
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operation.java52
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operator.java10
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Util.java89
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContext.java57
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImpl.java2
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicActor.java108
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperation.java265
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperator.java160
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperation.java245
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperator.java44
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartial.java983
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartial.java758
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicActorParams.java57
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicParams.java (renamed from models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParams.java)26
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/CommonActorParams.java102
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParams.java3
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParams.java94
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParams.java11
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/spi/Actor.java1
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicHandler.java79
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicManager.java37
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/Forwarder.java138
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/SelectorKey.java57
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicListenerImpl.java104
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceTest.java2
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandlerTest.java172
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/UtilTest.java82
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContextTest.java59
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImplTest.java9
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicActorTest.java242
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperationTest.java403
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperatorTest.java143
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpActorTest.java11
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperationTest.java674
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperatorTest.java118
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartialTest.java1295
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartialTest.java1251
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicActorParamsTest.java118
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicParamsTest.java (renamed from models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParamsTest.java)26
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/CommonActorParamsTest.java137
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParamsTest.java11
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParamsTest.java60
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParamsTest.java4
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/PipelineControllerFutureTest.java4
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicHandlerTest.java144
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/ForwarderTest.java199
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/SelectorKeyTest.java93
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicListenerImplTest.java154
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/resources/logback-test.xml7
-rw-r--r--models-interactions/model-actors/pom.xml2
83 files changed, 8588 insertions, 3366 deletions
diff --git a/models-interactions/model-actors/actor.aai/pom.xml b/models-interactions/model-actors/actor.aai/pom.xml
new file mode 100644
index 000000000..4e932a11b
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/pom.xml
@@ -0,0 +1,83 @@
+<?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.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
index 000000000..df427c32c
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiActorServiceProvider.java
@@ -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
index 000000000..e32734b7d
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiCustomQueryOperation.java
@@ -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.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.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+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();
+
+ logMessage(EventType.OUT, CommInfrastructure.REST, 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 CompletableFuture<OperationOutcome> 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));
+
+ return super.postProcessResponse(outcome, url, rawResponse, 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
index 000000000..ee1c4612d
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiGetOperation.java
@@ -0,0 +1,137 @@
+/*-
+ * ============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.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+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();
+
+ logMessage(EventType.OUT, CommInfrastructure.REST, 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 CompletableFuture<OperationOutcome> 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);
+
+ return super.postProcessResponse(outcome, url, rawResponse, 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
index 000000000..14edc3aa1
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiUtil.java
@@ -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
index 000000000..6a52e3f17
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/src/main/resources/META-INF/services/org.onap.policy.controlloop.actorServiceProvider.spi.Actor
@@ -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
index 000000000..513f339fb
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiActorServiceProviderTest.java
@@ -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
index 000000000..a93508757
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiCustomQueryOperationTest.java
@@ -0,0 +1,200 @@
+/*-
+ * ============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.junit.Assert.assertTrue;
+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.ExecutionException;
+import java.util.concurrent.TimeoutException;
+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 BasicAaiOperation<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, getResult(future2));
+
+ // 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, getResult(future2));
+
+ // 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, getResult(future2));
+ }
+
+ 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);
+ }
+
+ private PolicyResult getResult(CompletableFuture<OperationOutcome> future2)
+ throws InterruptedException, ExecutionException, TimeoutException {
+
+ executor.runAll(100);
+ assertTrue(future2.isDone());
+
+ return future2.get().getResult();
+ }
+
+ protected class MyTenantOperator extends HttpOperator {
+ public MyTenantOperator() {
+ super(AaiConstants.ACTOR_NAME, AaiGetOperation.TENANT);
+
+ HttpParams http = HttpParams.builder().clientName(MY_CLIENT).path(PATH).timeoutSec(1).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/AaiGetOperationTest.java b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiGetOperationTest.java
new file mode 100644
index 000000000..654864246
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiGetOperationTest.java
@@ -0,0 +1,137 @@
+/*-
+ * ============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.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import org.junit.Before;
+import org.junit.Test;
+import org.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 AaiGetOperationTest extends BasicAaiOperation<Void> {
+
+ private static final String INPUT_FIELD = "input";
+ private static final String TEXT = "my-text";
+
+ private AaiGetOperation oper;
+
+ public AaiGetOperationTest() {
+ 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());
+
+ executor.runAll(100);
+ assertTrue(future2.isDone());
+
+ assertEquals(PolicyResult.SUCCESS, future2.get().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());
+
+ executor.runAll(100);
+ assertTrue(future2.isDone());
+
+ assertEquals(PolicyResult.FAILURE, future2.get().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
index 000000000..ae38cca35
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiUtilTest.java
@@ -0,0 +1,36 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.aai;
+
+import java.util.Map;
+import org.junit.Test;
+
+public class AaiUtilTest extends BasicAaiOperation<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/BasicAaiOperation.java b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/BasicAaiOperation.java
new file mode 100644
index 000000000..00485c935
--- /dev/null
+++ b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/BasicAaiOperation.java
@@ -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 BasicAaiOperation<Q> extends BasicHttpOperation<Q> {
+
+ /**
+ * Constructs the object using a default actor and operation name.
+ */
+ public BasicAaiOperation() {
+ super();
+ }
+
+ /**
+ * Constructs the object.
+ *
+ * @param actor actor name
+ * @param operation operation name
+ */
+ public BasicAaiOperation(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"));
+ }
+}
diff --git a/models-interactions/model-actors/actor.appc/pom.xml b/models-interactions/model-actors/actor.appc/pom.xml
index 74bff9aa9..0cc243c3f 100644
--- a/models-interactions/model-actors/actor.appc/pom.xml
+++ b/models-interactions/model-actors/actor.appc/pom.xml
@@ -18,57 +18,82 @@
============LICENSE_END=========================================================
-->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
- <artifactId>model-actors</artifactId>
- <version>2.2.1-SNAPSHOT</version>
- </parent>
+ <parent>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+ <artifactId>model-actors</artifactId>
+ <version>2.2.1-SNAPSHOT</version>
+ </parent>
- <artifactId>actor.appc</artifactId>
+ <artifactId>actor.appc</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>appc</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>com.google.code.gson</groupId>
- <artifactId>gson</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.onap.policy.models.policy-models-interactions</groupId>
- <artifactId>simulators</artifactId>
- <version>${project.version}</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.onap.policy.common</groupId>
- <artifactId>policy-endpoints</artifactId>
- <version>${policy.common.version}</version>
- <scope>provided</scope>
- </dependency>
- </dependencies>
+ <dependencies>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+ <artifactId>actorServiceProvider</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>appc</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-actors</groupId>
+ <artifactId>actor.aai</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions</groupId>
+ <artifactId>simulators</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.common</groupId>
+ <artifactId>policy-endpoints</artifactId>
+ <version>${policy.common.version}</version>
+ <scope>provided</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.powermock</groupId>
+ <artifactId>powermock-api-mockito2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
</project>
diff --git a/models-interactions/model-actors/actor.appc/src/main/java/org/onap/policy/controlloop/actor/appc/AppcActorServiceProvider.java b/models-interactions/model-actors/actor.appc/src/main/java/org/onap/policy/controlloop/actor/appc/AppcActorServiceProvider.java
index 0da1e2a27..2491c33a1 100644
--- a/models-interactions/model-actors/actor.appc/src/main/java/org/onap/policy/controlloop/actor/appc/AppcActorServiceProvider.java
+++ b/models-interactions/model-actors/actor.appc/src/main/java/org/onap/policy/controlloop/actor/appc/AppcActorServiceProvider.java
@@ -33,17 +33,19 @@ import org.onap.policy.common.utils.coder.CoderException;
import org.onap.policy.common.utils.coder.StandardCoder;
import org.onap.policy.controlloop.ControlLoopOperation;
import org.onap.policy.controlloop.VirtualControlLoopEvent;
-import org.onap.policy.controlloop.actorserviceprovider.impl.ActorImpl;
+import org.onap.policy.controlloop.actorserviceprovider.impl.BidirectionalTopicActor;
import org.onap.policy.controlloop.policy.Policy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class AppcActorServiceProvider extends ActorImpl {
+public class AppcActorServiceProvider extends BidirectionalTopicActor {
private static final String NAME = "APPC";
private static final Logger logger = LoggerFactory.getLogger(AppcActorServiceProvider.class);
+ // TODO old code: remove lines down to **HERE**
+
private static final StandardCoder coder = new StandardCoder();
// Strings for targets
@@ -57,17 +59,26 @@ public class AppcActorServiceProvider extends ActorImpl {
private static final String RECIPE_MODIFY = "ModifyConfig";
private static final ImmutableList<String> recipes =
- ImmutableList.of(RECIPE_RESTART, RECIPE_REBUILD, RECIPE_MIGRATE, RECIPE_MODIFY);
+ ImmutableList.of(RECIPE_RESTART, RECIPE_REBUILD, RECIPE_MIGRATE, RECIPE_MODIFY);
private static final ImmutableMap<String, List<String>> targets = new ImmutableMap.Builder<String, List<String>>()
- .put(RECIPE_RESTART, ImmutableList.of(TARGET_VM)).put(RECIPE_REBUILD, ImmutableList.of(TARGET_VM))
- .put(RECIPE_MIGRATE, ImmutableList.of(TARGET_VM)).put(RECIPE_MODIFY, ImmutableList.of(TARGET_VNF)).build();
+ .put(RECIPE_RESTART, ImmutableList.of(TARGET_VM)).put(RECIPE_REBUILD, ImmutableList.of(TARGET_VM))
+ .put(RECIPE_MIGRATE, ImmutableList.of(TARGET_VM)).put(RECIPE_MODIFY, ImmutableList.of(TARGET_VNF))
+ .build();
private static final ImmutableMap<String, List<String>> payloads = new ImmutableMap.Builder<String, List<String>>()
- .put(RECIPE_MODIFY, ImmutableList.of("generic-vnf.vnf-id")).build();
+ .put(RECIPE_MODIFY, ImmutableList.of("generic-vnf.vnf-id")).build();
+
+ // **HERE**
+ /**
+ * Constructs the object.
+ */
public AppcActorServiceProvider() {
super(NAME);
}
+
+ // TODO old code: remove lines down to **HERE**
+
@Override
public String actor() {
return NAME;
@@ -89,17 +100,19 @@ public class AppcActorServiceProvider extends ActorImpl {
}
/**
- * Constructs an APPC request conforming to the legacy API. The legacy API will be deprecated in
- * future releases as all legacy functionality is moved into the LCM API.
+ * Constructs an APPC request conforming to the legacy API. The legacy API will be
+ * deprecated in future releases as all legacy functionality is moved into the LCM
+ * API.
*
* @param onset the event that is reporting the alert for policy to perform an action
- * @param operation the control loop operation specifying the actor, operation, target, etc.
- * @param policy the policy the was specified from the yaml generated by CLAMP or through the
- * Policy GUI/API
+ * @param operation the control loop operation specifying the actor, operation,
+ * target, etc.
+ * @param policy the policy the was specified from the yaml generated by CLAMP or
+ * through the Policy GUI/API
* @return an APPC request conforming to the legacy API
*/
public static Request constructRequest(VirtualControlLoopEvent onset, ControlLoopOperation operation, Policy policy,
- String targetVnf) {
+ String targetVnf) {
/*
* Construct an APPC request
*/
@@ -144,4 +157,5 @@ public class AppcActorServiceProvider extends ActorImpl {
}
}
+ // **HERE**
}
diff --git a/models-interactions/model-actors/actor.sdnc/pom.xml b/models-interactions/model-actors/actor.sdnc/pom.xml
index 04040dbbf..4bb03ecec 100644
--- a/models-interactions/model-actors/actor.sdnc/pom.xml
+++ b/models-interactions/model-actors/actor.sdnc/pom.xml
@@ -19,58 +19,71 @@
============LICENSE_END=========================================================
-->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
- <artifactId>model-actors</artifactId>
- <version>2.2.1-SNAPSHOT</version>
- </parent>
+ <parent>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+ <artifactId>model-actors</artifactId>
+ <version>2.2.1-SNAPSHOT</version>
+ </parent>
- <artifactId>actor.sdnc</artifactId>
+ <artifactId>actor.sdnc</artifactId>
- <dependencies>
- <dependency>
- <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
- <artifactId>actorServiceProvider</artifactId>
- <version>${project.version}</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
- <artifactId>sdnc</artifactId>
- <version>${project.version}</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
- <artifactId>events</artifactId>
- <version>${project.version}</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
- <artifactId>aai</artifactId>
- <version>${project.version}</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.onap.policy.common</groupId>
- <artifactId>policy-endpoints</artifactId>
- <version>${policy.common.version}</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.onap.policy.models.policy-models-interactions</groupId>
- <artifactId>simulators</artifactId>
- <version>${project.version}</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
+ <dependencies>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+ <artifactId>actorServiceProvider</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>sdnc</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>events</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>aai</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.common</groupId>
+ <artifactId>policy-endpoints</artifactId>
+ <version>${policy.common.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+ <artifactId>actor.test</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions</groupId>
+ <artifactId>simulators</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-api-mockito2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
</project>
diff --git a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperator.java b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperation.java
index 2927bd85b..26cdfada3 100644
--- a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperator.java
+++ b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperation.java
@@ -23,6 +23,8 @@ package org.onap.policy.controlloop.actor.sdnc;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
import org.onap.policy.sdnc.SdncHealRequest;
import org.onap.policy.sdnc.SdncHealRequestHeaderInfo;
import org.onap.policy.sdnc.SdncHealRequestInfo;
@@ -34,7 +36,7 @@ import org.onap.policy.sdnc.SdncHealVfModuleRequestInput;
import org.onap.policy.sdnc.SdncHealVnfInfo;
import org.onap.policy.sdnc.SdncRequest;
-public class BandwidthOnDemandOperator extends SdncOperator {
+public class BandwidthOnDemandOperation extends SdncOperation {
public static final String NAME = "BandwidthOnDemand";
public static final String URI = "/GENERIC-RESOURCE-API:vf-module-topology-operation";
@@ -46,14 +48,17 @@ public class BandwidthOnDemandOperator extends SdncOperator {
/**
* Constructs the object.
*
- * @param actorName name of the actor with which this operator is associated
+ * @param params operation parameters
+ * @param operator operator that created this operation
*/
- public BandwidthOnDemandOperator(String actorName) {
- super(actorName, NAME);
+ public BandwidthOnDemandOperation(ControlLoopOperationParams params, HttpOperator operator) {
+ super(params, operator);
}
@Override
- protected SdncRequest constructRequest(ControlLoopEventContext context) {
+ protected SdncRequest makeRequest(int attempt) {
+ ControlLoopEventContext context = params.getContext();
+
String serviceInstance = context.getEnrichment().get(SERVICE_ID_KEY);
if (StringUtils.isBlank(serviceInstance)) {
throw new IllegalArgumentException("missing enrichment data, " + SERVICE_ID_KEY);
diff --git a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperator.java b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperation.java
index da400f8eb..f255f3e84 100644
--- a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperator.java
+++ b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperation.java
@@ -23,6 +23,8 @@ package org.onap.policy.controlloop.actor.sdnc;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
import org.onap.policy.sdnc.SdncHealNetworkInfo;
import org.onap.policy.sdnc.SdncHealRequest;
import org.onap.policy.sdnc.SdncHealRequestHeaderInfo;
@@ -30,7 +32,7 @@ import org.onap.policy.sdnc.SdncHealRequestInfo;
import org.onap.policy.sdnc.SdncHealServiceInfo;
import org.onap.policy.sdnc.SdncRequest;
-public class RerouteOperator extends SdncOperator {
+public class RerouteOperation extends SdncOperation {
public static final String NAME = "Reroute";
public static final String URI = "/GENERIC-RESOURCE-API:network-topology-operation";
@@ -42,14 +44,17 @@ public class RerouteOperator extends SdncOperator {
/**
* Constructs the object.
*
- * @param actorName name of the actor with which this operator is associated
+ * @param params operation parameters
+ * @param operator operator that created this operation
*/
- public RerouteOperator(String actorName) {
- super(actorName, NAME);
+ public RerouteOperation(ControlLoopOperationParams params, HttpOperator operator) {
+ super(params, operator);
}
@Override
- protected SdncRequest constructRequest(ControlLoopEventContext context) {
+ protected SdncRequest makeRequest(int attempt) {
+ ControlLoopEventContext context = params.getContext();
+
String serviceInstance = context.getEnrichment().get(SERVICE_ID_KEY);
if (StringUtils.isBlank(serviceInstance)) {
throw new IllegalArgumentException("missing enrichment data, " + SERVICE_ID_KEY);
diff --git a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProvider.java b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProvider.java
index 8dc8ba50d..99a4fdadd 100644
--- a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProvider.java
+++ b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProvider.java
@@ -30,6 +30,7 @@ import java.util.UUID;
import org.onap.policy.controlloop.ControlLoopOperation;
import org.onap.policy.controlloop.VirtualControlLoopEvent;
import org.onap.policy.controlloop.actorserviceprovider.impl.HttpActor;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
import org.onap.policy.controlloop.policy.Policy;
import org.onap.policy.sdnc.SdncHealNetworkInfo;
import org.onap.policy.sdnc.SdncHealRequest;
@@ -76,8 +77,11 @@ public class SdncActorServiceProvider extends HttpActor {
public SdncActorServiceProvider() {
super(NAME);
- addOperator(new RerouteOperator(NAME));
- addOperator(new BandwidthOnDemandOperator(NAME));
+ addOperator(HttpOperator.makeOperator(NAME, RerouteOperation.NAME,
+ RerouteOperation::new));
+
+ addOperator(HttpOperator.makeOperator(NAME, BandwidthOnDemandOperation.NAME,
+ BandwidthOnDemandOperation::new));
}
diff --git a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperation.java b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperation.java
new file mode 100644
index 000000000..406722ef5
--- /dev/null
+++ b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperation.java
@@ -0,0 +1,95 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.sdnc;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperation;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.sdnc.SdncRequest;
+import org.onap.policy.sdnc.SdncResponse;
+
+/**
+ * Superclass for SDNC Operators.
+ */
+public abstract class SdncOperation extends HttpOperation<SdncResponse> {
+
+ /**
+ * Constructs the object.
+ *
+ * @param params operation parameters
+ * @param operator operator that created this operation
+ */
+ public SdncOperation(ControlLoopOperationParams params, HttpOperator operator) {
+ super(params, operator, SdncResponse.class);
+ }
+
+ /**
+ * Starts the GUARD.
+ */
+ @Override
+ protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
+ return startGuardAsync();
+ }
+
+ @Override
+ protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+ SdncRequest request = makeRequest(attempt);
+
+ Entity<SdncRequest> entity = Entity.entity(request, MediaType.APPLICATION_JSON);
+
+ Map<String, Object> headers = makeHeaders();
+
+ headers.put("Accept", MediaType.APPLICATION_JSON);
+ String url = makeUrl();
+
+ logMessage(EventType.OUT, CommInfrastructure.REST, url, request);
+
+ // @formatter:off
+ return handleResponse(outcome, url,
+ callback -> operator.getClient().post(callback, makePath(), entity, headers));
+ // @formatter:on
+ }
+
+ /**
+ * Makes the request.
+ *
+ * @param attempt current attempt, starting with "1"
+ * @return a new request to be posted
+ */
+ protected abstract SdncRequest makeRequest(int attempt);
+
+ /**
+ * Checks that the response has an "output" and that the output indicates success.
+ */
+ @Override
+ protected boolean isSuccess(Response rawResponse, SdncResponse response) {
+ return response.getResponseOutput() != null && "200".equals(response.getResponseOutput().getResponseCode());
+ }
+}
diff --git a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperator.java b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperator.java
deleted file mode 100644
index 479ee908d..000000000
--- a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperator.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*-
- * ============LICENSE_START=======================================================
- * ONAP
- * ================================================================================
- * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
- * ================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.policy.controlloop.actor.sdnc;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import org.onap.policy.common.endpoints.http.client.HttpClient;
-import org.onap.policy.common.utils.coder.CoderException;
-import org.onap.policy.common.utils.coder.StandardCoder;
-import org.onap.policy.controlloop.actorserviceprovider.AsyncResponseHandler;
-import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
-import org.onap.policy.controlloop.actorserviceprovider.Util;
-import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
-import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
-import org.onap.policy.controlloop.policy.PolicyResult;
-import org.onap.policy.sdnc.SdncRequest;
-import org.onap.policy.sdnc.SdncResponse;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Superclass for SDNC Operators.
- */
-public abstract class SdncOperator extends HttpOperator {
- private static final Logger logger = LoggerFactory.getLogger(SdncOperator.class);
-
- /**
- * Constructs the object.
- *
- * @param actorName name of the actor with which this operator is associated
- * @param name operation name
- */
- public SdncOperator(String actorName, String name) {
- super(actorName, name);
- }
-
- @Override
- protected CompletableFuture<OperationOutcome> startOperationAsync(ControlLoopOperationParams params, int attempt,
- OperationOutcome outcome) {
-
- SdncRequest request = constructRequest(params.getContext());
- return postRequest(params, outcome, request);
- }
-
- /**
- * Constructs the request.
- *
- * @param context associated event context
- * @return a new request
- */
- protected abstract SdncRequest constructRequest(ControlLoopEventContext context);
-
- /**
- * Posts the request and and arranges to retrieve the response.
- *
- * @param params operation parameters
- * @param outcome updated with the response
- * @param sdncRequest request to be posted
- * @return the result of the request
- */
- private CompletableFuture<OperationOutcome> postRequest(ControlLoopOperationParams params, OperationOutcome outcome,
- SdncRequest sdncRequest) {
- Map<String, Object> headers = new HashMap<>();
-
- headers.put("Accept", "application/json");
- String sdncUrl = getClient().getBaseUrl();
-
- Util.logRestRequest(sdncUrl, sdncRequest);
-
- Entity<SdncRequest> entity = Entity.entity(sdncRequest, MediaType.APPLICATION_JSON);
-
- ResponseHandler handler = new ResponseHandler(params, outcome, sdncUrl);
- return handler.handle(getClient().post(handler, getPath(), entity, headers));
- }
-
- private class ResponseHandler extends AsyncResponseHandler<Response> {
- private final String sdncUrl;
-
- public ResponseHandler(ControlLoopOperationParams params, OperationOutcome outcome, String sdncUrl) {
- super(params, outcome);
- this.sdncUrl = sdncUrl;
- }
-
- /**
- * Handles the response.
- */
- @Override
- protected OperationOutcome doComplete(Response rawResponse) {
- String strResponse = HttpClient.getBody(rawResponse, String.class);
-
- Util.logRestResponse(sdncUrl, strResponse);
-
- SdncResponse response;
- try {
- response = makeDecoder().decode(strResponse, SdncResponse.class);
- } catch (CoderException e) {
- logger.warn("Sdnc Heal cannot decode response with http error code {}", rawResponse.getStatus(), e);
- return SdncOperator.this.setOutcome(getParams(), getOutcome(), PolicyResult.FAILURE_EXCEPTION);
- }
-
- if (response.getResponseOutput() != null && "200".equals(response.getResponseOutput().getResponseCode())) {
- return SdncOperator.this.setOutcome(getParams(), getOutcome(), PolicyResult.SUCCESS);
-
- } else {
- logger.info("Sdnc Heal Restcall failed with http error code {}", rawResponse.getStatus());
- return SdncOperator.this.setOutcome(getParams(), getOutcome(), PolicyResult.FAILURE);
- }
- }
-
- /**
- * Handles exceptions.
- */
- @Override
- protected OperationOutcome doFailed(Throwable thrown) {
- logger.info("Sdnc Heal Restcall threw an exception", thrown);
- return SdncOperator.this.setOutcome(getParams(), getOutcome(), PolicyResult.FAILURE_EXCEPTION);
- }
- }
-
- // these may be overridden by junit tests
-
- protected StandardCoder makeDecoder() {
- return new StandardCoder();
- }
-}
diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperatorTest.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperationTest.java
index 02931a4f1..42042da67 100644
--- a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperatorTest.java
+++ b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperationTest.java
@@ -26,45 +26,51 @@ import static org.junit.Assert.assertNotNull;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
-import org.onap.policy.common.utils.coder.CoderException;
import org.onap.policy.sdnc.SdncRequest;
-public class BandwidthOnDemandOperatorTest extends BasicOperator {
+public class BandwidthOnDemandOperationTest extends BasicSdncOperation {
- private BandwidthOnDemandOperator oper;
+ private BandwidthOnDemandOperation oper;
+ public BandwidthOnDemandOperationTest() {
+ super(DEFAULT_ACTOR, BandwidthOnDemandOperation.NAME);
+ }
/**
* Set up.
*/
@Before
- public void setUp() {
- makeContext();
- oper = new BandwidthOnDemandOperator(ACTOR);
+ public void setUp() throws Exception {
+ super.setUp();
+ oper = new BandwidthOnDemandOperation(params, operator);
}
@Test
public void testBandwidthOnDemandOperator() {
- assertEquals(ACTOR, oper.getActorName());
- assertEquals(BandwidthOnDemandOperator.NAME, oper.getName());
+ assertEquals(DEFAULT_ACTOR, oper.getActorName());
+ assertEquals(BandwidthOnDemandOperation.NAME, oper.getName());
}
@Test
- public void testConstructRequest() throws CoderException {
- SdncRequest request = oper.constructRequest(context);
+ public void testMakeRequest() throws Exception {
+ SdncRequest request = oper.makeRequest(1);
assertEquals("my-service", request.getNsInstanceId());
assertEquals(REQ_ID, request.getRequestId());
- assertEquals(BandwidthOnDemandOperator.URI, request.getUrl());
+ assertEquals(BandwidthOnDemandOperation.URI, request.getUrl());
assertNotNull(request.getHealRequest().getRequestHeaderInfo().getSvcRequestId());
verifyRequest("bod.json", request);
- verifyMissing(oper, BandwidthOnDemandOperator.SERVICE_ID_KEY, "service");
+ verifyMissing(BandwidthOnDemandOperation.SERVICE_ID_KEY, "service", BandwidthOnDemandOperation::new);
+
+ // perform the operation
+ makeContext();
+ verifyRequest("bod.json", verifyOperation(oper));
}
@Override
protected Map<String, String> makeEnrichment() {
- return Map.of(BandwidthOnDemandOperator.SERVICE_ID_KEY, "my-service", BandwidthOnDemandOperator.VNF_ID,
+ return Map.of(BandwidthOnDemandOperation.SERVICE_ID_KEY, "my-service", BandwidthOnDemandOperation.VNF_ID,
"my-vnf");
}
}
diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicOperator.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicOperator.java
deleted file mode 100644
index b9028d462..000000000
--- a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicOperator.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*-
- * ============LICENSE_START=======================================================
- * ONAP
- * ================================================================================
- * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
- * ================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.policy.controlloop.actor.sdnc;
-
-import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
-import static org.junit.Assert.assertEquals;
-
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.UUID;
-import org.onap.policy.common.utils.coder.CoderException;
-import org.onap.policy.common.utils.coder.StandardCoder;
-import org.onap.policy.common.utils.resources.ResourceUtils;
-import org.onap.policy.controlloop.VirtualControlLoopEvent;
-import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
-
-/**
- * Superclass for various operator tests.
- */
-public abstract class BasicOperator {
- protected static final UUID REQ_ID = UUID.randomUUID();
- protected static final String ACTOR = "my-actor";
-
- protected Map<String, String> enrichment;
- protected VirtualControlLoopEvent event;
- protected ControlLoopEventContext context;
-
- /**
- * Pretty-prints a request and verifies that the result matches the expected JSON.
- *
- * @param <T> request type
- * @param expectedJsonFile name of the file containing the expected JSON
- * @param request request to verify
- * @throws CoderException if the request cannot be pretty-printed
- */
- protected <T> void verifyRequest(String expectedJsonFile, T request) throws CoderException {
- String json = new StandardCoder().encode(request, true);
- String expected = ResourceUtils.getResourceAsString(expectedJsonFile);
-
- // strip request id, because it changes each time
- final String stripper = "svc-request-id[^,]*";
- json = json.replaceFirst(stripper, "").trim();
- expected = expected.replaceFirst(stripper, "").trim();
-
- assertEquals(expected, json);
- }
-
- /**
- * Verifies that an exception is thrown if a field is missing from the enrichment
- * data.
- *
- * @param oper operator to construct the request
- * @param fieldName name of the field to be removed from the enrichment data
- * @param expectedText text expected in the exception message
- */
- protected void verifyMissing(SdncOperator oper, String fieldName, String expectedText) {
- makeContext();
- enrichment.remove(fieldName);
-
- assertThatIllegalArgumentException().isThrownBy(() -> oper.constructRequest(context))
- .withMessageContaining("missing").withMessageContaining(expectedText);
- }
-
- protected void makeContext() {
- // need a mutable map, so make a copy
- enrichment = new TreeMap<>(makeEnrichment());
-
- event = new VirtualControlLoopEvent();
- event.setRequestId(REQ_ID);
- event.setAai(enrichment);
-
- context = new ControlLoopEventContext(event);
- }
-
- protected abstract Map<String, String> makeEnrichment();
-}
diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicSdncOperation.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicSdncOperation.java
new file mode 100644
index 000000000..db8751d26
--- /dev/null
+++ b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicSdncOperation.java
@@ -0,0 +1,152 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.sdnc;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.function.BiFunction;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.resources.ResourceUtils;
+import org.onap.policy.controlloop.actor.test.BasicHttpOperation;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.onap.policy.sdnc.SdncRequest;
+import org.onap.policy.sdnc.SdncResponse;
+import org.onap.policy.sdnc.SdncResponseOutput;
+import org.powermock.reflect.Whitebox;
+
+/**
+ * Superclass for various operator tests.
+ */
+public abstract class BasicSdncOperation extends BasicHttpOperation<SdncRequest> {
+
+ protected SdncResponse response;
+
+ /**
+ * Constructs the object using a default actor and operation name.
+ */
+ public BasicSdncOperation() {
+ super();
+ }
+
+ /**
+ * Constructs the object.
+ *
+ * @param actor actor name
+ * @param operation operation name
+ */
+ public BasicSdncOperation(String actor, String operation) {
+ super(actor, operation);
+ }
+
+ /**
+ * Initializes mocks and sets up.
+ */
+ public void setUp() throws Exception {
+ super.setUp();
+
+ response = new SdncResponse();
+
+ SdncResponseOutput output = new SdncResponseOutput();
+ response.setResponseOutput(output);
+ output.setResponseCode("200");
+
+ when(rawResponse.readEntity(String.class)).thenReturn(new StandardCoder().encode(response));
+ }
+
+ /**
+ * Runs the operation and verifies that the response is successful.
+ *
+ * @param operation operation to run
+ * @return the request that was posted
+ */
+ protected SdncRequest verifyOperation(SdncOperation operation)
+ throws InterruptedException, ExecutionException, TimeoutException {
+
+ CompletableFuture<OperationOutcome> future2 = operation.start();
+ executor.runAll(100);
+ assertFalse(future2.isDone());
+
+ verify(client).post(callbackCaptor.capture(), any(), requestCaptor.capture(), any());
+ callbackCaptor.getValue().completed(rawResponse);
+
+ executor.runAll(100);
+ assertTrue(future2.isDone());
+
+ assertEquals(PolicyResult.SUCCESS, future2.get().getResult());
+
+ return requestCaptor.getValue().getEntity();
+ }
+
+ /**
+ * Pretty-prints a request and verifies that the result matches the expected JSON.
+ *
+ * @param <T> request type
+ * @param expectedJsonFile name of the file containing the expected JSON
+ * @param request request to verify
+ * @throws CoderException if the request cannot be pretty-printed
+ */
+ protected <T> void verifyRequest(String expectedJsonFile, T request) throws CoderException {
+ String json = new StandardCoder().encode(request, true);
+ String expected = ResourceUtils.getResourceAsString(expectedJsonFile);
+
+ // strip request id, because it changes each time
+ final String stripper = "svc-request-id[^,]*";
+ json = json.replaceFirst(stripper, "").trim();
+ expected = expected.replaceFirst(stripper, "").trim();
+
+ assertEquals(expected, json);
+ }
+
+ /**
+ * Verifies that an exception is thrown if a field is missing from the enrichment
+ * data.
+ *
+ * @param fieldName name of the field to be removed from the enrichment data
+ * @param expectedText text expected in the exception message
+ */
+ protected void verifyMissing(String fieldName, String expectedText,
+ BiFunction<ControlLoopOperationParams,HttpOperator,SdncOperation> maker) {
+
+ makeContext();
+ enrichment.remove(fieldName);
+
+ SdncOperation oper = maker.apply(params, operator);
+
+ assertThatIllegalArgumentException().isThrownBy(() -> Whitebox.invokeMethod(oper, "makeRequest", 1))
+ .withMessageContaining("missing").withMessageContaining(expectedText);
+ }
+
+ protected abstract Map<String, String> makeEnrichment();
+}
diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperatorTest.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperationTest.java
index 0a7bcad6f..a98c38180 100644
--- a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperatorTest.java
+++ b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperationTest.java
@@ -26,45 +26,51 @@ import static org.junit.Assert.assertNotNull;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
-import org.onap.policy.common.utils.coder.CoderException;
import org.onap.policy.sdnc.SdncRequest;
-public class RerouteOperatorTest extends BasicOperator {
+public class RerouteOperationTest extends BasicSdncOperation {
- private RerouteOperator oper;
+ private RerouteOperation oper;
+ public RerouteOperationTest() {
+ super(DEFAULT_ACTOR, RerouteOperation.NAME);
+ }
/**
* Set up.
*/
@Before
- public void setUp() {
- makeContext();
- oper = new RerouteOperator(ACTOR);
+ public void setUp() throws Exception {
+ super.setUp();
+ oper = new RerouteOperation(params, operator);
}
@Test
public void testRerouteOperator() {
- assertEquals(ACTOR, oper.getActorName());
- assertEquals(RerouteOperator.NAME, oper.getName());
+ assertEquals(DEFAULT_ACTOR, oper.getActorName());
+ assertEquals(RerouteOperation.NAME, oper.getName());
}
@Test
- public void testConstructRequest() throws CoderException {
- SdncRequest request = oper.constructRequest(context);
+ public void testMakeRequest() throws Exception {
+ SdncRequest request = oper.makeRequest(1);
assertEquals("my-service", request.getNsInstanceId());
assertEquals(REQ_ID, request.getRequestId());
- assertEquals(RerouteOperator.URI, request.getUrl());
+ assertEquals(RerouteOperation.URI, request.getUrl());
assertNotNull(request.getHealRequest().getRequestHeaderInfo().getSvcRequestId());
verifyRequest("reroute.json", request);
- verifyMissing(oper, RerouteOperator.SERVICE_ID_KEY, "service");
- verifyMissing(oper, RerouteOperator.NETWORK_ID_KEY, "network");
+ verifyMissing(RerouteOperation.SERVICE_ID_KEY, "service", RerouteOperation::new);
+ verifyMissing(RerouteOperation.NETWORK_ID_KEY, "network", RerouteOperation::new);
+
+ // perform the operation
+ makeContext();
+ verifyRequest("reroute.json", verifyOperation(oper));
}
@Override
protected Map<String, String> makeEnrichment() {
- return Map.of(RerouteOperator.SERVICE_ID_KEY, "my-service", RerouteOperator.NETWORK_ID_KEY, "my-network");
+ return Map.of(RerouteOperation.SERVICE_ID_KEY, "my-service", RerouteOperation.NETWORK_ID_KEY, "my-network");
}
}
diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProviderTest.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProviderTest.java
index 08655c349..ac81d49c9 100644
--- a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProviderTest.java
+++ b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProviderTest.java
@@ -41,7 +41,7 @@ import org.onap.policy.sdnc.SdncRequest;
public class SdncActorServiceProviderTest {
- private static final String REROUTE = RerouteOperator.NAME;
+ private static final String REROUTE = RerouteOperation.NAME;
/**
* Set up before test class.
@@ -63,7 +63,7 @@ public class SdncActorServiceProviderTest {
final SdncActorServiceProvider prov = new SdncActorServiceProvider();
// verify that it has the operators we expect
- var expected = Arrays.asList(BandwidthOnDemandOperator.NAME, RerouteOperator.NAME).stream().sorted()
+ var expected = Arrays.asList(BandwidthOnDemandOperation.NAME, RerouteOperation.NAME).stream().sorted()
.collect(Collectors.toList());
var actual = prov.getOperationNames().stream().sorted().collect(Collectors.toList());
diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncOperationTest.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncOperationTest.java
new file mode 100644
index 000000000..e0825e13b
--- /dev/null
+++ b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncOperationTest.java
@@ -0,0 +1,87 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.sdnc;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Map;
+import java.util.TreeMap;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.sdnc.SdncRequest;
+
+public class SdncOperationTest extends BasicSdncOperation {
+
+ private SdncRequest request;
+ private SdncOperation oper;
+
+ /**
+ * Sets up.
+ */
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ oper = new SdncOperation(params, operator) {
+ @Override
+ protected SdncRequest makeRequest(int attempt) {
+ return request;
+ }
+ };
+ }
+
+ @Test
+ public void testSdncOperator() {
+ assertEquals(DEFAULT_ACTOR, oper.getActorName());
+ assertEquals(DEFAULT_OPERATION, oper.getName());
+ }
+
+ @Test
+ public void testStartOperationAsync_testStartRequestAsync() throws Exception {
+ verifyOperation(oper);
+ }
+
+ @Test
+ public void testIsSuccess() {
+ // success case
+ response.getResponseOutput().setResponseCode("200");
+ assertTrue(oper.isSuccess(null, response));
+
+ // failure code
+ response.getResponseOutput().setResponseCode("555");
+ assertFalse(oper.isSuccess(null, response));
+
+ // null code
+ response.getResponseOutput().setResponseCode(null);
+ assertFalse(oper.isSuccess(null, response));
+
+ // null output
+ response.setResponseOutput(null);
+ assertFalse(oper.isSuccess(null, response));
+ }
+
+ @Override
+ protected Map<String, String> makeEnrichment() {
+ return new TreeMap<>();
+ }
+}
diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncOperatorTest.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncOperatorTest.java
deleted file mode 100644
index 25d383eb8..000000000
--- a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncOperatorTest.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*-
- * ============LICENSE_START=======================================================
- * ONAP
- * ================================================================================
- * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
- * ================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.policy.controlloop.actor.sdnc;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.Status;
-import lombok.Setter;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams;
-import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams.TopicParamsBuilder;
-import org.onap.policy.common.endpoints.http.client.HttpClient;
-import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance;
-import org.onap.policy.common.endpoints.http.server.HttpServletServer;
-import org.onap.policy.common.endpoints.http.server.HttpServletServerFactoryInstance;
-import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties;
-import org.onap.policy.common.gson.GsonMessageBodyHandler;
-import org.onap.policy.common.utils.coder.CoderException;
-import org.onap.policy.common.utils.coder.StandardCoder;
-import org.onap.policy.common.utils.network.NetworkUtil;
-import org.onap.policy.controlloop.VirtualControlLoopEvent;
-import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
-import org.onap.policy.controlloop.actorserviceprovider.Util;
-import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
-import org.onap.policy.controlloop.policy.PolicyResult;
-import org.onap.policy.sdnc.SdncHealRequest;
-import org.onap.policy.sdnc.SdncRequest;
-import org.onap.policy.sdnc.SdncResponse;
-import org.onap.policy.sdnc.SdncResponseOutput;
-
-public class SdncOperatorTest {
- public static final String MEDIA_TYPE_APPLICATION_JSON = "application/json";
- private static final String EXPECTED_EXCEPTION = "expected exception";
- public static final String HTTP_CLIENT = "my-http-client";
- public static final String HTTP_NO_SERVER = "my-http-no-server-client";
- private static final String ACTOR = "my-actor";
- private static final String OPERATION = "my-operation";
-
- /**
- * Outcome to be added to the response.
- */
- @Setter
- private static SdncResponseOutput output;
-
-
- private VirtualControlLoopEvent event;
- private ControlLoopEventContext context;
- private MyOper oper;
-
- /**
- * Starts the SDNC simulator.
- */
- @BeforeClass
- public static void setUpBeforeClass() throws Exception {
- // allocate a port
- int port = NetworkUtil.allocPort();
-
- /*
- * Start the simulator. Must use "Properties" to configure it, otherwise the
- * server will use the wrong serialization provider.
- */
- Properties svrprops = getServerProperties("my-server", port);
- HttpServletServerFactoryInstance.getServerFactory().build(svrprops).forEach(HttpServletServer::start);
-
- /*
- * Start the clients, one to the server, and one to a non-existent server.
- */
- TopicParamsBuilder builder = BusTopicParams.builder().managed(true).hostname("localhost").basePath("sdnc")
- .serializationProvider(GsonMessageBodyHandler.class.getName());
-
- HttpClientFactoryInstance.getClientFactory().build(builder.clientName(HTTP_CLIENT).port(port).build());
-
- HttpClientFactoryInstance.getClientFactory()
- .build(builder.clientName(HTTP_NO_SERVER).port(NetworkUtil.allocPort()).build());
- }
-
- @AfterClass
- public static void tearDownAfterClass() {
- HttpClientFactoryInstance.getClientFactory().destroy();
- HttpServletServerFactoryInstance.getServerFactory().destroy();
- }
-
- /**
- * Initializes {@link #oper} and sets {@link #output} to a success code.
- */
- @Before
- public void setUp() {
- event = new VirtualControlLoopEvent();
- context = new ControlLoopEventContext(event);
-
- initOper(HTTP_CLIENT);
-
- output = new SdncResponseOutput();
- output.setResponseCode("200");
- }
-
- @After
- public void tearDown() {
- oper.shutdown();
- }
-
- @Test
- public void testSdncOperator() {
- assertEquals(ACTOR, oper.getActorName());
- assertEquals(OPERATION, oper.getName());
- assertEquals(ACTOR + "." + OPERATION, oper.getFullName());
- }
-
- @Test
- public void testGetClient() {
- assertNotNull(oper.getTheClient());
- }
-
- @Test
- public void testStartOperationAsync_testPostRequest() throws Exception {
- OperationOutcome outcome = runOperation();
- assertNotNull(outcome);
- assertEquals(PolicyResult.SUCCESS, outcome.getResult());
- }
-
- /**
- * Tests postRequest() when decode() throws an exception.
- */
- @Test
- public void testPostRequestDecodeException() throws Exception {
-
- oper.setDecodeFailure(true);
-
- OperationOutcome outcome = runOperation();
- assertNotNull(outcome);
- assertEquals(PolicyResult.FAILURE_EXCEPTION, outcome.getResult());
- }
-
- /**
- * Tests postRequest() when there is no "output" field in the response.
- */
- @Test
- public void testPostRequestNoOutput() throws Exception {
-
- setOutput(null);
-
- OperationOutcome outcome = runOperation();
- assertNotNull(outcome);
- assertEquals(PolicyResult.FAILURE, outcome.getResult());
- }
-
- /**
- * Tests postRequest() when the output is not a success.
- */
- @Test
- public void testPostRequestOutputFailure() throws Exception {
-
- output.setResponseCode(null);
-
- OperationOutcome outcome = runOperation();
- assertNotNull(outcome);
- assertEquals(PolicyResult.FAILURE, outcome.getResult());
- }
-
- /**
- * Tests postRequest() when the post() request throws an exception retrieving the
- * response.
- */
- @Test
- public void testPostRequestException() throws Exception {
-
- // reset "oper" to point to a non-existent server
- oper.shutdown();
- initOper(HTTP_NO_SERVER);
-
- OperationOutcome outcome = runOperation();
- assertNotNull(outcome);
- assertEquals(PolicyResult.FAILURE_EXCEPTION, outcome.getResult());
- }
-
- private static Properties getServerProperties(String name, int port) {
- final Properties props = new Properties();
- props.setProperty(PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES, name);
-
- final String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + name;
-
- props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_REST_CLASSES_SUFFIX, Server.class.getName());
- props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_HOST_SUFFIX, "localhost");
- props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PORT_SUFFIX, String.valueOf(port));
- props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_MANAGED_SUFFIX, "true");
- props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SWAGGER_SUFFIX, "false");
-
- props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERIALIZATION_PROVIDER,
- GsonMessageBodyHandler.class.getName());
- return props;
- }
-
- /**
- * Initializes {@link #oper}.
- *
- * @param clientName name of the client which it should use
- */
- private void initOper(String clientName) {
- oper = new MyOper();
-
- HttpParams params = HttpParams.builder().clientName(clientName).path("request").build();
- Map<String, Object> mapParams = Util.translateToMap(OPERATION, params);
- oper.configure(mapParams);
- oper.start();
- }
-
- /**
- * Runs the operation.
- *
- * @return the outcome of the operation, or {@code null} if it does not complete in
- * time
- */
- private OperationOutcome runOperation() throws InterruptedException, ExecutionException, TimeoutException {
- ControlLoopOperationParams params =
- ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).context(context).build();
-
- CompletableFuture<OperationOutcome> future = oper.startOperationAsync(params, 1, params.makeOutcome());
-
- return future.get(5, TimeUnit.SECONDS);
- }
-
-
- private class MyOper extends SdncOperator {
-
- /**
- * Set to {@code true} to cause the decoder to throw an exception.
- */
- @Setter
- private boolean decodeFailure = false;
-
- public MyOper() {
- super(ACTOR, OPERATION);
- }
-
- protected HttpClient getTheClient() {
- return getClient();
- }
-
- @Override
- protected SdncRequest constructRequest(ControlLoopEventContext context) {
- SdncRequest request = new SdncRequest();
-
- SdncHealRequest heal = new SdncHealRequest();
- request.setHealRequest(heal);
-
- return request;
- }
-
- @Override
- protected StandardCoder makeDecoder() {
- if (decodeFailure) {
- // return a coder that throws exceptions when decode() is invoked
- return new StandardCoder() {
- @Override
- public <T> T decode(String json, Class<T> clazz) throws CoderException {
- throw new CoderException(EXPECTED_EXCEPTION);
- }
- };
-
- } else {
- return super.makeDecoder();
- }
- }
- }
-
- /**
- * SDNC Simulator.
- */
- @Path("/sdnc")
- @Produces(MEDIA_TYPE_APPLICATION_JSON)
- public static class Server {
-
- /**
- * Generates a response.
- *
- * @param request incoming request
- * @return resulting response
- */
- @POST
- @Path("/request")
- @Consumes(value = {MEDIA_TYPE_APPLICATION_JSON})
- public Response postRequest(SdncRequest request) {
-
- SdncResponse response = new SdncResponse();
- response.setResponseOutput(output);
-
- return Response.status(Status.OK).entity(response).build();
- }
- }
-}
diff --git a/models-interactions/model-actors/actor.test/pom.xml b/models-interactions/model-actors/actor.test/pom.xml
new file mode 100644
index 000000000..3a10fa3d1
--- /dev/null
+++ b/models-interactions/model-actors/actor.test/pom.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0"?>
+<!--
+ ============LICENSE_START=======================================================
+ Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ ================================================================================
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ============LICENSE_END=========================================================
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+ <artifactId>model-actors</artifactId>
+ <version>2.2.1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>actor.test</artifactId>
+ <description>Utilities for testing actors</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>events</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+ <artifactId>actorServiceProvider</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.common</groupId>
+ <artifactId>policy-endpoints</artifactId>
+ <version>${policy.common.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.common</groupId>
+ <artifactId>utils-test</artifactId>
+ <version>${policy.common.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-api-mockito2</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/models-interactions/model-actors/actor.test/src/main/java/org/onap/policy/controlloop/actor/test/BasicBidirectionalTopicOperation.java b/models-interactions/model-actors/actor.test/src/main/java/org/onap/policy/controlloop/actor/test/BasicBidirectionalTopicOperation.java
new file mode 100644
index 000000000..14c7ef576
--- /dev/null
+++ b/models-interactions/model-actors/actor.test/src/main/java/org/onap/policy/controlloop/actor/test/BasicBidirectionalTopicOperation.java
@@ -0,0 +1,181 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.test;
+
+import static org.mockito.Mockito.when;
+
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.UUID;
+import java.util.function.BiConsumer;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.utils.coder.Coder;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.common.utils.time.PseudoExecutor;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.ActorService;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.impl.BidirectionalTopicOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.BidirectionalTopicParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicHandler;
+import org.onap.policy.controlloop.actorserviceprovider.topic.Forwarder;
+
+/**
+ * Superclass for various BidirectionalTopicOperation tests.
+ */
+public class BasicBidirectionalTopicOperation {
+ protected static final UUID REQ_ID = UUID.randomUUID();
+ protected static final String DEFAULT_ACTOR = "default-actor";
+ protected static final String DEFAULT_OPERATION = "default-operation";
+ protected static final String MY_SINK = "my-sink";
+ protected static final String MY_SOURCE = "my-source";
+ protected static final String TARGET_ENTITY = "my-target";
+ protected static final Coder coder = new StandardCoder();
+ protected static final int TIMEOUT = 10;
+
+ protected final String actorName;
+ protected final String operationName;
+
+ @Captor
+ protected ArgumentCaptor<BiConsumer<String, StandardCoderObject>> listenerCaptor;
+
+ @Mock
+ protected ActorService service;
+ @Mock
+ protected BidirectionalTopicHandler topicHandler;
+ @Mock
+ protected Forwarder forwarder;
+ @Mock
+ protected BidirectionalTopicOperator operator;
+
+ protected BidirectionalTopicParams topicParams;
+ protected ControlLoopOperationParams params;
+ protected Map<String, String> enrichment;
+ protected VirtualControlLoopEvent event;
+ protected ControlLoopEventContext context;
+ protected OperationOutcome outcome;
+ protected PseudoExecutor executor;
+
+ /**
+ * Constructs the object using a default actor and operation name.
+ */
+ public BasicBidirectionalTopicOperation() {
+ this.actorName = DEFAULT_ACTOR;
+ this.operationName = DEFAULT_OPERATION;
+ }
+
+ /**
+ * Constructs the object.
+ *
+ * @param actor actor name
+ * @param operation operation name
+ */
+ public BasicBidirectionalTopicOperation(String actor, String operation) {
+ this.actorName = actor;
+ this.operationName = operation;
+ }
+
+ /**
+ * Initializes mocks and sets up.
+ */
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ executor = new PseudoExecutor();
+
+ makeContext();
+
+ outcome = params.makeOutcome();
+ topicParams = BidirectionalTopicParams.builder().sinkTopic(MY_SINK).sourceTopic(MY_SOURCE).timeoutSec(TIMEOUT)
+ .build();
+
+ initOperator();
+ }
+
+ /**
+ * Reinitializes {@link #enrichment}, {@link #event}, {@link #context}, and
+ * {@link #params}.
+ * <p/>
+ * Note: {@link #params} is configured to use {@link #executor}.
+ */
+ protected void makeContext() {
+ enrichment = new TreeMap<>(makeEnrichment());
+
+ event = new VirtualControlLoopEvent();
+ event.setRequestId(REQ_ID);
+ event.setAai(enrichment);
+
+ context = new ControlLoopEventContext(event);
+
+ params = ControlLoopOperationParams.builder().executor(executor).context(context).actorService(service)
+ .actor(actorName).operation(operationName).targetEntity(TARGET_ENTITY).payload(makePayload())
+ .build();
+ }
+
+ protected Map<String, String> makePayload() {
+ return null;
+ }
+
+ /**
+ * Initializes an operator so that it is "alive" and has the given names.
+ */
+ protected void initOperator() {
+ when(operator.isAlive()).thenReturn(true);
+ when(operator.getFullName()).thenReturn(actorName + "." + operationName);
+ when(operator.getActorName()).thenReturn(actorName);
+ when(operator.getName()).thenReturn(operationName);
+ when(operator.getTopicHandler()).thenReturn(topicHandler);
+ when(operator.getForwarder()).thenReturn(forwarder);
+ when(operator.getParams()).thenReturn(topicParams);
+ }
+
+ /**
+ * Makes enrichment data.
+ *
+ * @return enrichment data
+ */
+ protected Map<String, String> makeEnrichment() {
+ return new TreeMap<>();
+ }
+
+ /**
+ * Provides a response to the topic {@link #listenerCaptor}.
+ *
+ * @param listener listener to which to provide the response
+ * @param response response to be provided
+ */
+ protected void provideResponse(BiConsumer<String, StandardCoderObject> listener, String response) {
+ try {
+ StandardCoderObject sco = coder.decode(response, StandardCoderObject.class);
+ listener.accept(response, sco);
+
+ } catch (CoderException e) {
+ throw new IllegalArgumentException("response is not a Map", e);
+ }
+ }
+}
diff --git a/models-interactions/model-actors/actor.test/src/main/java/org/onap/policy/controlloop/actor/test/BasicHttpOperation.java b/models-interactions/model-actors/actor.test/src/main/java/org/onap/policy/controlloop/actor/test/BasicHttpOperation.java
new file mode 100644
index 000000000..492929296
--- /dev/null
+++ b/models-interactions/model-actors/actor.test/src/main/java/org/onap/policy/controlloop/actor/test/BasicHttpOperation.java
@@ -0,0 +1,190 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.test;
+
+import static org.mockito.Mockito.when;
+
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.InvocationCallback;
+import javax.ws.rs.core.Response;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.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.common.utils.time.PseudoExecutor;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.ActorService;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+
+/**
+ * Superclass for various HttpOperation tests.
+ *
+ * @param <Q> request type
+ */
+public class BasicHttpOperation<Q> {
+ protected static final UUID REQ_ID = UUID.randomUUID();
+ protected static final String DEFAULT_ACTOR = "default-actor";
+ protected static final String DEFAULT_OPERATION = "default-operation";
+ protected static final String MY_CLIENT = "my-client";
+ protected static final String BASE_URI = "/base-uri";
+ protected static final String PATH = "/my-path";
+ protected static final String TARGET_ENTITY = "my-target";
+
+ protected final String actorName;
+ protected final String operationName;
+
+ @Captor
+ protected ArgumentCaptor<InvocationCallback<Response>> callbackCaptor;
+
+ @Captor
+ protected ArgumentCaptor<Entity<Q>> requestCaptor;
+
+ @Captor
+ protected ArgumentCaptor<Map<String, Object>> headerCaptor;
+
+ @Mock
+ protected ActorService service;
+
+ @Mock
+ protected HttpClient client;
+
+ @Mock
+ protected HttpClientFactory factory;
+
+ @Mock
+ protected Response rawResponse;
+
+ @Mock
+ protected HttpOperator operator;
+
+ protected CompletableFuture<Response> future;
+ protected ControlLoopOperationParams params;
+ protected Map<String, String> enrichment;
+ protected VirtualControlLoopEvent event;
+ protected ControlLoopEventContext context;
+ protected OperationOutcome outcome;
+ protected PseudoExecutor executor;
+
+ /**
+ * Constructs the object using a default actor and operation name.
+ */
+ public BasicHttpOperation() {
+ this.actorName = DEFAULT_ACTOR;
+ this.operationName = DEFAULT_OPERATION;
+ }
+
+ /**
+ * Constructs the object.
+ *
+ * @param actor actor name
+ * @param operation operation name
+ */
+ public BasicHttpOperation(String actor, String operation) {
+ this.actorName = actor;
+ this.operationName = operation;
+ }
+
+ /**
+ * Initializes mocks and sets up.
+ */
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ when(factory.get(MY_CLIENT)).thenReturn(client);
+
+ when(rawResponse.getStatus()).thenReturn(200);
+
+ future = new CompletableFuture<>();
+ when(client.getBaseUrl()).thenReturn(BASE_URI);
+
+ executor = new PseudoExecutor();
+
+ makeContext();
+
+ outcome = params.makeOutcome();
+
+ initOperator();
+ }
+
+ /**
+ * Reinitializes {@link #enrichment}, {@link #event}, {@link #context}, and
+ * {@link #params}.
+ * <p/>
+ * Note: {@link #params} is configured to use {@link #executor}.
+ */
+ protected void makeContext() {
+ enrichment = new TreeMap<>(makeEnrichment());
+
+ event = new VirtualControlLoopEvent();
+ event.setRequestId(REQ_ID);
+ event.setAai(enrichment);
+
+ context = new ControlLoopEventContext(event);
+
+ params = ControlLoopOperationParams.builder().executor(executor).context(context).actorService(service)
+ .actor(actorName).operation(operationName).targetEntity(TARGET_ENTITY).build();
+ }
+
+ /**
+ * Initializes an operator so that it is "alive" and has the given names.
+ */
+ protected void initOperator() {
+ when(operator.isAlive()).thenReturn(true);
+ when(operator.getFullName()).thenReturn(actorName + "." + operationName);
+ when(operator.getActorName()).thenReturn(actorName);
+ when(operator.getName()).thenReturn(operationName);
+ when(operator.getClient()).thenReturn(client);
+ when(operator.getPath()).thenReturn(PATH);
+ }
+
+ /**
+ * Makes enrichment data.
+ *
+ * @return enrichment data
+ */
+ protected Map<String, String> makeEnrichment() {
+ return new TreeMap<>();
+ }
+
+ /**
+ * 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);
+ };
+ }
+}
diff --git a/models-interactions/model-actors/actor.test/src/test/java/org/onap/policy/controlloop/actor/test/BasicBidirectionalTopicOperationTest.java b/models-interactions/model-actors/actor.test/src/test/java/org/onap/policy/controlloop/actor/test/BasicBidirectionalTopicOperationTest.java
new file mode 100644
index 000000000..4fd559101
--- /dev/null
+++ b/models-interactions/model-actors/actor.test/src/test/java/org/onap/policy/controlloop/actor/test/BasicBidirectionalTopicOperationTest.java
@@ -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.actor.test;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import java.util.function.BiConsumer;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+
+public class BasicBidirectionalTopicOperationTest {
+ private static final String ACTOR = "my-actor";
+ private static final String OPERATION = "my-operation";
+
+ @Mock
+ private BiConsumer<String, StandardCoderObject> listener;
+
+ private BasicBidirectionalTopicOperation oper;
+
+
+ /**
+ * Sets up.
+ */
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ oper = new BasicBidirectionalTopicOperation(ACTOR, OPERATION);
+ oper.setUp();
+ }
+
+ @Test
+ public void testBasicBidirectionalTopicOperation() {
+ oper = new BasicBidirectionalTopicOperation();
+ assertEquals(BasicHttpOperation.DEFAULT_ACTOR, oper.actorName);
+ assertEquals(BasicHttpOperation.DEFAULT_OPERATION, oper.operationName);
+ }
+
+ @Test
+ public void testBasicBidirectionalTopicOperationStringString() {
+ assertEquals(ACTOR, oper.actorName);
+ assertEquals(OPERATION, oper.operationName);
+ }
+
+ @Test
+ public void testSetUp() {
+ assertNotNull(oper.topicParams);
+ assertNotNull(oper.context);
+ assertNotNull(oper.outcome);
+ assertNotNull(oper.executor);
+ assertTrue(oper.operator.isAlive());
+ }
+
+ @Test
+ public void testMakeContext() {
+ oper.makeContext();
+
+ assertTrue(oper.enrichment.isEmpty());
+
+ assertSame(BasicBidirectionalTopicOperation.REQ_ID, oper.event.getRequestId());
+ assertSame(oper.enrichment, oper.event.getAai());
+
+ assertSame(oper.event, oper.context.getEvent());
+
+ assertSame(oper.context, oper.params.getContext());
+ assertSame(oper.service, oper.params.getActorService());
+ assertSame(oper.executor, oper.params.getExecutor());
+ assertEquals(ACTOR, oper.params.getActor());
+ assertEquals(OPERATION, oper.params.getOperation());
+ assertEquals(BasicBidirectionalTopicOperation.TARGET_ENTITY, oper.params.getTargetEntity());
+ }
+
+ @Test
+ public void testMakePayload() {
+ assertNull(oper.makePayload());
+ }
+
+ @Test
+ public void testInitOperator() {
+ oper.initOperator();
+
+ assertTrue(oper.operator.isAlive());
+ assertEquals(ACTOR + "." + OPERATION, oper.operator.getFullName());
+ assertEquals(ACTOR, oper.operator.getActorName());
+ assertEquals(OPERATION, oper.operator.getName());
+ assertSame(oper.topicHandler, oper.operator.getTopicHandler());
+ assertSame(oper.forwarder, oper.operator.getForwarder());
+ assertSame(oper.topicParams, oper.operator.getParams());
+ }
+
+ @Test
+ public void testMakeEnrichment() {
+ assertTrue(oper.makeEnrichment().isEmpty());
+ }
+
+ @Test
+ public void testProvideResponse() {
+ String response = "{\"input\": 10}";
+
+ oper.provideResponse(listener, response);
+
+ ArgumentCaptor<StandardCoderObject> scoCaptor = ArgumentCaptor.forClass(StandardCoderObject.class);
+ verify(listener).accept(eq(response), scoCaptor.capture());
+
+ assertEquals("10", scoCaptor.getValue().getString("input"));
+
+ // try with an invalid response
+ assertThatIllegalArgumentException().isThrownBy(() -> oper.provideResponse(listener, "{invalid json"))
+ .withMessage("response is not a Map");
+ }
+}
diff --git a/models-interactions/model-actors/actor.test/src/test/java/org/onap/policy/controlloop/actor/test/BasicHttpOperationTest.java b/models-interactions/model-actors/actor.test/src/test/java/org/onap/policy/controlloop/actor/test/BasicHttpOperationTest.java
new file mode 100644
index 000000000..096b8b80d
--- /dev/null
+++ b/models-interactions/model-actors/actor.test/src/test/java/org/onap/policy/controlloop/actor/test/BasicHttpOperationTest.java
@@ -0,0 +1,129 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import 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;
+
+public class BasicHttpOperationTest {
+ private static final String ACTOR = "my-actor";
+ private static final String OPERATION = "my-operation";
+
+ private BasicHttpOperation<String> oper;
+
+
+ @Before
+ public void setUp() throws Exception {
+ oper = new BasicHttpOperation<>(ACTOR, OPERATION);
+ oper.setUp();
+ }
+
+ @Test
+ public void testBasicHttpOperation() {
+ oper = new BasicHttpOperation<>();
+ assertEquals(BasicHttpOperation.DEFAULT_ACTOR, oper.actorName);
+ assertEquals(BasicHttpOperation.DEFAULT_OPERATION, oper.operationName);
+ }
+
+ @Test
+ public void testBasicHttpOperationStringString() {
+ assertEquals(ACTOR, oper.actorName);
+ assertEquals(OPERATION, oper.operationName);
+ }
+
+ @Test
+ public void testSetUp() throws Exception {
+ assertNotNull(oper.client);
+ assertSame(oper.client, oper.factory.get(BasicHttpOperation.MY_CLIENT));
+ assertEquals(200, oper.rawResponse.getStatus());
+ assertNotNull(oper.future);
+ assertEquals(BasicHttpOperation.BASE_URI, oper.client.getBaseUrl());
+ assertNotNull(oper.context);
+ assertNotNull(oper.outcome);
+ assertNotNull(oper.executor);
+ assertTrue(oper.operator.isAlive());
+ }
+
+ @Test
+ public void testMakeContext() {
+ oper.makeContext();
+
+ assertTrue(oper.enrichment.isEmpty());
+
+ assertSame(BasicHttpOperation.REQ_ID, oper.event.getRequestId());
+ assertSame(oper.enrichment, oper.event.getAai());
+
+ assertSame(oper.event, oper.context.getEvent());
+
+ assertSame(oper.context, oper.params.getContext());
+ assertSame(oper.service, oper.params.getActorService());
+ assertSame(oper.executor, oper.params.getExecutor());
+ assertEquals(ACTOR, oper.params.getActor());
+ assertEquals(OPERATION, oper.params.getOperation());
+ assertEquals(BasicHttpOperation.TARGET_ENTITY, oper.params.getTargetEntity());
+ }
+
+ @Test
+ public void testInitOperator() throws Exception {
+ oper.initOperator();
+
+ assertTrue(oper.operator.isAlive());
+ assertEquals(ACTOR + "." + OPERATION, oper.operator.getFullName());
+ assertEquals(ACTOR, oper.operator.getActorName());
+ assertEquals(OPERATION, oper.operator.getName());
+ assertSame(oper.client, oper.operator.getClient());
+ assertEquals(BasicHttpOperation.PATH, oper.operator.getPath());
+ }
+
+ @Test
+ public void testMakeEnrichment() {
+ assertTrue(oper.makeEnrichment().isEmpty());
+ }
+
+ @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());
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/ActorService.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/ActorService.java
index 2886b1feb..24c2cfc23 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/ActorService.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/ActorService.java
@@ -40,7 +40,7 @@ import org.slf4j.LoggerFactory;
* {@link #start()} to start all of the actors. When finished using the actor service,
* invoke {@link #stop()} or {@link #shutdown()}.
*/
-public class ActorService extends StartConfigPartial<Map<String, Object>> {
+public class ActorService extends StartConfigPartial<Map<String, Map<String, Object>>> {
private static final Logger logger = LoggerFactory.getLogger(ActorService.class);
private final Map<String, Actor> name2actor;
@@ -116,14 +116,14 @@ public class ActorService extends StartConfigPartial<Map<String, Object>> {
}
@Override
- protected void doConfigure(Map<String, Object> parameters) {
+ protected void doConfigure(Map<String, Map<String, Object>> parameters) {
logger.info("configuring actors");
BeanValidationResult valres = new BeanValidationResult("ActorService", parameters);
for (Actor actor : name2actor.values()) {
String actorName = actor.getName();
- Map<String, Object> subparams = Util.translateToMap(actorName, parameters.get(actorName));
+ Map<String, Object> subparams = parameters.get(actorName);
if (subparams != null) {
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandler.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandler.java
deleted file mode 100644
index d78403809..000000000
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandler.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*-
- * ============LICENSE_START=======================================================
- * ONAP
- * ================================================================================
- * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
- * ================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.policy.controlloop.actorserviceprovider;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Future;
-import javax.ws.rs.client.InvocationCallback;
-import lombok.AccessLevel;
-import lombok.Getter;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
-import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Handler for a <i>single</i> asynchronous response.
- *
- * @param <T> response type
- */
-@Getter
-public abstract class AsyncResponseHandler<T> implements InvocationCallback<T> {
-
- private static final Logger logger = LoggerFactory.getLogger(AsyncResponseHandler.class);
-
- @Getter(AccessLevel.NONE)
- private final PipelineControllerFuture<OperationOutcome> result = new PipelineControllerFuture<>();
- private final ControlLoopOperationParams params;
- private final OperationOutcome outcome;
-
- /**
- * Constructs the object.
- *
- * @param params operation parameters
- * @param outcome outcome to be populated based on the response
- */
- public AsyncResponseHandler(ControlLoopOperationParams params, OperationOutcome outcome) {
- this.params = params;
- this.outcome = outcome;
- }
-
- /**
- * Handles the given future, arranging to cancel it when the response is received.
- *
- * @param future future to be handled
- * @return a future to be used to cancel or wait for the response
- */
- public CompletableFuture<OperationOutcome> handle(Future<T> future) {
- result.add(future);
- return result;
- }
-
- /**
- * Invokes {@link #doComplete()} and then completes "this" with the returned value.
- */
- @Override
- public void completed(T rawResponse) {
- try {
- logger.trace("{}.{}: response completed for {}", params.getActor(), params.getOperation(),
- params.getRequestId());
- result.complete(doComplete(rawResponse));
-
- } catch (RuntimeException e) {
- logger.trace("{}.{}: response handler threw an exception for {}", params.getActor(), params.getOperation(),
- params.getRequestId());
- result.completeExceptionally(e);
- }
- }
-
- /**
- * Invokes {@link #doFailed()} and then completes "this" with the returned value.
- */
- @Override
- public void failed(Throwable throwable) {
- try {
- logger.trace("{}.{}: response failure for {}", params.getActor(), params.getOperation(),
- params.getRequestId());
- result.complete(doFailed(throwable));
-
- } catch (RuntimeException e) {
- logger.trace("{}.{}: response failure handler threw an exception for {}", params.getActor(),
- params.getOperation(), params.getRequestId());
- result.completeExceptionally(e);
- }
- }
-
- /**
- * Completes the processing of a response.
- *
- * @param rawResponse raw response that was received
- * @return the outcome
- */
- protected abstract OperationOutcome doComplete(T rawResponse);
-
- /**
- * Handles a response exception.
- *
- * @param thrown exception that was thrown
- * @return the outcome
- */
- protected abstract OperationOutcome doFailed(Throwable thrown);
-}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operation.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operation.java
new file mode 100644
index 000000000..39977fd41
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operation.java
@@ -0,0 +1,52 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * This is the service interface for defining an Actor operation used in Control Loop
+ * Operational Policies for performing actions on runtime entities.
+ */
+public interface Operation {
+
+ /**
+ * Gets the name of the associated actor.
+ *
+ * @return the name of the associated actor
+ */
+ String getActorName();
+
+ /**
+ * Gets the name of the operation.
+ *
+ * @return the operation name
+ */
+ String getName();
+
+ /**
+ * Called by enforcement PDP engine to start the operation. As part of the operation,
+ * it invokes the "start" and "complete" call-backs found within the parameters.
+ *
+ * @return a future that can be used to cancel or await the result of the operation
+ */
+ CompletableFuture<OperationOutcome> start();
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operator.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operator.java
index c09460e34..24faafd40 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operator.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operator.java
@@ -21,7 +21,6 @@
package org.onap.policy.controlloop.actorserviceprovider;
import java.util.Map;
-import java.util.concurrent.CompletableFuture;
import org.onap.policy.common.capabilities.Configurable;
import org.onap.policy.common.capabilities.Startable;
import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
@@ -47,11 +46,10 @@ public interface Operator extends Startable, Configurable<Map<String, Object>> {
String getName();
/**
- * Called by enforcement PDP engine to start the operation. As part of the operation,
- * it invokes the "start" and "complete" call-backs found within the parameters.
+ * Called by enforcement PDP engine to build the operation.
*
- * @param params parameters needed to start the operation
- * @return a future that can be used to cancel or await the result of the operation
+ * @param params parameters needed by the operation
+ * @return a new operation
*/
- CompletableFuture<OperationOutcome> startOperation(ControlLoopOperationParams params);
+ Operation buildOperation(ControlLoopOperationParams params);
}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Util.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Util.java
index c3ddd17f3..ba4785922 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Util.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Util.java
@@ -23,9 +23,6 @@ package org.onap.policy.controlloop.actorserviceprovider;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
-import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
-import org.onap.policy.common.endpoints.utils.NetLoggerUtil;
-import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
import org.onap.policy.common.utils.coder.Coder;
import org.onap.policy.common.utils.coder.CoderException;
import org.onap.policy.common.utils.coder.StandardCoder;
@@ -37,6 +34,7 @@ import org.slf4j.LoggerFactory;
*/
public class Util {
private static final Logger logger = LoggerFactory.getLogger(Util.class);
+ private static final Coder coder = new StandardCoder();
private Util() {
// do nothing
@@ -56,82 +54,6 @@ public class Util {
}
/**
- * Logs a REST request. If the request is not of type, String, then it attempts to
- * pretty-print it into JSON before logging.
- *
- * @param url request URL
- * @param request request to be logged
- */
- public static <T> void logRestRequest(String url, T request) {
- logRestRequest(new StandardCoder(), url, request);
- }
-
- /**
- * Logs a REST request. If the request is not of type, String, then it attempts to
- * pretty-print it into JSON before logging.
- *
- * @param coder coder to be used to pretty-print the request
- * @param url request URL
- * @param request request to be logged
- */
- protected static <T> void logRestRequest(Coder coder, String url, T request) {
- String json;
- try {
- if (request instanceof String) {
- json = request.toString();
- } else {
- json = coder.encode(request, true);
- }
-
- } catch (CoderException e) {
- logger.warn("cannot pretty-print request", e);
- json = request.toString();
- }
-
- NetLoggerUtil.log(EventType.OUT, CommInfrastructure.REST, url, json);
- logger.info("[OUT|{}|{}|]{}{}", CommInfrastructure.REST, url, NetLoggerUtil.SYSTEM_LS, json);
- }
-
- /**
- * Logs a REST response. If the response is not of type, String, then it attempts to
- * pretty-print it into JSON before logging.
- *
- * @param url request URL
- * @param response response to be logged
- */
- public static <T> void logRestResponse(String url, T response) {
- logRestResponse(new StandardCoder(), url, response);
- }
-
- /**
- * Logs a REST response. If the request is not of type, String, then it attempts to
- * pretty-print it into JSON before logging.
- *
- * @param coder coder to be used to pretty-print the response
- * @param url request URL
- * @param response response to be logged
- */
- protected static <T> void logRestResponse(Coder coder, String url, T response) {
- String json;
- try {
- if (response == null) {
- json = null;
- } else if (response instanceof String) {
- json = response.toString();
- } else {
- json = coder.encode(response, true);
- }
-
- } catch (CoderException e) {
- logger.warn("cannot pretty-print response", e);
- json = response.toString();
- }
-
- NetLoggerUtil.log(EventType.IN, CommInfrastructure.REST, url, json);
- logger.info("[IN|{}|{}|]{}{}", CommInfrastructure.REST, url, NetLoggerUtil.SYSTEM_LS, json);
- }
-
- /**
* Runs a function and logs a message if it throws an exception. Does <i>not</i>
* re-throw the exception.
*
@@ -163,11 +85,8 @@ public class Util {
* @return the translated object
*/
public static <T> T translate(String identifier, Object source, Class<T> clazz) {
- Coder coder = new StandardCoder();
-
try {
- String json = coder.encode(source);
- return coder.decode(json, clazz);
+ return coder.convert(source, clazz);
} catch (CoderException | RuntimeException e) {
throw new IllegalArgumentException("cannot translate parameters for " + identifier, e);
@@ -184,10 +103,6 @@ public class Util {
*/
@SuppressWarnings("unchecked")
public static Map<String, Object> translateToMap(String identifier, Object source) {
- if (source == null) {
- return null;
- }
-
return translate(identifier, source, LinkedHashMap.class);
}
}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContext.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContext.java
index cd4d2570f..3e02da611 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContext.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContext.java
@@ -23,12 +23,15 @@ package org.onap.policy.controlloop.actorserviceprovider.controlloop;
import java.io.Serializable;
import java.util.Map;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
/**
* Context associated with a control loop event.
@@ -47,11 +50,23 @@ public class ControlLoopEventContext implements Serializable {
*/
private final Map<String, String> enrichment;
+ /**
+ * Set of properties that have been stored in the context.
+ */
@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
private Map<String, Serializable> properties = new ConcurrentHashMap<>();
/**
+ * When {@link #obtain(String, ControlLoopOperationParams)} is invoked and the
+ * specified property is not found in {@link #properties}, it is retrieved. This holds
+ * the futures for the operations retrieving the properties.
+ */
+ @Getter(AccessLevel.NONE)
+ @Setter(AccessLevel.NONE)
+ private transient Map<String, CompletableFuture<OperationOutcome>> retrievers = new ConcurrentHashMap<>();
+
+ /**
* Request ID extracted from the event, or a generated value if the event has no
* request id; never {@code null}.
*/
@@ -100,4 +115,46 @@ public class ControlLoopEventContext implements Serializable {
public void setProperty(String name, Serializable value) {
properties.put(name, value);
}
+
+ /**
+ * Obtains the given property.
+ *
+ * @param name name of the desired property
+ * @param params parameters needed to perform the operation to retrieve the desired
+ * property
+ * @return a future for retrieving the property, {@code null} if the property has
+ * already been retrieved
+ */
+ public CompletableFuture<OperationOutcome> obtain(String name, ControlLoopOperationParams params) {
+ if (properties.containsKey(name)) {
+ return null;
+ }
+
+ /*
+ * Return any existing future, if it wasn't canceled. Otherwise, start a new
+ * request.
+ */
+
+ // @formatter:off
+ CompletableFuture<OperationOutcome> oldFuture =
+ retrievers.compute(name, (key, future) -> (future == null || future.isCancelled() ? null : future));
+ // @formatter:on
+
+ if (oldFuture != null) {
+ return oldFuture;
+ }
+
+ /*
+ * Note: must NOT invoke params.start() within retrievers.compute(), as start()
+ * may invoke obtain() which would cause a recursive update to the retrievers map.
+ */
+ CompletableFuture<OperationOutcome> future = params.start();
+
+ if ((oldFuture = retrievers.putIfAbsent(name, future)) != null) {
+ future.cancel(false);
+ return oldFuture;
+ }
+
+ return future;
+ }
}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImpl.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImpl.java
index d7f322e8a..0c88ebee2 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImpl.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImpl.java
@@ -91,7 +91,7 @@ public class ActorImpl extends StartConfigPartial<Map<String, Object>> implement
public Operator getOperator(String name) {
Operator operator = name2operator.get(name);
if (operator == null) {
- throw new IllegalArgumentException("unknown operation " + getName() + "." + name);
+ throw new IllegalArgumentException("unknown operator " + getName() + "." + name);
}
return operator;
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicActor.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicActor.java
new file mode 100644
index 000000000..1e44a170c
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicActor.java
@@ -0,0 +1,108 @@
+/*-
+ * ============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.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import org.apache.commons.lang3.tuple.Pair;
+import org.onap.policy.common.endpoints.event.comm.client.BidirectionalTopicClientException;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.BidirectionalTopicActorParams;
+import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicHandler;
+import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicManager;
+
+/**
+ * Actor that uses a bidirectional topic. The actor's parameters must be a
+ * {@link BidirectionalTopicActorParams}.
+ */
+public class BidirectionalTopicActor extends ActorImpl implements BidirectionalTopicManager {
+
+ /**
+ * Maps a pair of sink and source topic names to their bidirectional topic.
+ */
+ private final Map<Pair<String, String>, BidirectionalTopicHandler> params2topic = new ConcurrentHashMap<>();
+
+
+ /**
+ * Constructs the object.
+ *
+ * @param name actor's name
+ */
+ public BidirectionalTopicActor(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void doStart() {
+ params2topic.values().forEach(BidirectionalTopicHandler::start);
+ super.doStart();
+ }
+
+ @Override
+ protected void doStop() {
+ params2topic.values().forEach(BidirectionalTopicHandler::stop);
+ super.doStop();
+ }
+
+ @Override
+ protected void doShutdown() {
+ params2topic.values().forEach(BidirectionalTopicHandler::shutdown);
+ params2topic.clear();
+ super.doShutdown();
+ }
+
+ @Override
+ public BidirectionalTopicHandler getTopicHandler(String sinkTopic, String sourceTopic) {
+ Pair<String, String> key = Pair.of(sinkTopic, sourceTopic);
+
+ return params2topic.computeIfAbsent(key, pair -> {
+ try {
+ return makeTopicHandler(sinkTopic, sourceTopic);
+ } catch (BidirectionalTopicClientException e) {
+ throw new IllegalArgumentException(e);
+ }
+ });
+ }
+
+ /**
+ * Translates the parameters to a {@link BidirectionalTopicActorParams} 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();
+
+ // @formatter:off
+ return Util.translate(actorName, actorParameters, BidirectionalTopicActorParams.class)
+ .doValidation(actorName)
+ .makeOperationParameters(actorName);
+ // @formatter:on
+ }
+
+ // may be overridden by junit tests
+
+ protected BidirectionalTopicHandler makeTopicHandler(String sinkTopic, String sourceTopic)
+ throws BidirectionalTopicClientException {
+
+ return new BidirectionalTopicHandler(sinkTopic, sourceTopic);
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperation.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperation.java
new file mode 100644
index 000000000..d1e21f8fd
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperation.java
@@ -0,0 +1,265 @@
+/*-
+ * ============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 java.util.function.BiConsumer;
+import lombok.Getter;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.BidirectionalTopicParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture;
+import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicHandler;
+import org.onap.policy.controlloop.actorserviceprovider.topic.Forwarder;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Operation that uses a bidirectional topic.
+ *
+ * @param <S> response type
+ */
+@Getter
+public abstract class BidirectionalTopicOperation<Q, S> extends OperationPartial {
+ private static final Logger logger = LoggerFactory.getLogger(BidirectionalTopicOperation.class);
+
+ /**
+ * Response status.
+ */
+ public enum Status {
+ SUCCESS, FAILURE, STILL_WAITING
+ }
+
+ // fields extracted from the operator
+
+ private final BidirectionalTopicHandler topicHandler;
+ private final Forwarder forwarder;
+ private final BidirectionalTopicParams topicParams;
+ 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 BidirectionalTopicOperation(ControlLoopOperationParams params, BidirectionalTopicOperator operator,
+ Class<S> clazz) {
+ super(params, operator);
+ this.topicHandler = operator.getTopicHandler();
+ this.forwarder = operator.getForwarder();
+ this.topicParams = operator.getParams();
+ this.responseClass = clazz;
+ this.timeoutMs = TimeUnit.MILLISECONDS.convert(topicParams.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 Executor executor = params.getExecutor();
+
+ // register a listener BEFORE publishing
+
+ BiConsumer<String, StandardCoderObject> listener = (rawResponse, scoResponse) -> {
+ OperationOutcome latestOutcome = processResponse(outcome, rawResponse, scoResponse);
+ if (latestOutcome != null) {
+ // final response - complete the controller
+ controller.completeAsync(() -> latestOutcome, executor);
+ }
+ };
+
+ 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;
+ }
+
+ 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);
+ }
+
+ if (!topicHandler.send(json)) {
+ throw new IllegalStateException("nothing published");
+ }
+
+ logMessage(EventType.OUT, topicHandler.getSinkTopicCommInfrastructure(), topicHandler.getSinkTopic(), 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, or {@code null} if still waiting for completion
+ */
+ protected OperationOutcome processResponse(OperationOutcome outcome, String rawResponse,
+ StandardCoderObject scoResponse) {
+
+ logger.info("{}.{}: response received for {}", params.getActor(), params.getOperation(), params.getRequestId());
+
+ logMessage(EventType.IN, topicHandler.getSourceTopicCommInfrastructure(), topicHandler.getSourceTopic(),
+ rawResponse);
+
+ // decode the response
+ 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);
+ }
+ }
+
+ // check its status
+ switch (detmStatus(rawResponse, response)) {
+ case SUCCESS:
+ logger.info("{}.{} request succeeded for {}", params.getActor(), params.getOperation(),
+ params.getRequestId());
+ setOutcome(outcome, PolicyResult.SUCCESS, response);
+ postProcessResponse(outcome, rawResponse, response);
+ return outcome;
+
+ case FAILURE:
+ logger.info("{}.{} request failed for {}", params.getActor(), params.getOperation(),
+ params.getRequestId());
+ return setOutcome(outcome, PolicyResult.FAILURE, response);
+
+ case STILL_WAITING:
+ default:
+ logger.info("{}.{} request incomplete for {}", params.getActor(), params.getOperation(),
+ params.getRequestId());
+ return null;
+ }
+ }
+
+ /**
+ * Sets an operation's outcome and default message based on the result.
+ *
+ * @param outcome operation to be updated
+ * @param result result of the operation
+ * @param response response used to populate the outcome
+ * @return the updated operation
+ */
+ public OperationOutcome setOutcome(OperationOutcome outcome, PolicyResult result, S response) {
+ return setOutcome(outcome, result);
+ }
+
+ /**
+ * 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 the status of the response.
+ *
+ * @param rawResponse raw response
+ * @param response decoded response
+ * @return the status of the response
+ */
+ protected abstract Status detmStatus(String rawResponse, S response);
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperator.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperator.java
new file mode 100644
index 000000000..51689e49b
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperator.java
@@ -0,0 +1,160 @@
+/*-
+ * ============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.BidirectionalTopicParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
+import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicHandler;
+import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicManager;
+import org.onap.policy.controlloop.actorserviceprovider.topic.Forwarder;
+import org.onap.policy.controlloop.actorserviceprovider.topic.SelectorKey;
+
+/**
+ * Operator that uses a bidirectional topic. Topic operators may share a
+ * {@link BidirectionalTopicHandler}.
+ */
+public abstract class BidirectionalTopicOperator extends OperatorPartial {
+
+ /**
+ * Manager from which to get the topic handlers.
+ */
+ private final BidirectionalTopicManager topicManager;
+
+ /**
+ * 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 BidirectionalTopicParams params;
+
+ /**
+ * Topic handler associated with the parameters.
+ */
+ @Getter
+ private BidirectionalTopicHandler topicHandler;
+
+ /**
+ * 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 topicManager manager from which to get the topic handler
+ * @param selectorKeys keys used to extract the fields used to select responses for
+ * this operator
+ */
+ public BidirectionalTopicOperator(String actorName, String name, BidirectionalTopicManager topicManager,
+ List<SelectorKey> selectorKeys) {
+ super(actorName, name);
+ this.topicManager = topicManager;
+ this.selectorKeys = selectorKeys;
+ }
+
+ @Override
+ protected void doConfigure(Map<String, Object> parameters) {
+ params = Util.translate(getFullName(), parameters, BidirectionalTopicParams.class);
+ ValidationResult result = params.validate(getFullName());
+ if (!result.isValid()) {
+ throw new ParameterValidationRuntimeException("invalid parameters", result);
+ }
+
+ topicHandler = topicManager.getTopicHandler(params.getSinkTopic(), params.getSourceTopic());
+ forwarder = topicHandler.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 topicManager manager from which to get the topic handler
+ * @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> BidirectionalTopicOperator makeOperator(String actorName, String operation,
+ BidirectionalTopicManager topicManager,
+ BiFunction<ControlLoopOperationParams, BidirectionalTopicOperator,
+ BidirectionalTopicOperation<Q, S>> operationMaker,
+ SelectorKey... keys) {
+ // @formatter:off
+
+ return makeOperator(actorName, operation, topicManager, 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 topicManager manager from which to get the topic handler
+ * @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> BidirectionalTopicOperator makeOperator(String actorName, String operation,
+ BidirectionalTopicManager topicManager,
+ List<SelectorKey> keys,
+ BiFunction<ControlLoopOperationParams, BidirectionalTopicOperator,
+ BidirectionalTopicOperation<Q,S>> operationMaker) {
+ // @formatter:on
+
+ return new BidirectionalTopicOperator(actorName, operation, topicManager, 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/impl/HttpOperation.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperation.java
new file mode 100644
index 000000000..c3c0f6dc2
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperation.java
@@ -0,0 +1,245 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.function.Function;
+import javax.ws.rs.client.InvocationCallback;
+import javax.ws.rs.core.Response;
+import lombok.Getter;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.http.client.HttpClient;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
+import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Operator that uses HTTP. The operator's parameters must be an {@link HttpParams}.
+ *
+ * @param <T> response type
+ */
+@Getter
+public abstract class HttpOperation<T> extends OperationPartial {
+ private static final Logger logger = LoggerFactory.getLogger(HttpOperation.class);
+
+ /**
+ * Operator that created this operation.
+ */
+ protected final HttpOperator operator;
+
+ /**
+ * Response class.
+ */
+ private final Class<T> responseClass;
+
+
+ /**
+ * Constructs the object.
+ *
+ * @param params operation parameters
+ * @param operator operator that created this operation
+ * @param clazz response class
+ */
+ public HttpOperation(ControlLoopOperationParams params, HttpOperator operator, Class<T> clazz) {
+ super(params, operator);
+ this.operator = operator;
+ this.responseClass = clazz;
+ }
+
+ /**
+ * If no timeout is specified, then it returns the operator's configured timeout.
+ */
+ @Override
+ protected long getTimeoutMs(Integer timeoutSec) {
+ return (timeoutSec == null || timeoutSec == 0 ? operator.getTimeoutMs() : super.getTimeoutMs(timeoutSec));
+ }
+
+ /**
+ * Makes the request headers. This simply returns an empty map.
+ *
+ * @return request headers, a non-null, modifiable map
+ */
+ protected Map<String, Object> makeHeaders() {
+ return new HashMap<>();
+ }
+
+ /**
+ * Gets the path to be used when performing the request; this is typically appended to
+ * the base URL. This method simply invokes {@link #getPath()}.
+ *
+ * @return the path URI suffix
+ */
+ public String makePath() {
+ return operator.getPath();
+ }
+
+ /**
+ * Makes the URL to which the "get" request should be posted. This ir primarily used
+ * for logging purposes. This particular method returns the base URL appended with the
+ * return value from {@link #makePath()}.
+ *
+ * @return the URL to which from which to get
+ */
+ public String makeUrl() {
+ return (operator.getClient().getBaseUrl() + makePath());
+ }
+
+ /**
+ * Arranges to handle a response.
+ *
+ * @param outcome outcome to be populate
+ * @param url URL to which to request was sent
+ * @param requester function to initiate the request and invoke the given callback
+ * when it completes
+ * @return a future for the response
+ */
+ protected CompletableFuture<OperationOutcome> handleResponse(OperationOutcome outcome, String url,
+ Function<InvocationCallback<Response>, Future<Response>> requester) {
+
+ final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+ final CompletableFuture<Response> future = new CompletableFuture<>();
+ final Executor executor = params.getExecutor();
+
+ // arrange for the callback to complete "future"
+ InvocationCallback<Response> callback = new InvocationCallback<>() {
+ @Override
+ public void completed(Response response) {
+ future.complete(response);
+ }
+
+ @Override
+ public void failed(Throwable throwable) {
+ logger.warn("{}.{}: response failure for {}", params.getActor(), params.getOperation(),
+ params.getRequestId());
+ future.completeExceptionally(throwable);
+ }
+ };
+
+ // start the request and arrange to cancel it if the controller is canceled
+ controller.add(requester.apply(callback));
+
+ // once "future" completes, process the response, and then complete the controller
+ future.thenComposeAsync(response -> processResponse(outcome, url, response), executor)
+ .whenCompleteAsync(controller.delayedComplete(), executor);
+
+ return controller;
+ }
+
+ /**
+ * Processes a response. This method decodes the response, sets the outcome based on
+ * the response, and then returns a completed future.
+ *
+ * @param outcome outcome to be populate
+ * @param url URL to which to request was sent
+ * @param response raw response to process
+ * @return a future to cancel or await the outcome
+ */
+ protected CompletableFuture<OperationOutcome> processResponse(OperationOutcome outcome, String url,
+ Response rawResponse) {
+
+ logger.info("{}.{}: response received for {}", params.getActor(), params.getOperation(), params.getRequestId());
+
+ String strResponse = HttpClient.getBody(rawResponse, String.class);
+
+ logMessage(EventType.IN, CommInfrastructure.REST, url, strResponse);
+
+ T response;
+ if (responseClass == String.class) {
+ response = responseClass.cast(strResponse);
+ } else {
+ try {
+ response = makeCoder().decode(strResponse, responseClass);
+ } catch (CoderException e) {
+ logger.warn("{}.{} cannot decode response for {}", params.getActor(), params.getOperation(),
+ params.getRequestId(), e);
+ throw new IllegalArgumentException("cannot decode response");
+ }
+ }
+
+ if (!isSuccess(rawResponse, response)) {
+ logger.info("{}.{} request failed with http error code {} for {}", params.getActor(), params.getOperation(),
+ rawResponse.getStatus(), params.getRequestId());
+ return CompletableFuture.completedFuture(setOutcome(outcome, PolicyResult.FAILURE, response));
+ }
+
+ logger.info("{}.{} request succeeded for {}", params.getActor(), params.getOperation(), params.getRequestId());
+ setOutcome(outcome, PolicyResult.SUCCESS, response);
+ return postProcessResponse(outcome, url, rawResponse, response);
+ }
+
+ /**
+ * Sets an operation's outcome and default message based on the result.
+ *
+ * @param outcome operation to be updated
+ * @param result result of the operation
+ * @param response response used to populate the outcome
+ * @return the updated operation
+ */
+ public OperationOutcome setOutcome(OperationOutcome outcome, PolicyResult result, T response) {
+ return setOutcome(outcome, result);
+ }
+
+ /**
+ * Processes a successful response. This method simply returns the outcome wrapped in
+ * a completed future.
+ *
+ * @param outcome outcome to be populate
+ * @param url URL to which to request was sent
+ * @param rawResponse raw response
+ * @param response decoded response
+ * @return a future to cancel or await the outcome
+ */
+ protected CompletableFuture<OperationOutcome> postProcessResponse(OperationOutcome outcome, String url,
+ Response rawResponse, T response) {
+
+ return CompletableFuture.completedFuture(outcome);
+ }
+
+ /**
+ * Determines if the response indicates success. This method simply checks the HTTP
+ * status code.
+ *
+ * @param rawResponse raw response
+ * @param response decoded response
+ * @return {@code true} if the response indicates success, {@code false} otherwise
+ */
+ protected boolean isSuccess(Response rawResponse, T response) {
+ return (rawResponse.getStatus() == 200);
+ }
+
+ @Override
+ public <Q> String logMessage(EventType direction, CommInfrastructure infra, String sink, Q request) {
+ String json = super.logMessage(direction, infra, sink, request);
+ NetLoggerUtil.log(direction, infra, sink, json);
+ return json;
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperator.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperator.java
index 566492907..add74aa42 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperator.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperator.java
@@ -21,31 +21,35 @@
package org.onap.policy.controlloop.actorserviceprovider.impl;
import java.util.Map;
-import lombok.AccessLevel;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiFunction;
import lombok.Getter;
import org.onap.policy.common.endpoints.http.client.HttpClient;
import org.onap.policy.common.endpoints.http.client.HttpClientFactory;
import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance;
import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
/**
- * Operator that uses HTTP. The operator's parameters must be a {@link HttpParams}.
+ * Operator that uses HTTP. The operator's parameters must be an {@link HttpParams}.
*/
-public class HttpOperator extends OperatorPartial {
+@Getter
+public abstract class HttpOperator extends OperatorPartial {
- @Getter(AccessLevel.PROTECTED)
private HttpClient client;
- @Getter
- private long timeoutSec;
+ /**
+ * Default timeout, in milliseconds, if none specified in the request.
+ */
+ private long timeoutMs;
/**
- * URI path for this particular operation.
+ * URI path for this particular operation. Includes a leading "/".
*/
- @Getter
private String path;
@@ -60,6 +64,26 @@ public class HttpOperator extends OperatorPartial {
}
/**
+ * Makes an operator that will construct operations.
+ *
+ * @param <T> response type
+ * @param actorName actor name
+ * @param operation operation name
+ * @param operationMaker function to make an operation
+ * @return a new operator
+ */
+ public static <T> HttpOperator makeOperator(String actorName, String operation,
+ BiFunction<ControlLoopOperationParams, HttpOperator, HttpOperation<T>> operationMaker) {
+
+ return new HttpOperator(actorName, operation) {
+ @Override
+ public Operation buildOperation(ControlLoopOperationParams params) {
+ return operationMaker.apply(params, this);
+ }
+ };
+ }
+
+ /**
* Translates the parameters to an {@link HttpParams} and then extracts the relevant
* values.
*/
@@ -73,10 +97,10 @@ public class HttpOperator extends OperatorPartial {
client = getClientFactory().get(params.getClientName());
path = params.getPath();
- timeoutSec = params.getTimeoutSec();
+ timeoutMs = TimeUnit.MILLISECONDS.convert(params.getTimeoutSec(), TimeUnit.SECONDS);
}
- // these may be overridden by junits
+ // these may be overridden by junit tests
protected HttpClientFactory getClientFactory() {
return HttpClientFactoryInstance.getClientFactory();
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartial.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartial.java
new file mode 100644
index 000000000..680a56f89
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartial.java
@@ -0,0 +1,983 @@
+/*-
+ * ============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.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+import org.onap.policy.common.utils.coder.Coder;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.controlloop.ControlLoopOperation;
+import org.onap.policy.controlloop.actorserviceprovider.CallbackManager;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Partial implementation of an operator. In general, it's preferable that subclasses
+ * would override {@link #startOperationAsync(int, OperationOutcome)
+ * startOperationAsync()}. However, if that proves to be too difficult, then they can
+ * simply override {@link #doOperation(int, OperationOutcome) doOperation()}. In addition,
+ * if the operation requires any preprocessor steps, the subclass may choose to override
+ * {@link #startPreprocessorAsync()}.
+ * <p/>
+ * The futures returned by the methods within this class can be canceled, and will
+ * propagate the cancellation to any subtasks. Thus it is also expected that any futures
+ * returned by overridden methods will do the same. Of course, if a class overrides
+ * {@link #doOperation(int, OperationOutcome) doOperation()}, then there's little that can
+ * be done to cancel that particular operation.
+ */
+public abstract class OperationPartial implements Operation {
+ private static final Logger logger = LoggerFactory.getLogger(OperationPartial.class);
+ private static final Coder coder = new StandardCoder();
+
+ public static final long DEFAULT_RETRY_WAIT_MS = 1000L;
+
+ // values extracted from the operator
+
+ private final OperatorPartial operator;
+
+ /**
+ * Operation parameters.
+ */
+ protected final ControlLoopOperationParams params;
+
+
+ /**
+ * Constructs the object.
+ *
+ * @param params operation parameters
+ * @param operator operator that created this operation
+ */
+ public OperationPartial(ControlLoopOperationParams params, OperatorPartial operator) {
+ this.params = params;
+ this.operator = operator;
+ }
+
+ public Executor getBlockingExecutor() {
+ return operator.getBlockingExecutor();
+ }
+
+ public String getFullName() {
+ return operator.getFullName();
+ }
+
+ public String getActorName() {
+ return operator.getActorName();
+ }
+
+ public String getName() {
+ return operator.getName();
+ }
+
+ @Override
+ public final CompletableFuture<OperationOutcome> start() {
+ if (!operator.isAlive()) {
+ throw new IllegalStateException("operation is not running: " + getFullName());
+ }
+
+ // allocate a controller for the entire operation
+ final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+
+ CompletableFuture<OperationOutcome> preproc = startPreprocessorAsync();
+ if (preproc == null) {
+ // no preprocessor required - just start the operation
+ return startOperationAttempt(controller, 1);
+ }
+
+ /*
+ * Do preprocessor first and then, if successful, start the operation. Note:
+ * operations create their own outcome, ignoring the outcome from any previous
+ * steps.
+ *
+ * Wrap the preprocessor to ensure "stop" is propagated to it.
+ */
+ // @formatter:off
+ controller.wrap(preproc)
+ .exceptionally(fromException("preprocessor of operation"))
+ .thenCompose(handlePreprocessorFailure(controller))
+ .thenCompose(unusedOutcome -> startOperationAttempt(controller, 1))
+ .whenCompleteAsync(controller.delayedComplete(), params.getExecutor());
+ // @formatter:on
+
+ return controller;
+ }
+
+ /**
+ * Handles a failure in the preprocessor pipeline. If a failure occurred, then it
+ * invokes the call-backs, marks the controller complete, and returns an incomplete
+ * future, effectively halting the pipeline. Otherwise, it returns the outcome that it
+ * received.
+ * <p/>
+ * Assumes that no callbacks have been invoked yet.
+ *
+ * @param controller pipeline controller
+ * @return a function that checks the outcome status and continues, if successful, or
+ * indicates a failure otherwise
+ */
+ private Function<OperationOutcome, CompletableFuture<OperationOutcome>> handlePreprocessorFailure(
+ PipelineControllerFuture<OperationOutcome> controller) {
+
+ return outcome -> {
+
+ if (outcome != null && isSuccess(outcome)) {
+ logger.info("{}: preprocessor succeeded for {}", getFullName(), params.getRequestId());
+ return CompletableFuture.completedFuture(outcome);
+ }
+
+ logger.warn("preprocessor failed, discontinuing operation {} for {}", getFullName(), params.getRequestId());
+
+ final Executor executor = params.getExecutor();
+ final CallbackManager callbacks = new CallbackManager();
+
+ // propagate "stop" to the callbacks
+ controller.add(callbacks);
+
+ final OperationOutcome outcome2 = params.makeOutcome();
+
+ // TODO need a FAILURE_MISSING_DATA (e.g., A&AI)
+
+ outcome2.setResult(PolicyResult.FAILURE_GUARD);
+ outcome2.setMessage(outcome != null ? outcome.getMessage() : null);
+
+ // @formatter:off
+ CompletableFuture.completedFuture(outcome2)
+ .whenCompleteAsync(callbackStarted(callbacks), executor)
+ .whenCompleteAsync(callbackCompleted(callbacks), executor)
+ .whenCompleteAsync(controller.delayedComplete(), executor);
+ // @formatter:on
+
+ return new CompletableFuture<>();
+ };
+ }
+
+ /**
+ * Invokes the operation's preprocessor step(s) as a "future". This method simply
+ * invokes {@link #startGuardAsync()}.
+ * <p/>
+ * This method assumes the following:
+ * <ul>
+ * <li>the operator is alive</li>
+ * <li>exceptions generated within the pipeline will be handled by the invoker</li>
+ * </ul>
+ *
+ * @return a function that will start the preprocessor and returns its outcome, or
+ * {@code null} if this operation needs no preprocessor
+ */
+ protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
+ return startGuardAsync();
+ }
+
+ /**
+ * Invokes the operation's guard step(s) as a "future". This method simply returns
+ * {@code null}.
+ * <p/>
+ * This method assumes the following:
+ * <ul>
+ * <li>the operator is alive</li>
+ * <li>exceptions generated within the pipeline will be handled by the invoker</li>
+ * </ul>
+ *
+ * @return a function that will start the guard checks and returns its outcome, or
+ * {@code null} if this operation has no guard
+ */
+ protected CompletableFuture<OperationOutcome> startGuardAsync() {
+ return null;
+ }
+
+ /**
+ * Starts the operation attempt, with no preprocessor. When all retries complete, it
+ * will complete the controller.
+ *
+ * @param controller controller for all operation attempts
+ * @param attempt attempt number, typically starting with 1
+ * @return a future that will return the final result of all attempts
+ */
+ private CompletableFuture<OperationOutcome> startOperationAttempt(
+ PipelineControllerFuture<OperationOutcome> controller, int attempt) {
+
+ // propagate "stop" to the operation attempt
+ controller.wrap(startAttemptWithoutRetries(attempt)).thenCompose(retryOnFailure(controller, attempt))
+ .whenCompleteAsync(controller.delayedComplete(), params.getExecutor());
+
+ return controller;
+ }
+
+ /**
+ * Starts the operation attempt, without doing any retries.
+ *
+ * @param params operation parameters
+ * @param attempt attempt number, typically starting with 1
+ * @return a future that will return the result of a single operation attempt
+ */
+ private CompletableFuture<OperationOutcome> startAttemptWithoutRetries(int attempt) {
+
+ logger.info("{}: start operation attempt {} for {}", getFullName(), attempt, params.getRequestId());
+
+ final Executor executor = params.getExecutor();
+ final OperationOutcome outcome = params.makeOutcome();
+ final CallbackManager callbacks = new CallbackManager();
+
+ // this operation attempt gets its own controller
+ final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+
+ // propagate "stop" to the callbacks
+ controller.add(callbacks);
+
+ // @formatter:off
+ CompletableFuture<OperationOutcome> future = CompletableFuture.completedFuture(outcome)
+ .whenCompleteAsync(callbackStarted(callbacks), executor)
+ .thenCompose(controller.wrap(outcome2 -> startOperationAsync(attempt, outcome2)));
+ // @formatter:on
+
+ // handle timeouts, if specified
+ long timeoutMillis = getTimeoutMs(params.getTimeoutSec());
+ if (timeoutMillis > 0) {
+ logger.info("{}: set timeout to {}ms for {}", getFullName(), timeoutMillis, params.getRequestId());
+ future = future.orTimeout(timeoutMillis, TimeUnit.MILLISECONDS);
+ }
+
+ /*
+ * Note: we re-invoke callbackStarted() just to be sure the callback is invoked
+ * before callbackCompleted() is invoked.
+ *
+ * Note: no need to remove "callbacks" from the pipeline, as we're going to stop
+ * the pipeline as the last step anyway.
+ */
+
+ // @formatter:off
+ future.exceptionally(fromException("operation"))
+ .thenApply(setRetryFlag(attempt))
+ .whenCompleteAsync(callbackStarted(callbacks), executor)
+ .whenCompleteAsync(callbackCompleted(callbacks), executor)
+ .whenCompleteAsync(controller.delayedComplete(), executor);
+ // @formatter:on
+
+ return controller;
+ }
+
+ /**
+ * Determines if the outcome was successful.
+ *
+ * @param outcome outcome to examine
+ * @return {@code true} if the outcome was successful
+ */
+ protected boolean isSuccess(OperationOutcome outcome) {
+ return (outcome.getResult() == PolicyResult.SUCCESS);
+ }
+
+ /**
+ * Determines if the outcome was a failure for this operator.
+ *
+ * @param outcome outcome to examine, or {@code null}
+ * @return {@code true} if the outcome is not {@code null} and was a failure
+ * <i>and</i> was associated with this operator, {@code false} otherwise
+ */
+ protected boolean isActorFailed(OperationOutcome outcome) {
+ return (isSameOperation(outcome) && outcome.getResult() == PolicyResult.FAILURE);
+ }
+
+ /**
+ * Determines if the given outcome is for this operation.
+ *
+ * @param outcome outcome to examine
+ * @return {@code true} if the outcome is for this operation, {@code false} otherwise
+ */
+ protected boolean isSameOperation(OperationOutcome outcome) {
+ return OperationOutcome.isFor(outcome, getActorName(), getName());
+ }
+
+ /**
+ * Invokes the operation as a "future". This method simply invokes
+ * {@link #doOperation()} using the {@link #blockingExecutor "blocking executor"},
+ * returning the result via a "future".
+ * <p/>
+ * Note: if the operation uses blocking I/O, then it should <i>not</i> be run using
+ * the executor in the "params", as that may bring the background thread pool to a
+ * grinding halt. The {@link #blockingExecutor "blocking executor"} should be used
+ * instead.
+ * <p/>
+ * This method assumes the following:
+ * <ul>
+ * <li>the operator is alive</li>
+ * <li>verifyRunning() has been invoked</li>
+ * <li>callbackStarted() has been invoked</li>
+ * <li>the invoker will perform appropriate timeout checks</li>
+ * <li>exceptions generated within the pipeline will be handled by the invoker</li>
+ * </ul>
+ *
+ * @param attempt attempt number, typically starting with 1
+ * @return a function that will start the operation and return its result when
+ * complete
+ */
+ protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+ return CompletableFuture.supplyAsync(() -> doOperation(attempt, outcome), getBlockingExecutor());
+ }
+
+ /**
+ * Low-level method that performs the operation. This can make the same assumptions
+ * that are made by {@link #doOperationAsFuture()}. This particular method simply
+ * throws an {@link UnsupportedOperationException}.
+ *
+ * @param attempt attempt number, typically starting with 1
+ * @param operation the operation being performed
+ * @return the outcome of the operation
+ */
+ protected OperationOutcome doOperation(int attempt, OperationOutcome operation) {
+
+ throw new UnsupportedOperationException("start operation " + getFullName());
+ }
+
+ /**
+ * Sets the outcome status to FAILURE_RETRIES, if the current operation outcome is
+ * FAILURE, assuming the policy specifies retries and the retry count has been
+ * exhausted.
+ *
+ * @param attempt latest attempt number, starting with 1
+ * @return a function to get the next future to execute
+ */
+ private Function<OperationOutcome, OperationOutcome> setRetryFlag(int attempt) {
+
+ return operation -> {
+ if (operation != null && !isActorFailed(operation)) {
+ /*
+ * wrong type or wrong operation - just leave it as is. No need to log
+ * anything here, as retryOnFailure() will log a message
+ */
+ return operation;
+ }
+
+ // get a non-null operation
+ OperationOutcome oper2;
+ if (operation != null) {
+ oper2 = operation;
+ } else {
+ oper2 = params.makeOutcome();
+ oper2.setResult(PolicyResult.FAILURE);
+ }
+
+ int retry = getRetry(params.getRetry());
+ if (retry > 0 && attempt > retry) {
+ /*
+ * retries were specified and we've already tried them all - change to
+ * FAILURE_RETRIES
+ */
+ logger.info("operation {} retries exhausted for {}", getFullName(), params.getRequestId());
+ oper2.setResult(PolicyResult.FAILURE_RETRIES);
+ }
+
+ return oper2;
+ };
+ }
+
+ /**
+ * Restarts the operation if it was a FAILURE. Assumes that {@link #setRetryFlag(int)}
+ * was previously invoked, and thus that the "operation" is not {@code null}.
+ *
+ * @param controller controller for all of the retries
+ * @param attempt latest attempt number, starting with 1
+ * @return a function to get the next future to execute
+ */
+ private Function<OperationOutcome, CompletableFuture<OperationOutcome>> retryOnFailure(
+ PipelineControllerFuture<OperationOutcome> controller, int attempt) {
+
+ return operation -> {
+ if (!isActorFailed(operation)) {
+ // wrong type or wrong operation - just leave it as is
+ logger.info("not retrying operation {} for {}", getFullName(), params.getRequestId());
+ controller.complete(operation);
+ return new CompletableFuture<>();
+ }
+
+ if (getRetry(params.getRetry()) <= 0) {
+ // no retries - already marked as FAILURE, so just return it
+ logger.info("operation {} no retries for {}", getFullName(), params.getRequestId());
+ controller.complete(operation);
+ return new CompletableFuture<>();
+ }
+
+ /*
+ * Retry the operation.
+ */
+ long waitMs = getRetryWaitMs();
+ logger.info("retry operation {} in {}ms for {}", getFullName(), waitMs, params.getRequestId());
+
+ return sleep(waitMs, TimeUnit.MILLISECONDS)
+ .thenCompose(unused -> startOperationAttempt(controller, attempt + 1));
+ };
+ }
+
+ /**
+ * Convenience method that starts a sleep(), running via a future.
+ *
+ * @param sleepTime time to sleep
+ * @param unit time unit
+ * @return a future that will complete when the sleep completes
+ */
+ protected CompletableFuture<Void> sleep(long sleepTime, TimeUnit unit) {
+ if (sleepTime <= 0) {
+ return CompletableFuture.completedFuture(null);
+ }
+
+ return new CompletableFuture<Void>().completeOnTimeout(null, sleepTime, unit);
+ }
+
+ /**
+ * Converts an exception into an operation outcome, returning a copy of the outcome to
+ * prevent background jobs from changing it.
+ *
+ * @param type type of item throwing the exception
+ * @return a function that will convert an exception into an operation outcome
+ */
+ private Function<Throwable, OperationOutcome> fromException(String type) {
+
+ return thrown -> {
+ OperationOutcome outcome = params.makeOutcome();
+
+ logger.warn("exception throw by {} {}.{} for {}", type, outcome.getActor(), outcome.getOperation(),
+ params.getRequestId(), thrown);
+
+ return setOutcome(outcome, thrown);
+ };
+ }
+
+ /**
+ * Similar to {@link CompletableFuture#anyOf(CompletableFuture...)}, but it cancels
+ * any outstanding futures when one completes.
+ *
+ * @param futureMakers function to make a future. If the function returns
+ * {@code null}, then no future is created for that function. On the other
+ * hand, if the function throws an exception, then the previously created
+ * functions are canceled and the exception is re-thrown
+ * @return a future to cancel or await an outcome, or {@code null} if no futures were
+ * created. If this future is canceled, then all of the futures will be
+ * canceled
+ */
+ protected CompletableFuture<OperationOutcome> anyOf(
+ @SuppressWarnings("unchecked") Supplier<CompletableFuture<OperationOutcome>>... futureMakers) {
+
+ return anyOf(Arrays.asList(futureMakers));
+ }
+
+ /**
+ * Similar to {@link CompletableFuture#anyOf(CompletableFuture...)}, but it cancels
+ * any outstanding futures when one completes.
+ *
+ * @param futureMakers function to make a future. If the function returns
+ * {@code null}, then no future is created for that function. On the other
+ * hand, if the function throws an exception, then the previously created
+ * functions are canceled and the exception is re-thrown
+ * @return a future to cancel or await an outcome, or {@code null} if no futures were
+ * created. If this future is canceled, then all of the futures will be
+ * canceled. Similarly, when this future completes, any incomplete futures
+ * will be canceled
+ */
+ protected CompletableFuture<OperationOutcome> anyOf(
+ List<Supplier<CompletableFuture<OperationOutcome>>> futureMakers) {
+
+ PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+
+ CompletableFuture<OperationOutcome>[] futures =
+ attachFutures(controller, futureMakers, UnaryOperator.identity());
+
+ if (futures.length == 0) {
+ // no futures were started
+ return null;
+ }
+
+ if (futures.length == 1) {
+ return futures[0];
+ }
+
+ CompletableFuture.anyOf(futures).thenApply(outcome -> (OperationOutcome) outcome)
+ .whenCompleteAsync(controller.delayedComplete(), params.getExecutor());
+
+ return controller;
+ }
+
+ /**
+ * Similar to {@link CompletableFuture#allOf(CompletableFuture...)}.
+ *
+ * @param futureMakers function to make a future. If the function returns
+ * {@code null}, then no future is created for that function. On the other
+ * hand, if the function throws an exception, then the previously created
+ * functions are canceled and the exception is re-thrown
+ * @return a future to cancel or await an outcome, or {@code null} if no futures were
+ * created. If this future is canceled, then all of the futures will be
+ * canceled
+ */
+ protected CompletableFuture<OperationOutcome> allOf(
+ @SuppressWarnings("unchecked") Supplier<CompletableFuture<OperationOutcome>>... futureMakers) {
+
+ return allOf(Arrays.asList(futureMakers));
+ }
+
+ /**
+ * Similar to {@link CompletableFuture#allOf(CompletableFuture...)}.
+ *
+ * @param futureMakers function to make a future. If the function returns
+ * {@code null}, then no future is created for that function. On the other
+ * hand, if the function throws an exception, then the previously created
+ * functions are canceled and the exception is re-thrown
+ * @return a future to cancel or await an outcome, or {@code null} if no futures were
+ * created. If this future is canceled, then all of the futures will be
+ * canceled. Similarly, when this future completes, any incomplete futures
+ * will be canceled
+ */
+ protected CompletableFuture<OperationOutcome> allOf(
+ List<Supplier<CompletableFuture<OperationOutcome>>> futureMakers) {
+ PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+
+ Queue<OperationOutcome> outcomes = new LinkedList<>();
+
+ CompletableFuture<OperationOutcome>[] futures =
+ attachFutures(controller, futureMakers, future -> future.thenApply(outcome -> {
+ synchronized (outcomes) {
+ outcomes.add(outcome);
+ }
+ return outcome;
+ }));
+
+ if (futures.length == 0) {
+ // no futures were started
+ return null;
+ }
+
+ if (futures.length == 1) {
+ return futures[0];
+ }
+
+ // @formatter:off
+ CompletableFuture.allOf(futures)
+ .thenApply(unused -> combineOutcomes(outcomes))
+ .whenCompleteAsync(controller.delayedComplete(), params.getExecutor());
+ // @formatter:on
+
+ return controller;
+ }
+
+ /**
+ * Invokes the functions to create the futures and attaches them to the controller.
+ *
+ * @param controller master controller for all of the futures
+ * @param futureMakers futures to be attached to the controller
+ * @param adorn function that "adorns" the future, possible adding onto its pipeline.
+ * Returns the adorned future
+ * @return an array of futures, possibly zero-length. If the array is of size one,
+ * then that one item should be returned instead of the controller
+ */
+ private CompletableFuture<OperationOutcome>[] attachFutures(PipelineControllerFuture<OperationOutcome> controller,
+ List<Supplier<CompletableFuture<OperationOutcome>>> futureMakers,
+ UnaryOperator<CompletableFuture<OperationOutcome>> adorn) {
+
+ if (futureMakers.isEmpty()) {
+ @SuppressWarnings("unchecked")
+ CompletableFuture<OperationOutcome>[] result = new CompletableFuture[0];
+ return result;
+ }
+
+ // the last, unadorned future that is created
+ CompletableFuture<OperationOutcome> lastFuture = null;
+
+ List<CompletableFuture<OperationOutcome>> futures = new ArrayList<>(futureMakers.size());
+
+ // make each future
+ for (var maker : futureMakers) {
+ try {
+ CompletableFuture<OperationOutcome> future = maker.get();
+ if (future == null) {
+ continue;
+ }
+
+ // propagate "stop" to the future
+ controller.add(future);
+
+ futures.add(adorn.apply(future));
+
+ lastFuture = future;
+
+ } catch (RuntimeException e) {
+ logger.warn("{}: exception creating 'future' for {}", getFullName(), params.getRequestId());
+ controller.cancel(false);
+ throw e;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ CompletableFuture<OperationOutcome>[] result = new CompletableFuture[futures.size()];
+
+ if (result.length == 1) {
+ // special case - return the unadorned future
+ result[0] = lastFuture;
+ return result;
+ }
+
+ return futures.toArray(result);
+ }
+
+ /**
+ * Combines the outcomes from a set of tasks.
+ *
+ * @param outcomes outcomes to be examined
+ * @return the combined outcome
+ */
+ private OperationOutcome combineOutcomes(Queue<OperationOutcome> outcomes) {
+
+ // identify the outcome with the highest priority
+ OperationOutcome outcome = outcomes.remove();
+ int priority = detmPriority(outcome);
+
+ for (OperationOutcome outcome2 : outcomes) {
+ int priority2 = detmPriority(outcome2);
+
+ if (priority2 > priority) {
+ outcome = outcome2;
+ priority = priority2;
+ }
+ }
+
+ logger.info("{}: combined outcome of tasks is {} for {}", getFullName(),
+ (outcome == null ? null : outcome.getResult()), params.getRequestId());
+
+ return outcome;
+ }
+
+ /**
+ * Determines the priority of an outcome based on its result.
+ *
+ * @param outcome outcome to examine, or {@code null}
+ * @return the outcome's priority
+ */
+ protected int detmPriority(OperationOutcome outcome) {
+ if (outcome == null || outcome.getResult() == null) {
+ return 1;
+ }
+
+ switch (outcome.getResult()) {
+ case SUCCESS:
+ return 0;
+
+ case FAILURE_GUARD:
+ return 2;
+
+ case FAILURE_RETRIES:
+ return 3;
+
+ case FAILURE:
+ return 4;
+
+ case FAILURE_TIMEOUT:
+ return 5;
+
+ case FAILURE_EXCEPTION:
+ default:
+ return 6;
+ }
+ }
+
+ /**
+ * Performs a sequence of tasks, stopping if a task fails. A given task's future is
+ * not created until the previous task completes. The pipeline returns the outcome of
+ * the last task executed.
+ *
+ * @param futureMakers functions to make the futures
+ * @return a future to cancel the sequence or await the outcome
+ */
+ protected CompletableFuture<OperationOutcome> sequence(
+ @SuppressWarnings("unchecked") Supplier<CompletableFuture<OperationOutcome>>... futureMakers) {
+
+ return sequence(Arrays.asList(futureMakers));
+ }
+
+ /**
+ * Performs a sequence of tasks, stopping if a task fails. A given task's future is
+ * not created until the previous task completes. The pipeline returns the outcome of
+ * the last task executed.
+ *
+ * @param futureMakers functions to make the futures
+ * @return a future to cancel the sequence or await the outcome, or {@code null} if
+ * there were no tasks to perform
+ */
+ protected CompletableFuture<OperationOutcome> sequence(
+ List<Supplier<CompletableFuture<OperationOutcome>>> futureMakers) {
+
+ Queue<Supplier<CompletableFuture<OperationOutcome>>> queue = new ArrayDeque<>(futureMakers);
+
+ CompletableFuture<OperationOutcome> nextTask = getNextTask(queue);
+ if (nextTask == null) {
+ // no tasks
+ return null;
+ }
+
+ if (queue.isEmpty()) {
+ // only one task - just return it rather than wrapping it in a controller
+ return nextTask;
+ }
+
+ /*
+ * multiple tasks - need a controller to stop whichever task is currently
+ * executing
+ */
+ final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+ final Executor executor = params.getExecutor();
+
+ // @formatter:off
+ controller.wrap(nextTask)
+ .thenComposeAsync(nextTaskOnSuccess(controller, queue), executor)
+ .whenCompleteAsync(controller.delayedComplete(), executor);
+ // @formatter:on
+
+ return controller;
+ }
+
+ /**
+ * Executes the next task in the queue, if the previous outcome was successful.
+ *
+ * @param controller pipeline controller
+ * @param taskQueue queue of tasks to be performed
+ * @return a future to execute the remaining tasks, or the current outcome, if it's a
+ * failure, or if there are no more tasks
+ */
+ private Function<OperationOutcome, CompletableFuture<OperationOutcome>> nextTaskOnSuccess(
+ PipelineControllerFuture<OperationOutcome> controller,
+ Queue<Supplier<CompletableFuture<OperationOutcome>>> taskQueue) {
+
+ return outcome -> {
+ if (!isSuccess(outcome)) {
+ // return the failure
+ return CompletableFuture.completedFuture(outcome);
+ }
+
+ CompletableFuture<OperationOutcome> nextTask = getNextTask(taskQueue);
+ if (nextTask == null) {
+ // no tasks - just return the success
+ return CompletableFuture.completedFuture(outcome);
+ }
+
+ // @formatter:off
+ return controller
+ .wrap(nextTask)
+ .thenComposeAsync(nextTaskOnSuccess(controller, taskQueue), params.getExecutor());
+ // @formatter:on
+ };
+ }
+
+ /**
+ * Gets the next task from the queue, skipping those that are {@code null}.
+ *
+ * @param taskQueue task queue
+ * @return the next task, or {@code null} if the queue is now empty
+ */
+ private CompletableFuture<OperationOutcome> getNextTask(
+ Queue<Supplier<CompletableFuture<OperationOutcome>>> taskQueue) {
+
+ Supplier<CompletableFuture<OperationOutcome>> maker;
+
+ while ((maker = taskQueue.poll()) != null) {
+ CompletableFuture<OperationOutcome> future = maker.get();
+ if (future != null) {
+ return future;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets the start time of the operation and invokes the callback to indicate that the
+ * operation has started. Does nothing if the pipeline has been stopped.
+ * <p/>
+ * This assumes that the "outcome" is not {@code null}.
+ *
+ * @param callbacks used to determine if the start callback can be invoked
+ * @return a function that sets the start time and invokes the callback
+ */
+ private BiConsumer<OperationOutcome, Throwable> callbackStarted(CallbackManager callbacks) {
+
+ return (outcome, thrown) -> {
+
+ if (callbacks.canStart()) {
+ // haven't invoked "start" callback yet
+ outcome.setStart(callbacks.getStartTime());
+ outcome.setEnd(null);
+ params.callbackStarted(outcome);
+ }
+ };
+ }
+
+ /**
+ * Sets the end time of the operation and invokes the callback to indicate that the
+ * operation has completed. Does nothing if the pipeline has been stopped.
+ * <p/>
+ * This assumes that the "outcome" is not {@code null}.
+ * <p/>
+ * Note: the start time must be a reference rather than a plain value, because it's
+ * value must be gotten on-demand, when the returned function is executed at a later
+ * time.
+ *
+ * @param callbacks used to determine if the end callback can be invoked
+ * @return a function that sets the end time and invokes the callback
+ */
+ private BiConsumer<OperationOutcome, Throwable> callbackCompleted(CallbackManager callbacks) {
+
+ return (outcome, thrown) -> {
+
+ if (callbacks.canEnd()) {
+ outcome.setStart(callbacks.getStartTime());
+ outcome.setEnd(callbacks.getEndTime());
+ params.callbackCompleted(outcome);
+ }
+ };
+ }
+
+ /**
+ * Sets an operation's outcome and message, based on a throwable.
+ *
+ * @param operation operation to be updated
+ * @return the updated operation
+ */
+ protected OperationOutcome setOutcome(OperationOutcome operation, Throwable thrown) {
+ PolicyResult result = (isTimeout(thrown) ? PolicyResult.FAILURE_TIMEOUT : PolicyResult.FAILURE_EXCEPTION);
+ return setOutcome(operation, result);
+ }
+
+ /**
+ * Sets an operation's outcome and default message based on the result.
+ *
+ * @param operation operation to be updated
+ * @param result result of the operation
+ * @return the updated operation
+ */
+ public OperationOutcome setOutcome(OperationOutcome operation, PolicyResult result) {
+ logger.trace("{}: set outcome {} for {}", getFullName(), result, params.getRequestId());
+ operation.setResult(result);
+ operation.setMessage(result == PolicyResult.SUCCESS ? ControlLoopOperation.SUCCESS_MSG
+ : ControlLoopOperation.FAILED_MSG);
+
+ return operation;
+ }
+
+ /**
+ * Determines if a throwable is due to a timeout.
+ *
+ * @param thrown throwable of interest
+ * @return {@code true} if the throwable is due to a timeout, {@code false} otherwise
+ */
+ protected boolean isTimeout(Throwable thrown) {
+ if (thrown instanceof CompletionException) {
+ thrown = thrown.getCause();
+ }
+
+ return (thrown instanceof TimeoutException);
+ }
+
+ /**
+ * Logs a response. If the response is not of type, String, then it attempts to
+ * pretty-print it into JSON before logging.
+ *
+ * @param direction IN or OUT
+ * @param infra communication infrastructure on which it was published
+ * @param source source name (e.g., the URL or Topic name)
+ * @param response response to be logged
+ * @return the JSON text that was logged
+ */
+ public <T> String logMessage(EventType direction, CommInfrastructure infra, String source, 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) {
+ String type = (direction == EventType.IN ? "response" : "request");
+ logger.warn("cannot pretty-print {}", type, e);
+ json = response.toString();
+ }
+
+ logger.info("[{}|{}|{}|]{}{}", direction, infra, source, NetLoggerUtil.SYSTEM_LS, json);
+
+ return json;
+ }
+
+ // these may be overridden by subclasses or junit tests
+
+ /**
+ * Gets the retry count.
+ *
+ * @param retry retry, extracted from the parameters, or {@code null}
+ * @return the number of retries, or {@code 0} if no retries were specified
+ */
+ protected int getRetry(Integer retry) {
+ return (retry == null ? 0 : retry);
+ }
+
+ /**
+ * Gets the retry wait, in milliseconds.
+ *
+ * @return the retry wait, in milliseconds
+ */
+ protected long getRetryWaitMs() {
+ return DEFAULT_RETRY_WAIT_MS;
+ }
+
+ /**
+ * Gets the operation timeout.
+ *
+ * @param timeoutSec timeout, in seconds, extracted from the parameters, or
+ * {@code null}
+ * @return the operation timeout, in milliseconds, or {@code 0} if no timeout was
+ * specified
+ */
+ protected long getTimeoutMs(Integer timeoutSec) {
+ return (timeoutSec == null ? 0 : TimeUnit.MILLISECONDS.convert(timeoutSec, TimeUnit.SECONDS));
+ }
+
+ // 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/OperatorPartial.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartial.java
index df5258d71..3e15c1be4 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartial.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartial.java
@@ -20,57 +20,24 @@
package org.onap.policy.controlloop.actorserviceprovider.impl;
-import java.util.List;
import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.function.BiConsumer;
-import java.util.function.Function;
-import lombok.AccessLevel;
import lombok.Getter;
-import lombok.Setter;
-import org.onap.policy.controlloop.ControlLoopOperation;
-import org.onap.policy.controlloop.actorserviceprovider.CallbackManager;
-import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
import org.onap.policy.controlloop.actorserviceprovider.Operator;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
-import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture;
-import org.onap.policy.controlloop.policy.PolicyResult;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
- * Partial implementation of an operator. In general, it's preferable that subclasses
- * would override
- * {@link #startOperationAsync(ControlLoopOperationParams, int, OperationOutcome)
- * startOperationAsync()}. However, if that proves to be too difficult, then they can
- * simply override {@link #doOperation(ControlLoopOperationParams, int, OperationOutcome)
- * doOperation()}. In addition, if the operation requires any preprocessor steps, the
- * subclass may choose to override
- * {@link #startPreprocessorAsync(ControlLoopOperationParams) startPreprocessorAsync()}.
- * <p/>
- * The futures returned by the methods within this class can be canceled, and will
- * propagate the cancellation to any subtasks. Thus it is also expected that any futures
- * returned by overridden methods will do the same. Of course, if a class overrides
- * {@link #doOperation(ControlLoopOperationParams, int, OperationOutcome) doOperation()},
- * then there's little that can be done to cancel that particular operation.
+ * Partial implementation of an operator.
*/
public abstract class OperatorPartial extends StartConfigPartial<Map<String, Object>> implements Operator {
- private static final Logger logger = LoggerFactory.getLogger(OperatorPartial.class);
-
/**
* Executor to be used for tasks that may perform blocking I/O. The default executor
* simply launches a new thread for each command that is submitted to it.
* <p/>
- * May be overridden by junit tests.
+ * The "get" method may be overridden by junit tests.
*/
- @Getter(AccessLevel.PROTECTED)
- @Setter(AccessLevel.PROTECTED)
- private Executor blockingExecutor = command -> {
+ @Getter
+ private final Executor blockingExecutor = command -> {
Thread thread = new Thread(command);
thread.setDaemon(true);
thread.start();
@@ -125,721 +92,4 @@ public abstract class OperatorPartial extends StartConfigPartial<Map<String, Obj
protected void doShutdown() {
// do nothing
}
-
- @Override
- public final CompletableFuture<OperationOutcome> startOperation(ControlLoopOperationParams params) {
- if (!isAlive()) {
- throw new IllegalStateException("operation is not running: " + getFullName());
- }
-
- // allocate a controller for the entire operation
- final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
- CompletableFuture<OperationOutcome> preproc = startPreprocessorAsync(params);
- if (preproc == null) {
- // no preprocessor required - just start the operation
- return startOperationAttempt(params, controller, 1);
- }
-
- /*
- * Do preprocessor first and then, if successful, start the operation. Note:
- * operations create their own outcome, ignoring the outcome from any previous
- * steps.
- *
- * Wrap the preprocessor to ensure "stop" is propagated to it.
- */
- // @formatter:off
- controller.wrap(preproc)
- .exceptionally(fromException(params, "preprocessor of operation"))
- .thenCompose(handlePreprocessorFailure(params, controller))
- .thenCompose(unusedOutcome -> startOperationAttempt(params, controller, 1));
- // @formatter:on
-
- return controller;
- }
-
- /**
- * Handles a failure in the preprocessor pipeline. If a failure occurred, then it
- * invokes the call-backs, marks the controller complete, and returns an incomplete
- * future, effectively halting the pipeline. Otherwise, it returns the outcome that it
- * received.
- * <p/>
- * Assumes that no callbacks have been invoked yet.
- *
- * @param params operation parameters
- * @param controller pipeline controller
- * @return a function that checks the outcome status and continues, if successful, or
- * indicates a failure otherwise
- */
- private Function<OperationOutcome, CompletableFuture<OperationOutcome>> handlePreprocessorFailure(
- ControlLoopOperationParams params, PipelineControllerFuture<OperationOutcome> controller) {
-
- return outcome -> {
-
- if (outcome != null && isSuccess(outcome)) {
- logger.trace("{}: preprocessor succeeded for {}", getFullName(), params.getRequestId());
- return CompletableFuture.completedFuture(outcome);
- }
-
- logger.warn("preprocessor failed, discontinuing operation {} for {}", getFullName(), params.getRequestId());
-
- final Executor executor = params.getExecutor();
- final CallbackManager callbacks = new CallbackManager();
-
- // propagate "stop" to the callbacks
- controller.add(callbacks);
-
- final OperationOutcome outcome2 = params.makeOutcome();
-
- // TODO need a FAILURE_MISSING_DATA (e.g., A&AI)
-
- outcome2.setResult(PolicyResult.FAILURE_GUARD);
- outcome2.setMessage(outcome != null ? outcome.getMessage() : null);
-
- // @formatter:off
- CompletableFuture.completedFuture(outcome2)
- .whenCompleteAsync(callbackStarted(params, callbacks), executor)
- .whenCompleteAsync(callbackCompleted(params, callbacks), executor)
- .whenCompleteAsync(controller.delayedComplete(), executor);
- // @formatter:on
-
- return new CompletableFuture<>();
- };
- }
-
- /**
- * Invokes the operation's preprocessor step(s) as a "future". This method simply
- * returns {@code null}.
- * <p/>
- * This method assumes the following:
- * <ul>
- * <li>the operator is alive</li>
- * <li>exceptions generated within the pipeline will be handled by the invoker</li>
- * </ul>
- *
- * @param params operation parameters
- * @return a function that will start the preprocessor and returns its outcome, or
- * {@code null} if this operation needs no preprocessor
- */
- protected CompletableFuture<OperationOutcome> startPreprocessorAsync(ControlLoopOperationParams params) {
- return null;
- }
-
- /**
- * Starts the operation attempt, with no preprocessor. When all retries complete, it
- * will complete the controller.
- *
- * @param params operation parameters
- * @param controller controller for all operation attempts
- * @param attempt attempt number, typically starting with 1
- * @return a future that will return the final result of all attempts
- */
- private CompletableFuture<OperationOutcome> startOperationAttempt(ControlLoopOperationParams params,
- PipelineControllerFuture<OperationOutcome> controller, int attempt) {
-
- // propagate "stop" to the operation attempt
- controller.wrap(startAttemptWithoutRetries(params, attempt))
- .thenCompose(retryOnFailure(params, controller, attempt));
-
- return controller;
- }
-
- /**
- * Starts the operation attempt, without doing any retries.
- *
- * @param params operation parameters
- * @param attempt attempt number, typically starting with 1
- * @return a future that will return the result of a single operation attempt
- */
- private CompletableFuture<OperationOutcome> startAttemptWithoutRetries(ControlLoopOperationParams params,
- int attempt) {
-
- logger.info("{}: start operation attempt {} for {}", getFullName(), attempt, params.getRequestId());
-
- final Executor executor = params.getExecutor();
- final OperationOutcome outcome = params.makeOutcome();
- final CallbackManager callbacks = new CallbackManager();
-
- // this operation attempt gets its own controller
- final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
- // propagate "stop" to the callbacks
- controller.add(callbacks);
-
- // @formatter:off
- CompletableFuture<OperationOutcome> future = CompletableFuture.completedFuture(outcome)
- .whenCompleteAsync(callbackStarted(params, callbacks), executor)
- .thenCompose(controller.wrap(outcome2 -> startOperationAsync(params, attempt, outcome2)));
- // @formatter:on
-
- // handle timeouts, if specified
- long timeoutMillis = getTimeOutMillis(params.getTimeoutSec());
- if (timeoutMillis > 0) {
- logger.info("{}: set timeout to {}ms for {}", getFullName(), timeoutMillis, params.getRequestId());
- future = future.orTimeout(timeoutMillis, TimeUnit.MILLISECONDS);
- }
-
- /*
- * Note: we re-invoke callbackStarted() just to be sure the callback is invoked
- * before callbackCompleted() is invoked.
- *
- * Note: no need to remove "callbacks" from the pipeline, as we're going to stop
- * the pipeline as the last step anyway.
- */
-
- // @formatter:off
- future.exceptionally(fromException(params, "operation"))
- .thenApply(setRetryFlag(params, attempt))
- .whenCompleteAsync(callbackStarted(params, callbacks), executor)
- .whenCompleteAsync(callbackCompleted(params, callbacks), executor)
- .whenCompleteAsync(controller.delayedComplete(), executor);
- // @formatter:on
-
- return controller;
- }
-
- /**
- * Determines if the outcome was successful.
- *
- * @param outcome outcome to examine
- * @return {@code true} if the outcome was successful
- */
- protected boolean isSuccess(OperationOutcome outcome) {
- return (outcome.getResult() == PolicyResult.SUCCESS);
- }
-
- /**
- * Determines if the outcome was a failure for this operator.
- *
- * @param outcome outcome to examine, or {@code null}
- * @return {@code true} if the outcome is not {@code null} and was a failure
- * <i>and</i> was associated with this operator, {@code false} otherwise
- */
- protected boolean isActorFailed(OperationOutcome outcome) {
- return (isSameOperation(outcome) && outcome.getResult() == PolicyResult.FAILURE);
- }
-
- /**
- * Determines if the given outcome is for this operation.
- *
- * @param outcome outcome to examine
- * @return {@code true} if the outcome is for this operation, {@code false} otherwise
- */
- protected boolean isSameOperation(OperationOutcome outcome) {
- return OperationOutcome.isFor(outcome, getActorName(), getName());
- }
-
- /**
- * Invokes the operation as a "future". This method simply invokes
- * {@link #doOperation(ControlLoopOperationParams)} using the {@link #blockingExecutor
- * "blocking executor"}, returning the result via a "future".
- * <p/>
- * Note: if the operation uses blocking I/O, then it should <i>not</i> be run using
- * the executor in the "params", as that may bring the background thread pool to a
- * grinding halt. The {@link #blockingExecutor "blocking executor"} should be used
- * instead.
- * <p/>
- * This method assumes the following:
- * <ul>
- * <li>the operator is alive</li>
- * <li>verifyRunning() has been invoked</li>
- * <li>callbackStarted() has been invoked</li>
- * <li>the invoker will perform appropriate timeout checks</li>
- * <li>exceptions generated within the pipeline will be handled by the invoker</li>
- * </ul>
- *
- * @param params operation parameters
- * @param attempt attempt number, typically starting with 1
- * @return a function that will start the operation and return its result when
- * complete
- */
- protected CompletableFuture<OperationOutcome> startOperationAsync(ControlLoopOperationParams params, int attempt,
- OperationOutcome outcome) {
-
- return CompletableFuture.supplyAsync(() -> doOperation(params, attempt, outcome), getBlockingExecutor());
- }
-
- /**
- * Low-level method that performs the operation. This can make the same assumptions
- * that are made by {@link #doOperationAsFuture(ControlLoopOperationParams)}. This
- * particular method simply throws an {@link UnsupportedOperationException}.
- *
- * @param params operation parameters
- * @param attempt attempt number, typically starting with 1
- * @param operation the operation being performed
- * @return the outcome of the operation
- */
- protected OperationOutcome doOperation(ControlLoopOperationParams params, int attempt, OperationOutcome operation) {
-
- throw new UnsupportedOperationException("start operation " + getFullName());
- }
-
- /**
- * Sets the outcome status to FAILURE_RETRIES, if the current operation outcome is
- * FAILURE, assuming the policy specifies retries and the retry count has been
- * exhausted.
- *
- * @param params operation parameters
- * @param attempt latest attempt number, starting with 1
- * @return a function to get the next future to execute
- */
- private Function<OperationOutcome, OperationOutcome> setRetryFlag(ControlLoopOperationParams params, int attempt) {
-
- return operation -> {
- if (operation != null && !isActorFailed(operation)) {
- /*
- * wrong type or wrong operation - just leave it as is. No need to log
- * anything here, as retryOnFailure() will log a message
- */
- return operation;
- }
-
- // get a non-null operation
- OperationOutcome oper2;
- if (operation != null) {
- oper2 = operation;
- } else {
- oper2 = params.makeOutcome();
- oper2.setResult(PolicyResult.FAILURE);
- }
-
- Integer retry = params.getRetry();
- if (retry != null && retry > 0 && attempt > retry) {
- /*
- * retries were specified and we've already tried them all - change to
- * FAILURE_RETRIES
- */
- logger.info("operation {} retries exhausted for {}", getFullName(), params.getRequestId());
- oper2.setResult(PolicyResult.FAILURE_RETRIES);
- }
-
- return oper2;
- };
- }
-
- /**
- * Restarts the operation if it was a FAILURE. Assumes that
- * {@link #setRetryFlag(ControlLoopOperationParams, int)} was previously invoked, and
- * thus that the "operation" is not {@code null}.
- *
- * @param params operation parameters
- * @param controller controller for all of the retries
- * @param attempt latest attempt number, starting with 1
- * @return a function to get the next future to execute
- */
- private Function<OperationOutcome, CompletableFuture<OperationOutcome>> retryOnFailure(
- ControlLoopOperationParams params, PipelineControllerFuture<OperationOutcome> controller,
- int attempt) {
-
- return operation -> {
- if (!isActorFailed(operation)) {
- // wrong type or wrong operation - just leave it as is
- logger.trace("not retrying operation {} for {}", getFullName(), params.getRequestId());
- controller.complete(operation);
- return new CompletableFuture<>();
- }
-
- Integer retry = params.getRetry();
- if (retry == null || retry <= 0) {
- // no retries - already marked as FAILURE, so just return it
- logger.info("operation {} no retries for {}", getFullName(), params.getRequestId());
- controller.complete(operation);
- return new CompletableFuture<>();
- }
-
-
- /*
- * Retry the operation.
- */
- logger.info("retry operation {} for {}", getFullName(), params.getRequestId());
-
- return startOperationAttempt(params, controller, attempt + 1);
- };
- }
-
- /**
- * Converts an exception into an operation outcome, returning a copy of the outcome to
- * prevent background jobs from changing it.
- *
- * @param params operation parameters
- * @param type type of item throwing the exception
- * @return a function that will convert an exception into an operation outcome
- */
- private Function<Throwable, OperationOutcome> fromException(ControlLoopOperationParams params, String type) {
-
- return thrown -> {
- OperationOutcome outcome = params.makeOutcome();
-
- logger.warn("exception throw by {} {}.{} for {}", type, outcome.getActor(), outcome.getOperation(),
- params.getRequestId(), thrown);
-
- return setOutcome(params, outcome, thrown);
- };
- }
-
- /**
- * Similar to {@link CompletableFuture#anyOf(CompletableFuture...)}, but it cancels
- * any outstanding futures when one completes.
- *
- * @param params operation parameters
- * @param futures futures for which to wait
- * @return a future to cancel or await an outcome. If this future is canceled, then
- * all of the futures will be canceled
- */
- protected CompletableFuture<OperationOutcome> anyOf(ControlLoopOperationParams params,
- List<CompletableFuture<OperationOutcome>> futures) {
-
- // convert list to an array
- @SuppressWarnings("rawtypes")
- CompletableFuture[] arrFutures = futures.toArray(new CompletableFuture[futures.size()]);
-
- @SuppressWarnings("unchecked")
- CompletableFuture<OperationOutcome> result = anyOf(params, arrFutures);
- return result;
- }
-
- /**
- * Same as {@link CompletableFuture#anyOf(CompletableFuture...)}, but it cancels any
- * outstanding futures when one completes.
- *
- * @param params operation parameters
- * @param futures futures for which to wait
- * @return a future to cancel or await an outcome. If this future is canceled, then
- * all of the futures will be canceled
- */
- protected CompletableFuture<OperationOutcome> anyOf(ControlLoopOperationParams params,
- @SuppressWarnings("unchecked") CompletableFuture<OperationOutcome>... futures) {
-
- final Executor executor = params.getExecutor();
- final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
- attachFutures(controller, futures);
-
- // @formatter:off
- CompletableFuture.anyOf(futures)
- .thenApply(object -> (OperationOutcome) object)
- .whenCompleteAsync(controller.delayedComplete(), executor);
- // @formatter:on
-
- return controller;
- }
-
- /**
- * Similar to {@link CompletableFuture#allOf(CompletableFuture...)}, but it cancels
- * the futures if returned future is canceled. The future returns the "worst" outcome,
- * based on priority (see {@link #detmPriority(OperationOutcome)}).
- *
- * @param params operation parameters
- * @param futures futures for which to wait
- * @return a future to cancel or await an outcome. If this future is canceled, then
- * all of the futures will be canceled
- */
- protected CompletableFuture<OperationOutcome> allOf(ControlLoopOperationParams params,
- List<CompletableFuture<OperationOutcome>> futures) {
-
- // convert list to an array
- @SuppressWarnings("rawtypes")
- CompletableFuture[] arrFutures = futures.toArray(new CompletableFuture[futures.size()]);
-
- @SuppressWarnings("unchecked")
- CompletableFuture<OperationOutcome> result = allOf(params, arrFutures);
- return result;
- }
-
- /**
- * Same as {@link CompletableFuture#allOf(CompletableFuture...)}, but it cancels the
- * futures if returned future is canceled. The future returns the "worst" outcome,
- * based on priority (see {@link #detmPriority(OperationOutcome)}).
- *
- * @param params operation parameters
- * @param futures futures for which to wait
- * @return a future to cancel or await an outcome. If this future is canceled, then
- * all of the futures will be canceled
- */
- protected CompletableFuture<OperationOutcome> allOf(ControlLoopOperationParams params,
- @SuppressWarnings("unchecked") CompletableFuture<OperationOutcome>... futures) {
-
- final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
- attachFutures(controller, futures);
-
- OperationOutcome[] outcomes = new OperationOutcome[futures.length];
-
- @SuppressWarnings("rawtypes")
- CompletableFuture[] futures2 = new CompletableFuture[futures.length];
-
- // record the outcomes of each future when it completes
- for (int count = 0; count < futures2.length; ++count) {
- final int count2 = count;
- futures2[count] = futures[count].whenComplete((outcome2, thrown) -> outcomes[count2] = outcome2);
- }
-
- CompletableFuture.allOf(futures2).whenComplete(combineOutcomes(params, controller, outcomes));
-
- return controller;
- }
-
- /**
- * Attaches the given futures to the controller.
- *
- * @param controller master controller for all of the futures
- * @param futures futures to be attached to the controller
- */
- private void attachFutures(PipelineControllerFuture<OperationOutcome> controller,
- @SuppressWarnings("unchecked") CompletableFuture<OperationOutcome>... futures) {
-
- // attach each task
- for (CompletableFuture<OperationOutcome> future : futures) {
- controller.add(future);
- }
- }
-
- /**
- * Combines the outcomes from a set of tasks.
- *
- * @param params operation parameters
- * @param future future to be completed with the combined result
- * @param outcomes outcomes to be examined
- */
- private BiConsumer<Void, Throwable> combineOutcomes(ControlLoopOperationParams params,
- CompletableFuture<OperationOutcome> future, OperationOutcome[] outcomes) {
-
- return (unused, thrown) -> {
- if (thrown != null) {
- future.completeExceptionally(thrown);
- return;
- }
-
- // identify the outcome with the highest priority
- OperationOutcome outcome = outcomes[0];
- int priority = detmPriority(outcome);
-
- // start with "1", as we've already dealt with "0"
- for (int count = 1; count < outcomes.length; ++count) {
- OperationOutcome outcome2 = outcomes[count];
- int priority2 = detmPriority(outcome2);
-
- if (priority2 > priority) {
- outcome = outcome2;
- priority = priority2;
- }
- }
-
- logger.trace("{}: combined outcome of tasks is {} for {}", getFullName(),
- (outcome == null ? null : outcome.getResult()), params.getRequestId());
-
- future.complete(outcome);
- };
- }
-
- /**
- * Determines the priority of an outcome based on its result.
- *
- * @param outcome outcome to examine, or {@code null}
- * @return the outcome's priority
- */
- protected int detmPriority(OperationOutcome outcome) {
- if (outcome == null) {
- return 1;
- }
-
- switch (outcome.getResult()) {
- case SUCCESS:
- return 0;
-
- case FAILURE_GUARD:
- return 2;
-
- case FAILURE_RETRIES:
- return 3;
-
- case FAILURE:
- return 4;
-
- case FAILURE_TIMEOUT:
- return 5;
-
- case FAILURE_EXCEPTION:
- default:
- return 6;
- }
- }
-
- /**
- * Performs a task, after verifying that the controller is still running. Also checks
- * that the previous outcome was successful, if specified.
- *
- * @param params operation parameters
- * @param controller overall pipeline controller
- * @param checkSuccess {@code true} to check the previous outcome, {@code false}
- * otherwise
- * @param outcome outcome of the previous task
- * @param tasks tasks to be performed
- * @return a function to perform the task. If everything checks out, then it returns
- * the task's future. Otherwise, it returns an incomplete future and completes
- * the controller instead.
- */
- // @formatter:off
- protected CompletableFuture<OperationOutcome> doTask(ControlLoopOperationParams params,
- PipelineControllerFuture<OperationOutcome> controller,
- boolean checkSuccess, OperationOutcome outcome,
- CompletableFuture<OperationOutcome> task) {
- // @formatter:on
-
- if (checkSuccess && !isSuccess(outcome)) {
- /*
- * must complete before canceling so that cancel() doesn't cause controller to
- * complete
- */
- controller.complete(outcome);
- task.cancel(false);
- return new CompletableFuture<>();
- }
-
- return controller.wrap(task);
- }
-
- /**
- * Performs a task, after verifying that the controller is still running. Also checks
- * that the previous outcome was successful, if specified.
- *
- * @param params operation parameters
- * @param controller overall pipeline controller
- * @param checkSuccess {@code true} to check the previous outcome, {@code false}
- * otherwise
- * @param tasks tasks to be performed
- * @return a function to perform the task. If everything checks out, then it returns
- * the task's future. Otherwise, it returns an incomplete future and completes
- * the controller instead.
- */
- // @formatter:off
- protected Function<OperationOutcome, CompletableFuture<OperationOutcome>> doTask(ControlLoopOperationParams params,
- PipelineControllerFuture<OperationOutcome> controller,
- boolean checkSuccess,
- Function<OperationOutcome, CompletableFuture<OperationOutcome>> task) {
- // @formatter:on
-
- return outcome -> {
-
- if (!controller.isRunning()) {
- return new CompletableFuture<>();
- }
-
- if (checkSuccess && !isSuccess(outcome)) {
- controller.complete(outcome);
- return new CompletableFuture<>();
- }
-
- return controller.wrap(task.apply(outcome));
- };
- }
-
- /**
- * Sets the start time of the operation and invokes the callback to indicate that the
- * operation has started. Does nothing if the pipeline has been stopped.
- * <p/>
- * This assumes that the "outcome" is not {@code null}.
- *
- * @param params operation parameters
- * @param callbacks used to determine if the start callback can be invoked
- * @return a function that sets the start time and invokes the callback
- */
- private BiConsumer<OperationOutcome, Throwable> callbackStarted(ControlLoopOperationParams params,
- CallbackManager callbacks) {
-
- return (outcome, thrown) -> {
-
- if (callbacks.canStart()) {
- // haven't invoked "start" callback yet
- outcome.setStart(callbacks.getStartTime());
- outcome.setEnd(null);
- params.callbackStarted(outcome);
- }
- };
- }
-
- /**
- * Sets the end time of the operation and invokes the callback to indicate that the
- * operation has completed. Does nothing if the pipeline has been stopped.
- * <p/>
- * This assumes that the "outcome" is not {@code null}.
- * <p/>
- * Note: the start time must be a reference rather than a plain value, because it's
- * value must be gotten on-demand, when the returned function is executed at a later
- * time.
- *
- * @param params operation parameters
- * @param callbacks used to determine if the end callback can be invoked
- * @return a function that sets the end time and invokes the callback
- */
- private BiConsumer<OperationOutcome, Throwable> callbackCompleted(ControlLoopOperationParams params,
- CallbackManager callbacks) {
-
- return (outcome, thrown) -> {
-
- if (callbacks.canEnd()) {
- outcome.setStart(callbacks.getStartTime());
- outcome.setEnd(callbacks.getEndTime());
- params.callbackCompleted(outcome);
- }
- };
- }
-
- /**
- * Sets an operation's outcome and message, based on a throwable.
- *
- * @param params operation parameters
- * @param operation operation to be updated
- * @return the updated operation
- */
- protected OperationOutcome setOutcome(ControlLoopOperationParams params, OperationOutcome operation,
- Throwable thrown) {
- PolicyResult result = (isTimeout(thrown) ? PolicyResult.FAILURE_TIMEOUT : PolicyResult.FAILURE_EXCEPTION);
- return setOutcome(params, operation, result);
- }
-
- /**
- * Sets an operation's outcome and default message based on the result.
- *
- * @param params operation parameters
- * @param operation operation to be updated
- * @param result result of the operation
- * @return the updated operation
- */
- protected OperationOutcome setOutcome(ControlLoopOperationParams params, OperationOutcome operation,
- PolicyResult result) {
- logger.trace("{}: set outcome {} for {}", getFullName(), result, params.getRequestId());
- operation.setResult(result);
- operation.setMessage(result == PolicyResult.SUCCESS ? ControlLoopOperation.SUCCESS_MSG
- : ControlLoopOperation.FAILED_MSG);
-
- return operation;
- }
-
- /**
- * Determines if a throwable is due to a timeout.
- *
- * @param thrown throwable of interest
- * @return {@code true} if the throwable is due to a timeout, {@code false} otherwise
- */
- protected boolean isTimeout(Throwable thrown) {
- if (thrown instanceof CompletionException) {
- thrown = thrown.getCause();
- }
-
- return (thrown instanceof TimeoutException);
- }
-
- // these may be overridden by junit tests
-
- /**
- * Gets the operation timeout. Subclasses may override this method to obtain the
- * timeout in some other way (e.g., through configuration properties).
- *
- * @param timeoutSec timeout, in seconds, or {@code null}
- * @return the operation timeout, in milliseconds
- */
- protected long getTimeOutMillis(Integer timeoutSec) {
- return (timeoutSec == null ? 0 : TimeUnit.MILLISECONDS.convert(timeoutSec, TimeUnit.SECONDS));
- }
}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicActorParams.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicActorParams.java
new file mode 100644
index 000000000..291aeeb23
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicActorParams.java
@@ -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.parameters;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+import org.onap.policy.common.parameters.annotations.Min;
+
+/**
+ * Parameters used by Actors whose Operators use bidirectional topic.
+ */
+@Getter
+@Setter
+@EqualsAndHashCode(callSuper = true)
+public class BidirectionalTopicActorParams extends CommonActorParams {
+
+ /*
+ * Optional, default values that are used if missing from the operation-specific
+ * parameters.
+ */
+
+ /**
+ * Sink topic name to which requests should be published.
+ */
+ private String sinkTopic;
+
+ /**
+ * Source topic name, from which to read responses.
+ */
+ private String sourceTopic;
+
+ /**
+ * Amount of time, in seconds, to wait for the HTTP request to complete. The default
+ * is 90 seconds.
+ */
+ @Min(1)
+ private int timeoutSec = 90;
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParams.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicParams.java
index 9e6d8a15e..cafca1fa6 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParams.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicParams.java
@@ -29,34 +29,36 @@ 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 bidirectional topic.
*/
@NotNull
@NotBlank
@Data
@Builder(toBuilder = true)
-public class TopicParams {
+public class BidirectionalTopicParams {
/**
- * Name of the target topic end point to which requests should be published.
+ * Sink topic name to which requests should be published.
*/
- private String target;
+ private String sinkTopic;
/**
- * Source topic end point, from which to read responses.
+ * Source topic name, from which to read responses.
*/
- private String source;
+ private String sourceTopic;
/**
- * 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.
+ * <p/>
+ * Note: this should NOT have a default value, as it receives its default value from
+ * {@link BidirectionalTopicActorParams}.
*/
- @Min(0)
- @Builder.Default
- private long timeoutSec = 0;
+ @Min(1)
+ private int timeoutSec;
+
/**
- * 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/parameters/CommonActorParams.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/CommonActorParams.java
new file mode 100644
index 000000000..dc6f2b657
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/CommonActorParams.java
@@ -0,0 +1,102 @@
+/*-
+ * ============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 java.util.TreeMap;
+import java.util.function.Function;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+import org.onap.policy.common.parameters.BeanValidator;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.common.parameters.annotations.NotNull;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+
+/**
+ * Superclass for Actor parameters that have default values in "this" object, and
+ * operation-specific values in {@link #operation}.
+ */
+@Getter
+@Setter
+@EqualsAndHashCode
+public class CommonActorParams {
+
+ /**
+ * Maps the operation name to its parameters.
+ */
+ @NotNull
+ protected Map<String, Map<String, Object>> operation;
+
+
+ /**
+ * Extracts a specific operation's parameters from "this".
+ *
+ * @param name name of the item containing "this"
+ * @return a function to extract an operation's parameters from "this". Note: the
+ * returned function is not thread-safe
+ */
+ public Function<String, Map<String, Object>> makeOperationParameters(String name) {
+
+ Map<String, Object> defaultParams = Util.translateToMap(name, this);
+ defaultParams.remove("operation");
+
+ return operationName -> {
+ Map<String, Object> specificParams = operation.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(name + "." + operationName, subparams);
+ };
+ }
+
+ /**
+ * Validates the parameters.
+ *
+ * @param name name of the object containing these parameters
+ * @return "this"
+ * @throws IllegalArgumentException if the parameters are invalid
+ */
+ public CommonActorParams 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) {
+ return new BeanValidator().validateTop(resultName, this);
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParams.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParams.java
index 57fce40d7..925916097 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParams.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParams.java
@@ -148,7 +148,8 @@ public class ControlLoopOperationParams {
return actorService
.getActor(getActor())
.getOperator(getOperation())
- .startOperation(this);
+ .buildOperation(this)
+ .start();
// @formatter:on
}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParams.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParams.java
index da4fb4f0c..d589e1d7e 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParams.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParams.java
@@ -20,26 +20,23 @@
package org.onap.policy.controlloop.actorserviceprovider.parameters;
-import java.util.Map;
-import java.util.function.Function;
-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 lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
import org.onap.policy.common.parameters.annotations.Min;
-import org.onap.policy.common.parameters.annotations.NotBlank;
-import org.onap.policy.common.parameters.annotations.NotNull;
-import org.onap.policy.controlloop.actorserviceprovider.Util;
/**
- * Parameters used by Actors that connect to a server via HTTP. This contains the
- * parameters that are common to all of the operations. Only the path changes for each
- * operation, thus it includes a mapping from operation name to path.
+ * Parameters used by Actors that connect to a server via HTTP.
*/
-@Data
-@NotNull
-@NotBlank
-public class HttpActorParams {
+@Getter
+@Setter
+@EqualsAndHashCode(callSuper = true)
+public class HttpActorParams extends CommonActorParams {
+
+ /*
+ * Optional, default values that are used if missing from the operation-specific
+ * parameters.
+ */
/**
* Name of the HttpClient, as found in the HttpClientFactory.
@@ -47,66 +44,9 @@ public class HttpActorParams {
private String clientName;
/**
- * Amount of time, in seconds to wait for the HTTP request to complete, where zero
- * indicates that it should wait forever. The default is zero.
- */
- @Min(0)
- private long timeoutSec = 0;
-
- /**
- * Maps the operation name to its URI path.
- */
- private Map<String, String> path;
-
- /**
- * Extracts a specific operation's parameters from "this".
- *
- * @param name name of the item containing "this"
- * @return a function to extract an operation's parameters from "this". Note: the
- * returned function is not thread-safe
- */
- public Function<String, Map<String, Object>> makeOperationParameters(String name) {
- HttpParams subparams = HttpParams.builder().clientName(getClientName()).timeoutSec(getTimeoutSec()).build();
-
- return operation -> {
- String subpath = path.get(operation);
- if (subpath == null) {
- return null;
- }
-
- subparams.setPath(subpath);
- return Util.translateToMap(name + "." + operation, subparams);
- };
- }
-
- /**
- * Validates the parameters.
- *
- * @param name name of the object containing these parameters
- * @return "this"
- * @throws IllegalArgumentException if the parameters are invalid
+ * Amount of time, in seconds, to wait for the HTTP request to complete. The default
+ * is 90 seconds.
*/
- public HttpActorParams 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);
-
- result.validateMap("path", path, (result2, entry) -> result2.validateNotNull(entry.getKey(), entry.getValue()));
-
- return result;
- }
+ @Min(1)
+ private int timeoutSec = 90;
}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParams.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParams.java
index 695ffe4dd..2d3ab8b54 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParams.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParams.java
@@ -48,12 +48,13 @@ public class HttpParams {
private String path;
/**
- * Amount of time, in seconds to wait for the HTTP request to complete, where zero
- * indicates that it should wait forever. The default is zero.
+ * Amount of time, in seconds, to wait for the HTTP request to complete.
+ * <p/>
+ * Note: this should NOT have a default value, as it receives its default value from
+ * {@link HttpActorParams}.
*/
- @Min(0)
- @Builder.Default
- private long timeoutSec = 0;
+ @Min(1)
+ private int timeoutSec;
/**
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/spi/Actor.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/spi/Actor.java
index 620950a3c..53bee5f00 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/spi/Actor.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/spi/Actor.java
@@ -22,7 +22,6 @@
package org.onap.policy.controlloop.actorserviceprovider.spi;
import java.util.Collection;
-
import java.util.List;
import java.util.Map;
import java.util.Set;
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicHandler.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicHandler.java
new file mode 100644
index 000000000..30ee1e2d0
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicHandler.java
@@ -0,0 +1,79 @@
+/*-
+ * ============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.List;
+import org.onap.policy.common.endpoints.event.comm.client.BidirectionalTopicClient;
+import org.onap.policy.common.endpoints.event.comm.client.BidirectionalTopicClientException;
+
+/**
+ * Handler for a bidirectional topic, supporting both publishing and forwarding of
+ * incoming messages.
+ */
+public class BidirectionalTopicHandler extends BidirectionalTopicClient {
+
+ /**
+ * Listener that will be attached to the topic to receive responses.
+ */
+ private final TopicListenerImpl listener = new TopicListenerImpl();
+
+
+ /**
+ * Constructs the object.
+ *
+ * @param sinkTopic sink topic name
+ * @param sourceTopic source topic name
+ * @throws BidirectionalTopicClientException if an error occurs
+ */
+ public BidirectionalTopicHandler(String sinkTopic, String sourceTopic) throws BidirectionalTopicClientException {
+ super(sinkTopic, sourceTopic);
+ }
+
+ /**
+ * Starts listening on the source topic(s).
+ */
+ public void start() {
+ getSource().register(listener);
+ }
+
+ /**
+ * Stops listening on the source topic(s).
+ */
+ public void stop() {
+ getSource().unregister(listener);
+ }
+
+ /**
+ * Stops listening on the source topic(s) and clears all of the forwarders.
+ */
+ public void shutdown() {
+ stop();
+ listener.shutdown();
+ }
+
+ public Forwarder addForwarder(SelectorKey... keys) {
+ return listener.addForwarder(keys);
+ }
+
+ public Forwarder addForwarder(List<SelectorKey> keys) {
+ return listener.addForwarder(keys);
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicManager.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicManager.java
new file mode 100644
index 000000000..10411875a
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicManager.java
@@ -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 bidirectional topics.
+ */
+@FunctionalInterface
+public interface BidirectionalTopicManager {
+
+ /**
+ * Gets the topic handler for the given parameters, creating one if it does not exist.
+ *
+ * @param sinkTopic sink topic name
+ * @param sourceTopic source topic name
+ * @return the topic handler associated with the given sink and source topic names
+ */
+ BidirectionalTopicHandler getTopicHandler(String sinkTopic, String sourceTopic);
+}
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
index 000000000..2d98b66fc
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/Forwarder.java
@@ -0,0 +1,138 @@
+/*-
+ * ============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 java.util.function.BiConsumer;
+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<BiConsumer<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, BiConsumer<String, StandardCoderObject> listener) {
+ if (keys.size() != values.size()) {
+ throw new IllegalArgumentException("key/value mismatch");
+ }
+
+ values2listeners.compute(values, (key, listeners) -> {
+ Map<BiConsumer<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, BiConsumer<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 textMessage original text message that was received
+ * @param scoMessage decoded text message
+ */
+ public void onMessage(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<BiConsumer<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 (BiConsumer<String, StandardCoderObject> listener : listeners.keySet()) {
+ try {
+ listener.accept(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
index 000000000..fc5727395
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/SelectorKey.java
@@ -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
index 000000000..93beab1cb
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicListenerImpl.java
@@ -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 final 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(message, object);
+ }
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceTest.java
index 851a79129..efc7bb830 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceTest.java
@@ -65,7 +65,7 @@ public class ActorServiceTest {
private Map<String, Object> sub2;
private Map<String, Object> sub3;
private Map<String, Object> sub4;
- private Map<String, Object> params;
+ private Map<String, Map<String, Object>> params;
private ActorService service;
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandlerTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandlerTest.java
deleted file mode 100644
index 31c6d2077..000000000
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandlerTest.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*-
- * ============LICENSE_START=======================================================
- * ONAP
- * ================================================================================
- * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
- * ================================================================================
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.policy.controlloop.actorserviceprovider;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-import java.util.UUID;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.atomic.AtomicReference;
-import org.junit.Before;
-import org.junit.Test;
-import org.onap.policy.controlloop.VirtualControlLoopEvent;
-import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
-import org.onap.policy.controlloop.policy.PolicyResult;
-
-public class AsyncResponseHandlerTest {
-
- private static final String ACTOR = "my-actor";
- private static final String OPERATION = "my-operation";
- private static final UUID REQ_ID = UUID.randomUUID();
- private static final String TEXT = "some text";
-
- private VirtualControlLoopEvent event;
- private ControlLoopEventContext context;
- private ControlLoopOperationParams params;
- private OperationOutcome outcome;
- private MyHandler handler;
-
- /**
- * Initializes all fields, including {@link #handler}.
- */
- @Before
- public void setUp() {
- event = new VirtualControlLoopEvent();
- event.setRequestId(REQ_ID);
-
- context = new ControlLoopEventContext(event);
- params = ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).context(context).build();
- outcome = params.makeOutcome();
-
- handler = new MyHandler(params, outcome);
- }
-
- @Test
- public void testAsyncResponseHandler_testGetParams_testGetOutcome() {
- assertSame(params, handler.getParams());
- assertSame(outcome, handler.getOutcome());
- }
-
- @Test
- public void testHandle() {
- CompletableFuture<String> future = new CompletableFuture<>();
- handler.handle(future).complete(outcome);
-
- assertTrue(future.isCancelled());
- }
-
- @Test
- public void testCompleted() throws Exception {
- CompletableFuture<OperationOutcome> result = handler.handle(new CompletableFuture<>());
- handler.completed(TEXT);
- assertTrue(result.isDone());
- assertSame(outcome, result.get());
- assertEquals(PolicyResult.FAILURE_RETRIES, outcome.getResult());
- assertEquals(TEXT, outcome.getMessage());
- }
-
- /**
- * Tests completed() when doCompleted() throws an exception.
- */
- @Test
- public void testCompletedException() throws Exception {
- IllegalStateException except = new IllegalStateException();
-
- outcome = params.makeOutcome();
- handler = new MyHandler(params, outcome) {
- @Override
- protected OperationOutcome doComplete(String rawResponse) {
- throw except;
- }
- };
-
- CompletableFuture<OperationOutcome> result = handler.handle(new CompletableFuture<>());
- handler.completed(TEXT);
- assertTrue(result.isCompletedExceptionally());
-
- AtomicReference<Throwable> thrown = new AtomicReference<>();
- result.whenComplete((unused, thrown2) -> thrown.set(thrown2));
-
- assertSame(except, thrown.get());
- }
-
- @Test
- public void testFailed() throws Exception {
- IllegalStateException except = new IllegalStateException();
-
- CompletableFuture<OperationOutcome> result = handler.handle(new CompletableFuture<>());
- handler.failed(except);
-
- assertTrue(result.isDone());
- assertSame(outcome, result.get());
- assertEquals(PolicyResult.FAILURE_GUARD, outcome.getResult());
- }
-
- /**
- * Tests failed() when doFailed() throws an exception.
- */
- @Test
- public void testFailedException() throws Exception {
- IllegalStateException except = new IllegalStateException();
-
- outcome = params.makeOutcome();
- handler = new MyHandler(params, outcome) {
- @Override
- protected OperationOutcome doFailed(Throwable thrown) {
- throw except;
- }
- };
-
- CompletableFuture<OperationOutcome> result = handler.handle(new CompletableFuture<>());
- handler.failed(except);
- assertTrue(result.isCompletedExceptionally());
-
- AtomicReference<Throwable> thrown = new AtomicReference<>();
- result.whenComplete((unused, thrown2) -> thrown.set(thrown2));
-
- assertSame(except, thrown.get());
- }
-
- private class MyHandler extends AsyncResponseHandler<String> {
-
- public MyHandler(ControlLoopOperationParams params, OperationOutcome outcome) {
- super(params, outcome);
- }
-
- @Override
- protected OperationOutcome doComplete(String rawResponse) {
- OperationOutcome outcome = getOutcome();
- outcome.setResult(PolicyResult.FAILURE_RETRIES);
- outcome.setMessage(rawResponse);
- return outcome;
- }
-
- @Override
- protected OperationOutcome doFailed(Throwable thrown) {
- OperationOutcome outcome = getOutcome();
- outcome.setResult(PolicyResult.FAILURE_GUARD);
- return outcome;
- }
- }
-}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/UtilTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/UtilTest.java
index 4a3f321cf..0a2a5a90e 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/UtilTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/UtilTest.java
@@ -39,16 +39,10 @@ import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
-import org.onap.policy.common.utils.coder.CoderException;
-import org.onap.policy.common.utils.coder.StandardCoder;
import org.onap.policy.common.utils.test.log.logback.ExtractAppender;
import org.slf4j.LoggerFactory;
public class UtilTest {
- private static final String MY_REQUEST = "my-request";
- private static final String URL = "my-url";
- private static final String OUT_URL = "OUT|REST|my-url";
- private static final String IN_URL = "IN|REST|my-url";
protected static final String EXPECTED_EXCEPTION = "expected exception";
/**
@@ -89,82 +83,6 @@ public class UtilTest {
}
@Test
- public void testLogRestRequest() throws CoderException {
- // log structured data
- appender.clearExtractions();
- Util.logRestRequest(URL, new Abc(10, null, null));
- List<String> output = appender.getExtracted();
- assertEquals(1, output.size());
-
- assertThat(output.get(0)).contains(OUT_URL).contains("{\n \"intValue\": 10\n}");
-
- // log a plain string
- appender.clearExtractions();
- Util.logRestRequest(URL, MY_REQUEST);
- output = appender.getExtracted();
- assertEquals(1, output.size());
-
- assertThat(output.get(0)).contains(OUT_URL).contains(MY_REQUEST);
-
- // exception from coder
- StandardCoder coder = new StandardCoder() {
- @Override
- public String encode(Object object, boolean pretty) throws CoderException {
- throw new CoderException(EXPECTED_EXCEPTION);
- }
- };
-
- appender.clearExtractions();
- Util.logRestRequest(coder, URL, new Abc(11, null, null));
- output = appender.getExtracted();
- assertEquals(2, output.size());
- assertThat(output.get(0)).contains("cannot pretty-print request");
- assertThat(output.get(1)).contains(OUT_URL);
- }
-
- @Test
- public void testLogRestResponse() throws CoderException {
- // log structured data
- appender.clearExtractions();
- Util.logRestResponse(URL, new Abc(10, null, null));
- List<String> output = appender.getExtracted();
- assertEquals(1, output.size());
-
- assertThat(output.get(0)).contains(IN_URL).contains("{\n \"intValue\": 10\n}");
-
- // log null response
- appender.clearExtractions();
- Util.logRestResponse(URL, null);
- output = appender.getExtracted();
- assertEquals(1, output.size());
-
- assertThat(output.get(0)).contains(IN_URL).contains("null");
-
- // log a plain string
- appender.clearExtractions();
- Util.logRestResponse(URL, MY_REQUEST);
- output = appender.getExtracted();
- assertEquals(1, output.size());
-
- assertThat(output.get(0)).contains(IN_URL).contains(MY_REQUEST);
-
- // exception from coder
- StandardCoder coder = new StandardCoder() {
- @Override
- public String encode(Object object, boolean pretty) throws CoderException {
- throw new CoderException(EXPECTED_EXCEPTION);
- }
- };
-
- appender.clearExtractions();
- Util.logRestResponse(coder, URL, new Abc(11, null, null));
- output = appender.getExtracted();
- assertEquals(2, output.size());
- assertThat(output.get(0)).contains("cannot pretty-print response");
- assertThat(output.get(1)).contains(IN_URL);
- }
-
- @Test
public void testRunFunction() {
// no exception, no log
AtomicInteger count = new AtomicInteger();
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContextTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContextTest.java
index 0d917ad3e..cf2426214 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContextTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContextTest.java
@@ -24,16 +24,25 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import java.util.Map;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
import org.junit.Before;
import org.junit.Test;
import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
public class ControlLoopEventContextTest {
private static final UUID REQ_ID = UUID.randomUUID();
+ private static final String ITEM_KEY = "obtain-C";
private Map<String, String> enrichment;
private VirtualControlLoopEvent event;
@@ -84,4 +93,54 @@ public class ControlLoopEventContextTest {
int intValue = context.getProperty("def");
assertEquals(100, intValue);
}
+
+ @Test
+ public void testObtain() {
+ final ControlLoopOperationParams params = mock(ControlLoopOperationParams.class);
+
+ // property is already loaded
+ context.setProperty("obtain-A", "value-A");
+ assertNull(context.obtain("obtain-A", params));
+
+ // new property - should retrieve
+ CompletableFuture<OperationOutcome> future = new CompletableFuture<>();
+ when(params.start()).thenReturn(future);
+ assertSame(future, context.obtain("obtain-B", params));
+
+ // repeat - should get the same future, without invoking start() again
+ assertSame(future, context.obtain("obtain-B", params));
+ verify(params).start();
+
+ // arrange for another invoker to start while this one is starting
+ CompletableFuture<OperationOutcome> future2 = new CompletableFuture<>();
+
+ when(params.start()).thenAnswer(args -> {
+
+ ControlLoopOperationParams params2 = mock(ControlLoopOperationParams.class);
+ when(params2.start()).thenReturn(future2);
+
+ assertSame(future2, context.obtain(ITEM_KEY, params2));
+ return future;
+ });
+
+ assertSame(future2, context.obtain(ITEM_KEY, params));
+
+ // should have canceled the interrupted future
+ assertTrue(future.isCancelled());
+
+ // return a new future next time start() is called
+ CompletableFuture<OperationOutcome> future3 = new CompletableFuture<>();
+ when(params.start()).thenReturn(future3);
+
+ // repeat - should get the same future
+ assertSame(future2, context.obtain(ITEM_KEY, params));
+ assertSame(future2, context.obtain(ITEM_KEY, params));
+
+ // future2 should still be active
+ assertFalse(future2.isCancelled());
+
+ // cancel it - now we should get the new future
+ future2.cancel(false);
+ assertSame(future3, context.obtain(ITEM_KEY, params));
+ }
}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImplTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImplTest.java
index a209fb0d8..92cbbe774 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImplTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImplTest.java
@@ -42,7 +42,9 @@ import org.junit.Before;
import org.junit.Test;
import org.onap.policy.common.parameters.ObjectValidationResult;
import org.onap.policy.common.parameters.ValidationStatus;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
import org.onap.policy.controlloop.actorserviceprovider.Operator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
public class ActorImplTest {
@@ -375,10 +377,15 @@ public class ActorImplTest {
return actor;
}
- private static class MyOper extends OperatorPartial implements Operator {
+ private static class MyOper extends OperatorPartial {
public MyOper(String name) {
super(ACTOR_NAME, name);
}
+
+ @Override
+ public Operation buildOperation(ControlLoopOperationParams params) {
+ return null;
+ }
}
}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicActorTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicActorTest.java
new file mode 100644
index 000000000..e1606aeaf
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicActorTest.java
@@ -0,0 +1,242 @@
+/*-
+ * ============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.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+import java.util.function.Function;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.endpoints.event.comm.TopicEndpointManager;
+import org.onap.policy.common.endpoints.event.comm.client.BidirectionalTopicClientException;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.BidirectionalTopicActorParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
+import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicHandler;
+
+public class BidirectionalTopicActorTest {
+
+ private static final String ACTOR = "my-actor";
+ private static final String UNKNOWN = "unknown";
+ private static final String MY_SINK = "my-sink";
+ private static final String MY_SOURCE1 = "my-source-A";
+ private static final String MY_SOURCE2 = "my-source-B";
+ private static final int TIMEOUT = 10;
+
+ @Mock
+ private BidirectionalTopicHandler handler1;
+ @Mock
+ private BidirectionalTopicHandler handler2;
+
+ private BidirectionalTopicActor actor;
+
+
+ /**
+ * Configures the endpoints.
+ */
+ @BeforeClass
+ public static void setUpBeforeClass() {
+ Properties props = new Properties();
+ props.setProperty("noop.sink.topics", MY_SINK);
+ props.setProperty("noop.source.topics", MY_SOURCE1 + "," + MY_SOURCE2);
+
+ // clear all topics and then configure one sink and two sources
+ TopicEndpointManager.getManager().shutdown();
+ TopicEndpointManager.getManager().addTopicSinks(props);
+ TopicEndpointManager.getManager().addTopicSources(props);
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() {
+ // clear all topics after the tests
+ TopicEndpointManager.getManager().shutdown();
+ }
+
+ /**
+ * Sets up.
+ */
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ actor = new MyActor();
+ actor.configure(Util.translateToMap(ACTOR, makeParams()));
+ }
+
+ @Test
+ public void testDoStart() throws BidirectionalTopicClientException {
+ // allocate some handlers
+ actor.getTopicHandler(MY_SINK, MY_SOURCE1);
+ actor.getTopicHandler(MY_SINK, MY_SOURCE2);
+
+ // start it
+ actor.start();
+
+ verify(handler1).start();
+ verify(handler2).start();
+
+ verify(handler1, never()).stop();
+ verify(handler2, never()).stop();
+
+ verify(handler1, never()).shutdown();
+ verify(handler2, never()).shutdown();
+ }
+
+ @Test
+ public void testDoStop() throws BidirectionalTopicClientException {
+ // allocate some handlers
+ actor.getTopicHandler(MY_SINK, MY_SOURCE1);
+ actor.getTopicHandler(MY_SINK, MY_SOURCE2);
+
+ // start it
+ actor.start();
+
+ // stop it
+ actor.stop();
+
+ verify(handler1).stop();
+ verify(handler2).stop();
+
+ verify(handler1, never()).shutdown();
+ verify(handler2, never()).shutdown();
+ }
+
+ @Test
+ public void testDoShutdown() {
+ // allocate some handlers
+ actor.getTopicHandler(MY_SINK, MY_SOURCE1);
+ actor.getTopicHandler(MY_SINK, MY_SOURCE2);
+
+ // start it
+ actor.start();
+
+ // stop it
+ actor.shutdown();
+
+ verify(handler1).shutdown();
+ verify(handler2).shutdown();
+
+ verify(handler1, never()).stop();
+ verify(handler2, never()).stop();
+ }
+
+ @Test
+ public void testMakeOperatorParameters() {
+ BidirectionalTopicActorParams params = makeParams();
+
+ final BidirectionalTopicActor prov = new BidirectionalTopicActor(ACTOR);
+ Function<String, Map<String, Object>> maker =
+ prov.makeOperatorParameters(Util.translateToMap(prov.getName(), params));
+
+ assertNull(maker.apply(UNKNOWN));
+
+ // use a TreeMap to ensure the properties are sorted
+ assertEquals("{sinkTopic=my-sink, sourceTopic=my-source-A, timeoutSec=10}",
+ new TreeMap<>(maker.apply("operA")).toString());
+
+ assertEquals("{sinkTopic=my-sink, sourceTopic=topicB, timeoutSec=10}",
+ new TreeMap<>(maker.apply("operB")).toString());
+
+ // with invalid actor parameters
+ params.setOperation(null);
+ assertThatThrownBy(() -> prov.makeOperatorParameters(Util.translateToMap(prov.getName(), params)))
+ .isInstanceOf(ParameterValidationRuntimeException.class);
+ }
+
+ @Test
+ public void testBidirectionalTopicActor() {
+ assertEquals(ACTOR, actor.getName());
+ assertEquals(ACTOR, actor.getFullName());
+ }
+
+ @Test
+ public void testGetTopicHandler() {
+ assertSame(handler1, actor.getTopicHandler(MY_SINK, MY_SOURCE1));
+ assertSame(handler2, actor.getTopicHandler(MY_SINK, MY_SOURCE2));
+
+ assertThatIllegalArgumentException().isThrownBy(() -> actor.getTopicHandler(UNKNOWN, MY_SOURCE1));
+ }
+
+ @Test
+ public void testMakeTopicHandler() {
+ // use a real actor
+ actor = new BidirectionalTopicActor(ACTOR);
+
+ handler1 = actor.getTopicHandler(MY_SINK, MY_SOURCE1);
+ handler2 = actor.getTopicHandler(MY_SINK, MY_SOURCE2);
+
+ assertNotNull(handler1);
+ assertNotNull(handler2);
+ assertNotSame(handler1, handler2);
+ }
+
+
+ private BidirectionalTopicActorParams makeParams() {
+ BidirectionalTopicActorParams params = new BidirectionalTopicActorParams();
+ params.setSinkTopic(MY_SINK);
+ params.setSourceTopic(MY_SOURCE1);
+ params.setTimeoutSec(TIMEOUT);
+
+ // @formatter:off
+ params.setOperation(Map.of(
+ "operA", Map.of(),
+ "operB", Map.of("sourceTopic", "topicB")));
+ // @formatter:on
+ return params;
+ }
+
+ private class MyActor extends BidirectionalTopicActor {
+
+ public MyActor() {
+ super(ACTOR);
+ }
+
+ @Override
+ protected BidirectionalTopicHandler makeTopicHandler(String sinkTopic, String sourceTopic)
+ throws BidirectionalTopicClientException {
+
+ if (MY_SINK.equals(sinkTopic)) {
+ if (MY_SOURCE1.equals(sourceTopic)) {
+ return handler1;
+ } else if (MY_SOURCE2.equals(sourceTopic)) {
+ return handler2;
+ }
+ }
+
+ throw new BidirectionalTopicClientException("no topic " + sinkTopic + "/" + sourceTopic);
+ }
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperationTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperationTest.java
new file mode 100644
index 000000000..ceb63fe91
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperationTest.java
@@ -0,0 +1,403 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.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 java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import lombok.Getter;
+import lombok.Setter;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+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.time.PseudoExecutor;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.BidirectionalTopicParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicHandler;
+import org.onap.policy.controlloop.actorserviceprovider.topic.Forwarder;
+import org.onap.policy.controlloop.policy.PolicyResult;
+
+public class BidirectionalTopicOperationTest {
+ private static final CommInfrastructure SINK_INFRA = CommInfrastructure.NOOP;
+ 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_SINK = "my-sink";
+ private static final String MY_SOURCE = "my-source";
+ 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 int MAX_REQUESTS = 100;
+
+ private static final StandardCoder coder = new StandardCoder();
+
+ @Mock
+ private BidirectionalTopicOperator operator;
+ @Mock
+ private BidirectionalTopicHandler handler;
+ @Mock
+ private Forwarder forwarder;
+
+ @Captor
+ private ArgumentCaptor<BiConsumer<String, StandardCoderObject>> listenerCaptor;
+
+ private ControlLoopOperationParams params;
+ private BidirectionalTopicParams topicParams;
+ private OperationOutcome outcome;
+ private StandardCoderObject stdResponse;
+ private String responseText;
+ private PseudoExecutor executor;
+ private int ntimes;
+ private BidirectionalTopicOperation<MyRequest, MyResponse> oper;
+
+ /**
+ * Sets up.
+ */
+ @Before
+ public void setUp() throws CoderException {
+ MockitoAnnotations.initMocks(this);
+
+ topicParams = BidirectionalTopicParams.builder().sourceTopic(MY_SOURCE).sinkTopic(MY_SINK)
+ .timeoutSec(TIMEOUT_SEC).build();
+
+ when(operator.getActorName()).thenReturn(ACTOR);
+ when(operator.getName()).thenReturn(OPERATION);
+ when(operator.getTopicHandler()).thenReturn(handler);
+ when(operator.getForwarder()).thenReturn(forwarder);
+ when(operator.getParams()).thenReturn(topicParams);
+ when(operator.isAlive()).thenReturn(true);
+
+ when(handler.send(any())).thenReturn(true);
+ when(handler.getSinkTopicCommInfrastructure()).thenReturn(SINK_INFRA);
+
+ executor = new PseudoExecutor();
+
+ params = ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).executor(executor).build();
+ outcome = params.makeOutcome();
+
+ responseText = coder.encode(new MyResponse());
+ stdResponse = coder.decode(responseText, StandardCoderObject.class);
+
+ ntimes = 1;
+
+ oper = new MyOperation();
+ }
+
+ @Test
+ public void testConstructor_testGetTopicHandler_testGetForwarder_testGetTopicParams() {
+ assertEquals(ACTOR, oper.getActorName());
+ assertEquals(OPERATION, oper.getName());
+ assertSame(handler, oper.getTopicHandler());
+ assertSame(forwarder, oper.getForwarder());
+ assertSame(topicParams, oper.getTopicParams());
+ assertEquals(TIMEOUT_MS, oper.getTimeoutMs());
+ assertSame(MyResponse.class, oper.getResponseClass());
+ }
+
+ @Test
+ public void testStartOperationAsync() throws Exception {
+
+ // tell it to expect three responses
+ ntimes = 3;
+
+ 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(handler).send(any());
+
+ // provide first response
+ listenerCaptor.getValue().accept(responseText, stdResponse);
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertFalse(future.isDone());
+
+ // provide second response
+ listenerCaptor.getValue().accept(responseText, stdResponse);
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertFalse(future.isDone());
+
+ // provide final response
+ listenerCaptor.getValue().accept(responseText, stdResponse);
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertTrue(future.isDone());
+
+ assertSame(outcome, future.get());
+ 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(handler.send(any())).thenReturn(false);
+
+ 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() {
+ assertThatCode(() -> oper.publishRequest(new MyRequest())).doesNotThrowAnyException();
+ }
+
+ /**
+ * Tests publishRequest() when nothing is published.
+ */
+ @Test
+ public void testPublishRequestUnpublished() {
+ when(handler.send(any())).thenReturn(false);
+ assertThatIllegalStateException().isThrownBy(() -> oper.publishRequest(new MyRequest()));
+ }
+
+ /**
+ * Tests publishRequest() when the request type is a String.
+ */
+ @Test
+ public void testPublishRequestString() {
+ MyStringOperation oper2 = new MyStringOperation();
+ assertThatCode(() -> oper2.publishRequest(TEXT)).doesNotThrowAnyException();
+ }
+
+ /**
+ * 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(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(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(outcome, responseText, stdResponse));
+ assertEquals(PolicyResult.FAILURE, outcome.getResult());
+ }
+
+ /**
+ * Tests processResponse() when the decoder succeeds.
+ */
+ @Test
+ public void testProcessResponseDecodeOk() throws CoderException {
+ assertSame(outcome, oper.processResponse(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(outcome, "{invalid json", stdResponse));
+ // @formatter:on
+ }
+
+ @Test
+ public void testPostProcessResponse() {
+ assertThatCode(() -> oper.postProcessResponse(outcome, null, null)).doesNotThrowAnyException();
+ }
+
+ @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 BidirectionalTopicOperation<String, String> {
+ public MyStringOperation() {
+ super(BidirectionalTopicOperationTest.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 Status detmStatus(String rawResponse, String response) {
+ return (response != null ? Status.SUCCESS : Status.FAILURE);
+ }
+ }
+
+
+ private class MyScoOperation extends BidirectionalTopicOperation<MyRequest, StandardCoderObject> {
+ public MyScoOperation() {
+ super(BidirectionalTopicOperationTest.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 Status detmStatus(String rawResponse, StandardCoderObject response) {
+ return (response.getString("output") == null ? Status.SUCCESS : Status.FAILURE);
+ }
+ }
+
+
+ private class MyOperation extends BidirectionalTopicOperation<MyRequest, MyResponse> {
+ public MyOperation() {
+ super(BidirectionalTopicOperationTest.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 Status detmStatus(String rawResponse, MyResponse response) {
+ if (--ntimes <= 0) {
+ return (response.getOutput() == null ? Status.SUCCESS : Status.FAILURE);
+ }
+
+ return Status.STILL_WAITING;
+ }
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperatorTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperatorTest.java
new file mode 100644
index 000000000..4fae782bd
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperatorTest.java
@@ -0,0 +1,143 @@
+/*-
+ * ============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.BidirectionalTopicParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
+import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicHandler;
+import org.onap.policy.controlloop.actorserviceprovider.topic.BidirectionalTopicManager;
+import org.onap.policy.controlloop.actorserviceprovider.topic.Forwarder;
+import org.onap.policy.controlloop.actorserviceprovider.topic.SelectorKey;
+
+public class BidirectionalTopicOperatorTest {
+ 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_SINK = "my-target";
+ private static final int TIMEOUT_SEC = 10;
+
+ @Mock
+ private BidirectionalTopicManager mgr;
+ @Mock
+ private BidirectionalTopicHandler handler;
+ @Mock
+ private Forwarder forwarder;
+ @Mock
+ private BidirectionalTopicOperation<String, Integer> operation;
+
+ private List<SelectorKey> keys;
+ private BidirectionalTopicParams params;
+ private MyOperator oper;
+
+ /**
+ * Sets up.
+ */
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ keys = List.of(new SelectorKey(""));
+
+ when(mgr.getTopicHandler(MY_SINK, MY_SOURCE)).thenReturn(handler);
+ when(handler.addForwarder(keys)).thenReturn(forwarder);
+
+ oper = new MyOperator(keys);
+
+ params = BidirectionalTopicParams.builder().sourceTopic(MY_SOURCE).sinkTopic(MY_SINK).timeoutSec(TIMEOUT_SEC)
+ .build();
+ oper.configure(Util.translateToMap(OPERATION, params));
+ oper.start();
+ }
+
+ @Test
+ public void testConstructor_testGetParams_testGetTopicHandler_testGetForwarder() {
+ assertEquals(ACTOR, oper.getActorName());
+ assertEquals(OPERATION, oper.getName());
+ assertEquals(params, oper.getParams());
+ assertSame(handler, oper.getTopicHandler());
+ assertSame(forwarder, oper.getForwarder());
+ }
+
+ @Test
+ public void testDoConfigure() {
+ oper.stop();
+
+ // invalid parameters
+ params.setSourceTopic(null);
+ assertThatThrownBy(() -> oper.configure(Util.translateToMap(OPERATION, params)))
+ .isInstanceOf(ParameterValidationRuntimeException.class);
+ }
+
+ @Test
+ public void testMakeOperator() {
+ AtomicReference<ControlLoopOperationParams> paramsRef = new AtomicReference<>();
+ AtomicReference<BidirectionalTopicOperator> operRef = new AtomicReference<>();
+
+ // @formatter:off
+ BiFunction<ControlLoopOperationParams, BidirectionalTopicOperator,
+ BidirectionalTopicOperation<String, Integer>> maker =
+ (params, operator) -> {
+ paramsRef.set(params);
+ operRef.set(operator);
+ return operation;
+ };
+ // @formatter:on
+
+ BidirectionalTopicOperator oper2 =
+ BidirectionalTopicOperator.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 BidirectionalTopicOperator {
+ 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/impl/HttpActorTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpActorTest.java
index 2da789989..80b1d427a 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpActorTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpActorTest.java
@@ -38,7 +38,7 @@ public class HttpActorTest {
private static final String ACTOR = "my-actor";
private static final String UNKNOWN = "unknown";
private static final String CLIENT = "my-client";
- private static final long TIMEOUT = 10L;
+ private static final int TIMEOUT = 10;
private HttpActor actor;
@@ -52,7 +52,12 @@ public class HttpActorTest {
HttpActorParams params = new HttpActorParams();
params.setClientName(CLIENT);
params.setTimeoutSec(TIMEOUT);
- params.setPath(Map.of("operA", "urlA", "operB", "urlB"));
+
+ // @formatter:off
+ params.setOperation(Map.of(
+ "operA", Map.of("path", "urlA"),
+ "operB", Map.of("path", "urlB")));
+ // @formatter:on
final HttpActor prov = new HttpActor(ACTOR);
Function<String, Map<String, Object>> maker =
@@ -68,7 +73,7 @@ public class HttpActorTest {
new TreeMap<>(maker.apply("operB")).toString());
// with invalid actor parameters
- params.setClientName(null);
+ params.setOperation(null);
assertThatThrownBy(() -> prov.makeOperatorParameters(Util.translateToMap(prov.getName(), params)))
.isInstanceOf(ParameterValidationRuntimeException.class);
}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperationTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperationTest.java
new file mode 100644
index 000000000..8189c74fe
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperationTest.java
@@ -0,0 +1,674 @@
+/*-
+ * ============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.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Properties;
+import java.util.UUID;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.InvocationCallback;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import lombok.Getter;
+import lombok.Setter;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams;
+import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams.TopicParamsBuilder;
+import org.onap.policy.common.endpoints.http.client.HttpClient;
+import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance;
+import org.onap.policy.common.endpoints.http.server.HttpServletServer;
+import org.onap.policy.common.endpoints.http.server.HttpServletServerFactoryInstance;
+import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+import org.onap.policy.common.gson.GsonMessageBodyHandler;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.network.NetworkUtil;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
+import org.onap.policy.controlloop.policy.PolicyResult;
+
+public class HttpOperationTest {
+
+ private static final IllegalStateException EXPECTED_EXCEPTION = new IllegalStateException("expected exception");
+ private static final String ACTOR = "my-actor";
+ private static final String OPERATION = "my-name";
+ private static final String HTTP_CLIENT = "my-client";
+ private static final String HTTP_NO_SERVER = "my-http-no-server-client";
+ private static final String MEDIA_TYPE_APPLICATION_JSON = "application/json";
+ private static final String BASE_URI = "oper";
+ private static final String PATH = "/my-path";
+ private static final String TEXT = "my-text";
+ private static final UUID REQ_ID = UUID.randomUUID();
+
+ /**
+ * {@code True} if the server should reject the request, {@code false} otherwise.
+ */
+ private static boolean rejectRequest;
+
+ // call counts of each method type in the server
+ private static int nget;
+ private static int npost;
+ private static int nput;
+ private static int ndelete;
+
+ @Mock
+ private HttpClient client;
+
+ @Mock
+ private Response response;
+
+ private VirtualControlLoopEvent event;
+ private ControlLoopEventContext context;
+ private ControlLoopOperationParams params;
+ private OperationOutcome outcome;
+ private AtomicReference<InvocationCallback<Response>> callback;
+ private Future<Response> future;
+ private HttpOperator operator;
+ private MyGetOperation<String> oper;
+
+ /**
+ * Starts the simulator.
+ */
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ // allocate a port
+ int port = NetworkUtil.allocPort();
+
+ /*
+ * Start the simulator. Must use "Properties" to configure it, otherwise the
+ * server will use the wrong serialization provider.
+ */
+ Properties svrprops = getServerProperties("my-server", port);
+ HttpServletServerFactoryInstance.getServerFactory().build(svrprops).forEach(HttpServletServer::start);
+
+ if (!NetworkUtil.isTcpPortOpen("localhost", port, 100, 100)) {
+ HttpServletServerFactoryInstance.getServerFactory().destroy();
+ throw new IllegalStateException("server is not running");
+ }
+
+ /*
+ * Start the clients, one to the server, and one to a non-existent server.
+ */
+ TopicParamsBuilder builder = BusTopicParams.builder().managed(true).hostname("localhost").basePath(BASE_URI)
+ .serializationProvider(GsonMessageBodyHandler.class.getName());
+
+ HttpClientFactoryInstance.getClientFactory().build(builder.clientName(HTTP_CLIENT).port(port).build());
+
+ HttpClientFactoryInstance.getClientFactory()
+ .build(builder.clientName(HTTP_NO_SERVER).port(NetworkUtil.allocPort()).build());
+ }
+
+ /**
+ * Destroys the Http factories and stops the appender.
+ */
+ @AfterClass
+ public static void tearDownAfterClass() {
+ HttpClientFactoryInstance.getClientFactory().destroy();
+ HttpServletServerFactoryInstance.getServerFactory().destroy();
+ }
+
+ /**
+ * Initializes fields, including {@link #oper}, and resets the static fields used by
+ * the REST server.
+ */
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ rejectRequest = false;
+ nget = 0;
+ npost = 0;
+ nput = 0;
+ ndelete = 0;
+
+ when(response.readEntity(String.class)).thenReturn(TEXT);
+ when(response.getStatus()).thenReturn(200);
+
+ event = new VirtualControlLoopEvent();
+ event.setRequestId(REQ_ID);
+
+ context = new ControlLoopEventContext(event);
+ params = ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).context(context).build();
+
+ outcome = params.makeOutcome();
+
+ callback = new AtomicReference<>();
+ future = new CompletableFuture<>();
+
+ operator = new HttpOperator(ACTOR, OPERATION) {
+ @Override
+ public Operation buildOperation(ControlLoopOperationParams params) {
+ return null;
+ }
+
+ @Override
+ public HttpClient getClient() {
+ return client;
+ }
+ };
+
+ initOper(operator, HTTP_CLIENT);
+
+ oper = new MyGetOperation<>(String.class);
+ }
+
+ @Test
+ public void testHttpOperator() {
+ assertEquals(ACTOR, oper.getActorName());
+ assertEquals(OPERATION, oper.getName());
+ assertEquals(ACTOR + "." + OPERATION, oper.getFullName());
+ }
+
+ @Test
+ public void testMakeHeaders() {
+ assertEquals(Collections.emptyMap(), oper.makeHeaders());
+ }
+
+ @Test
+ public void testMakePath() {
+ assertEquals(PATH, oper.makePath());
+ }
+
+ @Test
+ public void testMakeUrl() {
+ // use a real client
+ client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
+
+ assertThat(oper.makeUrl()).endsWith("/" + BASE_URI + PATH);
+ }
+
+ @Test
+ public void testDoConfigureMapOfStringObject_testGetClient_testGetPath_testGetTimeoutMs() {
+
+ // use value from operator
+ assertEquals(1000L, oper.getTimeoutMs(null));
+ assertEquals(1000L, oper.getTimeoutMs(0));
+
+ // should use given value
+ assertEquals(20 * 1000L, oper.getTimeoutMs(20));
+
+ // indicate we have a timeout value
+ operator = spy(operator);
+ when(operator.getTimeoutMs()).thenReturn(30L);
+
+ oper = new MyGetOperation<String>(String.class);
+
+ // should use default
+ assertEquals(30L, oper.getTimeoutMs(null));
+ assertEquals(30L, oper.getTimeoutMs(0));
+
+ // should use given value
+ assertEquals(40 * 1000L, oper.getTimeoutMs(40));
+ }
+
+ /**
+ * Tests handleResponse() when it completes.
+ */
+ @Test
+ public void testHandleResponseComplete() throws Exception {
+ CompletableFuture<OperationOutcome> future2 = oper.handleResponse(outcome, PATH, cb -> {
+ callback.set(cb);
+ return future;
+ });
+
+ assertFalse(future2.isDone());
+ assertNotNull(callback.get());
+ callback.get().completed(response);
+
+ assertSame(outcome, future2.get(5, TimeUnit.SECONDS));
+
+ assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+ }
+
+ /**
+ * Tests handleResponse() when it fails.
+ */
+ @Test
+ public void testHandleResponseFailed() throws Exception {
+ CompletableFuture<OperationOutcome> future2 = oper.handleResponse(outcome, PATH, cb -> {
+ callback.set(cb);
+ return future;
+ });
+
+ assertFalse(future2.isDone());
+ assertNotNull(callback.get());
+ callback.get().failed(EXPECTED_EXCEPTION);
+
+ assertThatThrownBy(() -> future2.get(5, TimeUnit.SECONDS)).hasCause(EXPECTED_EXCEPTION);
+
+ // future and future2 may be completed in parallel so we must wait again
+ assertThatThrownBy(() -> future.get(5, TimeUnit.SECONDS)).isInstanceOf(CancellationException.class);
+ assertTrue(future.isCancelled());
+ }
+
+ /**
+ * Tests processResponse() when it's a success and the response type is a String.
+ */
+ @Test
+ public void testProcessResponseSuccessString() throws Exception {
+ CompletableFuture<OperationOutcome> result = oper.processResponse(outcome, PATH, response);
+ assertTrue(result.isDone());
+ assertSame(outcome, result.get());
+ assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+ }
+
+ /**
+ * Tests processResponse() when it's a failure.
+ */
+ @Test
+ public void testProcessResponseFailure() throws Exception {
+ when(response.getStatus()).thenReturn(555);
+ CompletableFuture<OperationOutcome> result = oper.processResponse(outcome, PATH, response);
+ assertTrue(result.isDone());
+ assertSame(outcome, result.get());
+ assertEquals(PolicyResult.FAILURE, outcome.getResult());
+ }
+
+ /**
+ * Tests processResponse() when the decoder succeeds.
+ */
+ @Test
+ public void testProcessResponseDecodeOk() throws Exception {
+ when(response.readEntity(String.class)).thenReturn("10");
+
+ MyGetOperation<Integer> oper2 = new MyGetOperation<>(Integer.class);
+
+ CompletableFuture<OperationOutcome> result = oper2.processResponse(outcome, PATH, response);
+ assertTrue(result.isDone());
+ assertSame(outcome, result.get());
+ assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+ }
+
+ /**
+ * Tests processResponse() when the decoder throws an exception.
+ */
+ @Test
+ public void testProcessResponseDecodeExcept() throws CoderException {
+ MyGetOperation<Integer> oper2 = new MyGetOperation<>(Integer.class);
+
+ assertThatIllegalArgumentException().isThrownBy(() -> oper2.processResponse(outcome, PATH, response));
+ }
+
+ @Test
+ public void testPostProcessResponse() {
+ assertThatCode(() -> oper.postProcessResponse(outcome, PATH, null, null)).doesNotThrowAnyException();
+ }
+
+ @Test
+ public void testIsSuccess() {
+ when(response.getStatus()).thenReturn(200);
+ assertTrue(oper.isSuccess(response, null));
+
+ when(response.getStatus()).thenReturn(555);
+ assertFalse(oper.isSuccess(response, null));
+ }
+
+ /**
+ * Tests a GET.
+ */
+ @Test
+ public void testGet() throws Exception {
+ // use a real client
+ client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
+
+ MyGetOperation<MyResponse> oper2 = new MyGetOperation<>(MyResponse.class);
+
+ OperationOutcome outcome = runOperation(oper2);
+ assertNotNull(outcome);
+ assertEquals(1, nget);
+ assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+ }
+
+ /**
+ * Tests a DELETE.
+ */
+ @Test
+ public void testDelete() throws Exception {
+ // use a real client
+ client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
+
+ MyDeleteOperation oper2 = new MyDeleteOperation();
+
+ OperationOutcome outcome = runOperation(oper2);
+ assertNotNull(outcome);
+ assertEquals(1, ndelete);
+ assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+ }
+
+ /**
+ * Tests a POST.
+ */
+ @Test
+ public void testPost() throws Exception {
+ // use a real client
+ client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
+
+ MyPostOperation oper2 = new MyPostOperation();
+
+ OperationOutcome outcome = runOperation(oper2);
+ assertNotNull(outcome);
+ assertEquals(1, npost);
+ assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+ }
+
+ /**
+ * Tests a PUT.
+ */
+ @Test
+ public void testPut() throws Exception {
+ // use a real client
+ client = HttpClientFactoryInstance.getClientFactory().get(HTTP_CLIENT);
+
+ MyPutOperation oper2 = new MyPutOperation();
+
+ OperationOutcome outcome = runOperation(oper2);
+ assertNotNull(outcome);
+ assertEquals(1, nput);
+ assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+ }
+
+ @Test
+ public void testMakeDecoder() {
+ assertNotNull(oper.makeCoder());
+ }
+
+ /**
+ * Gets server properties.
+ *
+ * @param name server name
+ * @param port server port
+ * @return server properties
+ */
+ private static Properties getServerProperties(String name, int port) {
+ final Properties props = new Properties();
+ props.setProperty(PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES, name);
+
+ final String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + name;
+
+ props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_REST_CLASSES_SUFFIX, Server.class.getName());
+ props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_HOST_SUFFIX, "localhost");
+ props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PORT_SUFFIX, String.valueOf(port));
+ props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_MANAGED_SUFFIX, "true");
+ props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SWAGGER_SUFFIX, "false");
+
+ props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERIALIZATION_PROVIDER,
+ GsonMessageBodyHandler.class.getName());
+ return props;
+ }
+
+ /**
+ * Initializes the given operator.
+ *
+ * @param operator operator to be initialized
+ * @param clientName name of the client which it should use
+ */
+ private void initOper(HttpOperator operator, String clientName) {
+ operator.stop();
+
+ HttpParams params = HttpParams.builder().clientName(clientName).path(PATH).timeoutSec(1).build();
+ Map<String, Object> mapParams = Util.translateToMap(OPERATION, params);
+ operator.configure(mapParams);
+ operator.start();
+ }
+
+ /**
+ * Runs the operation.
+ *
+ * @param operator operator on which to start the operation
+ * @return the outcome of the operation, or {@code null} if it does not complete in
+ * time
+ */
+ private <T> OperationOutcome runOperation(HttpOperation<T> operator)
+ throws InterruptedException, ExecutionException, TimeoutException {
+
+ CompletableFuture<OperationOutcome> future = operator.start();
+
+ return future.get(5, TimeUnit.SECONDS);
+ }
+
+ @Getter
+ @Setter
+ public static class MyRequest {
+ private String input = "some input";
+ }
+
+ @Getter
+ @Setter
+ public static class MyResponse {
+ private String output = "some output";
+ }
+
+ private class MyGetOperation<T> extends HttpOperation<T> {
+ public MyGetOperation(Class<T> responseClass) {
+ super(HttpOperationTest.this.params, HttpOperationTest.this.operator, responseClass);
+ }
+
+ @Override
+ protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+ Map<String, Object> headers = makeHeaders();
+
+ headers.put("Accept", MediaType.APPLICATION_JSON);
+ String url = makeUrl();
+
+ logMessage(EventType.OUT, CommInfrastructure.REST, url, null);
+
+ // @formatter:off
+ return handleResponse(outcome, url,
+ callback -> operator.getClient().get(callback, makePath(), headers));
+ // @formatter:on
+ }
+ }
+
+ private class MyPostOperation extends HttpOperation<MyResponse> {
+ public MyPostOperation() {
+ super(HttpOperationTest.this.params, HttpOperationTest.this.operator, MyResponse.class);
+ }
+
+ @Override
+ protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+ MyRequest request = new MyRequest();
+
+ Entity<MyRequest> entity = Entity.entity(request, MediaType.APPLICATION_JSON);
+
+ Map<String, Object> headers = makeHeaders();
+
+ headers.put("Accept", MediaType.APPLICATION_JSON);
+ String url = makeUrl();
+
+ logMessage(EventType.OUT, CommInfrastructure.REST, url, request);
+
+ // @formatter:off
+ return handleResponse(outcome, url,
+ callback -> operator.getClient().post(callback, makePath(), entity, headers));
+ // @formatter:on
+ }
+ }
+
+ private class MyPutOperation extends HttpOperation<MyResponse> {
+ public MyPutOperation() {
+ super(HttpOperationTest.this.params, HttpOperationTest.this.operator, MyResponse.class);
+ }
+
+ @Override
+ protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+ MyRequest request = new MyRequest();
+
+ Entity<MyRequest> entity = Entity.entity(request, MediaType.APPLICATION_JSON);
+
+ Map<String, Object> headers = makeHeaders();
+
+ headers.put("Accept", MediaType.APPLICATION_JSON);
+ String url = makeUrl();
+
+ logMessage(EventType.OUT, CommInfrastructure.REST, url, request);
+
+ // @formatter:off
+ return handleResponse(outcome, url,
+ callback -> operator.getClient().put(callback, makePath(), entity, headers));
+ // @formatter:on
+ }
+ }
+
+ private class MyDeleteOperation extends HttpOperation<String> {
+ public MyDeleteOperation() {
+ super(HttpOperationTest.this.params, HttpOperationTest.this.operator, String.class);
+ }
+
+ @Override
+ protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+ Map<String, Object> headers = makeHeaders();
+
+ headers.put("Accept", MediaType.APPLICATION_JSON);
+ String url = makeUrl();
+
+ logMessage(EventType.OUT, CommInfrastructure.REST, url, null);
+
+ // @formatter:off
+ return handleResponse(outcome, url,
+ callback -> operator.getClient().delete(callback, makePath(), headers));
+ // @formatter:on
+ }
+ }
+
+ /**
+ * Simulator.
+ */
+ @Path("/" + BASE_URI)
+ @Produces(MEDIA_TYPE_APPLICATION_JSON)
+ @Consumes(value = {MEDIA_TYPE_APPLICATION_JSON})
+ public static class Server {
+
+ /**
+ * Generates a response to a GET.
+ *
+ * @return resulting response
+ */
+ @GET
+ @Path(PATH)
+ public Response getRequest() {
+ ++nget;
+
+ if (rejectRequest) {
+ return Response.status(Status.BAD_REQUEST).build();
+
+ } else {
+ return Response.status(Status.OK).entity(new MyResponse()).build();
+ }
+ }
+
+ /**
+ * Generates a response to a POST.
+ *
+ * @param request incoming request
+ * @return resulting response
+ */
+ @POST
+ @Path(PATH)
+ public Response postRequest(MyRequest request) {
+ ++npost;
+
+ if (rejectRequest) {
+ return Response.status(Status.BAD_REQUEST).build();
+
+ } else {
+ return Response.status(Status.OK).entity(new MyResponse()).build();
+ }
+ }
+
+ /**
+ * Generates a response to a PUT.
+ *
+ * @param request incoming request
+ * @return resulting response
+ */
+ @PUT
+ @Path(PATH)
+ public Response putRequest(MyRequest request) {
+ ++nput;
+
+ if (rejectRequest) {
+ return Response.status(Status.BAD_REQUEST).build();
+
+ } else {
+ return Response.status(Status.OK).entity(new MyResponse()).build();
+ }
+ }
+
+ /**
+ * Generates a response to a DELETE.
+ *
+ * @return resulting response
+ */
+ @DELETE
+ @Path(PATH)
+ public Response deleteRequest() {
+ ++ndelete;
+
+ if (rejectRequest) {
+ return Response.status(Status.BAD_REQUEST).build();
+
+ } else {
+ return Response.status(Status.OK).entity(new MyResponse()).build();
+ }
+ }
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperatorTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperatorTest.java
index c006cf333..081bb346b 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperatorTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperatorTest.java
@@ -23,19 +23,25 @@ package org.onap.policy.controlloop.actorserviceprovider.impl;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.onap.policy.common.endpoints.http.client.HttpClient;
import org.onap.policy.common.endpoints.http.client.HttpClientFactory;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
@@ -43,62 +49,116 @@ public class HttpOperatorTest {
private static final String ACTOR = "my-actor";
private static final String OPERATION = "my-name";
- private static final String CLIENT = "my-client";
- private static final String PATH = "my-path";
- private static final long TIMEOUT = 100;
+ private static final String HTTP_CLIENT = "my-client";
+ private static final String PATH = "/my-path";
+ private static final int TIMEOUT = 100;
@Mock
private HttpClient client;
- private HttpOperator oper;
+ @Mock
+ private HttpClientFactory factory;
+
+ private MyOperator oper;
/**
- * Initializes fields, including {@link #oper}.
+ * Initializes fields, including {@link #oper}, and resets the static fields used by
+ * the REST server.
*/
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- oper = new HttpOperator(ACTOR, OPERATION);
+ when(factory.get(HTTP_CLIENT)).thenReturn(client);
+
+ oper = new MyOperator();
+
+ HttpParams params = HttpParams.builder().clientName(HTTP_CLIENT).path(PATH).timeoutSec(TIMEOUT).build();
+ Map<String, Object> paramMap = Util.translateToMap(OPERATION, params);
+ oper.configure(paramMap);
}
@Test
- public void testDoConfigureMapOfStringObject_testGetClient_testGetPath_testGetTimeoutSec() {
+ public void testHttpOperator() {
+ assertEquals(ACTOR, oper.getActorName());
+ assertEquals(OPERATION, oper.getName());
+ assertEquals(ACTOR + "." + OPERATION, oper.getFullName());
+ }
+
+ @Test
+ public void testGetClient() {
+ assertNotNull(oper.getClient());
+ }
+
+ @Test
+ public void testMakeOperator() {
+ HttpOperator oper2 = HttpOperator.makeOperator(ACTOR, OPERATION, MyOperation::new);
+ assertNotNull(oper2);
+
+ VirtualControlLoopEvent event = new VirtualControlLoopEvent();
+ ControlLoopEventContext context = new ControlLoopEventContext(event);
+ ControlLoopOperationParams params =
+ ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).context(context).build();
+
+ Operation operation1 = oper2.buildOperation(params);
+ assertNotNull(operation1);
+
+ Operation operation2 = oper2.buildOperation(params);
+ assertNotNull(operation2);
+ assertNotSame(operation1, operation2);
+ }
+
+ @Test
+ public void testDoConfigureMapOfStringObject_testGetClient_testGetPath_testGetTimeoutMs() {
+ // start with an UNCONFIGURED operator
+ oper.shutdown();
+ oper = new MyOperator();
+
assertNull(oper.getClient());
assertNull(oper.getPath());
- assertEquals(0L, oper.getTimeoutSec());
-
- oper = new HttpOperator(ACTOR, OPERATION) {
- @Override
- protected HttpClientFactory getClientFactory() {
- HttpClientFactory factory = mock(HttpClientFactory.class);
- when(factory.get(CLIENT)).thenReturn(client);
- return factory;
- }
- };
-
- HttpParams params = HttpParams.builder().clientName(CLIENT).path(PATH).timeoutSec(TIMEOUT).build();
+
+ // no timeout yet
+ assertEquals(0L, oper.getTimeoutMs());
+
+ HttpParams params = HttpParams.builder().clientName(HTTP_CLIENT).path(PATH).timeoutSec(TIMEOUT).build();
Map<String, Object> paramMap = Util.translateToMap(OPERATION, params);
oper.configure(paramMap);
assertSame(client, oper.getClient());
assertEquals(PATH, oper.getPath());
- assertEquals(TIMEOUT, oper.getTimeoutSec());
+
+ // should use given value
+ assertEquals(TIMEOUT * 1000, oper.getTimeoutMs());
// test invalid parameters
paramMap.remove("path");
assertThatThrownBy(() -> oper.configure(paramMap)).isInstanceOf(ParameterValidationRuntimeException.class);
}
- @Test
- public void testHttpOperator() {
- assertEquals(ACTOR, oper.getActorName());
- assertEquals(OPERATION, oper.getName());
- assertEquals(ACTOR + "." + OPERATION, oper.getFullName());
+ private class MyOperator extends HttpOperator {
+ public MyOperator() {
+ super(ACTOR, OPERATION);
+ }
+
+ @Override
+ public Operation buildOperation(ControlLoopOperationParams params) {
+ return null;
+ }
+
+ @Override
+ protected HttpClientFactory getClientFactory() {
+ return factory;
+ }
}
- @Test
- public void testGetClient() {
- assertNotNull(oper.getClientFactory());
+ private class MyOperation extends HttpOperation<String> {
+ public MyOperation(ControlLoopOperationParams params, HttpOperator operator) {
+ super(params, operator, String.class);
+ }
+
+ @Override
+ protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+ return null;
+ }
}
}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartialTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartialTest.java
new file mode 100644
index 000000000..67ac27c8d
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartialTest.java
@@ -0,0 +1,1295 @@
+/*-
+ * ============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.assertThatIllegalStateException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import ch.qos.logback.classic.Logger;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import lombok.Getter;
+import lombok.Setter;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
+import org.onap.policy.common.utils.coder.Coder;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.test.log.logback.ExtractAppender;
+import org.onap.policy.common.utils.time.PseudoExecutor;
+import org.onap.policy.controlloop.ControlLoopOperation;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.slf4j.LoggerFactory;
+
+public class OperationPartialTest {
+ private static final CommInfrastructure SINK_INFRA = CommInfrastructure.NOOP;
+ private static final CommInfrastructure SOURCE_INFRA = CommInfrastructure.UEB;
+ private static final int MAX_REQUESTS = 100;
+ private static final int MAX_PARALLEL = 10;
+ private static final String EXPECTED_EXCEPTION = "expected exception";
+ private static final String ACTOR = "my-actor";
+ private static final String OPERATION = "my-operation";
+ private static final String MY_SINK = "my-sink";
+ private static final String MY_SOURCE = "my-source";
+ private static final String TEXT = "my-text";
+ private static final int TIMEOUT = 1000;
+ private static final UUID REQ_ID = UUID.randomUUID();
+
+ private static final List<PolicyResult> FAILURE_RESULTS = Arrays.asList(PolicyResult.values()).stream()
+ .filter(result -> result != PolicyResult.SUCCESS).collect(Collectors.toList());
+
+ /**
+ * Used to attach an appender to the class' logger.
+ */
+ private static final Logger logger = (Logger) LoggerFactory.getLogger(OperationPartial.class);
+ private static final ExtractAppender appender = new ExtractAppender();
+
+ private VirtualControlLoopEvent event;
+ private ControlLoopEventContext context;
+ private PseudoExecutor executor;
+ private ControlLoopOperationParams params;
+
+ private MyOper oper;
+
+ private int numStart;
+ private int numEnd;
+
+ private Instant tstart;
+
+ private OperationOutcome opstart;
+ private OperationOutcome opend;
+
+ private OperatorPartial operator;
+
+ /**
+ * 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();
+ }
+
+ /**
+ * Initializes the fields, including {@link #oper}.
+ */
+ @Before
+ public void setUp() {
+ event = new VirtualControlLoopEvent();
+ event.setRequestId(REQ_ID);
+
+ context = new ControlLoopEventContext(event);
+ executor = new PseudoExecutor();
+
+ params = ControlLoopOperationParams.builder().completeCallback(this::completer).context(context)
+ .executor(executor).actor(ACTOR).operation(OPERATION).timeoutSec(TIMEOUT)
+ .startCallback(this::starter).targetEntity(MY_SINK).build();
+
+ operator = new OperatorPartial(ACTOR, OPERATION) {
+ @Override
+ public Executor getBlockingExecutor() {
+ return executor;
+ }
+
+ @Override
+ public Operation buildOperation(ControlLoopOperationParams params) {
+ return null;
+ }
+ };
+
+ operator.configure(null);
+ operator.start();
+
+ oper = new MyOper();
+
+ tstart = null;
+
+ opstart = null;
+ opend = null;
+ }
+
+ @Test
+ public void testOperatorPartial_testGetActorName_testGetName() {
+ assertEquals(ACTOR, oper.getActorName());
+ assertEquals(OPERATION, oper.getName());
+ assertEquals(ACTOR + "." + OPERATION, oper.getFullName());
+ }
+
+ @Test
+ public void testGetBlockingThread() throws Exception {
+ CompletableFuture<Void> future = new CompletableFuture<>();
+
+ // use the real executor
+ OperatorPartial oper2 = new OperatorPartial(ACTOR, OPERATION) {
+ @Override
+ public Operation buildOperation(ControlLoopOperationParams params) {
+ return null;
+ }
+ };
+
+ oper2.getBlockingExecutor().execute(() -> future.complete(null));
+
+ assertNull(future.get(5, TimeUnit.SECONDS));
+ }
+
+ /**
+ * Exercises the doXxx() methods.
+ */
+ @Test
+ public void testDoXxx() {
+ assertThatCode(() -> operator.doConfigure(null)).doesNotThrowAnyException();
+ assertThatCode(() -> operator.doStart()).doesNotThrowAnyException();
+ assertThatCode(() -> operator.doStop()).doesNotThrowAnyException();
+ assertThatCode(() -> operator.doShutdown()).doesNotThrowAnyException();
+
+ }
+
+ @Test
+ public void testStart() {
+ verifyRun("testStart", 1, 1, PolicyResult.SUCCESS);
+ }
+
+ /**
+ * Tests startOperation() when the operator is not running.
+ */
+ @Test
+ public void testStartNotRunning() {
+ // stop the operator
+ operator.stop();
+
+ assertThatIllegalStateException().isThrownBy(() -> oper.start());
+ }
+
+ /**
+ * Tests startOperation() when the operation has a preprocessor.
+ */
+ @Test
+ public void testStartWithPreprocessor() {
+ AtomicInteger count = new AtomicInteger();
+
+ CompletableFuture<OperationOutcome> preproc = CompletableFuture.supplyAsync(() -> {
+ count.incrementAndGet();
+ return makeSuccess();
+ }, executor);
+
+ oper.setGuard(preproc);
+
+ verifyRun("testStartWithPreprocessor_testStartPreprocessor", 1, 1, PolicyResult.SUCCESS);
+
+ assertEquals(1, count.get());
+ }
+
+ /**
+ * Tests start() with multiple running requests.
+ */
+ @Test
+ public void testStartMultiple() {
+ for (int count = 0; count < MAX_PARALLEL; ++count) {
+ oper.start();
+ }
+
+ assertTrue(executor.runAll(MAX_REQUESTS * MAX_PARALLEL));
+
+ assertNotNull(opstart);
+ assertNotNull(opend);
+ assertEquals(PolicyResult.SUCCESS, opend.getResult());
+
+ assertEquals(MAX_PARALLEL, numStart);
+ assertEquals(MAX_PARALLEL, oper.getCount());
+ assertEquals(MAX_PARALLEL, numEnd);
+ }
+
+ /**
+ * Tests startPreprocessor() when the preprocessor returns a failure.
+ */
+ @Test
+ public void testStartPreprocessorFailure() {
+ oper.setGuard(CompletableFuture.completedFuture(makeFailure()));
+
+ verifyRun("testStartPreprocessorFailure", 1, 0, PolicyResult.FAILURE_GUARD);
+ }
+
+ /**
+ * Tests startPreprocessor() when the preprocessor throws an exception.
+ */
+ @Test
+ public void testStartPreprocessorException() {
+ // arrange for the preprocessor to throw an exception
+ oper.setGuard(CompletableFuture.failedFuture(new IllegalStateException(EXPECTED_EXCEPTION)));
+
+ verifyRun("testStartPreprocessorException", 1, 0, PolicyResult.FAILURE_GUARD);
+ }
+
+ /**
+ * Tests startPreprocessor() when the pipeline is not running.
+ */
+ @Test
+ public void testStartPreprocessorNotRunning() {
+ // arrange for the preprocessor to return success, which will be ignored
+ oper.setGuard(CompletableFuture.completedFuture(makeSuccess()));
+
+ oper.start().cancel(false);
+ assertTrue(executor.runAll(MAX_REQUESTS));
+
+ assertNull(opstart);
+ assertNull(opend);
+
+ assertEquals(0, numStart);
+ assertEquals(0, oper.getCount());
+ assertEquals(0, numEnd);
+ }
+
+ /**
+ * Tests startPreprocessor() when the preprocessor <b>builder</b> throws an exception.
+ */
+ @Test
+ public void testStartPreprocessorBuilderException() {
+ oper = new MyOper() {
+ @Override
+ protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
+ throw new IllegalStateException(EXPECTED_EXCEPTION);
+ }
+ };
+
+ assertThatIllegalStateException().isThrownBy(() -> oper.start());
+
+ // should be nothing in the queue
+ assertEquals(0, executor.getQueueLength());
+ }
+
+ @Test
+ public void testStartPreprocessorAsync() {
+ assertNull(oper.startPreprocessorAsync());
+ }
+
+ @Test
+ public void testStartGuardAsync() {
+ assertNull(oper.startGuardAsync());
+ }
+
+ @Test
+ public void testStartOperationAsync() {
+ oper.start();
+ assertTrue(executor.runAll(MAX_REQUESTS));
+
+ assertEquals(1, oper.getCount());
+ }
+
+ @Test
+ public void testIsSuccess() {
+ OperationOutcome outcome = new OperationOutcome();
+
+ outcome.setResult(PolicyResult.SUCCESS);
+ assertTrue(oper.isSuccess(outcome));
+
+ for (PolicyResult failure : FAILURE_RESULTS) {
+ outcome.setResult(failure);
+ assertFalse("testIsSuccess-" + failure, oper.isSuccess(outcome));
+ }
+ }
+
+ @Test
+ public void testIsActorFailed() {
+ assertFalse(oper.isActorFailed(null));
+
+ OperationOutcome outcome = params.makeOutcome();
+
+ // incorrect outcome
+ outcome.setResult(PolicyResult.SUCCESS);
+ assertFalse(oper.isActorFailed(outcome));
+
+ outcome.setResult(PolicyResult.FAILURE_RETRIES);
+ assertFalse(oper.isActorFailed(outcome));
+
+ // correct outcome
+ outcome.setResult(PolicyResult.FAILURE);
+
+ // incorrect actor
+ outcome.setActor(MY_SINK);
+ assertFalse(oper.isActorFailed(outcome));
+ outcome.setActor(null);
+ assertFalse(oper.isActorFailed(outcome));
+ outcome.setActor(ACTOR);
+
+ // incorrect operation
+ outcome.setOperation(MY_SINK);
+ assertFalse(oper.isActorFailed(outcome));
+ outcome.setOperation(null);
+ assertFalse(oper.isActorFailed(outcome));
+ outcome.setOperation(OPERATION);
+
+ // correct values
+ assertTrue(oper.isActorFailed(outcome));
+ }
+
+ @Test
+ public void testDoOperation() {
+ /*
+ * Use an operation that doesn't override doOperation().
+ */
+ OperationPartial oper2 = new OperationPartial(params, operator) {};
+
+ oper2.start();
+ assertTrue(executor.runAll(MAX_REQUESTS));
+
+ assertNotNull(opend);
+ assertEquals(PolicyResult.FAILURE_EXCEPTION, opend.getResult());
+ }
+
+ @Test
+ public void testTimeout() throws Exception {
+
+ // use a real executor
+ params = params.toBuilder().executor(ForkJoinPool.commonPool()).build();
+
+ // trigger timeout very quickly
+ oper = new MyOper() {
+ @Override
+ protected long getTimeoutMs(Integer timeoutSec) {
+ return 1;
+ }
+
+ @Override
+ protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+ OperationOutcome outcome2 = params.makeOutcome();
+ outcome2.setResult(PolicyResult.SUCCESS);
+
+ /*
+ * Create an incomplete future that will timeout after the operation's
+ * timeout. If it fires before the other timer, then it will return a
+ * SUCCESS outcome.
+ */
+ CompletableFuture<OperationOutcome> future = new CompletableFuture<>();
+ future = future.orTimeout(1, TimeUnit.SECONDS).handleAsync((unused1, unused2) -> outcome,
+ params.getExecutor());
+
+ return future;
+ }
+ };
+
+ assertEquals(PolicyResult.FAILURE_TIMEOUT, oper.start().get().getResult());
+ }
+
+ /**
+ * Tests retry functions, when the count is set to zero and retries are exhausted.
+ */
+ @Test
+ public void testSetRetryFlag_testRetryOnFailure_ZeroRetries_testStartOperationAttempt() {
+ params = params.toBuilder().retry(0).build();
+
+ // new params, thus need a new operation
+ oper = new MyOper();
+
+ oper.setMaxFailures(10);
+
+ verifyRun("testSetRetryFlag_testRetryOnFailure_ZeroRetries", 1, 1, PolicyResult.FAILURE);
+ }
+
+ /**
+ * Tests retry functions, when the count is null and retries are exhausted.
+ */
+ @Test
+ public void testSetRetryFlag_testRetryOnFailure_NullRetries() {
+ params = params.toBuilder().retry(null).build();
+
+ // new params, thus need a new operation
+ oper = new MyOper();
+
+ oper.setMaxFailures(10);
+
+ verifyRun("testSetRetryFlag_testRetryOnFailure_NullRetries", 1, 1, PolicyResult.FAILURE);
+ }
+
+ /**
+ * Tests retry functions, when retries are exhausted.
+ */
+ @Test
+ public void testSetRetryFlag_testRetryOnFailure_RetriesExhausted() {
+ final int maxRetries = 3;
+ params = params.toBuilder().retry(maxRetries).build();
+
+ // new params, thus need a new operation
+ oper = new MyOper();
+
+ oper.setMaxFailures(10);
+
+ verifyRun("testSetRetryFlag_testRetryOnFailure_RetriesExhausted", maxRetries + 1, maxRetries + 1,
+ PolicyResult.FAILURE_RETRIES);
+ }
+
+ /**
+ * Tests retry functions, when a success follows some retries.
+ */
+ @Test
+ public void testSetRetryFlag_testRetryOnFailure_SuccessAfterRetries() {
+ params = params.toBuilder().retry(10).build();
+
+ // new params, thus need a new operation
+ oper = new MyOper();
+
+ final int maxFailures = 3;
+ oper.setMaxFailures(maxFailures);
+
+ verifyRun("testSetRetryFlag_testRetryOnFailure_SuccessAfterRetries", maxFailures + 1, maxFailures + 1,
+ PolicyResult.SUCCESS);
+ }
+
+ /**
+ * Tests retry functions, when the outcome is {@code null}.
+ */
+ @Test
+ public void testSetRetryFlag_testRetryOnFailure_NullOutcome() {
+
+ // arrange to return null from doOperation()
+ oper = new MyOper() {
+ @Override
+ protected OperationOutcome doOperation(int attempt, OperationOutcome operation) {
+
+ // update counters
+ super.doOperation(attempt, operation);
+ return null;
+ }
+ };
+
+ verifyRun("testSetRetryFlag_testRetryOnFailure_NullOutcome", 1, 1, PolicyResult.FAILURE, null, noop());
+ }
+
+ @Test
+ public void testSleep() throws Exception {
+ CompletableFuture<Void> future = oper.sleep(-1, TimeUnit.SECONDS);
+ assertTrue(future.isDone());
+ assertNull(future.get());
+
+ // edge case
+ future = oper.sleep(0, TimeUnit.SECONDS);
+ assertTrue(future.isDone());
+ assertNull(future.get());
+
+ /*
+ * Start a second sleep we can use to check the first while it's running.
+ */
+ tstart = Instant.now();
+ future = oper.sleep(100, TimeUnit.MILLISECONDS);
+
+ CompletableFuture<Void> future2 = oper.sleep(10, TimeUnit.MILLISECONDS);
+
+ // wait for second to complete and verify that the first has not completed
+ future2.get();
+ assertFalse(future.isDone());
+
+ // wait for second to complete
+ future.get();
+
+ long diff = Instant.now().toEpochMilli() - tstart.toEpochMilli();
+ assertTrue(diff >= 99);
+ }
+
+ @Test
+ public void testIsSameOperation() {
+ assertFalse(oper.isSameOperation(null));
+
+ OperationOutcome outcome = params.makeOutcome();
+
+ // wrong actor - should be false
+ outcome.setActor(null);
+ assertFalse(oper.isSameOperation(outcome));
+ outcome.setActor(MY_SINK);
+ assertFalse(oper.isSameOperation(outcome));
+ outcome.setActor(ACTOR);
+
+ // wrong operation - should be null
+ outcome.setOperation(null);
+ assertFalse(oper.isSameOperation(outcome));
+ outcome.setOperation(MY_SINK);
+ assertFalse(oper.isSameOperation(outcome));
+ outcome.setOperation(OPERATION);
+
+ assertTrue(oper.isSameOperation(outcome));
+ }
+
+ /**
+ * Tests handleFailure() when the outcome is a success.
+ */
+ @Test
+ public void testHandlePreprocessorFailureTrue() {
+ oper.setGuard(CompletableFuture.completedFuture(makeSuccess()));
+ verifyRun("testHandlePreprocessorFailureTrue", 1, 1, PolicyResult.SUCCESS);
+ }
+
+ /**
+ * Tests handleFailure() when the outcome is <i>not</i> a success.
+ */
+ @Test
+ public void testHandlePreprocessorFailureFalse() throws Exception {
+ oper.setGuard(CompletableFuture.completedFuture(makeFailure()));
+ verifyRun("testHandlePreprocessorFailureFalse", 1, 0, PolicyResult.FAILURE_GUARD);
+ }
+
+ /**
+ * Tests handleFailure() when the outcome is {@code null}.
+ */
+ @Test
+ public void testHandlePreprocessorFailureNull() throws Exception {
+ // arrange to return null from the preprocessor
+ oper.setGuard(CompletableFuture.completedFuture(null));
+
+ verifyRun("testHandlePreprocessorFailureNull", 1, 0, PolicyResult.FAILURE_GUARD);
+ }
+
+ @Test
+ public void testFromException() {
+ // arrange to generate an exception when operation runs
+ oper.setGenException(true);
+
+ verifyRun("testFromException", 1, 1, PolicyResult.FAILURE_EXCEPTION);
+ }
+
+ /**
+ * Tests fromException() when there is no exception.
+ */
+ @Test
+ public void testFromExceptionNoExcept() {
+ verifyRun("testFromExceptionNoExcept", 1, 1, PolicyResult.SUCCESS);
+ }
+
+ /**
+ * Tests both flavors of anyOf(), because one invokes the other.
+ */
+ @Test
+ public void testAnyOf() throws Exception {
+ // first task completes, others do not
+ List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
+
+ final OperationOutcome outcome = params.makeOutcome();
+
+ tasks.add(() -> CompletableFuture.completedFuture(outcome));
+ tasks.add(() -> new CompletableFuture<>());
+ tasks.add(() -> null);
+ tasks.add(() -> new CompletableFuture<>());
+
+ CompletableFuture<OperationOutcome> result = oper.anyOf(tasks);
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertTrue(result.isDone());
+ assertSame(outcome, result.get());
+
+ // repeat using array form
+ @SuppressWarnings("unchecked")
+ Supplier<CompletableFuture<OperationOutcome>>[] taskArray = new Supplier[tasks.size()];
+ result = oper.anyOf(tasks.toArray(taskArray));
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertTrue(result.isDone());
+ assertSame(outcome, result.get());
+
+ // second task completes, others do not
+ tasks.clear();
+ tasks.add(() -> new CompletableFuture<>());
+ tasks.add(() -> CompletableFuture.completedFuture(outcome));
+ tasks.add(() -> new CompletableFuture<>());
+
+ result = oper.anyOf(tasks);
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertTrue(result.isDone());
+ assertSame(outcome, result.get());
+
+ // third task completes, others do not
+ tasks.clear();
+ tasks.add(() -> new CompletableFuture<>());
+ tasks.add(() -> new CompletableFuture<>());
+ tasks.add(() -> CompletableFuture.completedFuture(outcome));
+
+ result = oper.anyOf(tasks);
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertTrue(result.isDone());
+ assertSame(outcome, result.get());
+ }
+
+ /**
+ * Tests both flavors of anyOf(), for edge cases: zero items, and one item.
+ */
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testAnyOfEdge() throws Exception {
+ List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
+
+ // zero items: check both using a list and using an array
+ assertNull(oper.anyOf(tasks));
+ assertNull(oper.anyOf());
+
+ // one item: : check both using a list and using an array
+ CompletableFuture<OperationOutcome> future1 = new CompletableFuture<>();
+ tasks.add(() -> future1);
+
+ assertSame(future1, oper.anyOf(tasks));
+ assertSame(future1, oper.anyOf(() -> future1));
+ }
+
+ @Test
+ public void testAllOfArray() throws Exception {
+ final OperationOutcome outcome = params.makeOutcome();
+
+ CompletableFuture<OperationOutcome> future1 = new CompletableFuture<>();
+ CompletableFuture<OperationOutcome> future2 = new CompletableFuture<>();
+ CompletableFuture<OperationOutcome> future3 = new CompletableFuture<>();
+
+ @SuppressWarnings("unchecked")
+ CompletableFuture<OperationOutcome> result =
+ oper.allOf(() -> future1, () -> future2, () -> null, () -> future3);
+
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertFalse(result.isDone());
+ future1.complete(outcome);
+
+ // complete 3 before 2
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertFalse(result.isDone());
+ future3.complete(outcome);
+
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertFalse(result.isDone());
+ future2.complete(outcome);
+
+ // all of them are now done
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertTrue(result.isDone());
+ assertSame(outcome, result.get());
+ }
+
+ @Test
+ public void testAllOfList() throws Exception {
+ final OperationOutcome outcome = params.makeOutcome();
+
+ CompletableFuture<OperationOutcome> future1 = new CompletableFuture<>();
+ CompletableFuture<OperationOutcome> future2 = new CompletableFuture<>();
+ CompletableFuture<OperationOutcome> future3 = new CompletableFuture<>();
+
+ List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
+ tasks.add(() -> future1);
+ tasks.add(() -> future2);
+ tasks.add(() -> null);
+ tasks.add(() -> future3);
+
+ CompletableFuture<OperationOutcome> result = oper.allOf(tasks);
+
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertFalse(result.isDone());
+ future1.complete(outcome);
+
+ // complete 3 before 2
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertFalse(result.isDone());
+ future3.complete(outcome);
+
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertFalse(result.isDone());
+ future2.complete(outcome);
+
+ // all of them are now done
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertTrue(result.isDone());
+ assertSame(outcome, result.get());
+ }
+
+ /**
+ * Tests both flavors of allOf(), for edge cases: zero items, and one item.
+ */
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testAllOfEdge() throws Exception {
+ List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
+
+ // zero items: check both using a list and using an array
+ assertNull(oper.allOf(tasks));
+ assertNull(oper.allOf());
+
+ // one item: : check both using a list and using an array
+ CompletableFuture<OperationOutcome> future1 = new CompletableFuture<>();
+ tasks.add(() -> future1);
+
+ assertSame(future1, oper.allOf(tasks));
+ assertSame(future1, oper.allOf(() -> future1));
+ }
+
+ @Test
+ public void testAttachFutures() throws Exception {
+ List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
+
+ // third task throws an exception during construction
+ CompletableFuture<OperationOutcome> future1 = new CompletableFuture<>();
+ CompletableFuture<OperationOutcome> future2 = new CompletableFuture<>();
+ CompletableFuture<OperationOutcome> future3 = new CompletableFuture<>();
+ tasks.add(() -> future1);
+ tasks.add(() -> future2);
+ tasks.add(() -> {
+ throw new IllegalStateException(EXPECTED_EXCEPTION);
+ });
+ tasks.add(() -> future3);
+
+ assertThatIllegalStateException().isThrownBy(() -> oper.anyOf(tasks)).withMessage(EXPECTED_EXCEPTION);
+
+ // should have canceled the first two, but not the last
+ assertTrue(future1.isCancelled());
+ assertTrue(future2.isCancelled());
+ assertFalse(future3.isCancelled());
+ }
+
+ @Test
+ public void testCombineOutcomes() throws Exception {
+ // only one outcome
+ verifyOutcomes(0, PolicyResult.SUCCESS);
+ verifyOutcomes(0, PolicyResult.FAILURE_EXCEPTION);
+
+ // maximum is in different positions
+ verifyOutcomes(0, PolicyResult.FAILURE, PolicyResult.SUCCESS, PolicyResult.FAILURE_GUARD);
+ verifyOutcomes(1, PolicyResult.SUCCESS, PolicyResult.FAILURE, PolicyResult.FAILURE_GUARD);
+ verifyOutcomes(2, PolicyResult.SUCCESS, PolicyResult.FAILURE_GUARD, PolicyResult.FAILURE);
+
+ // null outcome - takes precedence over a success
+ List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
+ tasks.add(() -> CompletableFuture.completedFuture(params.makeOutcome()));
+ tasks.add(() -> CompletableFuture.completedFuture(null));
+ tasks.add(() -> CompletableFuture.completedFuture(params.makeOutcome()));
+ CompletableFuture<OperationOutcome> result = oper.allOf(tasks);
+
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertTrue(result.isDone());
+ assertNull(result.get());
+
+ // one throws an exception during execution
+ IllegalStateException except = new IllegalStateException(EXPECTED_EXCEPTION);
+
+ tasks.clear();
+ tasks.add(() -> CompletableFuture.completedFuture(params.makeOutcome()));
+ tasks.add(() -> CompletableFuture.failedFuture(except));
+ tasks.add(() -> CompletableFuture.completedFuture(params.makeOutcome()));
+ result = oper.allOf(tasks);
+
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertTrue(result.isCompletedExceptionally());
+ result.whenComplete((unused, thrown) -> assertSame(except, thrown));
+ }
+
+ /**
+ * Tests both flavors of sequence(), because one invokes the other.
+ */
+ @Test
+ public void testSequence() throws Exception {
+ final OperationOutcome outcome = params.makeOutcome();
+
+ List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
+ tasks.add(() -> CompletableFuture.completedFuture(outcome));
+ tasks.add(() -> null);
+ tasks.add(() -> CompletableFuture.completedFuture(outcome));
+ tasks.add(() -> CompletableFuture.completedFuture(outcome));
+
+ CompletableFuture<OperationOutcome> result = oper.sequence(tasks);
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertTrue(result.isDone());
+ assertSame(outcome, result.get());
+
+ // repeat using array form
+ @SuppressWarnings("unchecked")
+ Supplier<CompletableFuture<OperationOutcome>>[] taskArray = new Supplier[tasks.size()];
+ result = oper.sequence(tasks.toArray(taskArray));
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertTrue(result.isDone());
+ assertSame(outcome, result.get());
+
+ // second task fails, third should not run
+ OperationOutcome failure = params.makeOutcome();
+ failure.setResult(PolicyResult.FAILURE);
+ tasks.clear();
+ tasks.add(() -> CompletableFuture.completedFuture(outcome));
+ tasks.add(() -> CompletableFuture.completedFuture(failure));
+ tasks.add(() -> CompletableFuture.completedFuture(outcome));
+
+ result = oper.sequence(tasks);
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertTrue(result.isDone());
+ assertSame(failure, result.get());
+ }
+
+ /**
+ * Tests both flavors of sequence(), for edge cases: zero items, and one item.
+ */
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testSequenceEdge() throws Exception {
+ List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
+
+ // zero items: check both using a list and using an array
+ assertNull(oper.sequence(tasks));
+ assertNull(oper.sequence());
+
+ // one item: : check both using a list and using an array
+ CompletableFuture<OperationOutcome> future1 = new CompletableFuture<>();
+ tasks.add(() -> future1);
+
+ assertSame(future1, oper.sequence(tasks));
+ assertSame(future1, oper.sequence(() -> future1));
+ }
+
+ private void verifyOutcomes(int expected, PolicyResult... results) throws Exception {
+ List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>();
+
+ OperationOutcome expectedOutcome = null;
+
+ for (int count = 0; count < results.length; ++count) {
+ OperationOutcome outcome = params.makeOutcome();
+ outcome.setResult(results[count]);
+ tasks.add(() -> CompletableFuture.completedFuture(outcome));
+
+ if (count == expected) {
+ expectedOutcome = outcome;
+ }
+ }
+
+ CompletableFuture<OperationOutcome> result = oper.allOf(tasks);
+
+ assertTrue(executor.runAll(MAX_REQUESTS));
+ assertTrue(result.isDone());
+ assertSame(expectedOutcome, result.get());
+ }
+
+ @Test
+ public void testDetmPriority() throws CoderException {
+ assertEquals(1, oper.detmPriority(null));
+
+ OperationOutcome outcome = params.makeOutcome();
+
+ Map<PolicyResult, Integer> map = Map.of(PolicyResult.SUCCESS, 0, PolicyResult.FAILURE_GUARD, 2,
+ PolicyResult.FAILURE_RETRIES, 3, PolicyResult.FAILURE, 4, PolicyResult.FAILURE_TIMEOUT, 5,
+ PolicyResult.FAILURE_EXCEPTION, 6);
+
+ for (Entry<PolicyResult, Integer> ent : map.entrySet()) {
+ outcome.setResult(ent.getKey());
+ assertEquals(ent.getKey().toString(), ent.getValue().intValue(), oper.detmPriority(outcome));
+ }
+
+ /*
+ * Test null result. We can't actually set it to null, because the set() method
+ * won't allow it. Instead, we decode it from a structure.
+ */
+ outcome = new StandardCoder().decode("{\"result\":null}", OperationOutcome.class);
+ assertEquals(1, oper.detmPriority(outcome));
+ }
+
+ /**
+ * Tests callbackStarted() when the pipeline has already been stopped.
+ */
+ @Test
+ public void testCallbackStartedNotRunning() {
+ AtomicReference<Future<OperationOutcome>> future = new AtomicReference<>();
+
+ /*
+ * arrange to stop the controller when the start-callback is invoked, but capture
+ * the outcome
+ */
+ params = params.toBuilder().startCallback(oper -> {
+ starter(oper);
+ future.get().cancel(false);
+ }).build();
+
+ // new params, thus need a new operation
+ oper = new MyOper();
+
+ future.set(oper.start());
+ assertTrue(executor.runAll(MAX_REQUESTS));
+
+ // should have only run once
+ assertEquals(1, numStart);
+ }
+
+ /**
+ * Tests callbackCompleted() when the pipeline has already been stopped.
+ */
+ @Test
+ public void testCallbackCompletedNotRunning() {
+ AtomicReference<Future<OperationOutcome>> future = new AtomicReference<>();
+
+ // arrange to stop the controller when the start-callback is invoked
+ params = params.toBuilder().startCallback(oper -> {
+ future.get().cancel(false);
+ }).build();
+
+ // new params, thus need a new operation
+ oper = new MyOper();
+
+ future.set(oper.start());
+ assertTrue(executor.runAll(MAX_REQUESTS));
+
+ // should not have been set
+ assertNull(opend);
+ assertEquals(0, numEnd);
+ }
+
+ @Test
+ public void testSetOutcomeControlLoopOperationOutcomeThrowable() {
+ final CompletionException timex = new CompletionException(new TimeoutException(EXPECTED_EXCEPTION));
+
+ OperationOutcome outcome;
+
+ outcome = new OperationOutcome();
+ oper.setOutcome(outcome, timex);
+ assertEquals(ControlLoopOperation.FAILED_MSG, outcome.getMessage());
+ assertEquals(PolicyResult.FAILURE_TIMEOUT, outcome.getResult());
+
+ outcome = new OperationOutcome();
+ oper.setOutcome(outcome, new IllegalStateException(EXPECTED_EXCEPTION));
+ assertEquals(ControlLoopOperation.FAILED_MSG, outcome.getMessage());
+ assertEquals(PolicyResult.FAILURE_EXCEPTION, outcome.getResult());
+ }
+
+ @Test
+ public void testSetOutcomeControlLoopOperationOutcomePolicyResult() {
+ OperationOutcome outcome;
+
+ outcome = new OperationOutcome();
+ oper.setOutcome(outcome, PolicyResult.SUCCESS);
+ assertEquals(ControlLoopOperation.SUCCESS_MSG, outcome.getMessage());
+ assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+
+ for (PolicyResult result : FAILURE_RESULTS) {
+ outcome = new OperationOutcome();
+ oper.setOutcome(outcome, result);
+ assertEquals(result.toString(), ControlLoopOperation.FAILED_MSG, outcome.getMessage());
+ assertEquals(result.toString(), result, outcome.getResult());
+ }
+ }
+
+ @Test
+ public void testIsTimeout() {
+ final TimeoutException timex = new TimeoutException(EXPECTED_EXCEPTION);
+
+ assertFalse(oper.isTimeout(new IllegalStateException(EXPECTED_EXCEPTION)));
+ assertFalse(oper.isTimeout(new IllegalStateException(timex)));
+ assertFalse(oper.isTimeout(new CompletionException(new IllegalStateException(timex))));
+ assertFalse(oper.isTimeout(new CompletionException(null)));
+ assertFalse(oper.isTimeout(new CompletionException(new CompletionException(timex))));
+
+ assertTrue(oper.isTimeout(timex));
+ assertTrue(oper.isTimeout(new CompletionException(timex)));
+ }
+
+ @Test
+ public void testLogMessage() {
+ final String infraStr = SINK_INFRA.toString();
+
+ // log structured data
+ appender.clearExtractions();
+ oper.logMessage(EventType.OUT, SINK_INFRA, MY_SINK, new MyData());
+ List<String> output = appender.getExtracted();
+ assertEquals(1, output.size());
+
+ assertThat(output.get(0)).contains(infraStr).contains(MY_SINK).contains("OUT")
+ .contains("{\n \"text\": \"my-text\"\n}");
+
+ // repeat with a response
+ appender.clearExtractions();
+ oper.logMessage(EventType.IN, SOURCE_INFRA, MY_SOURCE, new MyData());
+ output = appender.getExtracted();
+ assertEquals(1, output.size());
+
+ assertThat(output.get(0)).contains(SOURCE_INFRA.toString()).contains(MY_SOURCE).contains("IN")
+ .contains("{\n \"text\": \"my-text\"\n}");
+
+ // log a plain string
+ appender.clearExtractions();
+ oper.logMessage(EventType.OUT, SINK_INFRA, MY_SINK, TEXT);
+ output = appender.getExtracted();
+ assertEquals(1, output.size());
+ assertThat(output.get(0)).contains(infraStr).contains(MY_SINK).contains(TEXT);
+
+ // log a null request
+ appender.clearExtractions();
+ oper.logMessage(EventType.OUT, SINK_INFRA, MY_SINK, null);
+ output = appender.getExtracted();
+ assertEquals(1, output.size());
+
+ assertThat(output.get(0)).contains(infraStr).contains(MY_SINK).contains("null");
+
+ // generate exception from coder
+ setOperCoderException();
+
+ appender.clearExtractions();
+ oper.logMessage(EventType.OUT, SINK_INFRA, MY_SINK, new MyData());
+ output = appender.getExtracted();
+ assertEquals(2, output.size());
+ assertThat(output.get(0)).contains("cannot pretty-print request");
+ assertThat(output.get(1)).contains(infraStr).contains(MY_SINK);
+
+ // repeat with a response
+ appender.clearExtractions();
+ oper.logMessage(EventType.IN, SOURCE_INFRA, MY_SOURCE, new MyData());
+ output = appender.getExtracted();
+ assertEquals(2, output.size());
+ assertThat(output.get(0)).contains("cannot pretty-print response");
+ assertThat(output.get(1)).contains(MY_SOURCE);
+ }
+
+ @Test
+ public void testGetRetry() {
+ assertEquals(0, oper.getRetry(null));
+ assertEquals(10, oper.getRetry(10));
+ }
+
+ @Test
+ public void testGetRetryWait() {
+ // need an operator that doesn't override the retry time
+ OperationPartial oper2 = new OperationPartial(params, operator) {};
+ assertEquals(OperationPartial.DEFAULT_RETRY_WAIT_MS, oper2.getRetryWaitMs());
+ }
+
+ @Test
+ public void testGetTimeOutMs() {
+ assertEquals(TIMEOUT * 1000, oper.getTimeoutMs(params.getTimeoutSec()));
+
+ params = params.toBuilder().timeoutSec(null).build();
+
+ // new params, thus need a new operation
+ oper = new MyOper();
+
+ assertEquals(0, oper.getTimeoutMs(params.getTimeoutSec()));
+ }
+
+ private void starter(OperationOutcome oper) {
+ ++numStart;
+ tstart = oper.getStart();
+ opstart = oper;
+ }
+
+ private void completer(OperationOutcome oper) {
+ ++numEnd;
+ opend = oper;
+ }
+
+ /**
+ * Gets a function that does nothing.
+ *
+ * @param <T> type of input parameter expected by the function
+ * @return a function that does nothing
+ */
+ private <T> Consumer<T> noop() {
+ return unused -> {
+ };
+ }
+
+ private OperationOutcome makeSuccess() {
+ OperationOutcome outcome = params.makeOutcome();
+ outcome.setResult(PolicyResult.SUCCESS);
+
+ return outcome;
+ }
+
+ private OperationOutcome makeFailure() {
+ OperationOutcome outcome = params.makeOutcome();
+ outcome.setResult(PolicyResult.FAILURE);
+
+ return outcome;
+ }
+
+ /**
+ * Verifies a run.
+ *
+ * @param testName test name
+ * @param expectedCallbacks number of callbacks expected
+ * @param expectedOperations number of operation invocations expected
+ * @param expectedResult expected outcome
+ */
+ private void verifyRun(String testName, int expectedCallbacks, int expectedOperations,
+ PolicyResult expectedResult) {
+
+ String expectedSubRequestId =
+ (expectedResult == PolicyResult.FAILURE_EXCEPTION ? null : String.valueOf(expectedOperations));
+
+ verifyRun(testName, expectedCallbacks, expectedOperations, expectedResult, expectedSubRequestId, noop());
+ }
+
+ /**
+ * Verifies a run.
+ *
+ * @param testName test name
+ * @param expectedCallbacks number of callbacks expected
+ * @param expectedOperations number of operation invocations expected
+ * @param expectedResult expected outcome
+ * @param expectedSubRequestId expected sub request ID
+ * @param manipulator function to modify the future returned by
+ * {@link OperationPartial#start(ControlLoopOperationParams)} before the tasks
+ * in the executor are run
+ */
+ private void verifyRun(String testName, int expectedCallbacks, int expectedOperations, PolicyResult expectedResult,
+ String expectedSubRequestId, Consumer<CompletableFuture<OperationOutcome>> manipulator) {
+
+ CompletableFuture<OperationOutcome> future = oper.start();
+
+ manipulator.accept(future);
+
+ assertTrue(testName, executor.runAll(MAX_REQUESTS));
+
+ assertEquals(testName, expectedCallbacks, numStart);
+ assertEquals(testName, expectedCallbacks, numEnd);
+
+ if (expectedCallbacks > 0) {
+ assertNotNull(testName, opstart);
+ assertNotNull(testName, opend);
+ assertEquals(testName, expectedResult, opend.getResult());
+
+ assertSame(testName, tstart, opstart.getStart());
+ assertSame(testName, tstart, opend.getStart());
+
+ try {
+ assertTrue(future.isDone());
+ assertSame(testName, opend, future.get());
+
+ } catch (InterruptedException | ExecutionException e) {
+ throw new IllegalStateException(e);
+ }
+
+ if (expectedOperations > 0) {
+ assertEquals(testName, expectedSubRequestId, opend.getSubRequestId());
+ }
+ }
+
+ assertEquals(testName, expectedOperations, oper.getCount());
+ }
+
+ /**
+ * Creates a new {@link #oper} whose coder will throw an exception.
+ */
+ private void setOperCoderException() {
+ oper = new MyOper() {
+ @Override
+ protected Coder makeCoder() {
+ return new StandardCoder() {
+ @Override
+ public String encode(Object object, boolean pretty) throws CoderException {
+ throw new CoderException(EXPECTED_EXCEPTION);
+ }
+ };
+ }
+ };
+ }
+
+
+ @Getter
+ public static class MyData {
+ private String text = TEXT;
+ }
+
+
+ private class MyOper extends OperationPartial {
+ @Getter
+ private int count = 0;
+
+ @Setter
+ private boolean genException;
+
+ @Setter
+ private int maxFailures = 0;
+
+ @Setter
+ private CompletableFuture<OperationOutcome> guard;
+
+
+ public MyOper() {
+ super(OperationPartialTest.this.params, operator);
+ }
+
+ @Override
+ protected OperationOutcome doOperation(int attempt, OperationOutcome operation) {
+ ++count;
+ if (genException) {
+ throw new IllegalStateException(EXPECTED_EXCEPTION);
+ }
+
+ operation.setSubRequestId(String.valueOf(attempt));
+
+ if (count > maxFailures) {
+ operation.setResult(PolicyResult.SUCCESS);
+ } else {
+ operation.setResult(PolicyResult.FAILURE);
+ }
+
+ return operation;
+ }
+
+ @Override
+ protected CompletableFuture<OperationOutcome> startGuardAsync() {
+ return (guard != null ? guard : super.startGuardAsync());
+ }
+
+ @Override
+ protected long getRetryWaitMs() {
+ /*
+ * Sleep timers run in the background, but we want to control things via the
+ * "executor", thus we avoid sleep timers altogether by simply returning 0.
+ */
+ return 0L;
+ }
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartialTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartialTest.java
index 21bc656f2..370426fd4 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartialTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartialTest.java
@@ -20,1271 +20,88 @@
package org.onap.policy.controlloop.actorserviceprovider.impl;
-import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import java.time.Instant;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Queue;
import java.util.TreeMap;
-import java.util.UUID;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ForkJoinPool;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import lombok.Getter;
-import lombok.Setter;
import org.junit.Before;
import org.junit.Test;
-import org.onap.policy.controlloop.ControlLoopOperation;
-import org.onap.policy.controlloop.VirtualControlLoopEvent;
-import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
-import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
-import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture;
-import org.onap.policy.controlloop.policy.PolicyResult;
public class OperatorPartialTest {
- private static final int MAX_PARALLEL_REQUESTS = 10;
- private static final String EXPECTED_EXCEPTION = "expected exception";
private static final String ACTOR = "my-actor";
- private static final String OPERATOR = "my-operator";
- private static final String TARGET = "my-target";
- private static final int TIMEOUT = 1000;
- private static final UUID REQ_ID = UUID.randomUUID();
+ private static final String OPERATION = "my-name";
- private static final List<PolicyResult> FAILURE_RESULTS = Arrays.asList(PolicyResult.values()).stream()
- .filter(result -> result != PolicyResult.SUCCESS).collect(Collectors.toList());
-
- private VirtualControlLoopEvent event;
- private Map<String, Object> config;
- private ControlLoopEventContext context;
- private MyExec executor;
- private ControlLoopOperationParams params;
-
- private MyOper oper;
-
- private int numStart;
- private int numEnd;
-
- private Instant tstart;
-
- private OperationOutcome opstart;
- private OperationOutcome opend;
+ private OperatorPartial operator;
/**
- * Initializes the fields, including {@link #oper}.
+ * Initializes {@link #operator}.
*/
@Before
public void setUp() {
- event = new VirtualControlLoopEvent();
- event.setRequestId(REQ_ID);
-
- config = new TreeMap<>();
- context = new ControlLoopEventContext(event);
- executor = new MyExec();
-
- params = ControlLoopOperationParams.builder().completeCallback(this::completer).context(context)
- .executor(executor).actor(ACTOR).operation(OPERATOR).timeoutSec(TIMEOUT)
- .startCallback(this::starter).targetEntity(TARGET).build();
-
- oper = new MyOper();
- oper.configure(new TreeMap<>());
- oper.start();
-
- tstart = null;
-
- opstart = null;
- opend = null;
- }
-
- @Test
- public void testOperatorPartial_testGetActorName_testGetName() {
- assertEquals(ACTOR, oper.getActorName());
- assertEquals(OPERATOR, oper.getName());
- assertEquals(ACTOR + "." + OPERATOR, oper.getFullName());
- }
-
- @Test
- public void testGetBlockingExecutor() throws InterruptedException {
- CountDownLatch latch = new CountDownLatch(1);
-
- /*
- * Use an operator that doesn't override getBlockingExecutor().
- */
- OperatorPartial oper2 = new OperatorPartial(ACTOR, OPERATOR) {};
- oper2.getBlockingExecutor().execute(() -> latch.countDown());
-
- assertTrue(latch.await(5, TimeUnit.SECONDS));
- }
-
- @Test
- public void testDoConfigure() {
- oper = spy(new MyOper());
-
- oper.configure(config);
- verify(oper).configure(config);
-
- // repeat - SHOULD be run again
- oper.configure(config);
- verify(oper, times(2)).configure(config);
- }
-
- @Test
- public void testDoStart() {
- oper = spy(new MyOper());
-
- oper.configure(config);
- oper.start();
-
- verify(oper).doStart();
-
- // others should not have been invoked
- verify(oper, never()).doStop();
- verify(oper, never()).doShutdown();
- }
-
- @Test
- public void testDoStop() {
- oper = spy(new MyOper());
-
- oper.configure(config);
- oper.start();
- oper.stop();
-
- verify(oper).doStop();
-
- // should not have been re-invoked
- verify(oper).doStart();
-
- // others should not have been invoked
- verify(oper, never()).doShutdown();
- }
-
- @Test
- public void testDoShutdown() {
- oper = spy(new MyOper());
-
- oper.configure(config);
- oper.start();
- oper.shutdown();
-
- verify(oper).doShutdown();
-
- // should not have been re-invoked
- verify(oper).doStart();
-
- // others should not have been invoked
- verify(oper, never()).doStop();
- }
-
- @Test
- public void testStartOperation() {
- verifyRun("testStartOperation", 1, 1, PolicyResult.SUCCESS);
- }
-
- /**
- * Tests startOperation() when the operator is not running.
- */
- @Test
- public void testStartOperationNotRunning() {
- // use a new operator, one that hasn't been started yet
- oper = new MyOper();
- oper.configure(new TreeMap<>());
-
- assertThatIllegalStateException().isThrownBy(() -> oper.startOperation(params));
- }
-
- /**
- * Tests startOperation() when the operation has a preprocessor.
- */
- @Test
- public void testStartOperationWithPreprocessor() {
- AtomicInteger count = new AtomicInteger();
-
- CompletableFuture<OperationOutcome> preproc = CompletableFuture.supplyAsync(() -> {
- count.incrementAndGet();
- return makeSuccess();
- }, executor);
-
- oper.setPreProcessor(preproc);
-
- verifyRun("testStartOperationWithPreprocessor_testStartPreprocessor", 1, 1, PolicyResult.SUCCESS);
-
- assertEquals(1, count.get());
- }
-
- /**
- * Tests startOperation() with multiple running requests.
- */
- @Test
- public void testStartOperationMultiple() {
- for (int count = 0; count < MAX_PARALLEL_REQUESTS; ++count) {
- oper.startOperation(params);
- }
-
- assertTrue(executor.runAll());
-
- assertNotNull(opstart);
- assertNotNull(opend);
- assertEquals(PolicyResult.SUCCESS, opend.getResult());
-
- assertEquals(MAX_PARALLEL_REQUESTS, numStart);
- assertEquals(MAX_PARALLEL_REQUESTS, oper.getCount());
- assertEquals(MAX_PARALLEL_REQUESTS, numEnd);
- }
-
- /**
- * Tests startPreprocessor() when the preprocessor returns a failure.
- */
- @Test
- public void testStartPreprocessorFailure() {
- oper.setPreProcessor(CompletableFuture.completedFuture(makeFailure()));
-
- verifyRun("testStartPreprocessorFailure", 1, 0, PolicyResult.FAILURE_GUARD);
- }
-
- /**
- * Tests startPreprocessor() when the preprocessor throws an exception.
- */
- @Test
- public void testStartPreprocessorException() {
- // arrange for the preprocessor to throw an exception
- oper.setPreProcessor(CompletableFuture.failedFuture(new IllegalStateException(EXPECTED_EXCEPTION)));
-
- verifyRun("testStartPreprocessorException", 1, 0, PolicyResult.FAILURE_GUARD);
- }
-
- /**
- * Tests startPreprocessor() when the pipeline is not running.
- */
- @Test
- public void testStartPreprocessorNotRunning() {
- // arrange for the preprocessor to return success, which will be ignored
- oper.setPreProcessor(CompletableFuture.completedFuture(makeSuccess()));
-
- oper.startOperation(params).cancel(false);
- assertTrue(executor.runAll());
-
- assertNull(opstart);
- assertNull(opend);
-
- assertEquals(0, numStart);
- assertEquals(0, oper.getCount());
- assertEquals(0, numEnd);
- }
-
- /**
- * Tests startPreprocessor() when the preprocessor <b>builder</b> throws an exception.
- */
- @Test
- public void testStartPreprocessorBuilderException() {
- oper = new MyOper() {
- @Override
- protected CompletableFuture<OperationOutcome> startPreprocessorAsync(ControlLoopOperationParams params) {
- throw new IllegalStateException(EXPECTED_EXCEPTION);
- }
- };
-
- oper.configure(new TreeMap<>());
- oper.start();
-
- assertThatIllegalStateException().isThrownBy(() -> oper.startOperation(params));
-
- // should be nothing in the queue
- assertEquals(0, executor.getQueueLength());
- }
-
- @Test
- public void testStartPreprocessorAsync() {
- assertNull(oper.startPreprocessorAsync(params));
- }
-
- @Test
- public void testStartOperationAsync() {
- oper.startOperation(params);
- assertTrue(executor.runAll());
-
- assertEquals(1, oper.getCount());
- }
-
- @Test
- public void testIsSuccess() {
- OperationOutcome outcome = new OperationOutcome();
-
- outcome.setResult(PolicyResult.SUCCESS);
- assertTrue(oper.isSuccess(outcome));
-
- for (PolicyResult failure : FAILURE_RESULTS) {
- outcome.setResult(failure);
- assertFalse("testIsSuccess-" + failure, oper.isSuccess(outcome));
- }
- }
-
- @Test
- public void testIsActorFailed() {
- assertFalse(oper.isActorFailed(null));
-
- OperationOutcome outcome = params.makeOutcome();
-
- // incorrect outcome
- outcome.setResult(PolicyResult.SUCCESS);
- assertFalse(oper.isActorFailed(outcome));
-
- outcome.setResult(PolicyResult.FAILURE_RETRIES);
- assertFalse(oper.isActorFailed(outcome));
-
- // correct outcome
- outcome.setResult(PolicyResult.FAILURE);
-
- // incorrect actor
- outcome.setActor(TARGET);
- assertFalse(oper.isActorFailed(outcome));
- outcome.setActor(null);
- assertFalse(oper.isActorFailed(outcome));
- outcome.setActor(ACTOR);
-
- // incorrect operation
- outcome.setOperation(TARGET);
- assertFalse(oper.isActorFailed(outcome));
- outcome.setOperation(null);
- assertFalse(oper.isActorFailed(outcome));
- outcome.setOperation(OPERATOR);
-
- // correct values
- assertTrue(oper.isActorFailed(outcome));
- }
-
- @Test
- public void testDoOperation() {
- /*
- * Use an operator that doesn't override doOperation().
- */
- OperatorPartial oper2 = new OperatorPartial(ACTOR, OPERATOR) {
- @Override
- protected Executor getBlockingExecutor() {
- return executor;
- }
- };
-
- oper2.configure(new TreeMap<>());
- oper2.start();
-
- oper2.startOperation(params);
- assertTrue(executor.runAll());
-
- assertNotNull(opend);
- assertEquals(PolicyResult.FAILURE_EXCEPTION, opend.getResult());
- }
-
- @Test
- public void testTimeout() throws Exception {
-
- // use a real executor
- params = params.toBuilder().executor(ForkJoinPool.commonPool()).build();
-
- // trigger timeout very quickly
- oper = new MyOper() {
- @Override
- protected long getTimeOutMillis(Integer timeoutSec) {
- return 1;
- }
-
- @Override
- protected CompletableFuture<OperationOutcome> startOperationAsync(ControlLoopOperationParams params,
- int attempt, OperationOutcome outcome) {
-
- OperationOutcome outcome2 = params.makeOutcome();
- outcome2.setResult(PolicyResult.SUCCESS);
-
- /*
- * Create an incomplete future that will timeout after the operation's
- * timeout. If it fires before the other timer, then it will return a
- * SUCCESS outcome.
- */
- CompletableFuture<OperationOutcome> future = new CompletableFuture<>();
- future = future.orTimeout(1, TimeUnit.SECONDS).handleAsync((unused1, unused2) -> outcome,
- params.getExecutor());
-
- return future;
- }
- };
-
- oper.configure(new TreeMap<>());
- oper.start();
-
- assertEquals(PolicyResult.FAILURE_TIMEOUT, oper.startOperation(params).get().getResult());
- }
-
- /**
- * Verifies that the timer doesn't encompass the preprocessor and doesn't stop the
- * operation once the preprocessor completes.
- */
- @Test
- public void testTimeoutInPreprocessor() throws Exception {
-
- // use a real executor
- params = params.toBuilder().executor(ForkJoinPool.commonPool()).build();
-
- // trigger timeout very quickly
- oper = new MyOper() {
+ operator = new OperatorPartial(ACTOR, OPERATION) {
@Override
- protected long getTimeOutMillis(Integer timeoutSec) {
- return 10;
- }
-
- @Override
- protected Executor getBlockingExecutor() {
- return command -> {
- Thread thread = new Thread(command);
- thread.start();
- };
- }
-
- @Override
- protected CompletableFuture<OperationOutcome> startPreprocessorAsync(ControlLoopOperationParams params) {
-
- OperationOutcome outcome = makeSuccess();
-
- /*
- * Create an incomplete future that will timeout after the operation's
- * timeout. If it fires before the other timer, then it will return a
- * SUCCESS outcome.
- */
- CompletableFuture<OperationOutcome> future = new CompletableFuture<>();
- future = future.orTimeout(200, TimeUnit.MILLISECONDS).handleAsync((unused1, unused2) -> outcome,
- params.getExecutor());
-
- return future;
- }
- };
-
- oper.configure(new TreeMap<>());
- oper.start();
-
- OperationOutcome result = oper.startOperation(params).get();
- assertEquals(PolicyResult.SUCCESS, result.getResult());
-
- assertNotNull(opstart);
- assertNotNull(opend);
- assertEquals(PolicyResult.SUCCESS, opend.getResult());
-
- assertEquals(1, numStart);
- assertEquals(1, oper.getCount());
- assertEquals(1, numEnd);
- }
-
- /**
- * Tests retry functions, when the count is set to zero and retries are exhausted.
- */
- @Test
- public void testSetRetryFlag_testRetryOnFailure_ZeroRetries_testStartOperationAttempt() {
- params = params.toBuilder().retry(0).build();
- oper.setMaxFailures(10);
-
- verifyRun("testSetRetryFlag_testRetryOnFailure_ZeroRetries", 1, 1, PolicyResult.FAILURE);
- }
-
- /**
- * Tests retry functions, when the count is null and retries are exhausted.
- */
- @Test
- public void testSetRetryFlag_testRetryOnFailure_NullRetries() {
- params = params.toBuilder().retry(null).build();
- oper.setMaxFailures(10);
-
- verifyRun("testSetRetryFlag_testRetryOnFailure_NullRetries", 1, 1, PolicyResult.FAILURE);
- }
-
- /**
- * Tests retry functions, when retries are exhausted.
- */
- @Test
- public void testSetRetryFlag_testRetryOnFailure_RetriesExhausted() {
- final int maxRetries = 3;
- params = params.toBuilder().retry(maxRetries).build();
- oper.setMaxFailures(10);
-
- verifyRun("testSetRetryFlag_testRetryOnFailure_RetriesExhausted", maxRetries + 1, maxRetries + 1,
- PolicyResult.FAILURE_RETRIES);
- }
-
- /**
- * Tests retry functions, when a success follows some retries.
- */
- @Test
- public void testSetRetryFlag_testRetryOnFailure_SuccessAfterRetries() {
- params = params.toBuilder().retry(10).build();
-
- final int maxFailures = 3;
- oper.setMaxFailures(maxFailures);
-
- verifyRun("testSetRetryFlag_testRetryOnFailure_SuccessAfterRetries", maxFailures + 1, maxFailures + 1,
- PolicyResult.SUCCESS);
- }
-
- /**
- * Tests retry functions, when the outcome is {@code null}.
- */
- @Test
- public void testSetRetryFlag_testRetryOnFailure_NullOutcome() {
-
- // arrange to return null from doOperation()
- oper = new MyOper() {
- @Override
- protected OperationOutcome doOperation(ControlLoopOperationParams params, int attempt,
- OperationOutcome operation) {
-
- // update counters
- super.doOperation(params, attempt, operation);
+ public Operation buildOperation(ControlLoopOperationParams params) {
return null;
}
};
-
- oper.configure(new TreeMap<>());
- oper.start();
-
- verifyRun("testSetRetryFlag_testRetryOnFailure_NullOutcome", 1, 1, PolicyResult.FAILURE, null, noop());
- }
-
- @Test
- public void testIsSameOperation() {
- assertFalse(oper.isSameOperation(null));
-
- OperationOutcome outcome = params.makeOutcome();
-
- // wrong actor - should be false
- outcome.setActor(null);
- assertFalse(oper.isSameOperation(outcome));
- outcome.setActor(TARGET);
- assertFalse(oper.isSameOperation(outcome));
- outcome.setActor(ACTOR);
-
- // wrong operation - should be null
- outcome.setOperation(null);
- assertFalse(oper.isSameOperation(outcome));
- outcome.setOperation(TARGET);
- assertFalse(oper.isSameOperation(outcome));
- outcome.setOperation(OPERATOR);
-
- assertTrue(oper.isSameOperation(outcome));
- }
-
- /**
- * Tests handleFailure() when the outcome is a success.
- */
- @Test
- public void testHandlePreprocessorFailureTrue() {
- oper.setPreProcessor(CompletableFuture.completedFuture(makeSuccess()));
- verifyRun("testHandlePreprocessorFailureTrue", 1, 1, PolicyResult.SUCCESS);
- }
-
- /**
- * Tests handleFailure() when the outcome is <i>not</i> a success.
- */
- @Test
- public void testHandlePreprocessorFailureFalse() throws Exception {
- oper.setPreProcessor(CompletableFuture.completedFuture(makeFailure()));
- verifyRun("testHandlePreprocessorFailureFalse", 1, 0, PolicyResult.FAILURE_GUARD);
- }
-
- /**
- * Tests handleFailure() when the outcome is {@code null}.
- */
- @Test
- public void testHandlePreprocessorFailureNull() throws Exception {
- // arrange to return null from the preprocessor
- oper.setPreProcessor(CompletableFuture.completedFuture(null));
-
- verifyRun("testHandlePreprocessorFailureNull", 1, 0, PolicyResult.FAILURE_GUARD);
- }
-
- @Test
- public void testFromException() {
- // arrange to generate an exception when operation runs
- oper.setGenException(true);
-
- verifyRun("testFromException", 1, 1, PolicyResult.FAILURE_EXCEPTION);
- }
-
- /**
- * Tests fromException() when there is no exception.
- */
- @Test
- public void testFromExceptionNoExcept() {
- verifyRun("testFromExceptionNoExcept", 1, 1, PolicyResult.SUCCESS);
- }
-
- /**
- * Tests both flavors of anyOf(), because one invokes the other.
- */
- @Test
- public void testAnyOf() throws Exception {
- // first task completes, others do not
- List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
-
- final OperationOutcome outcome = params.makeOutcome();
-
- tasks.add(CompletableFuture.completedFuture(outcome));
- tasks.add(new CompletableFuture<>());
- tasks.add(new CompletableFuture<>());
-
- CompletableFuture<OperationOutcome> result = oper.anyOf(params, tasks);
- assertTrue(executor.runAll());
-
- assertTrue(result.isDone());
- assertSame(outcome, result.get());
-
- // second task completes, others do not
- tasks = new LinkedList<>();
-
- tasks.add(new CompletableFuture<>());
- tasks.add(CompletableFuture.completedFuture(outcome));
- tasks.add(new CompletableFuture<>());
-
- result = oper.anyOf(params, tasks);
- assertTrue(executor.runAll());
-
- assertTrue(result.isDone());
- assertSame(outcome, result.get());
-
- // third task completes, others do not
- tasks = new LinkedList<>();
-
- tasks.add(new CompletableFuture<>());
- tasks.add(new CompletableFuture<>());
- tasks.add(CompletableFuture.completedFuture(outcome));
-
- result = oper.anyOf(params, tasks);
- assertTrue(executor.runAll());
-
- assertTrue(result.isDone());
- assertSame(outcome, result.get());
- }
-
- /**
- * Tests both flavors of allOf(), because one invokes the other.
- */
- @Test
- public void testAllOf() throws Exception {
- List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
-
- final OperationOutcome outcome = params.makeOutcome();
-
- CompletableFuture<OperationOutcome> future1 = new CompletableFuture<>();
- CompletableFuture<OperationOutcome> future2 = new CompletableFuture<>();
- CompletableFuture<OperationOutcome> future3 = new CompletableFuture<>();
-
- tasks.add(future1);
- tasks.add(future2);
- tasks.add(future3);
-
- CompletableFuture<OperationOutcome> result = oper.allOf(params, tasks);
-
- assertTrue(executor.runAll());
- assertFalse(result.isDone());
- future1.complete(outcome);
-
- // complete 3 before 2
- assertTrue(executor.runAll());
- assertFalse(result.isDone());
- future3.complete(outcome);
-
- assertTrue(executor.runAll());
- assertFalse(result.isDone());
- future2.complete(outcome);
-
- // all of them are now done
- assertTrue(executor.runAll());
- assertTrue(result.isDone());
- assertSame(outcome, result.get());
- }
-
- @Test
- public void testCombineOutcomes() throws Exception {
- // only one outcome
- verifyOutcomes(0, PolicyResult.SUCCESS);
- verifyOutcomes(0, PolicyResult.FAILURE_EXCEPTION);
-
- // maximum is in different positions
- verifyOutcomes(0, PolicyResult.FAILURE, PolicyResult.SUCCESS, PolicyResult.FAILURE_GUARD);
- verifyOutcomes(1, PolicyResult.SUCCESS, PolicyResult.FAILURE, PolicyResult.FAILURE_GUARD);
- verifyOutcomes(2, PolicyResult.SUCCESS, PolicyResult.FAILURE_GUARD, PolicyResult.FAILURE);
-
- // null outcome
- final List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
- tasks.add(CompletableFuture.completedFuture(null));
- CompletableFuture<OperationOutcome> result = oper.allOf(params, tasks);
-
- assertTrue(executor.runAll());
- assertTrue(result.isDone());
- assertNull(result.get());
-
- // one throws an exception during execution
- IllegalStateException except = new IllegalStateException(EXPECTED_EXCEPTION);
-
- tasks.clear();
- tasks.add(CompletableFuture.completedFuture(params.makeOutcome()));
- tasks.add(CompletableFuture.failedFuture(except));
- tasks.add(CompletableFuture.completedFuture(params.makeOutcome()));
- result = oper.allOf(params, tasks);
-
- assertTrue(executor.runAll());
- assertTrue(result.isCompletedExceptionally());
- result.whenComplete((unused, thrown) -> assertSame(except, thrown));
- }
-
- private void verifyOutcomes(int expected, PolicyResult... results) throws Exception {
- List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>();
-
-
- OperationOutcome expectedOutcome = null;
-
- for (int count = 0; count < results.length; ++count) {
- OperationOutcome outcome = params.makeOutcome();
- outcome.setResult(results[count]);
- tasks.add(CompletableFuture.completedFuture(outcome));
-
- if (count == expected) {
- expectedOutcome = outcome;
- }
- }
-
- CompletableFuture<OperationOutcome> result = oper.allOf(params, tasks);
-
- assertTrue(executor.runAll());
- assertTrue(result.isDone());
- assertSame(expectedOutcome, result.get());
- }
-
- private Function<OperationOutcome, CompletableFuture<OperationOutcome>> makeTask(
- final OperationOutcome taskOutcome) {
-
- return outcome -> CompletableFuture.completedFuture(taskOutcome);
- }
-
- @Test
- public void testDetmPriority() {
- assertEquals(1, oper.detmPriority(null));
-
- OperationOutcome outcome = params.makeOutcome();
-
- Map<PolicyResult, Integer> map = Map.of(PolicyResult.SUCCESS, 0, PolicyResult.FAILURE_GUARD, 2,
- PolicyResult.FAILURE_RETRIES, 3, PolicyResult.FAILURE, 4, PolicyResult.FAILURE_TIMEOUT, 5,
- PolicyResult.FAILURE_EXCEPTION, 6);
-
- for (Entry<PolicyResult, Integer> ent : map.entrySet()) {
- outcome.setResult(ent.getKey());
- assertEquals(ent.getKey().toString(), ent.getValue().intValue(), oper.detmPriority(outcome));
- }
- }
-
- /**
- * Tests doTask(Future) when the controller is not running.
- */
- @Test
- public void testDoTaskFutureNotRunning() throws Exception {
- CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>();
-
- PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
- controller.complete(params.makeOutcome());
-
- CompletableFuture<OperationOutcome> future =
- oper.doTask(params, controller, false, params.makeOutcome(), taskFuture);
- assertFalse(future.isDone());
- assertTrue(executor.runAll());
-
- // should not have run the task
- assertFalse(future.isDone());
-
- // should have canceled the task future
- assertTrue(taskFuture.isCancelled());
- }
-
- /**
- * Tests doTask(Future) when the previous outcome was successful.
- */
- @Test
- public void testDoTaskFutureSuccess() throws Exception {
- CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>();
- final OperationOutcome taskOutcome = params.makeOutcome();
-
- PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
- CompletableFuture<OperationOutcome> future =
- oper.doTask(params, controller, true, params.makeOutcome(), taskFuture);
-
- taskFuture.complete(taskOutcome);
- assertTrue(executor.runAll());
-
- assertTrue(future.isDone());
- assertSame(taskOutcome, future.get());
-
- // controller should not be done yet
- assertFalse(controller.isDone());
- }
-
- /**
- * Tests doTask(Future) when the previous outcome was failed.
- */
- @Test
- public void testDoTaskFutureFailure() throws Exception {
- CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>();
- final OperationOutcome failedOutcome = params.makeOutcome();
- failedOutcome.setResult(PolicyResult.FAILURE);
-
- PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
- CompletableFuture<OperationOutcome> future = oper.doTask(params, controller, true, failedOutcome, taskFuture);
- assertFalse(future.isDone());
- assertTrue(executor.runAll());
-
- // should not have run the task
- assertFalse(future.isDone());
-
- // should have canceled the task future
- assertTrue(taskFuture.isCancelled());
-
- // controller SHOULD be done now
- assertTrue(controller.isDone());
- assertSame(failedOutcome, controller.get());
- }
-
- /**
- * Tests doTask(Future) when the previous outcome was failed, but not checking
- * success.
- */
- @Test
- public void testDoTaskFutureUncheckedFailure() throws Exception {
- CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>();
- final OperationOutcome failedOutcome = params.makeOutcome();
- failedOutcome.setResult(PolicyResult.FAILURE);
-
- PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
- CompletableFuture<OperationOutcome> future = oper.doTask(params, controller, false, failedOutcome, taskFuture);
- assertFalse(future.isDone());
-
- // complete the task
- OperationOutcome taskOutcome = params.makeOutcome();
- taskFuture.complete(taskOutcome);
-
- assertTrue(executor.runAll());
-
- // should have run the task
- assertTrue(future.isDone());
-
- assertTrue(future.isDone());
- assertSame(taskOutcome, future.get());
-
- // controller should not be done yet
- assertFalse(controller.isDone());
- }
-
- /**
- * Tests doTask(Function) when the controller is not running.
- */
- @Test
- public void testDoTaskFunctionNotRunning() throws Exception {
- AtomicBoolean invoked = new AtomicBoolean();
-
- Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = outcome -> {
- invoked.set(true);
- return CompletableFuture.completedFuture(params.makeOutcome());
- };
-
- PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
- controller.complete(params.makeOutcome());
-
- CompletableFuture<OperationOutcome> future =
- oper.doTask(params, controller, false, task).apply(params.makeOutcome());
- assertFalse(future.isDone());
- assertTrue(executor.runAll());
-
- // should not have run the task
- assertFalse(future.isDone());
-
- // should not have even invoked the task
- assertFalse(invoked.get());
- }
-
- /**
- * Tests doTask(Function) when the previous outcome was successful.
- */
- @Test
- public void testDoTaskFunctionSuccess() throws Exception {
- final OperationOutcome taskOutcome = params.makeOutcome();
-
- final OperationOutcome failedOutcome = params.makeOutcome();
-
- Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = makeTask(taskOutcome);
-
- PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
- CompletableFuture<OperationOutcome> future = oper.doTask(params, controller, true, task).apply(failedOutcome);
-
- assertTrue(future.isDone());
- assertSame(taskOutcome, future.get());
-
- // controller should not be done yet
- assertFalse(controller.isDone());
- }
-
- /**
- * Tests doTask(Function) when the previous outcome was failed.
- */
- @Test
- public void testDoTaskFunctionFailure() throws Exception {
- final OperationOutcome failedOutcome = params.makeOutcome();
- failedOutcome.setResult(PolicyResult.FAILURE);
-
- AtomicBoolean invoked = new AtomicBoolean();
-
- Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = outcome -> {
- invoked.set(true);
- return CompletableFuture.completedFuture(params.makeOutcome());
- };
-
- PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
- CompletableFuture<OperationOutcome> future = oper.doTask(params, controller, true, task).apply(failedOutcome);
- assertFalse(future.isDone());
- assertTrue(executor.runAll());
-
- // should not have run the task
- assertFalse(future.isDone());
-
- // should not have even invoked the task
- assertFalse(invoked.get());
-
- // controller should have the failed task
- assertTrue(controller.isDone());
- assertSame(failedOutcome, controller.get());
- }
-
- /**
- * Tests doTask(Function) when the previous outcome was failed, but not checking
- * success.
- */
- @Test
- public void testDoTaskFunctionUncheckedFailure() throws Exception {
- final OperationOutcome taskOutcome = params.makeOutcome();
-
- final OperationOutcome failedOutcome = params.makeOutcome();
- failedOutcome.setResult(PolicyResult.FAILURE);
-
- Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = makeTask(taskOutcome);
-
- PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
-
- CompletableFuture<OperationOutcome> future = oper.doTask(params, controller, false, task).apply(failedOutcome);
-
- assertTrue(future.isDone());
- assertSame(taskOutcome, future.get());
-
- // controller should not be done yet
- assertFalse(controller.isDone());
}
- /**
- * Tests callbackStarted() when the pipeline has already been stopped.
- */
@Test
- public void testCallbackStartedNotRunning() {
- AtomicReference<Future<OperationOutcome>> future = new AtomicReference<>();
-
- /*
- * arrange to stop the controller when the start-callback is invoked, but capture
- * the outcome
- */
- params = params.toBuilder().startCallback(oper -> {
- starter(oper);
- future.get().cancel(false);
- }).build();
-
- future.set(oper.startOperation(params));
- assertTrue(executor.runAll());
-
- // should have only run once
- assertEquals(1, numStart);
+ public void testOperatorPartial_testGetActorName_testGetName() {
+ assertEquals(ACTOR, operator.getActorName());
+ assertEquals(OPERATION, operator.getName());
+ assertEquals(ACTOR + "." + OPERATION, operator.getFullName());
}
- /**
- * Tests callbackCompleted() when the pipeline has already been stopped.
- */
@Test
- public void testCallbackCompletedNotRunning() {
- AtomicReference<Future<OperationOutcome>> future = new AtomicReference<>();
-
- // arrange to stop the controller when the start-callback is invoked
- params = params.toBuilder().startCallback(oper -> {
- future.get().cancel(false);
- }).build();
+ public void testDoStart() {
+ operator.configure(null);
- future.set(oper.startOperation(params));
- assertTrue(executor.runAll());
+ operator = spy(operator);
+ operator.start();
- // should not have been set
- assertNull(opend);
- assertEquals(0, numEnd);
+ verify(operator).doStart();
}
@Test
- public void testSetOutcomeControlLoopOperationOutcomeThrowable() {
- final CompletionException timex = new CompletionException(new TimeoutException(EXPECTED_EXCEPTION));
-
- OperationOutcome outcome;
+ public void testDoStop() {
+ operator.configure(null);
+ operator.start();
- outcome = new OperationOutcome();
- oper.setOutcome(params, outcome, timex);
- assertEquals(ControlLoopOperation.FAILED_MSG, outcome.getMessage());
- assertEquals(PolicyResult.FAILURE_TIMEOUT, outcome.getResult());
+ operator = spy(operator);
+ operator.stop();
- outcome = new OperationOutcome();
- oper.setOutcome(params, outcome, new IllegalStateException(EXPECTED_EXCEPTION));
- assertEquals(ControlLoopOperation.FAILED_MSG, outcome.getMessage());
- assertEquals(PolicyResult.FAILURE_EXCEPTION, outcome.getResult());
+ verify(operator).doStop();
}
@Test
- public void testSetOutcomeControlLoopOperationOutcomePolicyResult() {
- OperationOutcome outcome;
+ public void testDoShutdown() {
+ operator.configure(null);
+ operator.start();
- outcome = new OperationOutcome();
- oper.setOutcome(params, outcome, PolicyResult.SUCCESS);
- assertEquals(ControlLoopOperation.SUCCESS_MSG, outcome.getMessage());
- assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+ operator = spy(operator);
+ operator.shutdown();
- for (PolicyResult result : FAILURE_RESULTS) {
- outcome = new OperationOutcome();
- oper.setOutcome(params, outcome, result);
- assertEquals(result.toString(), ControlLoopOperation.FAILED_MSG, outcome.getMessage());
- assertEquals(result.toString(), result, outcome.getResult());
- }
+ verify(operator).doShutdown();
}
@Test
- public void testIsTimeout() {
- final TimeoutException timex = new TimeoutException(EXPECTED_EXCEPTION);
+ public void testDoConfigureMapOfStringObject() {
+ operator = spy(operator);
- assertFalse(oper.isTimeout(new IllegalStateException(EXPECTED_EXCEPTION)));
- assertFalse(oper.isTimeout(new IllegalStateException(timex)));
- assertFalse(oper.isTimeout(new CompletionException(new IllegalStateException(timex))));
- assertFalse(oper.isTimeout(new CompletionException(null)));
- assertFalse(oper.isTimeout(new CompletionException(new CompletionException(timex))));
+ Map<String, Object> params = new TreeMap<>();
+ operator.configure(params);
- assertTrue(oper.isTimeout(timex));
- assertTrue(oper.isTimeout(new CompletionException(timex)));
+ verify(operator).doConfigure(params);
}
@Test
- public void testGetTimeOutMillis() {
- assertEquals(TIMEOUT * 1000, oper.getTimeOutMillis(params.getTimeoutSec()));
-
- params = params.toBuilder().timeoutSec(null).build();
- assertEquals(0, oper.getTimeOutMillis(params.getTimeoutSec()));
- }
-
- private void starter(OperationOutcome oper) {
- ++numStart;
- tstart = oper.getStart();
- opstart = oper;
- }
-
- private void completer(OperationOutcome oper) {
- ++numEnd;
- opend = oper;
- }
-
- /**
- * Gets a function that does nothing.
- *
- * @param <T> type of input parameter expected by the function
- * @return a function that does nothing
- */
- private <T> Consumer<T> noop() {
- return unused -> {
- };
- }
-
- private OperationOutcome makeSuccess() {
- OperationOutcome outcome = params.makeOutcome();
- outcome.setResult(PolicyResult.SUCCESS);
-
- return outcome;
- }
-
- private OperationOutcome makeFailure() {
- OperationOutcome outcome = params.makeOutcome();
- outcome.setResult(PolicyResult.FAILURE);
-
- return outcome;
- }
-
- /**
- * Verifies a run.
- *
- * @param testName test name
- * @param expectedCallbacks number of callbacks expected
- * @param expectedOperations number of operation invocations expected
- * @param expectedResult expected outcome
- */
- private void verifyRun(String testName, int expectedCallbacks, int expectedOperations,
- PolicyResult expectedResult) {
-
- String expectedSubRequestId =
- (expectedResult == PolicyResult.FAILURE_EXCEPTION ? null : String.valueOf(expectedOperations));
-
- verifyRun(testName, expectedCallbacks, expectedOperations, expectedResult, expectedSubRequestId, noop());
- }
-
- /**
- * Verifies a run.
- *
- * @param testName test name
- * @param expectedCallbacks number of callbacks expected
- * @param expectedOperations number of operation invocations expected
- * @param expectedResult expected outcome
- * @param expectedSubRequestId expected sub request ID
- * @param manipulator function to modify the future returned by
- * {@link OperatorPartial#startOperation(ControlLoopOperationParams)} before
- * the tasks in the executor are run
- */
- private void verifyRun(String testName, int expectedCallbacks, int expectedOperations, PolicyResult expectedResult,
- String expectedSubRequestId, Consumer<CompletableFuture<OperationOutcome>> manipulator) {
-
- CompletableFuture<OperationOutcome> future = oper.startOperation(params);
-
- manipulator.accept(future);
-
- assertTrue(testName, executor.runAll());
-
- assertEquals(testName, expectedCallbacks, numStart);
- assertEquals(testName, expectedCallbacks, numEnd);
-
- if (expectedCallbacks > 0) {
- assertNotNull(testName, opstart);
- assertNotNull(testName, opend);
- assertEquals(testName, expectedResult, opend.getResult());
-
- assertSame(testName, tstart, opstart.getStart());
- assertSame(testName, tstart, opend.getStart());
-
- try {
- assertTrue(future.isDone());
- assertSame(testName, opend, future.get());
-
- } catch (InterruptedException | ExecutionException e) {
- throw new IllegalStateException(e);
- }
-
- if (expectedOperations > 0) {
- assertEquals(testName, expectedSubRequestId, opend.getSubRequestId());
- }
- }
-
- assertEquals(testName, expectedOperations, oper.getCount());
- }
-
- private class MyOper extends OperatorPartial {
- @Getter
- private int count = 0;
-
- @Setter
- private boolean genException;
-
- @Setter
- private int maxFailures = 0;
-
- @Setter
- private CompletableFuture<OperationOutcome> preProcessor;
-
- public MyOper() {
- super(ACTOR, OPERATOR);
- }
-
- @Override
- protected OperationOutcome doOperation(ControlLoopOperationParams params, int attempt,
- OperationOutcome operation) {
- ++count;
- if (genException) {
- throw new IllegalStateException(EXPECTED_EXCEPTION);
- }
-
- operation.setSubRequestId(String.valueOf(attempt));
-
- if (count > maxFailures) {
- operation.setResult(PolicyResult.SUCCESS);
- } else {
- operation.setResult(PolicyResult.FAILURE);
- }
-
- return operation;
- }
-
- @Override
- protected CompletableFuture<OperationOutcome> startPreprocessorAsync(ControlLoopOperationParams params) {
- return (preProcessor != null ? preProcessor : super.startPreprocessorAsync(params));
- }
-
- @Override
- protected Executor getBlockingExecutor() {
- return executor;
- }
- }
-
- /**
- * Executor that will run tasks until the queue is empty or a maximum number of tasks
- * have been executed.
- */
- private static class MyExec implements Executor {
- private static final int MAX_TASKS = MAX_PARALLEL_REQUESTS * 100;
-
- private Queue<Runnable> commands = new LinkedList<>();
-
- public MyExec() {
- // do nothing
- }
-
- public int getQueueLength() {
- return commands.size();
- }
-
- @Override
- public void execute(Runnable command) {
- commands.add(command);
- }
-
- public boolean runAll() {
- for (int count = 0; count < MAX_TASKS && !commands.isEmpty(); ++count) {
- commands.remove().run();
- }
-
- return commands.isEmpty();
- }
+ public void testGetBlockingExecutor() {
+ assertNotNull(operator.getBlockingExecutor());
}
}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicActorParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicActorParamsTest.java
new file mode 100644
index 000000000..1f38ad371
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicActorParamsTest.java
@@ -0,0 +1,118 @@
+/*-
+ * ============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.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.function.Consumer;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+
+public class BidirectionalTopicActorParamsTest {
+ private static final String CONTAINER = "my-container";
+
+ private static final String DFLT_SOURCE = "default-source";
+ private static final String DFLT_SINK = "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_SINK = "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 Map<String, Map<String, Object>> operMap;
+ private BidirectionalTopicActorParams params;
+
+
+ /**
+ * Sets up.
+ */
+ @Before
+ public void setUp() {
+ BidirectionalTopicParams oper1 = BidirectionalTopicParams.builder().sourceTopic(OPER1_SOURCE)
+ .sinkTopic(OPER1_SINK).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 = makeBidirectionalTopicActorParams();
+ }
+
+ @Test
+ public void testValidate() {
+ assertTrue(params.validate(CONTAINER).isValid());
+
+ // only a few fields are required
+ BidirectionalTopicActorParams sparse = Util.translate(CONTAINER, Map.of("operation", operMap, "timeoutSec", 1),
+ BidirectionalTopicActorParams.class);
+ assertTrue(sparse.validate(CONTAINER).isValid());
+
+ testValidateField("operation", "null", params2 -> params2.setOperation(null));
+ testValidateField("timeoutSec", "minimum", params2 -> params2.setTimeoutSec(-1));
+
+ // check edge cases
+ params.setTimeoutSec(0);
+ assertFalse(params.validate(CONTAINER).isValid());
+
+ params.setTimeoutSec(1);
+ assertTrue(params.validate(CONTAINER).isValid());
+ }
+
+ private void testValidateField(String fieldName, String expected,
+ Consumer<BidirectionalTopicActorParams> makeInvalid) {
+
+ // original params should be valid
+ ValidationResult result = params.validate(CONTAINER);
+ assertTrue(fieldName, result.isValid());
+
+ // make invalid params
+ BidirectionalTopicActorParams params2 = makeBidirectionalTopicActorParams();
+ makeInvalid.accept(params2);
+ result = params2.validate(CONTAINER);
+ assertFalse(fieldName, result.isValid());
+ assertThat(result.getResult()).contains(CONTAINER).contains(fieldName).contains(expected);
+ }
+
+ private BidirectionalTopicActorParams makeBidirectionalTopicActorParams() {
+ BidirectionalTopicActorParams params2 = new BidirectionalTopicActorParams();
+ params2.setSinkTopic(DFLT_SINK);
+ params2.setSourceTopic(DFLT_SOURCE);
+ params2.setTimeoutSec(DFLT_TIMEOUT);
+ params2.setOperation(operMap);
+
+ return params2;
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicParamsTest.java
index 4834c98d2..7e44fa2e1 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParamsTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/BidirectionalTopicParamsTest.java
@@ -29,44 +29,46 @@ 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.BidirectionalTopicParams.BidirectionalTopicParamsBuilder;
-public class TopicParamsTest {
+public class BidirectionalTopicParamsTest {
private static final String CONTAINER = "my-container";
- private static final String TARGET = "my-target";
+ private static final String SINK = "my-sink";
private static final String SOURCE = "my-source";
- private static final long TIMEOUT = 10;
+ private static final int TIMEOUT = 10;
- private TopicParams params;
+ private BidirectionalTopicParams params;
@Before
public void setUp() {
- params = TopicParams.builder().target(TARGET).source(SOURCE).timeoutSec(TIMEOUT).build();
+ params = BidirectionalTopicParams.builder().sinkTopic(SINK).sourceTopic(SOURCE).timeoutSec(TIMEOUT).build();
}
@Test
public void testValidate() {
- testValidateField("target", "null", bldr -> bldr.target(null));
- testValidateField("source", "null", bldr -> bldr.source(null));
+ assertTrue(params.validate(CONTAINER).isValid());
+
+ testValidateField("sink", "null", bldr -> bldr.sinkTopic(null));
+ testValidateField("source", "null", bldr -> bldr.sourceTopic(null));
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());
}
@Test
public void testBuilder_testToBuilder() {
- assertEquals(TARGET, params.getTarget());
- assertEquals(SOURCE, params.getSource());
+ assertEquals(SINK, params.getSinkTopic());
+ assertEquals(SOURCE, params.getSourceTopic());
assertEquals(TIMEOUT, params.getTimeoutSec());
assertEquals(params, params.toBuilder().build());
}
private void testValidateField(String fieldName, String expected,
- Function<TopicParamsBuilder, TopicParamsBuilder> makeInvalid) {
+ Function<BidirectionalTopicParamsBuilder, BidirectionalTopicParamsBuilder> 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/parameters/CommonActorParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/CommonActorParamsTest.java
new file mode 100644
index 000000000..901420346
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/CommonActorParamsTest.java
@@ -0,0 +1,137 @@
+/*-
+ * ============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.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import lombok.Setter;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+
+public class CommonActorParamsTest {
+
+ private static final String CONTAINER = "my-container";
+
+ private static final String PATH1 = "path #1";
+ private static final String PATH2 = "path #2";
+ private static final String URI1 = "uri #1";
+ private static final String URI2 = "uri #2";
+ private static final String TEXT1 = "hello";
+ private static final String TEXT2 = "world";
+ private static final String TEXT2B = "bye";
+
+ private Map<String, Map<String, Object>> operations;
+ private CommonActorParams params;
+
+ /**
+ * Initializes {@link #operations} with two items and {@link params} with a fully
+ * populated object.
+ */
+ @Before
+ public void setUp() {
+ operations = new TreeMap<>();
+ operations.put(PATH1, Map.of("path", URI1));
+ operations.put(PATH2, Map.of("path", URI2, "text2", TEXT2B));
+
+ params = makeCommonActorParams();
+ }
+
+ @Test
+ public void testMakeOperationParameters() {
+ Function<String, Map<String, Object>> maker = params.makeOperationParameters(CONTAINER);
+ assertNull(maker.apply("unknown-operation"));
+
+ Map<String, Object> subparam = maker.apply(PATH1);
+ assertNotNull(subparam);
+ assertEquals("{path=uri #1, text1=hello, text2=world}", new TreeMap<>(subparam).toString());
+
+ subparam = maker.apply(PATH2);
+ assertNotNull(subparam);
+ assertEquals("{path=uri #2, text1=hello, text2=bye}", new TreeMap<>(subparam).toString());
+ }
+
+ @Test
+ public void testDoValidation() {
+ assertThatCode(() -> params.doValidation(CONTAINER)).doesNotThrowAnyException();
+
+ // invalid param
+ params.setOperation(null);
+ assertThatThrownBy(() -> params.doValidation(CONTAINER))
+ .isInstanceOf(ParameterValidationRuntimeException.class);
+ }
+
+ @Test
+ public void testValidate() {
+ assertTrue(params.validate(CONTAINER).isValid());
+
+ // only a few fields are required
+ CommonActorParams sparse = Util.translate(CONTAINER, Map.of("operation", operations, "timeoutSec", 1),
+ CommonActorParams.class);
+ assertTrue(sparse.validate(CONTAINER).isValid());
+
+ testValidateField("operation", "null", params2 -> params2.setOperation(null));
+ }
+
+ private void testValidateField(String fieldName, String expected, Consumer<CommonActorParams> makeInvalid) {
+
+ // original params should be valid
+ ValidationResult result = params.validate(CONTAINER);
+ assertTrue(fieldName, result.isValid());
+
+ // make invalid params
+ CommonActorParams params2 = makeCommonActorParams();
+ makeInvalid.accept(params2);
+ result = params2.validate(CONTAINER);
+ assertFalse(fieldName, result.isValid());
+ assertThat(result.getResult()).contains(CONTAINER).contains(fieldName).contains(expected);
+ }
+
+ private CommonActorParams makeCommonActorParams() {
+ MyParams params2 = new MyParams();
+ params2.setOperation(operations);
+ params2.setText1(TEXT1);
+ params2.setText2(TEXT2);
+
+ return params2;
+ }
+
+ @Setter
+ public static class MyParams extends CommonActorParams {
+ @SuppressWarnings("unused")
+ private String text1;
+
+ @SuppressWarnings("unused")
+ private String text2;
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParamsTest.java
index 9dd19d548..a5215a48f 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParamsTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParamsTest.java
@@ -51,6 +51,7 @@ import org.mockito.MockitoAnnotations;
import org.onap.policy.common.parameters.BeanValidationResult;
import org.onap.policy.controlloop.VirtualControlLoopEvent;
import org.onap.policy.controlloop.actorserviceprovider.ActorService;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
import org.onap.policy.controlloop.actorserviceprovider.Operator;
import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
@@ -87,12 +88,15 @@ public class ControlLoopOperationParamsTest {
private Executor executor;
@Mock
- private CompletableFuture<OperationOutcome> operation;
+ private CompletableFuture<OperationOutcome> operFuture;
@Mock
private Operator operator;
@Mock
+ private Operation operation;
+
+ @Mock
private Consumer<OperationOutcome> starter;
private Map<String, String> payload;
@@ -110,7 +114,8 @@ public class ControlLoopOperationParamsTest {
when(actorService.getActor(ACTOR)).thenReturn(actor);
when(actor.getOperator(OPERATION)).thenReturn(operator);
- when(operator.startOperation(any())).thenReturn(operation);
+ when(operator.buildOperation(any())).thenReturn(operation);
+ when(operation.start()).thenReturn(operFuture);
when(event.getRequestId()).thenReturn(REQ_ID);
@@ -128,7 +133,7 @@ public class ControlLoopOperationParamsTest {
@Test
public void testStart() {
- assertSame(operation, params.start());
+ assertSame(operFuture, params.start());
assertThatIllegalArgumentException().isThrownBy(() -> params.toBuilder().context(null).build().start());
}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParamsTest.java
index 6c1f538ec..9e708535f 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParamsTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParamsTest.java
@@ -21,90 +21,62 @@
package org.onap.policy.controlloop.actorserviceprovider.parameters;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatCode;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Consumer;
-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.Util;
public class HttpActorParamsTest {
private static final String CONTAINER = "my-container";
private static final String CLIENT = "my-client";
- private static final long TIMEOUT = 10;
+ private static final int TIMEOUT = 10;
private static final String PATH1 = "path #1";
private static final String PATH2 = "path #2";
private static final String URI1 = "uri #1";
private static final String URI2 = "uri #2";
- private Map<String, String> paths;
+ private Map<String, Map<String, Object>> operations;
private HttpActorParams params;
/**
- * Initializes {@link #paths} with two items and {@link params} with a fully populated
- * object.
+ * Initializes {@link #operations} with two items and {@link params} with a fully
+ * populated object.
*/
@Before
public void setUp() {
- paths = new TreeMap<>();
- paths.put(PATH1, URI1);
- paths.put(PATH2, URI2);
+ operations = new TreeMap<>();
+ operations.put(PATH1, Map.of("path", URI1));
+ operations.put(PATH2, Map.of("path", URI2));
params = makeHttpActorParams();
}
@Test
- public void testMakeOperationParameters() {
- Function<String, Map<String, Object>> maker = params.makeOperationParameters(CONTAINER);
- assertNull(maker.apply("unknown-operation"));
-
- Map<String, Object> subparam = maker.apply(PATH1);
- assertNotNull(subparam);
- assertEquals("{clientName=my-client, path=uri #1, timeoutSec=10}", new TreeMap<>(subparam).toString());
-
- subparam = maker.apply(PATH2);
- assertNotNull(subparam);
- assertEquals("{clientName=my-client, path=uri #2, timeoutSec=10}", new TreeMap<>(subparam).toString());
- }
-
- @Test
- public void testDoValidation() {
- assertThatCode(() -> params.doValidation(CONTAINER)).doesNotThrowAnyException();
-
- // invalid param
- params.setClientName(null);
- assertThatThrownBy(() -> params.doValidation(CONTAINER))
- .isInstanceOf(ParameterValidationRuntimeException.class);
- }
-
- @Test
public void testValidate() {
assertTrue(params.validate(CONTAINER).isValid());
- testValidateField("clientName", "null", params2 -> params2.setClientName(null));
- testValidateField("path", "null", params2 -> params2.setPath(null));
+ // only a few fields are required
+ HttpActorParams sparse = Util.translate(CONTAINER, Map.of("operation", operations, "timeoutSec", 1),
+ HttpActorParams.class);
+ assertTrue(sparse.validate(CONTAINER).isValid());
+
+ testValidateField("operation", "null", params2 -> params2.setOperation(null));
testValidateField("timeoutSec", "minimum", params2 -> params2.setTimeoutSec(-1));
// check edge cases
params.setTimeoutSec(0);
- assertTrue(params.validate(CONTAINER).isValid());
+ assertFalse(params.validate(CONTAINER).isValid());
params.setTimeoutSec(1);
assertTrue(params.validate(CONTAINER).isValid());
-
- // one path value is null
- testValidateField(PATH2, "null", params2 -> paths.put(PATH2, null));
}
private void testValidateField(String fieldName, String expected, Consumer<HttpActorParams> makeInvalid) {
@@ -125,7 +97,7 @@ public class HttpActorParamsTest {
HttpActorParams params2 = new HttpActorParams();
params2.setClientName(CLIENT);
params2.setTimeoutSec(TIMEOUT);
- params2.setPath(paths);
+ params2.setOperation(operations);
return params2;
}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParamsTest.java
index 6cf7328ca..fdfb4b495 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParamsTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParamsTest.java
@@ -36,7 +36,7 @@ public class HttpParamsTest {
private static final String CONTAINER = "my-container";
private static final String CLIENT = "my-client";
private static final String PATH = "my-path";
- private static final long TIMEOUT = 10;
+ private static final int TIMEOUT = 10;
private HttpParams params;
@@ -54,7 +54,7 @@ public class HttpParamsTest {
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());
}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/PipelineControllerFutureTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/PipelineControllerFutureTest.java
index a6b11ef65..4a00c065e 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/PipelineControllerFutureTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/PipelineControllerFutureTest.java
@@ -424,7 +424,7 @@ public class PipelineControllerFutureTest {
}
/**
- * Tests add(Function) when the controller is canceled after the future is added.
+ * Tests wrap(Function) when the controller is canceled after the future is added.
*/
@Test
public void testWrapFunctionCancel() throws Exception {
@@ -442,7 +442,7 @@ public class PipelineControllerFutureTest {
}
/**
- * Tests add(Function) when the controller is not running.
+ * Tests wrap(Function) when the controller is not running.
*/
@Test
public void testWrapFunctionNotRunning() {
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicHandlerTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicHandlerTest.java
new file mode 100644
index 000000000..54d56de53
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/BidirectionalTopicHandlerTest.java
@@ -0,0 +1,144 @@
+/*-
+ * ============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.assertThatThrownBy;
+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.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+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;
+import org.onap.policy.common.endpoints.event.comm.client.BidirectionalTopicClientException;
+
+public class BidirectionalTopicHandlerTest {
+ private static final String UNKNOWN = "unknown";
+ private static final String MY_SOURCE = "my-source";
+ private static final String MY_SINK = "my-sink";
+ private static final String KEY1 = "requestId";
+ private static final String KEY2 = "subRequestId";
+
+ @Mock
+ private TopicSink publisher;
+
+ @Mock
+ private TopicSource subscriber;
+
+ @Mock
+ private TopicEndpoint mgr;
+
+ private MyTopicHandler handler;
+
+
+ /**
+ * Sets up.
+ */
+ @Before
+ public void setUp() throws BidirectionalTopicClientException {
+ MockitoAnnotations.initMocks(this);
+
+ when(mgr.getTopicSinks(MY_SINK)).thenReturn(Arrays.asList(publisher));
+ when(mgr.getTopicSources(eq(Arrays.asList(MY_SOURCE)))).thenReturn(Arrays.asList(subscriber));
+
+ when(publisher.getTopicCommInfrastructure()).thenReturn(CommInfrastructure.NOOP);
+
+ handler = new MyTopicHandler(MY_SINK, MY_SOURCE);
+
+ handler.start();
+ }
+
+ @Test
+ public void testBidirectionalTopicHandler_testGetSource_testGetTarget() {
+ assertEquals(MY_SOURCE, handler.getSourceTopic());
+ assertEquals(MY_SINK, handler.getSinkTopic());
+
+ verify(mgr).getTopicSinks(anyString());
+ verify(mgr).getTopicSources(any());
+
+ // source not found
+ assertThatThrownBy(() -> new MyTopicHandler(MY_SINK, UNKNOWN))
+ .isInstanceOf(BidirectionalTopicClientException.class).hasMessageContaining("sources")
+ .hasMessageContaining(UNKNOWN);
+
+ // target not found
+ assertThatThrownBy(() -> new MyTopicHandler(UNKNOWN, MY_SOURCE))
+ .isInstanceOf(BidirectionalTopicClientException.class).hasMessageContaining("sinks")
+ .hasMessageContaining(UNKNOWN);
+ }
+
+ @Test
+ public void testShutdown() {
+ handler.shutdown();
+ verify(subscriber).unregister(any());
+ }
+
+ @Test
+ public void testStart() {
+ verify(subscriber).register(any());
+ }
+
+ @Test
+ public void testStop() {
+ handler.stop();
+ verify(subscriber).unregister(any());
+ }
+
+ @Test
+ public void testAddForwarder() {
+ // array form
+ Forwarder forwarder = handler.addForwarder(new SelectorKey(KEY1), new SelectorKey(KEY2));
+ assertNotNull(forwarder);
+
+ // repeat using list form
+ assertSame(forwarder, handler.addForwarder(Arrays.asList(new SelectorKey(KEY1), new SelectorKey(KEY2))));
+ }
+
+ @Test
+ public void testGetTopicEndpointManager() {
+ // setting "mgr" to null should cause it to use the superclass' method
+ mgr = null;
+ assertNotNull(handler.getTopicEndpointManager());
+ }
+
+
+ private class MyTopicHandler extends BidirectionalTopicHandler {
+ public MyTopicHandler(String sinkTopic, String sourceTopic) throws BidirectionalTopicClientException {
+ super(sinkTopic, sourceTopic);
+ }
+
+ @Override
+ protected TopicEndpoint getTopicEndpointManager() {
+ return (mgr != null ? mgr : super.getTopicEndpointManager());
+ }
+ }
+}
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
index 000000000..a01159bc2
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/ForwarderTest.java
@@ -0,0 +1,199 @@
+/*-
+ * ============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 java.util.function.BiConsumer;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+
+public class ForwarderTest {
+ 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 BiConsumer<String, StandardCoderObject> listener1;
+
+ @Mock
+ private BiConsumer<String, StandardCoderObject> listener1b;
+
+ @Mock
+ private BiConsumer<String, StandardCoderObject> listener2;
+
+ @Mock
+ private BiConsumer<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(TEXT, sco);
+
+ verify(listener1).accept(TEXT, sco);
+ verify(listener1b, never()).accept(any(), any());
+
+ // remove listener1
+ forwarder.unregister(Arrays.asList(VALUEA_REQID, VALUEA_SUBREQID), listener1);
+ forwarder.onMessage(TEXT, sco);
+
+ // route a message to listener2
+ sco = makeMessage(Map.of(KEY1, VALUEB_REQID, KEY2, Map.of(SUBKEY, VALUEB_SUBREQID)));
+ forwarder.onMessage(TEXT, sco);
+ verify(listener2).accept(TEXT, sco);
+
+ // no more messages to listener1 or 1b
+ verify(listener1).accept(any(), any());
+ verify(listener1b, never()).accept(any(), any());
+ }
+
+ @Test
+ public void testOnMessage() {
+ StandardCoderObject sco = makeMessage(Map.of(KEY1, VALUEA_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+ forwarder.onMessage(TEXT, sco);
+
+ verify(listener1).accept(TEXT, sco);
+ verify(listener1b).accept(TEXT, sco);
+
+ // repeat - counts should increment
+ forwarder.onMessage(TEXT, sco);
+
+ verify(listener1, times(2)).accept(TEXT, sco);
+ verify(listener1b, times(2)).accept(TEXT, sco);
+
+ // should not have been invoked
+ verify(listener2, never()).accept(any(), any());
+ verify(listener3, never()).accept(any(), any());
+
+ // try other listeners now
+ sco = makeMessage(Map.of(KEY1, VALUEB_REQID, KEY2, Map.of(SUBKEY, VALUEB_SUBREQID)));
+ forwarder.onMessage(TEXT, sco);
+ verify(listener2).accept(TEXT, sco);
+
+ sco = makeMessage(Map.of(KEY1, VALUEC_REQID, KEY2, Map.of(SUBKEY, VALUEC_SUBREQID)));
+ forwarder.onMessage(TEXT, sco);
+ verify(listener3).accept(TEXT, sco);
+
+ // message has no listeners
+ sco = makeMessage(Map.of(KEY1, "xyzzy", KEY2, Map.of(SUBKEY, VALUEB_SUBREQID)));
+ forwarder.onMessage(TEXT, sco);
+
+ // message doesn't have both keys
+ sco = makeMessage(Map.of(KEY1, VALUEA_REQID));
+ forwarder.onMessage(TEXT, sco);
+
+ // counts should not have incremented
+ verify(listener1, times(2)).accept(any(), any());
+ verify(listener1b, times(2)).accept(any(), any());
+ verify(listener2).accept(any(), any());
+ verify(listener3).accept(any(), any());
+
+ // listener throws an exception
+ doThrow(new IllegalStateException("expected exception")).when(listener1).accept(any(), any());
+ }
+
+ /*
+ * Tests onMessage() when listener1 throws an exception.
+ */
+ @Test
+ public void testOnMessageListenerException1() {
+ doThrow(new IllegalStateException("expected exception")).when(listener1).accept(any(), any());
+
+ StandardCoderObject sco = makeMessage(Map.of(KEY1, VALUEA_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+ forwarder.onMessage(TEXT, sco);
+
+ verify(listener1b).accept(TEXT, sco);
+ }
+
+ /*
+ * Tests onMessage() when listener1b throws an exception.
+ */
+ @Test
+ public void testOnMessageListenerException1b() {
+ doThrow(new IllegalStateException("expected exception")).when(listener1b).accept(any(), any());
+
+ StandardCoderObject sco = makeMessage(Map.of(KEY1, VALUEA_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+ forwarder.onMessage(TEXT, sco);
+
+ verify(listener1).accept(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
index 000000000..19df9c2d8
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/SelectorKeyTest.java
@@ -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
index 000000000..3012ff6af
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/topic/TopicListenerImplTest.java
@@ -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 java.util.function.BiConsumer;
+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.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 BiConsumer<String, StandardCoderObject> listener1;
+
+ @Mock
+ private BiConsumer<String, StandardCoderObject> listener1b;
+
+ @Mock
+ private BiConsumer<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(msg), any());
+ verify(listener2).accept(eq(msg), any());
+
+ // not to listener1b
+ verify(listener1b, never()).accept(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(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());
+ verify(listener1b).accept(any(), any());
+ verify(listener2).accept(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/resources/logback-test.xml b/models-interactions/model-actors/actorServiceProvider/src/test/resources/logback-test.xml
index c7fe46e47..7b5b9fc32 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/resources/logback-test.xml
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/resources/logback-test.xml
@@ -39,4 +39,11 @@
<logger name="org.onap.policy.controlloop.actorserviceprovider.Util" level="info" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
+
+ <!-- this is required for OperationPartialTest -->
+ <logger
+ name="org.onap.policy.controlloop.actorserviceprovider.impl.OperationPartial"
+ level="info" additivity="false">
+ <appender-ref ref="STDOUT" />
+ </logger>
</configuration>
diff --git a/models-interactions/model-actors/pom.xml b/models-interactions/model-actors/pom.xml
index 8765eb44b..30891142d 100644
--- a/models-interactions/model-actors/pom.xml
+++ b/models-interactions/model-actors/pom.xml
@@ -36,6 +36,8 @@
<modules>
<module>actorServiceProvider</module>
+ <module>actor.test</module>
+ <module>actor.aai</module>
<module>actor.appc</module>
<module>actor.vfc</module>
<module>actor.sdnc</module>