diff options
91 files changed, 4887 insertions, 1049 deletions
diff --git a/models-examples/src/main/resources/policies/vCPE.policy.operational.input.json b/models-examples/src/main/resources/policies/vCPE.policy.operational.legacy.input.json index 7d26e8e67..7d26e8e67 100644 --- a/models-examples/src/main/resources/policies/vCPE.policy.operational.input.json +++ b/models-examples/src/main/resources/policies/vCPE.policy.operational.legacy.input.json diff --git a/models-examples/src/main/resources/policies/vCPE.policy.operational.output.json b/models-examples/src/main/resources/policies/vCPE.policy.operational.legacy.output.json index 7d26e8e67..7d26e8e67 100644 --- a/models-examples/src/main/resources/policies/vCPE.policy.operational.output.json +++ b/models-examples/src/main/resources/policies/vCPE.policy.operational.legacy.output.json diff --git a/models-examples/src/main/resources/policies/vCPE.policy.operational.output.tosca.yaml b/models-examples/src/main/resources/policies/vCPE.policy.operational.output.tosca.yaml index e4a06947f..2c7981a29 100644 --- a/models-examples/src/main/resources/policies/vCPE.policy.operational.output.tosca.yaml +++ b/models-examples/src/main/resources/policies/vCPE.policy.operational.output.tosca.yaml @@ -23,7 +23,7 @@ topology_template: actor: APPC recipe: Restart target: - type: VM + targetType: VM retry: 3 timeout: 1200 success: final_success diff --git a/models-examples/src/main/resources/policies/vDNS.policy.operational.input.json b/models-examples/src/main/resources/policies/vDNS.policy.operational.legacy.input.json index 5ce44e0ba..5ce44e0ba 100644 --- a/models-examples/src/main/resources/policies/vDNS.policy.operational.input.json +++ b/models-examples/src/main/resources/policies/vDNS.policy.operational.legacy.input.json diff --git a/models-examples/src/main/resources/policies/vDNS.policy.operational.output.json b/models-examples/src/main/resources/policies/vDNS.policy.operational.legacy.output.json index 5ce44e0ba..5ce44e0ba 100644 --- a/models-examples/src/main/resources/policies/vDNS.policy.operational.output.json +++ b/models-examples/src/main/resources/policies/vDNS.policy.operational.legacy.output.json diff --git a/models-examples/src/main/resources/policies/vFirewall.policy.operational.input.tosca.json b/models-examples/src/main/resources/policies/vFirewall.policy.operational.input.tosca.json index 63c0d8b85..f6f15fe50 100644 --- a/models-examples/src/main/resources/policies/vFirewall.policy.operational.input.tosca.json +++ b/models-examples/src/main/resources/policies/vFirewall.policy.operational.input.tosca.json @@ -4,32 +4,32 @@ "policies": [ { "operational.modifyconfig": { - "type": "onap.policies.controlloop.Operational", - "version": "1.0.0", + "type": "onap.policies.controlloop.operational.common.Drools", + "type_version": "1.0.0", "metadata": { "policy-id": "operational.modifyconfig" }, "properties": { - "controlLoop": { - "version": "2.0.0", - "controlLoopName": "ControlLoop-vFirewall-d0a1dfc6-94f5-4fd4-a5b5-4630b438850a", - "trigger_policy": "unique-policy-id-1-modifyConfig", - "timeout": 1200, - "abatement": false - }, - "policies": [ + "id": "ControlLoop-vFirewall-d0a1dfc6-94f5-4fd4-a5b5-4630b438850a", + "timeout": 1200, + "abatement": false, + "trigger": "unique-policy-id-1-modifyConfig", + "operations": [ { "id": "unique-policy-id-1-modifyConfig", - "name": "modify packet gen config", - "description": null, - "actor": "APPC", - "recipe": "ModifyConfig", - "target": { - "resourceID": "Eace933104d443b496b8.nodes.heat.vpg", - "type": "VNF" + "description": "Modify the packet generator", + "operation": { + "actor": "APPC", + "operation": "ModifyConfig", + "target": { + "targetType": "VNF", + "entityId": { + "resourceID": "bbb3cefd-01c8-413c-9bdd-2b92f9ca3d38" + } + } }, - "retry": 0, "timeout": 300, + "retries": 0, "success": "final_success", "failure": "final_failure", "failure_timeout": "final_failure_timeout", @@ -37,7 +37,8 @@ "failure_exception": "final_failure_exception", "failure_guard": "final_failure_guard" } - ] + ], + "controllerName": "usecases" } } } diff --git a/models-examples/src/main/resources/policies/vFirewall.policy.operational.input.json b/models-examples/src/main/resources/policies/vFirewall.policy.operational.legacy.input.json index bb8b907f8..bb8b907f8 100644 --- a/models-examples/src/main/resources/policies/vFirewall.policy.operational.input.json +++ b/models-examples/src/main/resources/policies/vFirewall.policy.operational.legacy.input.json diff --git a/models-examples/src/main/resources/policies/vFirewall.policy.operational.output.json b/models-examples/src/main/resources/policies/vFirewall.policy.operational.legacy.output.json index bb8b907f8..bb8b907f8 100644 --- a/models-examples/src/main/resources/policies/vFirewall.policy.operational.output.json +++ b/models-examples/src/main/resources/policies/vFirewall.policy.operational.legacy.output.json diff --git a/models-examples/src/main/resources/policytypes/onap.policies.controlloop.Operational.yaml b/models-examples/src/main/resources/policytypes/onap.policies.controlloop.Operational.yaml index f21fd5afb..773e0c90b 100644 --- a/models-examples/src/main/resources/policytypes/onap.policies.controlloop.Operational.yaml +++ b/models-examples/src/main/resources/policytypes/onap.policies.controlloop.Operational.yaml @@ -3,4 +3,4 @@ policy_types: onap.policies.controlloop.Operational: derived_from: tosca.policies.Root version: 1.0.0 - description: Operational Policy for Control Loops
\ No newline at end of file + description: Operational Policy for Control Loops Supporting Legacy YAML Policy Definition.
\ No newline at end of file diff --git a/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.Common.yaml b/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.Common.yaml index 0dbe7e41a..4a918bef1 100644 --- a/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.Common.yaml +++ b/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.Common.yaml @@ -3,7 +3,9 @@ policy_types: onap.policies.controlloop.operational.Common: derived_from: tosca.policies.Root version: 1.0.0 - description: Operational Policy for Control Loop execution + description: | + Operational Policy for Control Loop execution. Originated in Frankfurt to support TOSCA Compliant + Policy Types. This does NOT support the legacy Policy YAML policy type. properties: id: type: string @@ -31,36 +33,7 @@ policy_types: entry_schema: type: onap.datatype.controlloop.Operation - onap.policies.controlloop.operational.common.Drools: - derived_from: onap.policies.controlloop.operational.Common - type_version: 1.0.0 - version: 1.0.0 - description: Operational policies for Drools PDP - properties: - controllerName: - type: string - description: Drools controller properties - required: false - data_types: - # TBD if this is needed - onap.datatype.controlloop.operation.Failure: - derived_from: tosca.datatypes.Root - description: Captures information of an operational failure performed for control loop - properties: - messages: - type: string - description: error message - required: true - category: - type: string - description: | - The category the error occurred in. Whether this is a general error from the actor, or the operation - timed out, retries were exhausted in trying to execute the operation, a guard policy prevented the - operation from occuring, or an exception in the system caused the failure. - constraints: - - valid_values: [error, timeout, retries, guard, exception] - onap.datatype.controlloop.Target: derived_from: tosca.datatypes.Root description: Definition for a entity in A&AI to perform a control loop operation on @@ -77,6 +50,8 @@ data_types: Map of values that identify the resource. If none are provided, it is assumed that the entity that generated the ONSET event will be the target. required: false + entry_schema: + type: string onap.datatype.controlloop.Actor: derived_from: tosca.datatypes.Root @@ -91,7 +66,7 @@ data_types: description: The operation the actor is performing. required: true target: - type: string + type: onap.datatype.controlloop.Target description: The resource the operation should be performed on. required: true metadata: diff --git a/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.common.Apex.yaml b/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.common.Apex.yaml index e1555e8ed..9c6c6120d 100644 --- a/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.common.Apex.yaml +++ b/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.common.Apex.yaml @@ -2,6 +2,7 @@ tosca_definitions_version: tosca_simple_yaml_1_0_0 policy_types: onap.policies.controlloop.operational.common.Apex: derived_from: onap.policies.controlloop.operational.Common + type_version: 1.0.0 version: 1.0.0 description: Operational policies for Apex PDP properties: diff --git a/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.common.Drools.yaml b/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.common.Drools.yaml new file mode 100644 index 000000000..2d793cc4b --- /dev/null +++ b/models-examples/src/main/resources/policytypes/onap.policies.controlloop.operational.common.Drools.yaml @@ -0,0 +1,13 @@ +tosca_definitions_version: tosca_simple_yaml_1_0_0 +policy_types: + onap.policies.controlloop.operational.common.Drools: + derived_from: onap.policies.controlloop.operational.Common + type_version: 1.0.0 + version: 1.0.0 + description: Operational policies for Drools PDP + properties: + controllerName: + type: string + description: Drools controller properties + required: false + diff --git a/models-examples/src/main/resources/policytypes/onap.policies.monitoring.cdap.tca.hi.lo.app.yaml b/models-examples/src/main/resources/policytypes/onap.policies.monitoring.cdap.tca.hi.lo.app.yaml index 2f5abdde3..5fa4308db 100644 --- a/models-examples/src/main/resources/policytypes/onap.policies.monitoring.cdap.tca.hi.lo.app.yaml +++ b/models-examples/src/main/resources/policytypes/onap.policies.monitoring.cdap.tca.hi.lo.app.yaml @@ -2,7 +2,8 @@ tosca_definitions_version: tosca_simple_yaml_1_0_0 policy_types: onap.policies.Monitoring: derived_from: tosca.policies.Root - description: a base policy type for all policies that governs monitoring provisioning + version: 1.0.0 + description: a base policy type for all policies that govern monitoring provisioning onap.policies.monitoring.cdap.tca.hi.lo.app: derived_from: onap.policies.Monitoring version: 1.0.0 diff --git a/models-examples/src/main/resources/policytypes/onap.policies.monitoring.dcaegen2.collectors.datafile.datafile-app-server.yaml b/models-examples/src/main/resources/policytypes/onap.policies.monitoring.dcaegen2.collectors.datafile.datafile-app-server.yaml index cf70e9bb2..8419b096f 100644 --- a/models-examples/src/main/resources/policytypes/onap.policies.monitoring.dcaegen2.collectors.datafile.datafile-app-server.yaml +++ b/models-examples/src/main/resources/policytypes/onap.policies.monitoring.dcaegen2.collectors.datafile.datafile-app-server.yaml @@ -2,10 +2,10 @@ tosca_definitions_version: tosca_simple_yaml_1_0_0 policy_types: onap.policies.Monitoring: derived_from: tosca.policies.Root - description: a base policy type for all policies that govern monitoring provision + description: a base policy type for all policies that govern monitoring provisioning version: 1.0.0 onap.policies.monitoring.dcaegen2.collectors.datafile.datafile-app-server: - derived_from: policy.nodes.Root + derived_from: onap.policies.Monitoring version: 1.0.0 properties: buscontroller_feed_publishing_endpoint: diff --git a/models-interactions/model-actors/actor.aai/pom.xml b/models-interactions/model-actors/actor.aai/pom.xml new file mode 100644 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..bc2dde9d8 --- /dev/null +++ b/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiCustomQueryOperation.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.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 void postProcessResponse(OperationOutcome outcome, String url, Response rawResponse, String response) { + + logger.info("{}: caching response for {}", getFullName(), params.getRequestId()); + params.getContext().setProperty(AaiCqResponse.CONTEXT_KEY, new AaiCqResponse(response)); + } +} diff --git a/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiGetOperation.java b/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiGetOperation.java new file mode 100644 index 000000000..60a28209b --- /dev/null +++ b/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiGetOperation.java @@ -0,0 +1,135 @@ +/*- + * ============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 void postProcessResponse(OperationOutcome outcome, String url, Response rawResponse, + StandardCoderObject response) { + String entity = params.getTargetEntity(); + + logger.info("{}: caching response of {} for {}", getFullName(), entity, params.getRequestId()); + + params.getContext().setProperty(propertyPrefix + entity, response); + } + + /** + * Provides a default retry value, if none specified. + */ + @Override + protected int getRetry(Integer retry) { + return (retry == null ? DEFAULT_RETRY : retry); + } +} diff --git a/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiUtil.java b/models-interactions/model-actors/actor.aai/src/main/java/org/onap/policy/controlloop/actor/aai/AaiUtil.java new file mode 100644 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..c95425e7a --- /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 BasicAaiOperator<Map<String, String>> { + private static final StandardCoder coder = new StandardCoder(); + + private static final String MY_LINK = "my-link"; + + @Mock + private Actor tenantActor; + + private AaiCustomQueryOperation oper; + + public AaiCustomQueryOperationTest() { + super(AaiConstants.ACTOR_NAME, AaiCustomQueryOperation.NAME); + } + + /** + * Sets up. + */ + @Before + public void setUp() throws Exception { + super.setUp(); + + MyTenantOperator tenantOperator = new MyTenantOperator(); + + when(service.getActor(AaiConstants.ACTOR_NAME)).thenReturn(tenantActor); + when(tenantActor.getOperator(AaiGetOperation.TENANT)).thenReturn(tenantOperator); + + oper = new AaiCustomQueryOperation(params, operator); + } + + @Test + public void testAaiCustomQueryOperation() { + assertEquals(AaiConstants.ACTOR_NAME, oper.getActorName()); + assertEquals(AaiCustomQueryOperation.NAME, oper.getName()); + } + + @Test + public void testStartOperationAsync_testStartPreprocessorAsync_testMakeRequest_testPostProcess() throws Exception { + // need two responses + when(rawResponse.readEntity(String.class)).thenReturn(makeTenantReply()).thenReturn(makeCqReply()); + when(client.get(any(), any(), any())).thenAnswer(provideResponse(rawResponse)); + when(client.put(any(), any(), any(), any())).thenAnswer(provideResponse(rawResponse)); + + CompletableFuture<OperationOutcome> future2 = oper.start(); + + assertEquals(PolicyResult.SUCCESS, 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/AaiGetOperatorTest.java b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiGetOperatorTest.java new file mode 100644 index 000000000..ebe953570 --- /dev/null +++ b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/AaiGetOperatorTest.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 AaiGetOperatorTest extends BasicAaiOperator<Void> { + + private static final String INPUT_FIELD = "input"; + private static final String TEXT = "my-text"; + + private AaiGetOperation oper; + + public AaiGetOperatorTest() { + super(AaiConstants.ACTOR_NAME, AaiGetOperation.TENANT); + } + + /** + * Sets up. + */ + @Before + public void setUp() throws Exception { + super.setUp(); + oper = new AaiGetOperation(params, operator); + } + + @Test + public void testGetRetry() { + // use default if null retry + assertEquals(AaiGetOperation.DEFAULT_RETRY, oper.getRetry(null)); + + // otherwise, use specified value + assertEquals(0, oper.getRetry(0)); + assertEquals(10, oper.getRetry(10)); + } + + @Test + public void testStartOperationAsync_testStartQueryAsync_testPostProcessResponse() throws Exception { + + // return a map in the reply + Map<String, String> reply = Map.of(INPUT_FIELD, TEXT); + when(rawResponse.readEntity(String.class)).thenReturn(new StandardCoder().encode(reply)); + + when(client.get(any(), any(), any())).thenAnswer(provideResponse(rawResponse)); + + CompletableFuture<OperationOutcome> future2 = oper.startOperationAsync(1, outcome); + assertFalse(future2.isDone()); + + 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..39ed6fe88 --- /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 BasicAaiOperator<Void> { + + @Test + public void testMakeHeaders() { + makeContext(); + + Map<String, Object> headers = AaiUtil.makeHeaders(params); + + verifyHeaders(headers); + } +} diff --git a/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/BasicAaiOperator.java b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/BasicAaiOperator.java new file mode 100644 index 000000000..50b562afb --- /dev/null +++ b/models-interactions/model-actors/actor.aai/src/test/java/org/onap/policy/controlloop/actor/aai/BasicAaiOperator.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 BasicAaiOperator<Q> extends BasicHttpOperation<Q> { + + /** + * Constructs the object using a default actor and operation name. + */ + public BasicAaiOperator() { + super(); + } + + /** + * Constructs the object. + * + * @param actor actor name + * @param operation operation name + */ + public BasicAaiOperator(String actor, String operation) { + super(actor, operation); + } + + protected void verifyHeaders(Map<String, Object> headers) { + assertEquals("POLICY", headers.get("X-FromAppId").toString()); + assertEquals(params.getRequestId().toString(), headers.get("X-TransactionId")); + } +} diff --git a/models-interactions/model-actors/actor.appc/pom.xml b/models-interactions/model-actors/actor.appc/pom.xml index 74bff9aa9..26eb7c1b7 100644 --- a/models-interactions/model-actors/actor.appc/pom.xml +++ b/models-interactions/model-actors/actor.appc/pom.xml @@ -18,57 +18,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.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> + </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/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 index 9d42c49d9..406722ef5 100644 --- 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 @@ -25,6 +25,8 @@ 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; @@ -47,6 +49,14 @@ public abstract class SdncOperation extends HttpOperation<SdncResponse> { super(params, operator, SdncResponse.class); } + /** + * Starts the GUARD. + */ + @Override + protected CompletableFuture<OperationOutcome> startPreprocessorAsync() { + return startGuardAsync(); + } + @Override protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) { @@ -59,7 +69,7 @@ public abstract class SdncOperation extends HttpOperation<SdncResponse> { headers.put("Accept", MediaType.APPLICATION_JSON); String url = makeUrl(); - logRestRequest(url, request); + logMessage(EventType.OUT, CommInfrastructure.REST, url, request); // @formatter:off return handleResponse(outcome, url, diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicSdncOperator.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicSdncOperator.java index d8c707cc3..deafc4e9d 100644 --- a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicSdncOperator.java +++ b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicSdncOperator.java @@ -23,6 +23,7 @@ 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; @@ -30,7 +31,6 @@ import static org.mockito.Mockito.when; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.BiFunction; import org.onap.policy.common.utils.coder.CoderException; @@ -100,7 +100,10 @@ public abstract class BasicSdncOperator extends BasicHttpOperation<SdncRequest> verify(client).post(callbackCaptor.capture(), any(), requestCaptor.capture(), any()); callbackCaptor.getValue().completed(rawResponse); - assertEquals(PolicyResult.SUCCESS, future2.get(5, TimeUnit.SECONDS).getResult()); + executor.runAll(100); + assertTrue(future2.isDone()); + + assertEquals(PolicyResult.SUCCESS, future2.get().getResult()); return requestCaptor.getValue().getEntity(); } diff --git a/models-interactions/model-actors/actor.test/pom.xml b/models-interactions/model-actors/actor.test/pom.xml index 6b0580748..3a10fa3d1 100644 --- a/models-interactions/model-actors/actor.test/pom.xml +++ b/models-interactions/model-actors/actor.test/pom.xml @@ -56,6 +56,11 @@ <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> 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 index 15e4848c6..e160479b3 100644 --- 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 @@ -33,8 +33,10 @@ 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; @@ -89,6 +91,7 @@ public class BasicHttpOperation<Q> { protected VirtualControlLoopEvent event; protected ControlLoopEventContext context; protected OperationOutcome outcome; + protected PseudoExecutor executor; /** * Constructs the object using a default actor and operation name. @@ -122,6 +125,8 @@ public class BasicHttpOperation<Q> { future = new CompletableFuture<>(); when(client.getBaseUrl()).thenReturn(BASE_URI); + executor = new PseudoExecutor(); + makeContext(); outcome = params.makeOutcome(); @@ -132,6 +137,8 @@ public class BasicHttpOperation<Q> { /** * 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()); @@ -142,8 +149,8 @@ public class BasicHttpOperation<Q> { context = new ControlLoopEventContext(event); - params = ControlLoopOperationParams.builder().context(context).actorService(service).actor(actorName) - .operation(operationName).targetEntity(TARGET_ENTITY).build(); + params = ControlLoopOperationParams.builder().executor(executor).context(context).actorService(service) + .actor(actorName).operation(operationName).targetEntity(TARGET_ENTITY).build(); } /** @@ -166,4 +173,18 @@ public class BasicHttpOperation<Q> { protected Map<String, String> makeEnrichment() { return new TreeMap<>(); } + + /** + * Provides a response to an asynchronous HttpClient call. + * + * @param response response to be provided to the call + * @return a function that provides the response to the call + */ + protected Answer<CompletableFuture<Response>> provideResponse(Response response) { + return args -> { + InvocationCallback<Response> cb = args.getArgument(0); + cb.completed(response); + return CompletableFuture.completedFuture(response); + }; + } } 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 index c33483d26..096b8b80d 100644 --- 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 @@ -24,7 +24,11 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import javax.ws.rs.client.InvocationCallback; +import javax.ws.rs.core.Response; import org.junit.Before; import org.junit.Test; @@ -55,7 +59,7 @@ public class BasicHttpOperationTest { } @Test - public void testSetUp() { + public void testSetUp() throws Exception { assertNotNull(oper.client); assertSame(oper.client, oper.factory.get(BasicHttpOperation.MY_CLIENT)); assertEquals(200, oper.rawResponse.getStatus()); @@ -63,6 +67,7 @@ public class BasicHttpOperationTest { assertEquals(BasicHttpOperation.BASE_URI, oper.client.getBaseUrl()); assertNotNull(oper.context); assertNotNull(oper.outcome); + assertNotNull(oper.executor); assertTrue(oper.operator.isAlive()); } @@ -79,6 +84,7 @@ public class BasicHttpOperationTest { 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()); @@ -101,4 +107,23 @@ public class BasicHttpOperationTest { assertTrue(oper.makeEnrichment().isEmpty()); } + @Test + public void testProvideResponse() throws Exception { + InvocationCallback<Response> cb = new InvocationCallback<>() { + @Override + public void completed(Response response) { + // do nothing + } + + @Override + public void failed(Throwable throwable) { + // do nothing + } + }; + + + when(oper.client.get(any(), any(), any())).thenAnswer(oper.provideResponse(oper.rawResponse)); + + assertSame(oper.rawResponse, oper.client.get(cb, null, null).get()); + } } 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/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..f82015d6b --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/BidirectionalTopicOperation.java @@ -0,0 +1,253 @@ +/*- + * ============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); + postProcessResponse(outcome, rawResponse, response); + return outcome; + + case FAILURE: + logger.info("{}.{} request failed for {}", params.getActor(), params.getOperation(), + params.getRequestId()); + return setOutcome(outcome, PolicyResult.FAILURE); + + case STILL_WAITING: + default: + logger.info("{}.{} request incomplete for {}", params.getActor(), params.getOperation(), + params.getRequestId()); + return null; + } + } + + /** + * 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 index c4bf5f484..f1829d79a 100644 --- 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 @@ -33,9 +33,7 @@ import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure; import org.onap.policy.common.endpoints.http.client.HttpClient; import org.onap.policy.common.endpoints.utils.NetLoggerUtil; import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType; -import org.onap.policy.common.utils.coder.Coder; import org.onap.policy.common.utils.coder.CoderException; -import org.onap.policy.common.utils.coder.StandardCoder; import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome; import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams; import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams; @@ -52,7 +50,6 @@ import org.slf4j.LoggerFactory; @Getter public abstract class HttpOperation<T> extends OperationPartial { private static final Logger logger = LoggerFactory.getLogger(HttpOperation.class); - private static final Coder coder = new StandardCoder(); /** * Operator that created this operation. @@ -171,7 +168,7 @@ public abstract class HttpOperation<T> extends OperationPartial { String strResponse = HttpClient.getBody(rawResponse, String.class); - logRestResponse(url, strResponse); + logMessage(EventType.IN, CommInfrastructure.REST, url, strResponse); T response; if (responseClass == String.class) { @@ -181,9 +178,9 @@ public abstract class HttpOperation<T> extends OperationPartial { try { response = makeCoder().decode(strResponse, responseClass); } catch (CoderException e) { - logger.warn("{}.{} cannot decode response with http error code {} for {}", params.getActor(), - params.getOperation(), rawResponse.getStatus(), params.getRequestId(), e); - return setOutcome(outcome, PolicyResult.FAILURE_EXCEPTION); + logger.warn("{}.{} cannot decode response for {}", params.getActor(), params.getOperation(), + params.getRequestId(), e); + throw new IllegalArgumentException("cannot decode response"); } } @@ -224,63 +221,10 @@ public abstract class HttpOperation<T> extends OperationPartial { return (rawResponse.getStatus() == 200); } - /** - * Logs a REST request. If the request is not of type, String, then it attempts to - * pretty-print it into JSON before logging. - * - * @param url request URL - * @param request request to be logged - */ - public <Q> void logRestRequest(String url, Q request) { - String json; - try { - if (request == null) { - json = null; - } else if (request instanceof String) { - json = request.toString(); - } else { - json = makeCoder().encode(request, true); - } - - } catch (CoderException e) { - logger.warn("cannot pretty-print request", e); - json = request.toString(); - } - - NetLoggerUtil.log(EventType.OUT, CommInfrastructure.REST, url, json); - logger.info("[OUT|{}|{}|]{}{}", CommInfrastructure.REST, url, NetLoggerUtil.SYSTEM_LS, json); - } - - /** - * Logs a REST response. If the response is not of type, String, then it attempts to - * pretty-print it into JSON before logging. - * - * @param url request URL - * @param response response to be logged - */ - public <S> void logRestResponse(String url, S response) { - String json; - try { - if (response == null) { - json = null; - } else if (response instanceof String) { - json = response.toString(); - } else { - json = makeCoder().encode(response, true); - } - - } catch (CoderException e) { - logger.warn("cannot pretty-print response", e); - json = response.toString(); - } - - NetLoggerUtil.log(EventType.IN, CommInfrastructure.REST, url, json); - logger.info("[IN|{}|{}|]{}{}", CommInfrastructure.REST, url, NetLoggerUtil.SYSTEM_LS, json); - } - - // these may be overridden by junit tests - - protected Coder makeCoder() { - return coder; + @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/OperationPartial.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperationPartial.java index d00b88bb5..0b3497197 100644 --- 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 @@ -20,7 +20,12 @@ 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; @@ -28,6 +33,14 @@ 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; @@ -53,8 +66,8 @@ import org.slf4j.LoggerFactory; * 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 @@ -470,103 +483,110 @@ public abstract class OperationPartial implements Operation { * Similar to {@link CompletableFuture#anyOf(CompletableFuture...)}, but it cancels * any outstanding futures when one completes. * - * @param futures futures for which to wait - * @return a future to cancel or await an outcome. If this future is canceled, then - * all of the futures will be canceled + * @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(List<CompletableFuture<OperationOutcome>> futures) { - - // convert list to an array - @SuppressWarnings("rawtypes") - CompletableFuture[] arrFutures = futures.toArray(new CompletableFuture[futures.size()]); + protected CompletableFuture<OperationOutcome> anyOf( + @SuppressWarnings("unchecked") Supplier<CompletableFuture<OperationOutcome>>... futureMakers) { - @SuppressWarnings("unchecked") - CompletableFuture<OperationOutcome> result = anyOf(arrFutures); - return result; + return anyOf(Arrays.asList(futureMakers)); } /** - * Same as {@link CompletableFuture#anyOf(CompletableFuture...)}, but it cancels any - * outstanding futures when one completes. + * Similar to {@link CompletableFuture#anyOf(CompletableFuture...)}, but it cancels + * any outstanding futures when one completes. * - * @param futures futures for which to wait - * @return a future to cancel or await an outcome. If this future is canceled, then - * all of the futures will be canceled + * @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( - @SuppressWarnings("unchecked") CompletableFuture<OperationOutcome>... futures) { + 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]; } - 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 + CompletableFuture.anyOf(futures).thenApply(outcome -> (OperationOutcome) outcome) + .whenCompleteAsync(controller.delayedComplete(), params.getExecutor()); 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)}). + * Similar to {@link CompletableFuture#allOf(CompletableFuture...)}. * - * @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 + * @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(List<CompletableFuture<OperationOutcome>> futures) { - - // convert list to an array - @SuppressWarnings("rawtypes") - CompletableFuture[] arrFutures = futures.toArray(new CompletableFuture[futures.size()]); + protected CompletableFuture<OperationOutcome> allOf( + @SuppressWarnings("unchecked") Supplier<CompletableFuture<OperationOutcome>>... futureMakers) { - @SuppressWarnings("unchecked") - CompletableFuture<OperationOutcome> result = allOf(arrFutures); - return result; + return allOf(Arrays.asList(futureMakers)); } /** - * 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)}). + * Similar to {@link CompletableFuture#allOf(CompletableFuture...)}. * - * @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 + * @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( - @SuppressWarnings("unchecked") CompletableFuture<OperationOutcome>... futures) { - - if (futures.length == 1) { - return futures[0]; - } + List<Supplier<CompletableFuture<OperationOutcome>>> futureMakers) { + PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); - final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); - - attachFutures(controller, futures); + Queue<OperationOutcome> outcomes = new LinkedList<>(); - OperationOutcome[] outcomes = new OperationOutcome[futures.length]; + CompletableFuture<OperationOutcome>[] futures = + attachFutures(controller, futureMakers, future -> future.thenApply(outcome -> { + synchronized (outcomes) { + outcomes.add(outcome); + } + return outcome; + })); - @SuppressWarnings("rawtypes") - CompletableFuture[] futures2 = new CompletableFuture[futures.length]; + if (futures.length == 0) { + // no futures were started + return null; + } - // 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); + if (futures.length == 1) { + return futures[0]; } // @formatter:off - CompletableFuture.allOf(futures2) + CompletableFuture.allOf(futures) .thenApply(unused -> combineOutcomes(outcomes)) .whenCompleteAsync(controller.delayedComplete(), params.getExecutor()); // @formatter:on @@ -575,22 +595,62 @@ public abstract class OperationPartial implements Operation { } /** - * Attaches the given futures to the controller. + * Invokes the functions to create the futures and attaches them 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) { + * @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; + } - if (futures.length == 0) { - throw new IllegalArgumentException("empty list of futures"); + // 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; + } } - // attach each task - for (CompletableFuture<OperationOutcome> future : futures) { - controller.add(future); + @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); } /** @@ -599,15 +659,13 @@ public abstract class OperationPartial implements Operation { * @param outcomes outcomes to be examined * @return the combined outcome */ - private OperationOutcome combineOutcomes(OperationOutcome[] outcomes) { + private OperationOutcome combineOutcomes(Queue<OperationOutcome> outcomes) { // identify the outcome with the highest priority - OperationOutcome outcome = outcomes[0]; + OperationOutcome outcome = outcomes.remove(); 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]; + for (OperationOutcome outcome2 : outcomes) { int priority2 = detmPriority(outcome2); if (priority2 > priority) { @@ -656,72 +714,114 @@ public abstract class OperationPartial implements Operation { } /** - * Performs a task, after verifying that the controller is still running. Also checks - * that the previous outcome was successful, if specified. + * 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 controller overall pipeline controller - * @param checkSuccess {@code true} to check the previous outcome, {@code false} - * otherwise - * @param outcome outcome of the previous task - * @param task task to be performed - * @return the task, if everything checks out. Otherwise, it returns an incomplete - * future and completes the controller instead + * @param futureMakers functions to make the futures + * @return a future to cancel the sequence or await the outcome */ - // @formatter:off - protected CompletableFuture<OperationOutcome> doTask( - PipelineControllerFuture<OperationOutcome> controller, - boolean checkSuccess, OperationOutcome outcome, - CompletableFuture<OperationOutcome> task) { - // @formatter:on + protected CompletableFuture<OperationOutcome> sequence( + @SuppressWarnings("unchecked") Supplier<CompletableFuture<OperationOutcome>>... futureMakers) { - 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 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; } - return controller.wrap(task); + /* + * 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; } /** - * Performs a task, after verifying that the controller is still running. Also checks - * that the previous outcome was successful, if specified. + * Executes the next task in the queue, if the previous outcome was successful. * - * @param controller overall pipeline controller - * @param checkSuccess {@code true} to check the previous outcome, {@code false} - * otherwise - * @param task function to start the task to be performed - * @return a function to perform the task. If everything checks out, then it returns - * the task. Otherwise, it returns an incomplete future and completes the - * controller instead + * @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 */ - // @formatter:off - protected Function<OperationOutcome, CompletableFuture<OperationOutcome>> doTask( + private Function<OperationOutcome, CompletableFuture<OperationOutcome>> nextTaskOnSuccess( PipelineControllerFuture<OperationOutcome> controller, - boolean checkSuccess, - Function<OperationOutcome, CompletableFuture<OperationOutcome>> task) { - // @formatter:on + Queue<Supplier<CompletableFuture<OperationOutcome>>> taskQueue) { return outcome -> { - - if (!controller.isRunning()) { - return new CompletableFuture<>(); + if (!isSuccess(outcome)) { + // return the failure + return CompletableFuture.completedFuture(outcome); } - if (checkSuccess && !isSuccess(outcome)) { - controller.complete(outcome); - return new CompletableFuture<>(); + CompletableFuture<OperationOutcome> nextTask = getNextTask(taskQueue); + if (nextTask == null) { + // no tasks - just return the success + return CompletableFuture.completedFuture(outcome); } - return controller.wrap(task.apply(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/> @@ -809,6 +909,38 @@ public abstract class OperationPartial implements Operation { 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 /** @@ -841,4 +973,10 @@ public abstract class OperationPartial implements Operation { 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/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/HttpActorParams.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParams.java index 275c8bc4e..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 int 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 93711c032..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 int timeoutSec = 0; + @Min(1) + private int timeoutSec; /** 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..fcb463518 --- /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 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/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 8ce3b3230..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 @@ -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 index 19f781d61..50cb8fa8f 100644 --- 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 @@ -22,6 +22,7 @@ package org.onap.policy.controlloop.actorserviceprovider.impl; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -31,9 +32,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -import ch.qos.logback.classic.Logger; import java.util.Collections; -import java.util.List; import java.util.Map; import java.util.Properties; import java.util.UUID; @@ -64,6 +63,7 @@ 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; @@ -71,12 +71,10 @@ 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.Coder; import org.onap.policy.common.utils.coder.CoderException; -import org.onap.policy.common.utils.coder.StandardCoder; import org.onap.policy.common.utils.network.NetworkUtil; -import org.onap.policy.common.utils.test.log.logback.ExtractAppender; import org.onap.policy.controlloop.VirtualControlLoopEvent; import org.onap.policy.controlloop.actorserviceprovider.Operation; import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome; @@ -85,7 +83,6 @@ import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopE import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams; import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams; import org.onap.policy.controlloop.policy.PolicyResult; -import org.slf4j.LoggerFactory; public class HttpOperationTest { @@ -95,19 +92,12 @@ public class HttpOperationTest { private static final String HTTP_CLIENT = "my-client"; private static final String HTTP_NO_SERVER = "my-http-no-server-client"; private static final String MEDIA_TYPE_APPLICATION_JSON = "application/json"; - private static final String MY_REQUEST = "my-request"; private static final String BASE_URI = "oper"; private static final String PATH = "/my-path"; private static final String TEXT = "my-text"; private static final UUID REQ_ID = UUID.randomUUID(); /** - * Used to attach an appender to the class' logger. - */ - private static final Logger logger = (Logger) LoggerFactory.getLogger(HttpOperation.class); - private static final ExtractAppender appender = new ExtractAppender(); - - /** * {@code True} if the server should reject the request, {@code false} otherwise. */ private static boolean rejectRequest; @@ -163,14 +153,6 @@ public class HttpOperationTest { HttpClientFactoryInstance.getClientFactory() .build(builder.clientName(HTTP_NO_SERVER).port(NetworkUtil.allocPort()).build()); - - /** - * Attach appender to the logger. - */ - appender.setContext(logger.getLoggerContext()); - appender.start(); - - logger.addAppender(appender); } /** @@ -178,8 +160,6 @@ public class HttpOperationTest { */ @AfterClass public static void tearDownAfterClass() { - appender.stop(); - HttpClientFactoryInstance.getClientFactory().destroy(); HttpServletServerFactoryInstance.getServerFactory().destroy(); } @@ -192,8 +172,6 @@ public class HttpOperationTest { public void setUp() { MockitoAnnotations.initMocks(this); - appender.clearExtractions(); - rejectRequest = false; nget = 0; npost = 0; @@ -259,9 +237,9 @@ public class HttpOperationTest { @Test public void testDoConfigureMapOfStringObject_testGetClient_testGetPath_testGetTimeoutMs() { - // no default yet - assertEquals(0L, oper.getTimeoutMs(null)); - assertEquals(0L, oper.getTimeoutMs(0)); + // use value from operator + assertEquals(1000L, oper.getTimeoutMs(null)); + assertEquals(1000L, oper.getTimeoutMs(0)); // should use given value assertEquals(20 * 1000L, oper.getTimeoutMs(20)); @@ -359,8 +337,7 @@ public class HttpOperationTest { public void testProcessResponseDecodeExcept() throws CoderException { MyGetOperation<Integer> oper2 = new MyGetOperation<>(Integer.class); - assertSame(outcome, oper2.processResponse(outcome, PATH, response)); - assertEquals(PolicyResult.FAILURE_EXCEPTION, outcome.getResult()); + assertThatIllegalArgumentException().isThrownBy(() -> oper2.processResponse(outcome, PATH, response)); } @Test @@ -442,96 +419,6 @@ public class HttpOperationTest { } @Test - public void testLogRestRequest() throws CoderException { - // log structured data - appender.clearExtractions(); - oper.logRestRequest(PATH, new MyRequest()); - List<String> output = appender.getExtracted(); - assertEquals(1, output.size()); - - assertThat(output.get(0)).contains(PATH).contains("{\n \"input\": \"some input\"\n}"); - - // log a plain string - appender.clearExtractions(); - oper.logRestRequest(PATH, MY_REQUEST); - output = appender.getExtracted(); - assertEquals(1, output.size()); - - assertThat(output.get(0)).contains(PATH).contains(MY_REQUEST); - - // log a null request - appender.clearExtractions(); - oper.logRestRequest(PATH, null); - output = appender.getExtracted(); - assertEquals(1, output.size()); - - // exception from coder - oper = new MyGetOperation<>(String.class) { - @Override - protected Coder makeCoder() { - return new StandardCoder() { - @Override - public String encode(Object object, boolean pretty) throws CoderException { - throw new CoderException(EXPECTED_EXCEPTION); - } - }; - } - }; - - appender.clearExtractions(); - oper.logRestRequest(PATH, new MyRequest()); - output = appender.getExtracted(); - assertEquals(2, output.size()); - assertThat(output.get(0)).contains("cannot pretty-print request"); - assertThat(output.get(1)).contains(PATH); - } - - @Test - public void testLogRestResponse() throws CoderException { - // log structured data - appender.clearExtractions(); - oper.logRestResponse(PATH, new MyResponse()); - List<String> output = appender.getExtracted(); - assertEquals(1, output.size()); - - assertThat(output.get(0)).contains(PATH).contains("{\n \"output\": \"some output\"\n}"); - - // log a plain string - appender.clearExtractions(); - oper.logRestResponse(PATH, MY_REQUEST); - output = appender.getExtracted(); - assertEquals(1, output.size()); - - // log a null response - appender.clearExtractions(); - oper.logRestResponse(PATH, null); - output = appender.getExtracted(); - assertEquals(1, output.size()); - - assertThat(output.get(0)).contains(PATH).contains("null"); - - // exception from coder - oper = new MyGetOperation<>(String.class) { - @Override - protected Coder makeCoder() { - return new StandardCoder() { - @Override - public String encode(Object object, boolean pretty) throws CoderException { - throw new CoderException(EXPECTED_EXCEPTION); - } - }; - } - }; - - appender.clearExtractions(); - oper.logRestResponse(PATH, new MyResponse()); - output = appender.getExtracted(); - assertEquals(2, output.size()); - assertThat(output.get(0)).contains("cannot pretty-print response"); - assertThat(output.get(1)).contains(PATH); - } - - @Test public void testMakeDecoder() { assertNotNull(oper.makeCoder()); } @@ -569,7 +456,7 @@ public class HttpOperationTest { private void initOper(HttpOperator operator, String clientName) { operator.stop(); - HttpParams params = HttpParams.builder().clientName(clientName).path(PATH).build(); + HttpParams params = HttpParams.builder().clientName(clientName).path(PATH).timeoutSec(1).build(); Map<String, Object> mapParams = Util.translateToMap(OPERATION, params); operator.configure(mapParams); operator.start(); @@ -614,7 +501,7 @@ public class HttpOperationTest { headers.put("Accept", MediaType.APPLICATION_JSON); String url = makeUrl(); - logRestRequest(url, null); + logMessage(EventType.OUT, CommInfrastructure.REST, url, null); // @formatter:off return handleResponse(outcome, url, @@ -640,7 +527,7 @@ public class HttpOperationTest { headers.put("Accept", MediaType.APPLICATION_JSON); String url = makeUrl(); - logRestRequest(url, request); + logMessage(EventType.OUT, CommInfrastructure.REST, url, request); // @formatter:off return handleResponse(outcome, url, @@ -666,7 +553,7 @@ public class HttpOperationTest { headers.put("Accept", MediaType.APPLICATION_JSON); String url = makeUrl(); - logRestRequest(url, request); + logMessage(EventType.OUT, CommInfrastructure.REST, url, request); // @formatter:off return handleResponse(outcome, url, @@ -687,7 +574,7 @@ public class HttpOperationTest { headers.put("Accept", MediaType.APPLICATION_JSON); String url = makeUrl(); - logRestRequest(url, null); + logMessage(EventType.OUT, CommInfrastructure.REST, url, null); // @formatter:off return handleResponse(outcome, url, 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 index 0d5cb2444..67ac27c8d 100644 --- 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 @@ -20,8 +20,8 @@ package org.onap.policy.controlloop.actorserviceprovider.impl; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -30,13 +30,13 @@ 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.Queue; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -46,42 +46,59 @@ 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.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.actorserviceprovider.pipeline.PipelineControllerFuture; import org.onap.policy.controlloop.policy.PolicyResult; +import org.slf4j.LoggerFactory; public class OperationPartialTest { - private static final int MAX_PARALLEL_REQUESTS = 10; + 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 TARGET = "my-target"; + 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 MyExec executor; + private PseudoExecutor executor; private ControlLoopOperationParams params; private MyOper oper; @@ -97,6 +114,28 @@ public class OperationPartialTest { 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 @@ -105,11 +144,11 @@ public class OperationPartialTest { event.setRequestId(REQ_ID); context = new ControlLoopEventContext(event); - executor = new MyExec(); + executor = new PseudoExecutor(); params = ControlLoopOperationParams.builder().completeCallback(this::completer).context(context) .executor(executor).actor(ACTOR).operation(OPERATION).timeoutSec(TIMEOUT) - .startCallback(this::starter).targetEntity(TARGET).build(); + .startCallback(this::starter).targetEntity(MY_SINK).build(); operator = new OperatorPartial(ACTOR, OPERATION) { @Override @@ -210,19 +249,19 @@ public class OperationPartialTest { */ @Test public void testStartMultiple() { - for (int count = 0; count < MAX_PARALLEL_REQUESTS; ++count) { + for (int count = 0; count < MAX_PARALLEL; ++count) { oper.start(); } - assertTrue(executor.runAll()); + assertTrue(executor.runAll(MAX_REQUESTS * MAX_PARALLEL)); 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); + assertEquals(MAX_PARALLEL, numStart); + assertEquals(MAX_PARALLEL, oper.getCount()); + assertEquals(MAX_PARALLEL, numEnd); } /** @@ -255,7 +294,7 @@ public class OperationPartialTest { oper.setGuard(CompletableFuture.completedFuture(makeSuccess())); oper.start().cancel(false); - assertTrue(executor.runAll()); + assertTrue(executor.runAll(MAX_REQUESTS)); assertNull(opstart); assertNull(opend); @@ -296,7 +335,7 @@ public class OperationPartialTest { @Test public void testStartOperationAsync() { oper.start(); - assertTrue(executor.runAll()); + assertTrue(executor.runAll(MAX_REQUESTS)); assertEquals(1, oper.getCount()); } @@ -331,14 +370,14 @@ public class OperationPartialTest { outcome.setResult(PolicyResult.FAILURE); // incorrect actor - outcome.setActor(TARGET); + outcome.setActor(MY_SINK); assertFalse(oper.isActorFailed(outcome)); outcome.setActor(null); assertFalse(oper.isActorFailed(outcome)); outcome.setActor(ACTOR); // incorrect operation - outcome.setOperation(TARGET); + outcome.setOperation(MY_SINK); assertFalse(oper.isActorFailed(outcome)); outcome.setOperation(null); assertFalse(oper.isActorFailed(outcome)); @@ -356,7 +395,7 @@ public class OperationPartialTest { OperationPartial oper2 = new OperationPartial(params, operator) {}; oper2.start(); - assertTrue(executor.runAll()); + assertTrue(executor.runAll(MAX_REQUESTS)); assertNotNull(opend); assertEquals(PolicyResult.FAILURE_EXCEPTION, opend.getResult()); @@ -520,14 +559,14 @@ public class OperationPartialTest { // wrong actor - should be false outcome.setActor(null); assertFalse(oper.isSameOperation(outcome)); - outcome.setActor(TARGET); + 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(TARGET); + outcome.setOperation(MY_SINK); assertFalse(oper.isSameOperation(outcome)); outcome.setOperation(OPERATION); @@ -585,43 +624,47 @@ public class OperationPartialTest { @Test public void testAnyOf() throws Exception { // first task completes, others do not - List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>(); + List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>(); final OperationOutcome outcome = params.makeOutcome(); - tasks.add(CompletableFuture.completedFuture(outcome)); - tasks.add(new CompletableFuture<>()); - tasks.add(new CompletableFuture<>()); + 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()); + 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 = new LinkedList<>(); - - tasks.add(new CompletableFuture<>()); - tasks.add(CompletableFuture.completedFuture(outcome)); - tasks.add(new CompletableFuture<>()); + tasks.clear(); + tasks.add(() -> new CompletableFuture<>()); + tasks.add(() -> CompletableFuture.completedFuture(outcome)); + tasks.add(() -> new CompletableFuture<>()); result = oper.anyOf(tasks); - assertTrue(executor.runAll()); - + assertTrue(executor.runAll(MAX_REQUESTS)); 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)); + tasks.clear(); + tasks.add(() -> new CompletableFuture<>()); + tasks.add(() -> new CompletableFuture<>()); + tasks.add(() -> CompletableFuture.completedFuture(outcome)); result = oper.anyOf(tasks); - assertTrue(executor.runAll()); - + assertTrue(executor.runAll(MAX_REQUESTS)); assertTrue(result.isDone()); assertSame(outcome, result.get()); } @@ -632,54 +675,82 @@ public class OperationPartialTest { @Test @SuppressWarnings("unchecked") public void testAnyOfEdge() throws Exception { - List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>(); + List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>(); // zero items: check both using a list and using an array - assertThatIllegalArgumentException().isThrownBy(() -> oper.anyOf(tasks)); - assertThatIllegalArgumentException().isThrownBy(() -> oper.anyOf()); + 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); + tasks.add(() -> future1); assertSame(future1, oper.anyOf(tasks)); - assertSame(future1, oper.anyOf(future1)); + assertSame(future1, oper.anyOf(() -> future1)); } - /** - * Tests both flavors of allOf(), because one invokes the other. - */ @Test - public void testAllOf() throws Exception { - List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>(); + 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<>(); - tasks.add(future1); - tasks.add(future2); - tasks.add(future3); + 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()); + assertTrue(executor.runAll(MAX_REQUESTS)); assertFalse(result.isDone()); future1.complete(outcome); // complete 3 before 2 - assertTrue(executor.runAll()); + assertTrue(executor.runAll(MAX_REQUESTS)); assertFalse(result.isDone()); future3.complete(outcome); - assertTrue(executor.runAll()); + assertTrue(executor.runAll(MAX_REQUESTS)); assertFalse(result.isDone()); future2.complete(outcome); // all of them are now done - assertTrue(executor.runAll()); + assertTrue(executor.runAll(MAX_REQUESTS)); assertTrue(result.isDone()); assertSame(outcome, result.get()); } @@ -690,18 +761,41 @@ public class OperationPartialTest { @Test @SuppressWarnings("unchecked") public void testAllOfEdge() throws Exception { - List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>(); + List<Supplier<CompletableFuture<OperationOutcome>>> tasks = new LinkedList<>(); // zero items: check both using a list and using an array - assertThatIllegalArgumentException().isThrownBy(() -> oper.allOf(tasks)); - assertThatIllegalArgumentException().isThrownBy(() -> oper.allOf()); + 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); + tasks.add(() -> future1); assertSame(future1, oper.allOf(tasks)); - assertSame(future1, oper.allOf(future1)); + 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 @@ -715,12 +809,14 @@ public class OperationPartialTest { 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)); + // 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()); + assertTrue(executor.runAll(MAX_REQUESTS)); assertTrue(result.isDone()); assertNull(result.get()); @@ -728,26 +824,85 @@ public class OperationPartialTest { 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())); + tasks.add(() -> CompletableFuture.completedFuture(params.makeOutcome())); + tasks.add(() -> CompletableFuture.failedFuture(except)); + tasks.add(() -> CompletableFuture.completedFuture(params.makeOutcome())); result = oper.allOf(tasks); - assertTrue(executor.runAll()); + assertTrue(executor.runAll(MAX_REQUESTS)); 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<>(); + /** + * 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)); + tasks.add(() -> CompletableFuture.completedFuture(outcome)); if (count == expected) { expectedOutcome = outcome; @@ -756,17 +911,11 @@ public class OperationPartialTest { CompletableFuture<OperationOutcome> result = oper.allOf(tasks); - assertTrue(executor.runAll()); + assertTrue(executor.runAll(MAX_REQUESTS)); assertTrue(result.isDone()); assertSame(expectedOutcome, result.get()); } - private Function<OperationOutcome, CompletableFuture<OperationOutcome>> makeTask( - final OperationOutcome taskOutcome) { - - return outcome -> CompletableFuture.completedFuture(taskOutcome); - } - @Test public void testDetmPriority() throws CoderException { assertEquals(1, oper.detmPriority(null)); @@ -791,210 +940,6 @@ public class OperationPartialTest { } /** - * Tests doTask(Future) when the controller is not running. - */ - @Test - public void testDoTaskFutureNotRunning() throws Exception { - CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>(); - - PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); - controller.complete(params.makeOutcome()); - - CompletableFuture<OperationOutcome> future = oper.doTask(controller, false, params.makeOutcome(), taskFuture); - assertFalse(future.isDone()); - assertTrue(executor.runAll()); - - // should not have run the task - assertFalse(future.isDone()); - - // should have canceled the task future - assertTrue(taskFuture.isCancelled()); - } - - /** - * Tests doTask(Future) when the previous outcome was successful. - */ - @Test - public void testDoTaskFutureSuccess() throws Exception { - CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>(); - final OperationOutcome taskOutcome = params.makeOutcome(); - - PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); - - CompletableFuture<OperationOutcome> future = oper.doTask(controller, true, params.makeOutcome(), taskFuture); - - taskFuture.complete(taskOutcome); - assertTrue(executor.runAll()); - - assertTrue(future.isDone()); - assertSame(taskOutcome, future.get()); - - // controller should not be done yet - assertFalse(controller.isDone()); - } - - /** - * Tests doTask(Future) when the previous outcome was failed. - */ - @Test - public void testDoTaskFutureFailure() throws Exception { - CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>(); - final OperationOutcome failedOutcome = params.makeOutcome(); - failedOutcome.setResult(PolicyResult.FAILURE); - - PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); - - CompletableFuture<OperationOutcome> future = oper.doTask(controller, true, failedOutcome, taskFuture); - assertFalse(future.isDone()); - assertTrue(executor.runAll()); - - // should not have run the task - assertFalse(future.isDone()); - - // should have canceled the task future - assertTrue(taskFuture.isCancelled()); - - // controller SHOULD be done now - assertTrue(controller.isDone()); - assertSame(failedOutcome, controller.get()); - } - - /** - * Tests doTask(Future) when the previous outcome was failed, but not checking - * success. - */ - @Test - public void testDoTaskFutureUncheckedFailure() throws Exception { - CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>(); - final OperationOutcome failedOutcome = params.makeOutcome(); - failedOutcome.setResult(PolicyResult.FAILURE); - - PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); - - CompletableFuture<OperationOutcome> future = oper.doTask(controller, false, failedOutcome, taskFuture); - assertFalse(future.isDone()); - - // complete the task - OperationOutcome taskOutcome = params.makeOutcome(); - taskFuture.complete(taskOutcome); - - assertTrue(executor.runAll()); - - // should have run the task - assertTrue(future.isDone()); - - assertTrue(future.isDone()); - assertSame(taskOutcome, future.get()); - - // controller should not be done yet - assertFalse(controller.isDone()); - } - - /** - * Tests doTask(Function) when the controller is not running. - */ - @Test - public void testDoTaskFunctionNotRunning() throws Exception { - AtomicBoolean invoked = new AtomicBoolean(); - - Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = outcome -> { - invoked.set(true); - return CompletableFuture.completedFuture(params.makeOutcome()); - }; - - PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); - controller.complete(params.makeOutcome()); - - CompletableFuture<OperationOutcome> future = oper.doTask(controller, false, task).apply(params.makeOutcome()); - assertFalse(future.isDone()); - assertTrue(executor.runAll()); - - // should not have run the task - assertFalse(future.isDone()); - - // should not have even invoked the task - assertFalse(invoked.get()); - } - - /** - * Tests doTask(Function) when the previous outcome was successful. - */ - @Test - public void testDoTaskFunctionSuccess() throws Exception { - final OperationOutcome taskOutcome = params.makeOutcome(); - - final OperationOutcome failedOutcome = params.makeOutcome(); - - Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = makeTask(taskOutcome); - - PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); - - CompletableFuture<OperationOutcome> future = oper.doTask(controller, true, task).apply(failedOutcome); - - assertTrue(future.isDone()); - assertSame(taskOutcome, future.get()); - - // controller should not be done yet - assertFalse(controller.isDone()); - } - - /** - * Tests doTask(Function) when the previous outcome was failed. - */ - @Test - public void testDoTaskFunctionFailure() throws Exception { - final OperationOutcome failedOutcome = params.makeOutcome(); - failedOutcome.setResult(PolicyResult.FAILURE); - - AtomicBoolean invoked = new AtomicBoolean(); - - Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = outcome -> { - invoked.set(true); - return CompletableFuture.completedFuture(params.makeOutcome()); - }; - - PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); - - CompletableFuture<OperationOutcome> future = oper.doTask(controller, true, task).apply(failedOutcome); - assertFalse(future.isDone()); - assertTrue(executor.runAll()); - - // should not have run the task - assertFalse(future.isDone()); - - // should not have even invoked the task - assertFalse(invoked.get()); - - // controller should have the failed task - assertTrue(controller.isDone()); - assertSame(failedOutcome, controller.get()); - } - - /** - * Tests doTask(Function) when the previous outcome was failed, but not checking - * success. - */ - @Test - public void testDoTaskFunctionUncheckedFailure() throws Exception { - final OperationOutcome taskOutcome = params.makeOutcome(); - - final OperationOutcome failedOutcome = params.makeOutcome(); - failedOutcome.setResult(PolicyResult.FAILURE); - - Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = makeTask(taskOutcome); - - PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); - - CompletableFuture<OperationOutcome> future = oper.doTask(controller, false, task).apply(failedOutcome); - - assertTrue(future.isDone()); - assertSame(taskOutcome, future.get()); - - // controller should not be done yet - assertFalse(controller.isDone()); - } - - /** * Tests callbackStarted() when the pipeline has already been stopped. */ @Test @@ -1014,7 +959,7 @@ public class OperationPartialTest { oper = new MyOper(); future.set(oper.start()); - assertTrue(executor.runAll()); + assertTrue(executor.runAll(MAX_REQUESTS)); // should have only run once assertEquals(1, numStart); @@ -1036,7 +981,7 @@ public class OperationPartialTest { oper = new MyOper(); future.set(oper.start()); - assertTrue(executor.runAll()); + assertTrue(executor.runAll(MAX_REQUESTS)); // should not have been set assertNull(opend); @@ -1092,6 +1037,62 @@ public class OperationPartialTest { } @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)); @@ -1188,7 +1189,7 @@ public class OperationPartialTest { manipulator.accept(future); - assertTrue(testName, executor.runAll()); + assertTrue(testName, executor.runAll(MAX_REQUESTS)); assertEquals(testName, expectedCallbacks, numStart); assertEquals(testName, expectedCallbacks, numEnd); @@ -1217,6 +1218,30 @@ public class OperationPartialTest { 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; @@ -1267,36 +1292,4 @@ public class OperationPartialTest { return 0L; } } - - /** - * Executor that will run tasks until the queue is empty or a maximum number of tasks - * have been executed. Doesn't actually run anything until {@link #runAll()} is - * invoked. - */ - private static class MyExec implements Executor { - private static final int MAX_TASKS = MAX_PARALLEL_REQUESTS * 100; - - private Queue<Runnable> commands = new LinkedList<>(); - - public MyExec() { - // do nothing - } - - public int getQueueLength() { - return commands.size(); - } - - @Override - public void execute(Runnable command) { - commands.add(command); - } - - public boolean runAll() { - for (int count = 0; count < MAX_TASKS && !commands.isEmpty(); ++count) { - commands.remove().run(); - } - - return commands.isEmpty(); - } - } } diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/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/HttpActorParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParamsTest.java index daa0affec..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,21 +21,16 @@ 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 { @@ -48,63 +43,40 @@ public class HttpActorParamsTest { 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 ae4a79fe2..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 @@ -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/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 860468821..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 @@ -40,8 +40,10 @@ <appender-ref ref="STDOUT" /> </logger> - <!-- this is required for HttpOperationTest --> - <logger name="org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperation" level="info" additivity="false"> + <!-- 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 029ac7f40..30891142d 100644 --- a/models-interactions/model-actors/pom.xml +++ b/models-interactions/model-actors/pom.xml @@ -37,6 +37,7 @@ <modules> <module>actorServiceProvider</module> <module>actor.test</module> + <module>actor.aai</module> <module>actor.appc</module> <module>actor.vfc</module> <module>actor.sdnc</module> diff --git a/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpEngineWorkerStatistics.java b/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpEngineWorkerStatistics.java index 43fa6c072..06f603069 100644 --- a/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpEngineWorkerStatistics.java +++ b/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpEngineWorkerStatistics.java @@ -1,6 +1,6 @@ /*- * ============LICENSE_START======================================================= - * Copyright (C) 2019 Nordix Foundation. + * Copyright (C) 2019-2020 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,16 +22,13 @@ package org.onap.policy.models.pdp.concepts; import java.io.Serializable; import javax.persistence.Embeddable; -import lombok.Getter; +import lombok.Data; import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.ToString; +import lombok.NonNull; import org.onap.policy.models.pdp.enums.PdpEngineWorkerState; @Embeddable -@Getter -@Setter -@ToString +@Data @NoArgsConstructor public class PdpEngineWorkerStatistics implements Serializable { private static final long serialVersionUID = 8262176849743624013L; @@ -51,7 +48,7 @@ public class PdpEngineWorkerStatistics implements Serializable { * * @param source source from which to copy */ - public PdpEngineWorkerStatistics(PdpEngineWorkerStatistics source) { + public PdpEngineWorkerStatistics(@NonNull PdpEngineWorkerStatistics source) { this.engineId = source.engineId; this.engineWorkerState = source.engineWorkerState; this.engineTimeStamp = source.engineTimeStamp; diff --git a/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpStatistics.java b/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpStatistics.java index ad5547ecf..1ba983be2 100644 --- a/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpStatistics.java +++ b/models-pdp/src/main/java/org/onap/policy/models/pdp/concepts/PdpStatistics.java @@ -2,6 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2019 Nordix Foundation. * Modifications Copyright (C) 2019 AT&T Intellectual Property. + * Modifications Copyright (C) 2020 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,10 +24,9 @@ package org.onap.policy.models.pdp.concepts; import java.util.Date; import java.util.List; -import lombok.Getter; +import lombok.Data; import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.ToString; +import lombok.NonNull; import org.onap.policy.models.base.PfUtils; /** @@ -34,9 +34,7 @@ import org.onap.policy.models.base.PfUtils; * * @author Ram Krishna Verma (ram.krishna.verma@est.tech) */ -@Getter -@Setter -@ToString +@Data @NoArgsConstructor public class PdpStatistics { @@ -57,7 +55,7 @@ public class PdpStatistics { * * @param source source from which to copy */ - public PdpStatistics(PdpStatistics source) { + public PdpStatistics(@NonNull PdpStatistics source) { this.pdpInstanceId = source.pdpInstanceId; this.timeStamp = source.timeStamp == null ? null : new Date(source.timeStamp.getTime()); this.pdpGroupName = source.pdpGroupName; diff --git a/models-pdp/src/test/java/org/onap/policy/models/pdp/concepts/PdpEngineWorkerStatisticsTest.java b/models-pdp/src/test/java/org/onap/policy/models/pdp/concepts/PdpEngineWorkerStatisticsTest.java new file mode 100644 index 000000000..c0d2ba6bf --- /dev/null +++ b/models-pdp/src/test/java/org/onap/policy/models/pdp/concepts/PdpEngineWorkerStatisticsTest.java @@ -0,0 +1,62 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2020 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.models.pdp.concepts; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; + +import java.util.Date; +import org.junit.Test; +import org.onap.policy.models.pdp.enums.PdpEngineWorkerState; + +public class PdpEngineWorkerStatisticsTest { + + @Test + public void testCopyConstructor() { + assertThatThrownBy(() -> new PdpEngineWorkerStatistics(null)).hasMessageContaining("source"); + + PdpEngineWorkerStatistics stat = createPdpEngineWorkerStatistics(); + PdpEngineWorkerStatistics stat2 = new PdpEngineWorkerStatistics(stat); + assertEquals(stat, stat2); + } + + @Test + public void testClean() { + PdpEngineWorkerStatistics stat = createPdpEngineWorkerStatistics(); + stat.setEngineId(" Engine0 "); + stat.clean(); + assertEquals("Engine0", stat.getEngineId()); + } + + private PdpEngineWorkerStatistics createPdpEngineWorkerStatistics() { + PdpEngineWorkerStatistics stat = new PdpEngineWorkerStatistics(); + stat.setEngineId("Engine0"); + stat.setEngineWorkerState(PdpEngineWorkerState.READY); + stat.setEngineTimeStamp(new Date().getTime()); + stat.setEventCount(1); + stat.setLastExecutionTime(100); + stat.setAverageExecutionTime(99); + stat.setUpTime(1000); + stat.setLastEnterTime(2000); + stat.setLastStart(3000); + return stat; + } +}
\ No newline at end of file diff --git a/models-pdp/src/test/java/org/onap/policy/models/pdp/concepts/PdpStatisticsTest.java b/models-pdp/src/test/java/org/onap/policy/models/pdp/concepts/PdpStatisticsTest.java index 08098cc28..adf9b9f7c 100644 --- a/models-pdp/src/test/java/org/onap/policy/models/pdp/concepts/PdpStatisticsTest.java +++ b/models-pdp/src/test/java/org/onap/policy/models/pdp/concepts/PdpStatisticsTest.java @@ -3,6 +3,7 @@ * ONAP Policy Models * ================================================================================ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * Modifications Copyright (C) 2020 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,32 +23,35 @@ package org.onap.policy.models.pdp.concepts; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.assertEquals; -import static org.onap.policy.models.pdp.concepts.PdpMessageUtils.removeVariableFields; +import java.util.ArrayList; +import java.util.Date; import org.junit.Test; public class PdpStatisticsTest { @Test public void testCopyConstructor() { - assertThatThrownBy(() -> new PdpStatistics(null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new PdpStatistics(null)).hasMessageContaining("source"); - PdpStatistics orig = new PdpStatistics(); - - // verify with null values - assertEquals(removeVariableFields(orig.toString()), removeVariableFields(new PdpStatistics(orig).toString())); - - // verify with all values - orig.setPdpInstanceId("my-instance"); - - int count = 1; - orig.setPolicyDeployCount(count++); - orig.setPolicyDeployFailCount(count++); - orig.setPolicyDeploySuccessCount(count++); - orig.setPolicyExecutedCount(count++); - orig.setPolicyExecutedFailCount(count++); - orig.setPolicyExecutedSuccessCount(count++); + PdpStatistics orig = createPdpStatistics(); + PdpStatistics copied = new PdpStatistics(orig); + assertEquals(orig, copied); + } - assertEquals(removeVariableFields(orig.toString()), removeVariableFields(new PdpStatistics(orig).toString())); + private PdpStatistics createPdpStatistics() { + PdpStatistics pdpStat = new PdpStatistics(); + pdpStat.setPdpInstanceId("PDP0"); + pdpStat.setPdpGroupName("PDPGroup0"); + pdpStat.setPdpSubGroupName("PDPSubGroup0"); + pdpStat.setTimeStamp(new Date()); + pdpStat.setPolicyDeployCount(3); + pdpStat.setPolicyDeploySuccessCount(1); + pdpStat.setPolicyDeployFailCount(2); + pdpStat.setPolicyExecutedCount(9); + pdpStat.setPolicyExecutedSuccessCount(4); + pdpStat.setPolicyExecutedFailCount(5); + pdpStat.setEngineStats(new ArrayList<>()); + return pdpStat; } } diff --git a/models-pdp/src/test/java/org/onap/policy/models/pdp/persistence/concepts/JpaPdpStatisticsTest.java b/models-pdp/src/test/java/org/onap/policy/models/pdp/persistence/concepts/JpaPdpStatisticsTest.java index 62f0c5be0..0b22e1b3e 100644 --- a/models-pdp/src/test/java/org/onap/policy/models/pdp/persistence/concepts/JpaPdpStatisticsTest.java +++ b/models-pdp/src/test/java/org/onap/policy/models/pdp/persistence/concepts/JpaPdpStatisticsTest.java @@ -1,6 +1,6 @@ /*- * ============LICENSE_START======================================================= - * Copyright (C) 2019 Nordix Foundation. + * Copyright (C) 2019-2020 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,22 +21,123 @@ package org.onap.policy.models.pdp.persistence.concepts; 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.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import java.util.ArrayList; +import java.util.Date; import org.junit.Test; +import org.onap.policy.models.base.PfTimestampKey; +import org.onap.policy.models.base.PfValidationResult; +import org.onap.policy.models.pdp.concepts.PdpStatistics; /** * Test the {@link JpaPdpStatistics} class. - * */ public class JpaPdpStatisticsTest { - private static final String NULL_KEY_ERROR = "key is marked @NonNull but is null"; - private static final String PDP1 = "ThePDP"; - // TODO More unit test cases will be added later. @Test - public void testJpaPdpStatistics() { - assertThatThrownBy(() -> { - new JpaPdpStatistics((JpaPdpStatistics) null); - }).hasMessage("copyConcept is marked @NonNull but is null"); + public void testConstructor() { + assertThatThrownBy(() -> new JpaPdpStatistics((PfTimestampKey) null)).hasMessageContaining("key"); + + assertThatThrownBy(() -> new JpaPdpStatistics((JpaPdpStatistics) null)) + .hasMessageContaining("copyConcept"); + + assertThatThrownBy(() -> new JpaPdpStatistics((PdpStatistics) null)) + .hasMessageContaining("authorativeConcept"); + + assertNotNull(new JpaPdpStatistics()); + assertNotNull(new JpaPdpStatistics(new PfTimestampKey())); + + PdpStatistics pdpStat = createPdpStatistics(); + JpaPdpStatistics jpaPdpStat = new JpaPdpStatistics(createPdpStatistics()); + checkEquals(pdpStat, jpaPdpStat); + + JpaPdpStatistics jpaPdpStat2 = new JpaPdpStatistics(jpaPdpStat); + assertEquals(0, jpaPdpStat2.compareTo(jpaPdpStat)); + } + + @Test + public void testFromAuthorative() { + PdpStatistics pdpStat = createPdpStatistics(); + JpaPdpStatistics jpaPdpStat = new JpaPdpStatistics(); + jpaPdpStat.fromAuthorative(pdpStat); + checkEquals(pdpStat, jpaPdpStat); + } + + @Test + public void testToAuthorative() { + PdpStatistics pdpStat = createPdpStatistics(); + JpaPdpStatistics jpaPdpStat = new JpaPdpStatistics(pdpStat); + PdpStatistics toPdpStat = jpaPdpStat.toAuthorative(); + assertEquals(pdpStat, toPdpStat); + } + + @Test + public void testCompareTo() { + PdpStatistics pdpStat = createPdpStatistics(); + JpaPdpStatistics jpaPdpStat1 = new JpaPdpStatistics(pdpStat); + assertEquals(-1, jpaPdpStat1.compareTo(null)); + + JpaPdpStatistics jpaPdpStat2 = new JpaPdpStatistics(pdpStat); + assertEquals(0, jpaPdpStat1.compareTo(jpaPdpStat2)); + + PdpStatistics pdpStat3 = createPdpStatistics(); + pdpStat3.setPdpInstanceId("PDP3"); + JpaPdpStatistics jpaPdpStat3 = new JpaPdpStatistics(pdpStat3); + assertNotEquals(0, jpaPdpStat1.compareTo(jpaPdpStat3)); + } + + @Test + public void testValidate() { + JpaPdpStatistics nullKeyJpaPdpStat = new JpaPdpStatistics(); + assertFalse(nullKeyJpaPdpStat.validate(new PfValidationResult()).isOk()); + + PdpStatistics pdpStat = createPdpStatistics(); + JpaPdpStatistics jpaPdpStat2 = new JpaPdpStatistics(pdpStat); + assertTrue(jpaPdpStat2.validate(new PfValidationResult()).isOk()); + } + + @Test + public void testClean() { + PdpStatistics pdpStat = createPdpStatistics(); + JpaPdpStatistics jpaPdpStat = new JpaPdpStatistics(pdpStat); + jpaPdpStat.setPdpGroupName(" PDPGroup0 "); + jpaPdpStat.setPdpSubGroupName(" PDPSubGroup0 "); + jpaPdpStat.clean(); + assertEquals("PDPGroup0", jpaPdpStat.getPdpGroupName()); + assertEquals("PDPSubGroup0", jpaPdpStat.getPdpSubGroupName()); + } + + private void checkEquals(PdpStatistics pdpStat, JpaPdpStatistics jpaPdpStat) { + assertEquals(pdpStat.getPdpInstanceId(), jpaPdpStat.getKey().getName()); + assertEquals(pdpStat.getPdpGroupName(), jpaPdpStat.getPdpGroupName()); + assertEquals(pdpStat.getPdpSubGroupName(), jpaPdpStat.getPdpSubGroupName()); + assertEquals(pdpStat.getTimeStamp(), jpaPdpStat.getKey().getTimeStamp()); + assertEquals(pdpStat.getPolicyDeployCount(), jpaPdpStat.getPolicyDeployCount()); + assertEquals(pdpStat.getPolicyDeploySuccessCount(), jpaPdpStat.getPolicyDeploySuccessCount()); + assertEquals(pdpStat.getPolicyDeployFailCount(), jpaPdpStat.getPolicyDeployFailCount()); + assertEquals(pdpStat.getPolicyExecutedCount(), jpaPdpStat.getPolicyExecutedCount()); + assertEquals(pdpStat.getPolicyExecutedSuccessCount(), jpaPdpStat.getPolicyExecutedSuccessCount()); + assertEquals(pdpStat.getPolicyExecutedFailCount(), jpaPdpStat.getPolicyExecutedFailCount()); + } + + private PdpStatistics createPdpStatistics() { + PdpStatistics pdpStat = new PdpStatistics(); + pdpStat.setPdpInstanceId("PDP0"); + pdpStat.setPdpGroupName("PDPGroup0"); + pdpStat.setPdpSubGroupName("PDPSubGroup0"); + pdpStat.setTimeStamp(new Date()); + pdpStat.setPolicyDeployCount(3); + pdpStat.setPolicyDeploySuccessCount(1); + pdpStat.setPolicyDeployFailCount(2); + pdpStat.setPolicyExecutedCount(9); + pdpStat.setPolicyExecutedSuccessCount(4); + pdpStat.setPolicyExecutedFailCount(5); + pdpStat.setEngineStats(new ArrayList<>()); + return pdpStat; } } diff --git a/models-pdp/src/test/resources/META-INF/persistence.xml b/models-pdp/src/test/resources/META-INF/persistence.xml index b1a1795ea..5c7caae2c 100644 --- a/models-pdp/src/test/resources/META-INF/persistence.xml +++ b/models-pdp/src/test/resources/META-INF/persistence.xml @@ -32,6 +32,8 @@ <class>org.onap.policy.models.pdp.persistence.concepts.JpaPdpSubGroup</class> <class>org.onap.policy.models.pdp.persistence.concepts.JpaPdp</class> <class>org.onap.policy.models.pdp.persistence.concepts.JpaPdpStatistics</class> + <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaTrigger</class> + <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaProperty</class> <properties> <property name="eclipselink.ddl-generation" value="drop-and-create-tables" /> diff --git a/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyLegacyOperationalPersistenceTest.java b/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyLegacyOperationalPersistenceTest.java index 0eee4b2b6..30428b099 100644 --- a/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyLegacyOperationalPersistenceTest.java +++ b/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyLegacyOperationalPersistenceTest.java @@ -1,6 +1,7 @@ /*- * ============LICENSE_START======================================================= * Copyright (C) 2019-2020 Nordix Foundation. + * Modifications Copyright (C) 2020 AT&T. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,15 +56,15 @@ public class PolicyLegacyOperationalPersistenceTest { // @formatter:off private String[] policyInputResourceNames = { - "policies/vCPE.policy.operational.input.json", - "policies/vDNS.policy.operational.input.json", - "policies/vFirewall.policy.operational.input.json" + "policies/vCPE.policy.operational.legacy.input.json", + "policies/vDNS.policy.operational.legacy.input.json", + "policies/vFirewall.policy.operational.legacy.input.json" }; private String[] policyOutputResourceNames = { - "policies/vCPE.policy.operational.output.json", - "policies/vDNS.policy.operational.output.json", - "policies/vFirewall.policy.operational.output.json" + "policies/vCPE.policy.operational.legacy.output.json", + "policies/vDNS.policy.operational.legacy.output.json", + "policies/vFirewall.policy.operational.legacy.output.json" }; // @formatter:on @@ -148,8 +149,8 @@ public class PolicyLegacyOperationalPersistenceTest { private void createPolicyTypes() throws CoderException, PfModelException, URISyntaxException { Set<String> policyTypeResources = ResourceUtils.getDirectoryContents("policytypes"); - for (String policyTyoeResource : policyTypeResources) { - Object yamlObject = new Yaml().load(ResourceUtils.getResourceAsString(policyTyoeResource)); + for (String policyTypeResource : policyTypeResources) { + Object yamlObject = new Yaml().load(ResourceUtils.getResourceAsString(policyTypeResource)); String yamlAsJsonString = new StandardCoder().encode(yamlObject); ToscaServiceTemplate toscaServiceTemplatePolicyType = diff --git a/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyToscaPersistenceTest.java b/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyToscaPersistenceTest.java index 0cdc2ad49..311686831 100644 --- a/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyToscaPersistenceTest.java +++ b/models-provider/src/test/java/org/onap/policy/models/provider/impl/PolicyToscaPersistenceTest.java @@ -36,6 +36,7 @@ import org.junit.Before; import org.junit.Test; import org.onap.policy.common.utils.coder.CoderException; import org.onap.policy.common.utils.coder.StandardCoder; +import org.onap.policy.common.utils.coder.YamlJsonTranslator; import org.onap.policy.common.utils.resources.ResourceUtils; import org.onap.policy.models.base.PfModelException; import org.onap.policy.models.provider.PolicyModelsProvider; @@ -44,7 +45,8 @@ import org.onap.policy.models.provider.PolicyModelsProviderParameters; import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy; import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyFilter; import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate; -import org.yaml.snakeyaml.Yaml; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Test persistence of monitoring policies to and from the database. @@ -52,7 +54,10 @@ import org.yaml.snakeyaml.Yaml; * @author Liam Fallon (liam.fallon@est.tech) */ public class PolicyToscaPersistenceTest { - private StandardCoder standardCoder; + private static final Logger LOGGER = LoggerFactory.getLogger(PolicyToscaPersistenceTest.class); + + private YamlJsonTranslator yamlJsonTranslator = new YamlJsonTranslator(); + private StandardCoder standardCoder = new StandardCoder(); private PolicyModelsProvider databaseProvider; @@ -78,14 +83,6 @@ public class PolicyToscaPersistenceTest { createPolicyTypes(); } - /** - * Set up standard coder. - */ - @Before - public void setupStandardCoder() { - standardCoder = new StandardCoder(); - } - @After public void teardown() throws Exception { databaseProvider.close(); @@ -103,29 +100,44 @@ public class PolicyToscaPersistenceTest { String policyString = ResourceUtils.getResourceAsString(policyResource); if (policyResource.endsWith("yaml")) { - testYamlStringPolicyPersistence(policyString); + testPolicyPersistence(yamlJsonTranslator.fromYaml(policyString, ToscaServiceTemplate.class)); } else { - testJsonStringPolicyPersistence(policyString); + testPolicyPersistence(standardCoder.decode(policyString, ToscaServiceTemplate.class)); } } } - private void testYamlStringPolicyPersistence(final String policyString) throws Exception { - Object yamlObject = new Yaml().load(policyString); - String yamlAsJsonString = new StandardCoder().encode(yamlObject); + @Test + public void testNamingPolicyGet() throws PfModelException { + String policyYamlString = ResourceUtils.getResourceAsString("policies/sdnc.policy.naming.input.tosca.yaml"); + ToscaServiceTemplate serviceTemplate = + yamlJsonTranslator.fromYaml(policyYamlString, ToscaServiceTemplate.class); - testJsonStringPolicyPersistence(yamlAsJsonString); + long createStartTime = System.currentTimeMillis(); + databaseProvider.createPolicies(serviceTemplate); + LOGGER.trace("Naming policy create time (ms): {}", System.currentTimeMillis() - createStartTime); + + long getStartTime = System.currentTimeMillis(); + ToscaServiceTemplate namingServiceTemplate = + databaseProvider.getPolicies("SDNC_Policy.ONAP_VNF_NAMING_TIMESTAMP", "1.0.0"); + LOGGER.trace("Naming policy get time (ms): {}", System.currentTimeMillis() - getStartTime); + + assertEquals(1, namingServiceTemplate.getToscaTopologyTemplate().getPoliciesAsMap().size()); + assertEquals(1, namingServiceTemplate.getPolicyTypesAsMap().size()); + assertEquals(3, namingServiceTemplate.getDataTypesAsMap().size()); + + long deleteStartTime = System.currentTimeMillis(); + databaseProvider.deletePolicy("SDNC_Policy.ONAP_VNF_NAMING_TIMESTAMP", "1.0.0"); + LOGGER.trace("Naming policy delete time (ms): {}", System.currentTimeMillis() - deleteStartTime); } /** * Check persistence of a policy. * - * @param policyString the policy as a string + * @param serviceTemplate the service template containing the policy * @throws Exception any exception thrown */ - public void testJsonStringPolicyPersistence(@NonNull final String policyString) throws Exception { - ToscaServiceTemplate serviceTemplate = standardCoder.decode(policyString, ToscaServiceTemplate.class); - + public void testPolicyPersistence(@NonNull final ToscaServiceTemplate serviceTemplate) throws Exception { assertNotNull(serviceTemplate); databaseProvider.createPolicies(serviceTemplate); @@ -170,11 +182,9 @@ public class PolicyToscaPersistenceTest { Set<String> policyTypeResources = ResourceUtils.getDirectoryContents("policytypes"); for (String policyTypeResource : policyTypeResources) { - Object yamlObject = new Yaml().load(ResourceUtils.getResourceAsString(policyTypeResource)); - String yamlAsJsonString = new StandardCoder().encode(yamlObject); - + String policyTypeYamlString = ResourceUtils.getResourceAsString(policyTypeResource); ToscaServiceTemplate toscaServiceTemplatePolicyType = - standardCoder.decode(yamlAsJsonString, ToscaServiceTemplate.class); + yamlJsonTranslator.fromYaml(policyTypeYamlString, ToscaServiceTemplate.class); assertNotNull(toscaServiceTemplatePolicyType); databaseProvider.createPolicyTypes(toscaServiceTemplatePolicyType); diff --git a/models-provider/src/test/resources/META-INF/persistence.xml b/models-provider/src/test/resources/META-INF/persistence.xml index 77062ce23..d63c415fd 100644 --- a/models-provider/src/test/resources/META-INF/persistence.xml +++ b/models-provider/src/test/resources/META-INF/persistence.xml @@ -34,6 +34,8 @@ <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaPolicyTypes</class> <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaTopologyTemplate</class> <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaServiceTemplate</class> + <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaTrigger</class> + <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaProperty</class> <class>org.onap.policy.models.pdp.persistence.concepts.JpaPdpGroup</class> <class>org.onap.policy.models.pdp.persistence.concepts.JpaPdpSubGroup</class> <class>org.onap.policy.models.pdp.persistence.concepts.JpaPdp</class> diff --git a/models-tosca/src/main/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProvider.java b/models-tosca/src/main/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProvider.java index 6e60303a0..a65cdbe69 100644 --- a/models-tosca/src/main/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProvider.java +++ b/models-tosca/src/main/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProvider.java @@ -423,6 +423,7 @@ public class AuthorativeToscaProvider { */ private <T extends ToscaEntity> List<T> handlePfModelRuntimeException(final PfModelRuntimeException pfme) { if (Status.NOT_FOUND.equals(pfme.getErrorResponse().getResponseCode())) { + LOGGER.trace("request did not find any results", pfme); return Collections.emptyList(); } else { throw pfme; diff --git a/models-tosca/src/main/java/org/onap/policy/models/tosca/legacy/provider/LegacyProvider.java b/models-tosca/src/main/java/org/onap/policy/models/tosca/legacy/provider/LegacyProvider.java index f5335fe79..09cc6c0ca 100644 --- a/models-tosca/src/main/java/org/onap/policy/models/tosca/legacy/provider/LegacyProvider.java +++ b/models-tosca/src/main/java/org/onap/policy/models/tosca/legacy/provider/LegacyProvider.java @@ -93,13 +93,13 @@ public class LegacyProvider { LOGGER.debug("->createOperationalPolicy: legacyOperationalPolicy={}", legacyOperationalPolicy); - JpaToscaServiceTemplate incomingServiceTemplate = + JpaToscaServiceTemplate legacyOperationalServiceTemplate = new LegacyOperationalPolicyMapper().toToscaServiceTemplate(legacyOperationalPolicy); - JpaToscaServiceTemplate outgoingingServiceTemplate = - new SimpleToscaProvider().createPolicies(dao, incomingServiceTemplate); + + new SimpleToscaProvider().createPolicies(dao, legacyOperationalServiceTemplate); LegacyOperationalPolicy createdLegacyOperationalPolicy = - new LegacyOperationalPolicyMapper().fromToscaServiceTemplate(outgoingingServiceTemplate); + new LegacyOperationalPolicyMapper().fromToscaServiceTemplate(legacyOperationalServiceTemplate); LOGGER.debug("<-createOperationalPolicy: createdLegacyOperationalPolicy={}", createdLegacyOperationalPolicy); return createdLegacyOperationalPolicy; @@ -223,7 +223,6 @@ public class LegacyProvider { return updatedLegacyGuardPolicyMap; } - /** * Delete legacy guard policy. * diff --git a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicy.java b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicy.java index 2816df004..b500a8bc9 100644 --- a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicy.java +++ b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicy.java @@ -261,6 +261,11 @@ public class JpaToscaPolicy extends JpaToscaEntityType<ToscaPolicy> implements P public PfValidationResult validate(@NonNull final PfValidationResult resultIn) { PfValidationResult result = super.validate(resultIn); + if (PfKey.NULL_KEY_VERSION.equals(getKey().getVersion())) { + result.addValidationMessage(new PfValidationMessage(getKey(), this.getClass(), ValidationResult.INVALID, + "key version is a null version")); + } + if (type == null || type.isNullKey()) { result.addValidationMessage(new PfValidationMessage(getKey(), this.getClass(), ValidationResult.INVALID, "type is null or a null key")); diff --git a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicyType.java b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicyType.java index b068beaa0..423620dd7 100644 --- a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicyType.java +++ b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/concepts/JpaToscaPolicyType.java @@ -203,6 +203,11 @@ public class JpaToscaPolicyType extends JpaToscaEntityType<ToscaPolicyType> impl public PfValidationResult validate(@NonNull final PfValidationResult resultIn) { PfValidationResult result = super.validate(resultIn); + if (PfKey.NULL_KEY_VERSION.equals(getKey().getVersion())) { + result.addValidationMessage(new PfValidationMessage(getKey(), this.getClass(), ValidationResult.INVALID, + "key version is a null version")); + } + if (properties != null) { result = validateProperties(result); } diff --git a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/provider/SimpleToscaProvider.java b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/provider/SimpleToscaProvider.java index 9c7d6d305..c537bbcb5 100644 --- a/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/provider/SimpleToscaProvider.java +++ b/models-tosca/src/main/java/org/onap/policy/models/tosca/simple/provider/SimpleToscaProvider.java @@ -106,7 +106,7 @@ public class SimpleToscaProvider { PfValidationResult result = serviceTemplateToWrite.validate(new PfValidationResult()); if (!result.isValid()) { - throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, result.toString()); + throw new PfModelRuntimeException(Response.Status.NOT_ACCEPTABLE, result.toString()); } new SimpleToscaServiceTemplateProvider().write(dao, serviceTemplateToWrite); @@ -264,6 +264,9 @@ public class SimpleToscaProvider { "policy types for " + name + ":" + version + DO_NOT_EXIST); } + JpaToscaServiceTemplate dataTypeServiceTemplate = new JpaToscaServiceTemplate(serviceTemplate); + dataTypeServiceTemplate.setPolicyTypes(null); + for (JpaToscaPolicyType policyType : serviceTemplate.getPolicyTypes().getConceptMap().values()) { Collection<PfConceptKey> referencedDataTypeKeys = policyType.getReferencedDataTypes(); @@ -271,11 +274,13 @@ public class SimpleToscaProvider { JpaToscaServiceTemplate dataTypeEntityTreeServiceTemplate = getDataTypes(dao, referencedDataTypeKey.getName(), referencedDataTypeKey.getVersion()); - serviceTemplate = - ToscaServiceTemplateUtils.addFragment(serviceTemplate, dataTypeEntityTreeServiceTemplate); + dataTypeServiceTemplate = ToscaServiceTemplateUtils.addFragment(dataTypeServiceTemplate, + dataTypeEntityTreeServiceTemplate); } } + serviceTemplate = ToscaServiceTemplateUtils.addFragment(serviceTemplate, dataTypeServiceTemplate); + LOGGER.debug("<-getPolicyTypes: name={}, version={}, serviceTemplate={}", name, version, serviceTemplate); return serviceTemplate; } @@ -372,7 +377,11 @@ public class SimpleToscaProvider { throws PfModelException { LOGGER.debug("->getPolicies: name={}, version={}", name, version); - JpaToscaServiceTemplate serviceTemplate = getServiceTemplate(dao); + JpaToscaServiceTemplate dbServiceTemplate = getServiceTemplate(dao); + + JpaToscaServiceTemplate serviceTemplate = new JpaToscaServiceTemplate(dbServiceTemplate); + serviceTemplate.setDataTypes(new JpaToscaDataTypes()); + serviceTemplate.setPolicyTypes(new JpaToscaPolicyTypes()); if (!ToscaUtils.doPoliciesExist(serviceTemplate)) { throw new PfModelRuntimeException(Response.Status.NOT_FOUND, @@ -386,18 +395,20 @@ public class SimpleToscaProvider { "policies for " + name + ":" + version + DO_NOT_EXIST); } + JpaToscaServiceTemplate returnServiceTemplate = new JpaToscaServiceTemplate(serviceTemplate); + returnServiceTemplate.getTopologyTemplate().setPolicies(new JpaToscaPolicies()); + for (JpaToscaPolicy policy : serviceTemplate.getTopologyTemplate().getPolicies().getConceptMap().values()) { - if (policy.getDerivedFrom() != null) { - JpaToscaServiceTemplate referencedEntitiesServiceTemplate = - getPolicyTypes(dao, policy.getDerivedFrom().getName(), policy.getDerivedFrom().getVersion()); + JpaToscaServiceTemplate referencedEntitiesServiceTemplate = + getPolicyTypes(dao, policy.getType().getName(), policy.getType().getVersion()); - serviceTemplate = - ToscaServiceTemplateUtils.addFragment(serviceTemplate, referencedEntitiesServiceTemplate); - } + returnServiceTemplate.getTopologyTemplate().getPolicies().getConceptMap().put(policy.getKey(), policy); + returnServiceTemplate = + ToscaServiceTemplateUtils.addFragment(returnServiceTemplate, referencedEntitiesServiceTemplate); } - LOGGER.debug("<-getPolicies: name={}, version={}, serviceTemplate={}", name, version, serviceTemplate); - return serviceTemplate; + LOGGER.debug("<-getPolicies: name={}, version={}, serviceTemplate={}", name, version, returnServiceTemplate); + return returnServiceTemplate; } /** @@ -497,7 +508,7 @@ public class SimpleToscaProvider { if (policyType == null) { String errorMessage = "policy type " + policyTypeKey.getId() + " for policy " + policy.getId() + " does not exist"; - throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, errorMessage); + throw new PfModelRuntimeException(Response.Status.NOT_ACCEPTABLE, errorMessage); } } @@ -526,7 +537,7 @@ public class SimpleToscaProvider { // We should have one and only one returned entry if (filterdPolicyTypeList.size() != 1) { String errorMessage = "search for latest policy type " + policyTypeName + " returned more than one entry"; - throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, errorMessage); + throw new PfModelRuntimeException(Response.Status.CONFLICT, errorMessage); } return (JpaToscaPolicyType) filterdPolicyTypeList.get(0); diff --git a/models-tosca/src/main/java/org/onap/policy/models/tosca/utils/ToscaServiceTemplateUtils.java b/models-tosca/src/main/java/org/onap/policy/models/tosca/utils/ToscaServiceTemplateUtils.java index 87b499b4c..6f21c1942 100644 --- a/models-tosca/src/main/java/org/onap/policy/models/tosca/utils/ToscaServiceTemplateUtils.java +++ b/models-tosca/src/main/java/org/onap/policy/models/tosca/utils/ToscaServiceTemplateUtils.java @@ -74,7 +74,7 @@ public class ToscaServiceTemplateUtils { compositeTemplate.setPolicyTypes( addFragmentEntitites(compositeTemplate.getPolicyTypes(), fragmentTemplate.getPolicyTypes(), result)); - if (originalTemplate.getTopologyTemplate() != null) { + if (originalTemplate.getTopologyTemplate() != null && fragmentTemplate.getTopologyTemplate() != null) { if (originalTemplate.getTopologyTemplate() .compareToWithoutEntities(fragmentTemplate.getTopologyTemplate()) == 0) { compositeTemplate.getTopologyTemplate() @@ -95,7 +95,7 @@ public class ToscaServiceTemplateUtils { if (!result.isValid()) { String message = result.toString(); - throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, message); + throw new PfModelRuntimeException(Response.Status.NOT_ACCEPTABLE, message); } return compositeTemplate; diff --git a/models-tosca/src/main/java/org/onap/policy/models/tosca/utils/ToscaUtils.java b/models-tosca/src/main/java/org/onap/policy/models/tosca/utils/ToscaUtils.java index cc0431946..b75273e5e 100644 --- a/models-tosca/src/main/java/org/onap/policy/models/tosca/utils/ToscaUtils.java +++ b/models-tosca/src/main/java/org/onap/policy/models/tosca/utils/ToscaUtils.java @@ -147,7 +147,7 @@ public final class ToscaUtils { final Function<JpaToscaServiceTemplate, String> checkerFunction) { String message = checkerFunction.apply(serviceTemplate); if (message != null) { - throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, message); + throw new PfModelRuntimeException(Response.Status.NOT_FOUND, message); } } @@ -263,7 +263,7 @@ public final class ToscaUtils { } if (!result.isValid()) { - throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, result.toString()); + throw new PfModelRuntimeException(Response.Status.NOT_ACCEPTABLE, result.toString()); } entityTypes.getConceptMap().entrySet() diff --git a/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/concepts/ToscaPolicyFilterTest.java b/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/concepts/ToscaPolicyFilterTest.java index 0bf371022..858ac09fe 100644 --- a/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/concepts/ToscaPolicyFilterTest.java +++ b/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/concepts/ToscaPolicyFilterTest.java @@ -87,6 +87,7 @@ public class ToscaPolicyFilterTest { for (String policyResourceName : policyResourceNames) { String policyString = ResourceUtils.getResourceAsString(policyResourceName); if (policyResourceName.endsWith("yaml")) { + LOGGER.info("loading {}", policyResourceName); Object yamlObject = new Yaml().load(policyString); policyString = new GsonBuilder().setPrettyPrinting().create().toJson(yamlObject); } @@ -150,7 +151,7 @@ public class ToscaPolicyFilterTest { assertEquals(VERSION_100, filteredList.get(7).getVersion()); assertEquals(VERSION_100, filteredList.get(12).getVersion()); - assertEquals(24, policyList.size()); + assertEquals(23, policyList.size()); assertEquals(22, filteredList.size()); policyList.get(10).setVersion("2.0.0"); @@ -172,7 +173,7 @@ public class ToscaPolicyFilterTest { public void testFilterNameVersion() { ToscaPolicyFilter filter = ToscaPolicyFilter.builder().name("operational.modifyconfig").build(); List<ToscaPolicy> filteredList = filter.filter(policyList); - assertEquals(2, filteredList.size()); + assertEquals(1, filteredList.size()); filter = ToscaPolicyFilter.builder().name("guard.frequency.scaleout").build(); filteredList = filter.filter(policyList); @@ -184,7 +185,7 @@ public class ToscaPolicyFilterTest { filter = ToscaPolicyFilter.builder().version(VERSION_100).build(); filteredList = filter.filter(policyList); - assertEquals(22, filteredList.size()); + assertEquals(21, filteredList.size()); filter = ToscaPolicyFilter.builder().name("OSDF_CASABLANCA.SubscriberPolicy_v1").version(VERSION_100).build(); filteredList = filter.filter(policyList); @@ -192,7 +193,7 @@ public class ToscaPolicyFilterTest { filter = ToscaPolicyFilter.builder().name("operational.modifyconfig").version(VERSION_100).build(); filteredList = filter.filter(policyList); - assertEquals(1, filteredList.size()); + assertEquals(0, filteredList.size()); } @Test @@ -200,11 +201,11 @@ public class ToscaPolicyFilterTest { // null pattern ToscaPolicyFilter filter = ToscaPolicyFilter.builder().versionPrefix(null).build(); List<ToscaPolicy> filteredList = filter.filter(policyList); - assertEquals(24, filteredList.size()); + assertEquals(23, filteredList.size()); filter = ToscaPolicyFilter.builder().versionPrefix("1.").build(); filteredList = filter.filter(policyList); - assertEquals(22, filteredList.size()); + assertEquals(21, filteredList.size()); filter = ToscaPolicyFilter.builder().versionPrefix("100.").build(); filteredList = filter.filter(policyList); @@ -215,7 +216,11 @@ public class ToscaPolicyFilterTest { public void testFilterTypeVersion() { ToscaPolicyFilter filter = ToscaPolicyFilter.builder().type("onap.policies.controlloop.Operational").build(); List<ToscaPolicy> filteredList = filter.filter(policyList); - assertEquals(1, filteredList.size()); + assertEquals(0, filteredList.size()); + + filter = ToscaPolicyFilter.builder().type("onap.policies.controlloop.operational.common.Apex").build(); + filteredList = filter.filter(policyList); + assertEquals(0, filteredList.size()); filter = ToscaPolicyFilter.builder().type("onap.policies.controlloop.operational.common.Drools").build(); filteredList = filter.filter(policyList); @@ -231,7 +236,7 @@ public class ToscaPolicyFilterTest { filter = ToscaPolicyFilter.builder().typeVersion(VERSION_000).build(); filteredList = filter.filter(policyList); - assertEquals(4, filteredList.size()); + assertEquals(3, filteredList.size()); filter = ToscaPolicyFilter.builder().type("onap.policies.optimization.resource.HpaPolicy") .typeVersion(VERSION_100).build(); @@ -241,6 +246,6 @@ public class ToscaPolicyFilterTest { filter = ToscaPolicyFilter.builder().type("onap.policies.controlloop.Operational").typeVersion(VERSION_000) .build(); filteredList = filter.filter(policyList); - assertEquals(1, filteredList.size()); + assertEquals(0, filteredList.size()); } } diff --git a/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/concepts/ToscaPolicyTypeFilterTest.java b/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/concepts/ToscaPolicyTypeFilterTest.java index 30696ce9d..0f038d3cd 100644 --- a/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/concepts/ToscaPolicyTypeFilterTest.java +++ b/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/concepts/ToscaPolicyTypeFilterTest.java @@ -163,7 +163,7 @@ public class ToscaPolicyTypeFilterTest { public void testFilterNameVersion() { ToscaPolicyTypeFilter filter = ToscaPolicyTypeFilter.builder().name("onap.policies.Monitoring").build(); List<ToscaPolicyType> filteredList = filter.filter(typeList); - assertEquals(2, filteredList.size()); + assertEquals(1, filteredList.size()); filter = ToscaPolicyTypeFilter.builder().name("onap.policies.monitoring.cdap.tca.hi.lo.app").build(); filteredList = filter.filter(typeList); @@ -173,9 +173,9 @@ public class ToscaPolicyTypeFilterTest { filteredList = filter.filter(typeList); assertEquals(0, filteredList.size()); - filter = ToscaPolicyTypeFilter.builder().version(VERSION_000).build(); + filter = ToscaPolicyTypeFilter.builder().version(VERSION_100).build(); filteredList = filter.filter(typeList); - assertEquals(1, filteredList.size()); + assertEquals(20, filteredList.size()); filter = ToscaPolicyTypeFilter.builder().name("onap.policies.optimization.Vim_fit").version(VERSION_000) .build(); diff --git a/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProviderPolicyTypeTest.java b/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProviderPolicyTypeTest.java index ae350bd90..3f0d9e2a5 100644 --- a/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProviderPolicyTypeTest.java +++ b/models-tosca/src/test/java/org/onap/policy/models/tosca/authorative/provider/AuthorativeToscaProviderPolicyTypeTest.java @@ -58,11 +58,11 @@ import org.yaml.snakeyaml.Yaml; */ public class AuthorativeToscaProviderPolicyTypeTest { private static final String VERSION = "version"; - private static final String POLICY_NO_VERSION_VERSION0 = "onap.policies.NoVersion:0.0.0"; + private static final String POLICY_NO_VERSION_VERSION1 = "onap.policies.NoVersion:0.0.1"; private static final String POLICY_NO_VERSION = "onap.policies.NoVersion"; private static final String MISSING_POLICY_TYPES = "no policy types specified on service template"; private static final String DAO_IS_NULL = "^dao is marked .*on.*ull but is null$"; - private static final String VERSION_000 = "0.0.0"; + private static final String VERSION_001 = "0.0.1"; private static String yamlAsJsonString; private PfDao pfDao; private StandardCoder standardCoder; @@ -135,7 +135,7 @@ public class AuthorativeToscaProviderPolicyTypeTest { ToscaServiceTemplate createdServiceTemplate = new AuthorativeToscaProvider().createPolicyTypes(pfDao, toscaServiceTemplate); - PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION0); + PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION1); ToscaPolicyType beforePolicyType = toscaServiceTemplate.getPolicyTypes().get(policyTypeKey.getName()); ToscaPolicyType createdPolicyType = createdServiceTemplate.getPolicyTypes().get(policyTypeKey.getName()); @@ -150,7 +150,7 @@ public class AuthorativeToscaProviderPolicyTypeTest { assertEquals(0, ObjectUtils.compare(beforePolicyType.getDescription(), createdPolicyType.getDescription())); List<ToscaPolicyType> gotPolicyTypeList = - new AuthorativeToscaProvider().getPolicyTypeList(pfDao, POLICY_NO_VERSION, VERSION_000); + new AuthorativeToscaProvider().getPolicyTypeList(pfDao, POLICY_NO_VERSION, VERSION_001); assertEquals(2, gotPolicyTypeList.size()); assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName())); @@ -162,14 +162,14 @@ public class AuthorativeToscaProviderPolicyTypeTest { assertEquals(2, gotPolicyTypeList.size()); assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName())); - gotPolicyTypeList = new AuthorativeToscaProvider().getPolicyTypeList(pfDao, null, VERSION_000); + gotPolicyTypeList = new AuthorativeToscaProvider().getPolicyTypeList(pfDao, null, VERSION_001); assertEquals(2, gotPolicyTypeList.size()); assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName())); assertThatThrownBy(() -> new AuthorativeToscaProvider().getPolicyTypeList(new DefaultPfDao(), POLICY_NO_VERSION, - VERSION_000)).hasMessageContaining("Policy Framework DAO has not been initialized"); + VERSION_001)).hasMessageContaining("Policy Framework DAO has not been initialized"); - assertTrue(new AuthorativeToscaProvider().getPolicyTypeList(pfDao, "i.dont.Exist", VERSION_000).isEmpty()); + assertTrue(new AuthorativeToscaProvider().getPolicyTypeList(pfDao, "i.dont.Exist", VERSION_001).isEmpty()); } @Test @@ -212,7 +212,7 @@ public class AuthorativeToscaProviderPolicyTypeTest { ToscaServiceTemplate createdServiceTemplate = new AuthorativeToscaProvider().createPolicyTypes(pfDao, toscaServiceTemplate); - PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION0); + PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION1); ToscaPolicyType beforePolicyType = toscaServiceTemplate.getPolicyTypes().get(policyTypeKey.getName()); ToscaPolicyType createdPolicyType = createdServiceTemplate.getPolicyTypes().get(policyTypeKey.getName()); @@ -234,14 +234,14 @@ public class AuthorativeToscaProviderPolicyTypeTest { assertEquals(0, ObjectUtils.compare(beforePolicyType.getDescription(), gotPolicyType.getDescription())); gotServiceTemplate = new AuthorativeToscaProvider().getFilteredPolicyTypes(pfDao, - ToscaPolicyTypeFilter.builder().name(policyTypeKey.getName()).version(VERSION_000).build()); + ToscaPolicyTypeFilter.builder().name(policyTypeKey.getName()).version(VERSION_001).build()); gotPolicyType = gotServiceTemplate.getPolicyTypes().get(policyTypeKey.getName()); assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName())); assertEquals(0, ObjectUtils.compare(beforePolicyType.getDescription(), gotPolicyType.getDescription())); List<ToscaPolicyType> gotPolicyTypeList = - new AuthorativeToscaProvider().getPolicyTypeList(pfDao, POLICY_NO_VERSION, VERSION_000); + new AuthorativeToscaProvider().getPolicyTypeList(pfDao, POLICY_NO_VERSION, VERSION_001); assertEquals(2, gotPolicyTypeList.size()); assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName())); @@ -256,7 +256,7 @@ public class AuthorativeToscaProviderPolicyTypeTest { assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName())); gotPolicyTypeList = new AuthorativeToscaProvider().getFilteredPolicyTypeList(pfDao, - ToscaPolicyTypeFilter.builder().name(policyTypeKey.getName()).version(VERSION_000).build()); + ToscaPolicyTypeFilter.builder().name(policyTypeKey.getName()).version(VERSION_001).build()); assertEquals(1, gotPolicyTypeList.size()); assertEquals(true, beforePolicyType.getName().equals(gotPolicyType.getName())); @@ -296,7 +296,7 @@ public class AuthorativeToscaProviderPolicyTypeTest { ToscaServiceTemplate createdServiceTemplate = new AuthorativeToscaProvider().createPolicyTypes(pfDao, toscaServiceTemplate); - PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION0); + PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION1); ToscaPolicyType beforePolicyType = toscaServiceTemplate.getPolicyTypes().get(policyTypeKey.getName()); ToscaPolicyType createdPolicyType = createdServiceTemplate.getPolicyTypes().get(policyTypeKey.getName()); @@ -328,7 +328,7 @@ public class AuthorativeToscaProviderPolicyTypeTest { ToscaServiceTemplate createdServiceTemplate = new AuthorativeToscaProvider().createPolicyTypes(pfDao, toscaServiceTemplate); - PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION0); + PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION1); ToscaPolicyType beforePolicyType = toscaServiceTemplate.getPolicyTypes().get(policyTypeKey.getName()); ToscaPolicyType createdPolicyType = createdServiceTemplate.getPolicyTypes().get(policyTypeKey.getName()); @@ -379,7 +379,7 @@ public class AuthorativeToscaProviderPolicyTypeTest { ToscaServiceTemplate createdServiceTemplate = new AuthorativeToscaProvider().createPolicyTypes(pfDao, toscaServiceTemplate); - PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION0); + PfConceptKey policyTypeKey = new PfConceptKey(POLICY_NO_VERSION_VERSION1); ToscaPolicyType beforePolicyType = toscaServiceTemplate.getPolicyTypes().get(policyTypeKey.getName()); ToscaPolicyType createdPolicyType = createdServiceTemplate.getPolicyTypes().get(policyTypeKey.getName()); @@ -395,7 +395,7 @@ public class AuthorativeToscaProviderPolicyTypeTest { assertThatThrownBy(() -> { new AuthorativeToscaProvider().getPolicyTypes(pfDao, policyTypeKey.getName(), policyTypeKey.getVersion()); - }).hasMessage("policy types for onap.policies.NoVersion:0.0.0 do not exist"); + }).hasMessage("policy types for onap.policies.NoVersion:0.0.1 do not exist"); } @Test diff --git a/models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/mapping/LegacyOperationalPolicyMapperTest.java b/models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/mapping/LegacyOperationalPolicyMapperTest.java index 0aa1da0d7..4dcfeafc9 100644 --- a/models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/mapping/LegacyOperationalPolicyMapperTest.java +++ b/models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/mapping/LegacyOperationalPolicyMapperTest.java @@ -1,7 +1,7 @@ /*- * ============LICENSE_START======================================================= * Copyright (C) 2019-2020 Nordix Foundation. - * Modifications Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * Modifications Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,7 +71,7 @@ public class LegacyOperationalPolicyMapperTest { JpaToscaServiceTemplate policyTypeServiceTemplate = new JpaToscaServiceTemplate(); policyTypeServiceTemplate.fromAuthorative(policyTypes); - String vcpePolicyJson = ResourceUtils.getResourceAsString("policies/vCPE.policy.operational.input.json"); + String vcpePolicyJson = ResourceUtils.getResourceAsString("policies/vCPE.policy.operational.legacy.input.json"); LegacyOperationalPolicy legacyOperationalPolicy = standardCoder.decode(vcpePolicyJson, LegacyOperationalPolicy.class); diff --git a/models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/provider/LegacyProvider4LegacyOperationalTest.java b/models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/provider/LegacyProvider4LegacyOperationalTest.java index ec03122a6..4d0fd6fd6 100644 --- a/models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/provider/LegacyProvider4LegacyOperationalTest.java +++ b/models-tosca/src/test/java/org/onap/policy/models/tosca/legacy/provider/LegacyProvider4LegacyOperationalTest.java @@ -1,7 +1,7 @@ /*- * ============LICENSE_START======================================================= * Copyright (C) 2019-2020 Nordix Foundation. - * Modifications Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * Modifications Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,8 +51,8 @@ import org.yaml.snakeyaml.Yaml; */ public class LegacyProvider4LegacyOperationalTest { private static final String POLICY_ID_IS_NULL = "^policyId is marked .*on.*ull but is null$"; - private static final String VCPE_OUTPUT_JSON = "policies/vCPE.policy.operational.output.json"; - private static final String VCPE_INPUT_JSON = "policies/vCPE.policy.operational.input.json"; + private static final String VCPE_OUTPUT_JSON = "policies/vCPE.policy.operational.legacy.output.json"; + private static final String VCPE_INPUT_JSON = "policies/vCPE.policy.operational.legacy.input.json"; private static final String DAO_IS_NULL = "^dao is marked .*on.*ull but is null$"; private PfDao pfDao; private StandardCoder standardCoder; diff --git a/models-tosca/src/test/java/org/onap/policy/models/tosca/simple/serialization/MonitoringPolicyTypeSerializationTest.java b/models-tosca/src/test/java/org/onap/policy/models/tosca/simple/serialization/MonitoringPolicyTypeSerializationTest.java index 01a52f7bd..0a8283e98 100644 --- a/models-tosca/src/test/java/org/onap/policy/models/tosca/simple/serialization/MonitoringPolicyTypeSerializationTest.java +++ b/models-tosca/src/test/java/org/onap/policy/models/tosca/simple/serialization/MonitoringPolicyTypeSerializationTest.java @@ -1,7 +1,7 @@ /*- * ============LICENSE_START======================================================= * Copyright (C) 2019-2020 Nordix Foundation. - * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -155,9 +155,9 @@ public class MonitoringPolicyTypeSerializationTest { Entry<PfConceptKey, JpaToscaPolicyType> firstPolicyType = policyTypesIter.next(); assertEquals(MONITORING, firstPolicyType.getKey().getName()); - assertEquals(VERSION_000, firstPolicyType.getKey().getVersion()); + assertEquals(VERSION_100, firstPolicyType.getKey().getVersion()); assertEquals("tosca.policies.Root", firstPolicyType.getValue().getDerivedFrom().getName()); - assertEquals("a base policy type for all policies that governs monitoring provisioning", + assertEquals("a base policy type for all policies that govern monitoring provisioning", firstPolicyType.getValue().getDescription()); Entry<PfConceptKey, JpaToscaPolicyType> secondPolicyType = policyTypesIter.next(); @@ -376,13 +376,13 @@ public class MonitoringPolicyTypeSerializationTest { assertEquals(MONITORING, firstPolicyType.getKey().getName()); assertEquals(VERSION_100, firstPolicyType.getKey().getVersion()); assertEquals("tosca.policies.Root", firstPolicyType.getValue().getDerivedFrom().getName()); - assertEquals("a base policy type for all policies that govern monitoring provision", + assertEquals("a base policy type for all policies that govern monitoring provisioning", firstPolicyType.getValue().getDescription()); Entry<PfConceptKey, JpaToscaPolicyType> secondPolicyType = policyTypesIter.next(); assertEquals(DCAE, secondPolicyType.getKey().getName()); assertEquals(VERSION_100, secondPolicyType.getKey().getVersion()); - assertEquals("policy.nodes.Root", secondPolicyType.getValue().getDerivedFrom().getName()); + assertEquals("onap.policies.Monitoring", secondPolicyType.getValue().getDerivedFrom().getName()); assertTrue(secondPolicyType.getValue().getProperties().size() == 2); Iterator<JpaToscaProperty> propertiesIter = secondPolicyType.getValue().getProperties().values().iterator(); diff --git a/models-tosca/src/test/java/org/onap/policy/models/tosca/utils/ToscaServiceTemplateUtilsTest.java b/models-tosca/src/test/java/org/onap/policy/models/tosca/utils/ToscaServiceTemplateUtilsTest.java index ca46de1c3..f9a0143fd 100644 --- a/models-tosca/src/test/java/org/onap/policy/models/tosca/utils/ToscaServiceTemplateUtilsTest.java +++ b/models-tosca/src/test/java/org/onap/policy/models/tosca/utils/ToscaServiceTemplateUtilsTest.java @@ -201,5 +201,19 @@ public class ToscaServiceTemplateUtilsTest { assertEquals(dt1, dtIterator.next()); assertEquals(pt0, compositeTemplate05.getPolicyTypes().getAll(null).iterator().next()); assertEquals(p0, compositeTemplate05.getTopologyTemplate().getPolicies().getAll(null).iterator().next()); + + JpaToscaServiceTemplate fragmentTemplate09 = new JpaToscaServiceTemplate(); + + fragmentTemplate09.setDataTypes(new JpaToscaDataTypes()); + fragmentTemplate09.getDataTypes().getConceptMap().put(dt1.getKey(), dt1); + + fragmentTemplate09.setPolicyTypes(new JpaToscaPolicyTypes()); + fragmentTemplate09.getPolicyTypes().getConceptMap().put(pt0.getKey(), pt0); + + fragmentTemplate09.setTopologyTemplate(null); + + JpaToscaServiceTemplate compositeTemplate06 = + ToscaServiceTemplateUtils.addFragment(compositeTemplate05, fragmentTemplate09); + assertEquals(compositeTemplate05.getTopologyTemplate(), compositeTemplate06.getTopologyTemplate()); } } diff --git a/models-tosca/src/test/resources/META-INF/persistence.xml b/models-tosca/src/test/resources/META-INF/persistence.xml index d6fba8f8a..de27dd9bc 100644 --- a/models-tosca/src/test/resources/META-INF/persistence.xml +++ b/models-tosca/src/test/resources/META-INF/persistence.xml @@ -34,6 +34,8 @@ <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaPolicyTypes</class> <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaTopologyTemplate</class> <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaServiceTemplate</class> + <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaTrigger</class> + <class>org.onap.policy.models.tosca.simple.concepts.JpaToscaProperty</class> <properties> <property name="eclipselink.ddl-generation" value="drop-and-create-tables" /> diff --git a/models-tosca/src/test/resources/onap.policies.NoVersion.yaml b/models-tosca/src/test/resources/onap.policies.NoVersion.yaml index 5923eb22d..2dda556a6 100644 --- a/models-tosca/src/test/resources/onap.policies.NoVersion.yaml +++ b/models-tosca/src/test/resources/onap.policies.NoVersion.yaml @@ -6,6 +6,7 @@ policy_types: description: The base policy type for all policies that govern optimization onap.policies.NoVersion: derived_from: onap.policies.Optimization + version: 0.0.1 properties: applicableResources: type: list |